If language is detected from the URL, this language will apply to user interface translations as well as the $language_url global which is used when generating URLs.

If, however, language is detected via some other means, such as from the browser language setting, this language will apply to user interface translations, but will not be applied to the global $language_url variable. This results in a page that is translated to the browser's language, for example to Spanish. But all links in the page point to the default language, for example French: /fr/node/1.

Shouldn't $language_url be set regardless of the language detection method?

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

mfb’s picture

I looked into this and found a few things:

  1. URL language ($language_url) is a totally separate variable from the user interface language ($language);
  2. There is no way to configure URL language detection (unlike user interface language detection); and
  3. URL language does not react at all to the user interface language detection.

$language_url is set in drupal_language_initialize() and language_initialize(). A module could presumably implement hook language_init() to set $language_url = $language during bootstrap.

In any case, I don't really understand the logic behind the user interface language detecting browser language while URL language ignores it.

Frank Ralf’s picture

plach’s picture

Priority: Normal » Major
Status: Active » Postponed (maintainer needs more info)

@mfb:

Shouldn't $language_url be set regardless of the language detection method?

No, otherwise there would be no point in having two distinct variables: the reason is that they may actually be different.

$language_url is set in drupal_language_initialize() and language_initialize(). A module could presumably implement hook language_init() to set $language_url = $language during bootstrap.
In any case, I don't really understand the logic behind the user interface language detecting browser language while URL language ignores it.

See http://drupal.org/update/modules/6/7#language_negotiation for a detailed explanation. You may also want to give a look to this (closed) related issue, that may help to shed some light on this one: #855380: $language_url should be used to lookup the current path alias.

Anyway the behavior you are reporting in the OP is wrong and we must fix it, I suspect your system is configured so that all languages have a non-empy language prefix, right?

If the default language had an empty prefix you would get a page with URLs with an empty prefix, and every page would behave as the viewed one, e.g.: you get a Spanish interface and URLs are unprefixed. If I understood the bug report well, here we are viewing a page with an unprefixed URL, say /node, but all the enabled languages have a prefix defined. In this case (and only in this case) I agree that if we don't find a language prefix we must fall back to the page language.

mfb’s picture

Status: Postponed (maintainer needs more info) » Active

Yes, on this site we configured each language to have a prefix -- we did not want to appear to favor one language over another by using an empty prefix for the "default" language. so far I'm not up to speed enough on the language subsystem to try to patch this, but I'll read thru the links you provided.

plach’s picture

Assigned: Unassigned » plach

I'll provide a patch myself. I have an idea on how to quick fix this.

plach’s picture

Status: Active » Needs review
FileSize
3.8 KB

Here is a patch for the bot. We still needs tests.

mfb’s picture

This seems to resolve the issue, after I cleared and re-configured language variables and cleared caches. Thanks! Noticed a few typos:

+ * The language is determined using the current content language if no URL
+ * language is available.

Should say interface language?

+ * If no URL language can be detcted and the default language is configured to

should be "detected".

mfb’s picture

[Deleted - posted a comment in the wrong tab :p]

plach’s picture

FileSize
8.13 KB

The attached patch fixes the typos, provides an update function that refreshes the language negotiation settings and adds test coverage. We should be ready to go now.

Status: Needs review » Needs work

The last submitted patch, language-956256-9.patch, failed testing.

plach’s picture

Status: Needs work » Needs review

Tests pass on my box

#9: language-956256-9.patch queued for re-testing.

Status: Needs review » Needs work

The last submitted patch, language-956256-9.patch, failed testing.

plach’s picture

Status: Needs work » Needs review
FileSize
8.13 KB

Let's try this.

plach’s picture

FileSize
8.23 KB

There was the usual problem with clean urls. This one should pass.

Status: Needs review » Needs work

The last submitted patch, language-956256-14.patch, failed testing.

plach’s picture

Status: Needs review » Needs work
FileSize
8.23 KB

There was also an issue with the base path. Hopefullly this one should be ok, provided that the patch is in a correct format (I had to manually edit the previous one, since I cannot login into cvs.drupal.org atm).

plach’s picture

Status: Needs work » Needs review

The last submitted patch, language-956256-16.patch, failed testing.

plach’s picture

Status: Needs work » Needs review
FileSize
8.23 KB

Rerolled #16 now that cvs.drupal.org is back online.

plach’s picture

whew

sun’s picture

Status: Needs review » Needs work
+++ includes/locale.inc	2 Nov 2010 14:35:03 -0000
@@ -23,6 +23,12 @@ define('LOCALE_LANGUAGE_NEGOTIATION_BROW
+ * The language is determined using an already detected one if no URL language
+ * is available.
...
+define('LOCALE_LANGUAGE_NEGOTIATION_URL_FALLBACK', 'locale-url-fallback');

Had to read this twice to understand what it tries to tell. Can we move the if clause to the beginning?

+++ includes/locale.inc	2 Nov 2010 14:35:03 -0000
@@ -78,6 +84,28 @@ function locale_language_from_interface(
+ * Identifies the language to be assigned to URLs when none is detected.

The summary states that a language is assigned *to* a URL, but the actual function name states that a language is determined *from* a URL. Which of both?

+++ includes/locale.inc	2 Nov 2010 14:35:03 -0000
@@ -78,6 +84,28 @@ function locale_language_from_interface(
+ * If no URL language can be detected and the default language is configured to
+ * always provide language information via URL prefix or domain, we need to fall
+ * back to the most suitable language, which is an already detected language.

1) "we" should not be contained here.

2) Without reading the rest of this patch, it's not clear to me,

a) from where locale_language_from_url_fallback() is invoked

b) whether a special configuration is required to make the language system invoke this function

c) how and where the assumption that other languages have been already detected is accounted for.

+++ includes/locale.inc	2 Nov 2010 14:35:03 -0000
@@ -78,6 +84,28 @@ function locale_language_from_interface(
+ * @param $languages
+ *   Optional. An array of valid language objects. Defaults to NULL.
+ * @param $language_type
+ *   Optional. The language type to fall back to. Defaults to the interface
+ *   language.

1) "Optional." should be "(optional)" (lowercase, parenthesis). See http://drupal.org/node/1354 for details.

2) $language doesn't seem to be used in this function. It's not clear to me what happens if I pass a certain $language, or what the default/special value of NULL means.

+++ includes/locale.inc	2 Nov 2010 14:35:03 -0000
@@ -78,6 +84,28 @@ function locale_language_from_interface(
+  $prefix = variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX) == LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX;
+  return ($prefix && empty($default->prefix)) || (!$prefix && empty($default->domain)) ? FALSE : $GLOBALS[$language_type]->language;

1) Ideally, there should be parenthesis around the entire comparison for $prefix; i.e.:

$prefix = (variable_get() == LANGUAGE);

2) The return line is too condensed and should be written with a proper if/else construct (which additionally allows to write inline comments above the if/else conditions to explain why a certain check means foobar.

+++ modules/locale/locale.install	2 Nov 2010 14:35:03 -0000
@@ -113,6 +113,18 @@ function locale_update_7001() {
+function locale_update_7002() {
...
+  return array();

return can be removed.

+++ modules/locale/locale.install	2 Nov 2010 14:35:03 -0000
@@ -113,6 +113,18 @@ function locale_update_7001() {
+  $language_types_info = language_types_info();
+  $info = $language_types_info[LANGUAGE_TYPE_URL];
+  if (isset($info['fixed'])) {
+    language_negotiation_set(LANGUAGE_TYPE_URL, array_flip($info['fixed']));

1) $language_types_info is $language_types elsewhere, I think.

2) $info is very generic, why not $url_language ?

+++ modules/locale/locale.module	2 Nov 2010 14:35:03 -0000
@@ -582,6 +582,15 @@ function locale_language_negotiation_inf
     'description' => t('Use the detected interface language.'),
...
+    'description' => t('Return a suitable language for URLs if none is detected.'),

I guess that other descriptions start with "Use", which makes more sense from a UX perspective than a technical "Return", so we should re-phrase this description to also start with "Use".

+++ modules/locale/locale.module	2 Nov 2010 14:35:03 -0000
@@ -582,6 +582,15 @@ function locale_language_negotiation_inf
+    'name' => t('URL Fallback'),

"Fallback" should be lowercase, I think.

+++ modules/locale/locale.test	2 Nov 2010 14:35:03 -0000
@@ -1892,6 +1891,55 @@ class LocaleUILanguageNegotiationTest ex
+  function testUrlLanguageFallback() {
+    drupal_static_reset('language_list');

This initial static reset is either superfluous or needs an inline comment for why it is required. (I bet it's superfluous.)

+++ modules/locale/locale.test	2 Nov 2010 14:35:03 -0000
@@ -1892,6 +1891,55 @@ class LocaleUILanguageNegotiationTest ex
+    $path = !empty($GLOBALS['conf']['clean_url']) ? $language_browser_fallback : "?q=$language_browser_fallback";
+    $args = array(':url' => base_path() . $path);
+    $xpath = '//div[@id="block-locale-language"]//a[@class="language-link active" and @href=:url]';
+    $fields = $this->xpath($xpath, $args);
...
+    $xpath = '//div[@id="site-name"]//a[@rel="home" and @href=:url]//span';
+    $fields = $this->xpath($xpath, $args);

These separated code lines look a bit weird. Can we write this like we do elsewhere?

$elements = $this->xpath('//div...', array(
  ':url' => base_path() . (!empty(...),
));

Powered by Dreditor.

plach’s picture

Status: Needs work » Needs review
FileSize
8.69 KB

Thanks sun! Everything should be fixed except:

1) $language_types_info is $language_types elsewhere, I think.

language_types_info() return an indexed array whose keys are language types, but values are again indexed arrays of data. The only other piece of core code invoking it uses $language_types_info.

2) $info is very generic, why not $url_language ?

Because $info holds the language negotiation info for URL language not a language value.

plach’s picture

FileSize
8.66 KB
+++ includes/locale.inc	4 Nov 2010 10:26:34 -0000
@@ -211,6 +217,42 @@ function locale_language_from_url($langu
+ *   A valid language code on success, FALSE otherwise.

We always have a valid language code.

Powered by Dreditor.

plach’s picture

FileSize
9.31 KB

Discussed with sun a far better PHP doc for locale_language_url_fallback().

sun’s picture

Status: Needs review » Reviewed & tested by the community

Thanks! We can with this patch and documentation for now. Ideally, we'll revisit the entire high-level and low-level language system documentation in a separate (non-major) issue.

plach’s picture

Issue tags: +Needs committer feedback

This needs to be committed before rc-1, since we are introducing a couple of new strings (although in core they are never displayed).

Dries’s picture

Status: Reviewed & tested by the community » Fixed

Committed to CVS HEAD. Thanks.

plach’s picture

Thanks Dries!

plach’s picture

Status: Fixed » Closed (fixed)
Issue tags: -Needs committer feedback

Automatically closed -- issue fixed for 2 weeks with no activity.

xjm’s picture

Issue summary: View changes
Issue tags: -Needs committer feedback