Index: includes/locale.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/locale.inc,v
retrieving revision 1.261
diff -u -p -r1.261 locale.inc
--- includes/locale.inc	26 Oct 2010 15:24:50 -0000	1.261
+++ includes/locale.inc	4 Nov 2010 19:04:28 -0000
@@ -23,6 +23,12 @@ define('LOCALE_LANGUAGE_NEGOTIATION_BROW
 define('LOCALE_LANGUAGE_NEGOTIATION_INTERFACE', 'locale-interface');
 
 /**
+ * If no URL language is available language is determined using an already
+ * detected one.
+ */
+define('LOCALE_LANGUAGE_NEGOTIATION_URL_FALLBACK', 'locale-url-fallback');
+
+/**
  * The language is set based on the user language settings.
  */
 define('LOCALE_LANGUAGE_NEGOTIATION_USER', 'locale-user');
@@ -211,6 +217,53 @@ function locale_language_from_url($langu
 }
 
 /**
+ * Determines the language to be assigned to URLs when none is detected.
+ *
+ * The language negotiation process has a fallback chain that ends with the
+ * default language provider. Each built-in language type has a separate
+ * initialization:
+ * - Interface language, which is the only configurable one, always gets a valid
+ *   value. If no request-specific language is detected, the default language
+ *   will be used.
+ * - Content language merely inherits the interface language by default.
+ * - URL language is detected from the requested URL and will be used to rewrite
+ *   URLs appearing in the page being rendered. If no language can be detected,
+ *   there are two possibilities:
+ *   - If the default language has no configured path prefix or domain, then the
+ *     default language is used. This guarantees that (missing) URL prefixes are
+ *     preserved when navigating through the site.
+ *   - If the default language has a configured path prefix or domain, a
+ *     requested URL having an empty prefix or domain is an anomaly that must be
+ *     fixed. This is done by introducing a prefix or domain in the rendered
+ *     page matching the detected interface language.
+ *
+ * @param $languages
+ *   (optional) An array of valid language objects. This is passed by
+ *   language_provider_invoke() to every language provider callback, but it is
+ *   not actually needed here. Defaults to NULL.
+ * @param $language_type
+ *   (optional) The language type to fall back to. Defaults to the interface
+ *   language.
+ *
+ * @return
+ *   A valid language code.
+ */
+function locale_language_url_fallback($language = NULL, $language_type = LANGUAGE_TYPE_INTERFACE) {
+  $default = language_default();
+  $prefix = (variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX) == LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX);
+
+  // If the default language is not configured to convey language information,
+  // a missing URL language information indicates that URL language should be
+  // the default one, otherwise we fall back to an already detected language.
+  if (($prefix && empty($default->prefix)) || (!$prefix && empty($default->domain))) {
+    return $default->language;
+  }
+  else {
+    return $GLOBALS[$language_type]->language;
+  }
+}
+
+/**
  * Return the URL language switcher block. Translation links may be provided by
  * other modules.
  */
Index: modules/locale/locale.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.install,v
retrieving revision 1.69
diff -u -p -r1.69 locale.install
--- modules/locale/locale.install	20 Oct 2010 00:37:20 -0000	1.69
+++ modules/locale/locale.install	4 Nov 2010 17:01:40 -0000
@@ -113,6 +113,17 @@ function locale_update_7001() {
 }
 
 /**
+ * Updates URL language negotiation by adding the URL fallback detection method.
+ */
+function locale_update_7002() {
+  $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']));
+  }
+}
+
+/**
  * @} End of "defgroup updates-6.x-to-7.x"
  */
 
Index: modules/locale/locale.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.module,v
retrieving revision 1.304
diff -u -p -r1.304 locale.module
--- modules/locale/locale.module	25 Oct 2010 15:14:33 -0000	1.304
+++ modules/locale/locale.module	4 Nov 2010 17:01:40 -0000
@@ -503,8 +503,8 @@ function locale_entity_info_alter(&$enti
  *   language negotiated value. It is used by the Field API to determine the
  *   display language for fields if no explicit value is specified.
  * - URL language is by default non-configurable and is determined through the
- *   URL language provider. It is used by l() as the default language if none is
- *   specified.
+ *   URL language provider or the URL fallback provider if no language can be
+ *   detected. It is used by l() as the default language if none is specified.
  */
 function locale_language_types_info() {
   require_once DRUPAL_ROOT . '/includes/locale.inc';
@@ -517,7 +517,7 @@ function locale_language_types_info() {
       'fixed' => array(LOCALE_LANGUAGE_NEGOTIATION_INTERFACE),
     ),
     LANGUAGE_TYPE_URL => array(
-      'fixed' => array(LOCALE_LANGUAGE_NEGOTIATION_URL),
+      'fixed' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LOCALE_LANGUAGE_NEGOTIATION_URL_FALLBACK),
     ),
   );
 }
@@ -582,6 +582,15 @@ function locale_language_negotiation_inf
     'description' => t('Use the detected interface language.'),
   );
 
+  $providers[LOCALE_LANGUAGE_NEGOTIATION_URL_FALLBACK] = array(
+    'types' => array(LANGUAGE_TYPE_URL),
+    'callbacks' => array('language' => 'locale_language_url_fallback'),
+    'file' => $file,
+    'weight' => 8,
+    'name' => t('URL fallback'),
+    'description' => t('Use an already detected language for URLs if none is found.'),
+  );
+
   return $providers;
 }
 
Index: modules/locale/locale.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.test,v
retrieving revision 1.83
diff -u -p -r1.83 locale.test
--- modules/locale/locale.test	26 Oct 2010 15:24:50 -0000	1.83
+++ modules/locale/locale.test	4 Nov 2010 17:01:40 -0000
@@ -1730,15 +1730,14 @@ class LocaleUILanguageNegotiationTest ex
     parent::setUp('locale', 'locale_test');
     require_once DRUPAL_ROOT . '/includes/language.inc';
     drupal_load('module', 'locale');
+    $admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages', 'administer blocks'));
+    $this->drupalLogin($admin_user);
   }
 
   /**
    * Tests for language switching by URL path.
    */
   function testUILanguageNegotiation() {
-    $admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages'));
-    $this->drupalLogin($admin_user);
-
     // A few languages to switch to.
     // This one is unknown, should get the default lang version.
     $language_unknown = 'blah-blah';
@@ -1892,6 +1891,50 @@ class LocaleUILanguageNegotiationTest ex
     $this->drupalGet($test['path'], array(), $test['http_header']);
     $this->assertText($test['expect'], $test['message']);
   }
+
+  /**
+   * Test URL language detection when the requested URL has no language.
+   */
+  function testUrlLanguageFallback() {
+    // Add the Italian language.
+    $language_browser_fallback = 'it';
+    locale_add_language($language_browser_fallback);
+    $languages = language_list();
+
+    // Enable the path prefix for the default language: this way any unprefixed
+    // URL must have a valid fallback value.
+    $edit = array('prefix' => 'en');
+    $this->drupalPost('admin/config/regional/language/edit/en', $edit, t('Save language'));
+
+    // Enable browser and URL language detection.
+    $edit = array(
+      'language[enabled][locale-browser]' => TRUE,
+      'language[enabled][locale-url]' => TRUE,
+      'language[weight][locale-browser]' => -8,
+      'language[weight][locale-url]' => -10,
+    );
+    $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
+    $this->drupalGet('admin/config/regional/language/configure');
+
+    // Enable the language switcher block.
+    $edit = array('blocks[locale_language][region]' => 'sidebar_first');
+    $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
+
+    // Access the front page without specifying any valid URL language prefix
+    // and having as browser language preference a non-default language.
+    $http_header = array("Accept-Language: $language_browser_fallback;q=1");
+    $this->drupalGet('', array(), $http_header);
+
+    // Check that the language switcher active link matches the given browser
+    // language.
+    $args = array(':url' => base_path() . (!empty($GLOBALS['conf']['clean_url']) ? $language_browser_fallback : "?q=$language_browser_fallback"));
+    $fields = $this->xpath('//div[@id="block-locale-language"]//a[@class="language-link active" and @href=:url]', $args);
+    $this->assertTrue($fields[0] == $languages[$language_browser_fallback]->native, t('The browser language is the URL active language'));
+
+    // Check that URLs are rewritten using the given browser language.
+    $fields = $this->xpath('//div[@id="site-name"]//a[@rel="home" and @href=:url]//span', $args);
+    $this->assertTrue($fields[0] == 'Drupal', t('URLs are rewritten using the browser language.'));
+  }
 }
 
 /**
