Index: includes/bootstrap.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v
retrieving revision 1.299
diff -u -p -r1.299 bootstrap.inc
--- includes/bootstrap.inc	21 Aug 2009 00:00:43 -0000	1.299
+++ includes/bootstrap.inc	21 Aug 2009 09:55:39 -0000
@@ -167,20 +167,24 @@ define('LANGUAGE_NEGOTIATION_NONE', 0);
  * Path based negotiation with fallback to default language
  * if no defined path prefix identified.
  */
-define('LANGUAGE_NEGOTIATION_PATH_DEFAULT', 1);
+define('LANGUAGE_NEGOTIATION_URL_DEFAULT', 1);
 
 /**
  * Path based negotiation with fallback to user preferences
  * and browser language detection if no defined path prefix
  * identified.
  */
-define('LANGUAGE_NEGOTIATION_PATH', 2);
+define('LANGUAGE_NEGOTIATION_URL', 2);
 
 /**
- * Domain based negotiation with fallback to default language
- * if no language identified by domain.
+ * TODO
  */
-define('LANGUAGE_NEGOTIATION_DOMAIN', 3);
+define('LANGUAGE_NEGOTIATION_URL_PREFIX', 3);
+
+/**
+ * TODO
+ */
+define('LANGUAGE_NEGOTIATION_URL_DOMAIN', 4);
 
 /**
  * Language written left to right. Possible value of $language->direction.
@@ -1620,16 +1624,20 @@ function get_t() {
  *  Choose a language for the current page, based on site and user preferences.
  */
 function drupal_language_initialize() {
-  global $language, $user;
+  global $language, $content_language, $user;
 
   // Ensure the language is correctly returned, even without multilanguage support.
   // Useful for eg. XML/HTML 'lang' attributes.
   if (variable_get('language_count', 1) == 1) {
-    $language = language_default();
+    $language = $content_language = language_default();
   }
   else {
     include_once DRUPAL_ROOT . '/includes/language.inc';
     $language = language_initialize();
+    $content_language = language_from_url();
+    if ($content_language->language == language_none()->language) {
+      $content_language = language_default();
+    }
   }
 }
 
@@ -1680,6 +1688,25 @@ function language_default($property = NU
 }
 
 /**
+ * Language neutral content language.
+ */
+function language_none() {
+  return (object) array(
+    'language' => 'zxx',
+    'name' => '',
+    'native' => '',
+    'direction' => 0,
+    'enabled' => 1,
+    'plurals' => '',
+    'formula' => '',
+    'domain' => '',
+    'prefix' => '',
+    'weight' => 0,
+    'javascript' => '',
+  );
+}
+
+/**
  * If Drupal is behind a reverse proxy, we use the X-Forwarded-For header
  * instead of $_SERVER['REMOTE_ADDR'], which would be the IP address of
  * the proxy server, and not the client's. If Drupal is run in a cluster
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.965
diff -u -p -r1.965 common.inc
--- includes/common.inc	21 Aug 2009 07:50:07 -0000	1.965
+++ includes/common.inc	21 Aug 2009 09:55:42 -0000
@@ -2112,6 +2112,7 @@ function url($path = NULL, array $option
     'alias' => FALSE,
     'prefix' => ''
   );
+
   if (!isset($options['external'])) {
     // Return an external link if $path contains an allowed absolute URL.
     // Only call the slow filter_xss_bad_protocol if $path contains a ':' before
@@ -2275,7 +2276,7 @@ function drupal_attributes(array $attrib
  *   an HTML string containing a link to the given path.
  */
 function l($text, $path, array $options = array()) {
-  global $language;
+  global $content_language;
 
   // Merge in defaults.
   $options += array(
@@ -2285,7 +2286,7 @@ function l($text, $path, array $options 
 
   // Append active class.
   if (($path == $_GET['q'] || ($path == '<front>' && drupal_is_front_page())) &&
-      (empty($options['language']) || $options['language']->language == $language->language)) {
+      (empty($options['language']) || $options['language']->language == $content_language->language)) {
     if (isset($options['attributes']['class'])) {
       $options['attributes']['class'] .= ' active';
     }
Index: includes/language.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/language.inc,v
retrieving revision 1.19
diff -u -p -r1.19 language.inc
--- includes/language.inc	1 Feb 2009 16:45:53 -0000	1.19
+++ includes/language.inc	21 Aug 2009 08:09:06 -0000
@@ -12,53 +12,39 @@
 function language_initialize() {
   global $user;
 
+  // User preference, if allowed.
+  if ($user->uid && !variable_get('language_negotiation_user_override', TRUE)) {
+    return language_from_user();
+  }
+
   // Configured presentation language mode.
   $mode = variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE);
-  // Get a list of enabled languages.
-  $languages = language_list('enabled');
-  $languages = $languages[1];
-
   switch ($mode) {
     case LANGUAGE_NEGOTIATION_NONE:
       return language_default();
 
-    case LANGUAGE_NEGOTIATION_DOMAIN:
-      foreach ($languages as $language) {
-        $host = parse_url($language->domain, PHP_URL_HOST);
-        if ($host && ($_SERVER['HTTP_HOST'] == $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;
-        }
+    case LANGUAGE_NEGOTIATION_URL_DEFAULT:
+    case LANGUAGE_NEGOTIATION_URL:
+      $language = language_from_url();
+      // If the URL language is defined we can use it.
+      if ($language->language != language_none()->language) {
+        return $language;
       }
-      if ($mode == LANGUAGE_NEGOTIATION_PATH_DEFAULT) {
-        // If we did not found the language by prefix, choose the default.
+      if ($mode == LANGUAGE_NEGOTIATION_URL_DEFAULT) {
+        // If we did not found the language by URL, choose the default.
         return language_default();
       }
+      else if ($user->uid) {
+        // Fall back to the user preferred language.
+        return language_from_user();
+      }
       break;
   }
 
-  // User language.
-  if ($user->uid && isset($languages[$user->language])) {
-    return $languages[$user->language];
-  }
-
-  // Browser accept-language parsing.
-  if ($language = language_from_browser()) {
+  // Browser accept-language parsing: only if cache is disabled, otherwise
+  // we would cache a user-specific preference.
+  $cache_mode = variable_get('cache');
+  if ($cache_mode == CACHE_DISABLED && $language = language_from_browser()) {
     return $language;
   }
 
@@ -99,27 +85,87 @@ function language_from_browser() {
 }
 
 /**
+ * Identify the current content language via URL prefix or domain.
+ */
+function language_from_url() {
+  $content_language = &drupal_static(__FUNCTION__);
+
+  if (!isset($content_language)) {
+    $languages = language_list('enabled');
+    $languages = $languages[1];
+    // $_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);
+    $content_language = language_none();
+  
+    switch (variable_get('language_negotiation_url', LANGUAGE_NEGOTIATION_URL_PREFIX)) {
+      case LANGUAGE_NEGOTIATION_URL_PREFIX:
+        // 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);
+            $content_language = $language;
+            break;
+          }
+        }
+        break;
+  
+      case LANGUAGE_NEGOTIATION_URL_DOMAIN:
+        foreach ($languages as $language) {
+          $host = parse_url($language->domain, PHP_URL_HOST);
+          if ($host && ($_SERVER['HTTP_HOST'] == $host)) {
+            $content_language = $language;
+            break;
+          }
+        }
+        break;
+    }
+  }
+
+  return $content_language;
+}
+
+/**
+ * TODO
+ */
+function language_from_user() {
+  $languages = language_list('enabled');
+  $languages = $languages[1];
+
+  // Request parameter.
+  if (isset($_GET['ui_language']) && isset($languages[$langcode = $_GET['ui_language']])) {
+    return $_SESSION['ui_language'] = $languages[$langcode];
+  }
+  // Session parameter.
+  if (isset($_SESSION['ui_language'])) {
+    return $_SESSION['ui_language'];
+  }
+  // User preference.
+  global $user;
+  if (isset($languages[$user->language])) {
+    return $languages[$user->language];
+  }
+}
+
+/**
  * Rewrite URLs with language based prefix. Parameters are the same
  * as those of the url() function.
  */
 function language_url_rewrite(&$path, &$options) {
-  global $language;
+  global $content_language;
 
   // Only modify relative (insite) URLs.
   if (!$options['external']) {
 
     // Language can be passed as an option, or we go for current language.
     if (!isset($options['language'])) {
-      $options['language'] = $language;
+      $options['language'] = $content_language;
     }
 
-    switch (variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE)) {
-      case LANGUAGE_NEGOTIATION_NONE:
-        // No language dependent path allowed in this mode.
-        unset($options['language']);
-        break;
-
-      case LANGUAGE_NEGOTIATION_DOMAIN:
+    switch (variable_get('language_negotiation_url', LANGUAGE_NEGOTIATION_URL_PREFIX)) {
+      case LANGUAGE_NEGOTIATION_URL_DOMAIN:
         if ($options['language']->domain) {
           // Ask for an absolute URL with our modified base_url.
           $options['absolute'] = TRUE;
@@ -127,14 +173,7 @@ function language_url_rewrite(&$path, &$
         }
         break;
 
-      case LANGUAGE_NEGOTIATION_PATH_DEFAULT:
-        $default = language_default();
-        if ($options['language']->language == $default->language) {
-          break;
-        }
-        // Intentionally no break here.
-
-      case LANGUAGE_NEGOTIATION_PATH:
+      case LANGUAGE_NEGOTIATION_URL_PREFIX:
         if (!empty($options['language']->prefix)) {
           $options['prefix'] = $options['language']->prefix . '/';
         }
Index: includes/locale.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/locale.inc,v
retrieving revision 1.224
diff -u -p -r1.224 locale.inc
--- includes/locale.inc	17 Aug 2009 19:14:39 -0000	1.224
+++ includes/locale.inc	20 Aug 2009 10:07:06 -0000
@@ -477,15 +477,30 @@ function locale_languages_delete_form_su
  */
 function locale_languages_configure_form() {
   $form['language_negotiation'] = array(
-    '#title' => t('Language negotiation'),
+    '#title' => t('Interface language negotiation'),
     '#type' => 'radios',
     '#options' => array(
       LANGUAGE_NEGOTIATION_NONE => t('None.'),
-      LANGUAGE_NEGOTIATION_PATH_DEFAULT => t('Path prefix only.'),
-      LANGUAGE_NEGOTIATION_PATH => t('Path prefix with language fallback.'),
-      LANGUAGE_NEGOTIATION_DOMAIN => t('Domain name only.')),
+      LANGUAGE_NEGOTIATION_URL_DEFAULT => t('URL only.'),
+      LANGUAGE_NEGOTIATION_URL => t('URL with language fallback.'),
+    ),
     '#default_value' => variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE),
-    '#description' => t("Select the mechanism used to determine your site's presentation language. <strong>Modifying this setting may break all incoming URLs and should be used with caution in a production environment.</strong>")
+    '#description' => t("Select the mechanism used to determine your site's presentation language.")
+  );
+  $form['language_negotiation_user_override'] = array(
+    '#title' => t('Override user preferences'),
+    '#type' => 'checkbox',
+    '#default_value' => variable_get('language_negotiation_user_override', TRUE),
+  );
+  $form['language_negotiation_url'] = array(
+    '#title' => t('Content language negotiation'),
+    '#type' => 'radios',
+    '#options' => array(
+      LANGUAGE_NEGOTIATION_URL_PREFIX => t('URL Prefix.'),
+      LANGUAGE_NEGOTIATION_URL_DOMAIN => t('URL Domain.'),
+    ),
+    '#default_value' => variable_get('language_negotiation_url', LANGUAGE_NEGOTIATION_URL_PREFIX),
+    '#description' => t("Select which part of the URL will determine the content language.<br/><strong>Modifying this setting may break all incoming URLs and should be used with caution in a production environment.</strong>")
   );
   $form['submit'] = array(
     '#type' => 'submit',
@@ -499,6 +514,8 @@ function locale_languages_configure_form
  */
 function locale_languages_configure_form_submit($form, &$form_state) {
   variable_set('language_negotiation', $form_state['values']['language_negotiation']);
+  variable_set('language_negotiation_user_override', $form_state['values']['language_negotiation_user_override']);
+  variable_set('language_negotiation_url', $form_state['values']['language_negotiation_url']);
   drupal_set_message(t('Language negotiation configuration saved.'));
   $form_state['redirect'] = 'admin/config/international/language';
   return;
Index: includes/theme.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.inc,v
retrieving revision 1.507
diff -u -p -r1.507 theme.inc
--- includes/theme.inc	20 Aug 2009 15:18:09 -0000	1.507
+++ includes/theme.inc	21 Aug 2009 09:55:40 -0000
@@ -1390,7 +1390,7 @@ function theme_status_messages($display 
  *   A string containing an unordered list of links.
  */
 function theme_links($links, $attributes = array('class' => 'links')) {
-  global $language;
+  global $content_language;
   $output = '';
 
   if (count($links) > 0) {
@@ -1410,7 +1410,7 @@ function theme_links($links, $attributes
         $class .= ' last';
       }
       if (isset($link['href']) && ($link['href'] == $_GET['q'] || ($link['href'] == '<front>' && drupal_is_front_page()))
-          && (empty($link['language']) || $link['language']->language == $language->language)) {
+          && (empty($link['language']) || $link['language']->language == $content_language->language)) {
         $class .= ' active';
       }
       $output .= '<li' . drupal_attributes(array('class' => $class)) . '>';
Index: modules/locale/locale.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.module,v
retrieving revision 1.250
diff -u -p -r1.250 locale.module
--- modules/locale/locale.module	17 Aug 2009 20:32:29 -0000	1.250
+++ modules/locale/locale.module	21 Aug 2009 08:18:04 -0000
@@ -251,13 +251,13 @@ function locale_language_selector_form($
   );
 
   // Get language negotiation settings.
-  $mode = variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE);
+  $mode = !variable_get('language_negotiation_user_override', TRUE);
   $form['locale']['language'] = array(
     '#type' => (count($names) <= 5 ? 'radios' : 'select'),
     '#title' => t('Language'),
     '#default_value' => $user_preferred_language->language,
     '#options' => $names,
-    '#description' => ($mode == LANGUAGE_NEGOTIATION_PATH) ? t("This account's default language for e-mails, and preferred language for site presentation.") : t("This account's default language for e-mails."),
+    '#description' => $mode ? t("This account's default language for e-mails, and preferred language for site presentation.") : t("This account's default language for e-mails."),
   );
   return $form;
 }
