I don't understand the logic behind the implementation of the site-wide language negotiation setting vs. user-specified language preference setting. In my opinion, the help text seems to indicate that the user has some option of enforcing a language for the interface, but according to keith, the language is limited only to site e-mails.
I'm trying to clear the confusion by looking at the code.
This is the language_initialize() function taken from language.inc. I have broken it in smaller blocks and provided my interpretation of the code:
/**
* Choose a language for the page, based on language negotiation settings.
*/
function language_initialize() {
global $user;
// Configured presentation language mode.
$mode = variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE);
This retrieves the language negotiation mode, which should be an integer from 0 to 3 corresponding to the four choices defined in bootstrap.inc.
In case no setting for this value was found, the default value of LANGUAGE_NEGOTIATION_NONE is used. This is the usual path, since the variable language_negotiation
doesn't exist on a fresh Drupal 6 install.
// Get a list of enabled languages.
$languages = language_list('enabled');
$languages = $languages[1];
switch ($mode) {
case LANGUAGE_NEGOTIATION_NONE:
return language_default();
This case statement ends the execution of the function and returns the default language specified for the site. I see this as a very deterministic phase: now we have the language, nothing to do here anymore, so let's exit and use this language. And this is the part that will be executed always, when no negotiation setting has been set. (I'm starting to repeat myself...)
In the case some other negotiation mode is selected, the appropriate processing will be done:
case LANGUAGE_NEGOTIATION_DOMAIN:
foreach ($languages as $language) {
$parts = parse_url($language->domain);
if (!empty($parts['host']) && ($_SERVER['SERVER_NAME'] == $parts['host'])) {
return $language;
}
}
return language_default();
case LANGUAGE_NEGOTIATION_PATH_DEFAULT:
case LANGUAGE_NEGOTIATION_PATH:
// $_GET['q'] might not be available at this time, because
// path initialization runs after the language bootstrap phase.
$args = isset($_GET['q']) ? explode('/', $_GET['q']) : array();
$prefix = array_shift($args);
// Search prefix within enabled languages.
foreach ($languages as $language) {
if (!empty($language->prefix) && $language->prefix == $prefix) {
// Rebuild $GET['q'] with the language removed.
$_GET['q'] = implode('/', $args);
return $language;
}
}
if ($mode == LANGUAGE_NEGOTIATION_PATH_DEFAULT) {
// If we did not found the language by prefix, choose the default.
return language_default();
}
break;
}
Lot's of "returns $languages;", as if all cases would be covered?
Actually, no.
If none of the above conditions (yes, there are more of them!) is met, the function still continues with these lines:
// User language.
if ($user->uid && isset($languages[$user->language])) {
return $languages[$user->language];
}
// Browser accept-language parsing.
if ($language = language_from_browser()) {
return $language;
}
// Fall back on the default if everything else fails.
return language_default();
}
This was the interesting part, since I don't see when the execution is supposed to get to this last part:
if ($user->uid && isset($languages[$user->language])) {
return $languages[$user->language];
}
I would really like that Drupal just execute this block. Now the whole function seems to be coded as if this section is not meant to be executed at all. Or is it? I don't know. It's confusing -- exactly like the help text for the setting.
By looking at the code, it looks like I am supposed to cheat by inserting any value NOT listed in the case blocks:
UPDATE variable SET value = 's:2:"-1";' WHERE name = 'language_negotiation';
To make it cleaner, I would create a new constant:
define('LANGUAGE_NEGOTIATION_REALLY_NONE', -1);
.. and make a proper case block in language_initialize() to handle this.
In my opinion, this should be classified as a bug, not a user interface text issue. And definitively not something to be pushed to 7.0.
Comments
Comment #1
Damien Tournoud CreditAttribution: Damien Tournoud commentedOk, just for fun, let's look at the available options, from the help text:
That's what you are seeing, right? (In all cases, the email preferences are dealt with by user_preferred_language())
This is:
This dealt with by this block:
This is why the code flow continues below to:
Ok. So guess what? There is no implementation error here. As I told you on #222401: Code block never executes in language_initialize(), please open a new issue only if you want to discuss the design choice behind language management. The proper way to do this is by opening a *feature request*. The request will have to go to 7.x, because Drupal 6 is feature-frozen.
This is nothing more than a duplicate of #222401.
Comment #2
htalvitie CreditAttribution: htalvitie commentedI don't agree with the part where you say "This is why the code flow continues below to:".
How could it?
There are four constants available for the language negotiation:
- LANGUAGE_NEGOTIATION_NONE
- LANGUAGE_NEGOTIATION_DOMAIN
- LANGUAGE_NEGOTIATION_PATH_DEFAULT
- LANGUAGE_NEGOTIATION_PATH
The switch-case block in the beginning of language_initialize() covers all these, and always returns before this code is reached, where the user option is processed:
If you disagree, please describe when and how that block could be reached? Also, debug and confirm it before you come back.
Can we maybe get a second opinion here, please? I am really getting tired arguing with somebody, who clearly doesn't have a debugger in hand.
Comment #3
Damien Tournoud CreditAttribution: Damien Tournoud commentedOf course not, open your eyes:
This will not return if $prefix is not found in any of the $language->prefix.
And the following:
And this will not return if $mode is LANGUAGE_NEGOTIATION_PATH.
So if $mode == LANGUAGE_NEGOTIATION_PATH *and* $prefix is not found in any of the $language->prefix, the code flow continues.
God, I wrote that 5 times already. I'm *really* tired of it. This is the time you say "ok, you are right, I'm a moron", and you apologize.
Comment #4
htalvitie CreditAttribution: htalvitie commentedOk, I checked and confirmed that when $mode is LANGUAGE_NEGOTIATION_PATH and when there are no prefixes to match the path, the code continues.
But (and this is the big one): When I add a new language, it creates a prefix value to the language table. This also has a side-effect, which alters the links (from http://mysite.com/page to http://mysite.com/[prefix]/page). This breaks things, because I don't want to have separate urls to the same page.
Why would I want to have a separate URL for the same page for a user who has selected the UI language to be different from another user? The content is still the same, only the menus and navigation etc. are in a different language.
So to resolve the issue, I have to manually remove the prefix values from the language table. This makes my links stay as they are, and honors the user language option.
Is this really necessary?
(The status could be changed to "Needs more information?")
Comment #5
htalvitie CreditAttribution: htalvitie commentedAlso, to clarify why our views of the code flow differed:
An option with a name "LANGUAGE_NEGOTIATION_PATH" implies, that I would want to perform the prefix-checking for every page request, when I really don't. I simply want to provide the user a selection of languages for the UI, and not to have my links break based on user's preferences.
So, please allow Drupal to work without a forced language negotiation overhead and allow for a mode, where the user still can choose the UI language.
Comment #6
Gábor Hojtsyhttp://drupal.org/node/222401#comment-920801