Index: includes/bootstrap.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v
retrieving revision 1.302
diff -u -p -r1.302 bootstrap.inc
--- includes/bootstrap.inc	24 Aug 2009 00:14:18 -0000	1.302
+++ includes/bootstrap.inc	28 Aug 2009 00:24:03 -0000
@@ -159,28 +159,39 @@ define('DRUPAL_AUTHENTICATED_RID', 2);
 define('DRUPAL_KILOBYTE', 1024);
 
 /**
- * No language negotiation. The default language is used.
+ * No UI language negotiation. The default language is used.
  */
-define('LANGUAGE_NEGOTIATION_NONE', 0);
+define('LANGUAGE_NEGOTIATION_DEFAULT', 0);
 
 /**
- * Path based negotiation with fallback to default language
- * if no defined path prefix identified.
+ * The UI language is the same as the content language if an URL
+ * language indicator is found.
  */
-define('LANGUAGE_NEGOTIATION_PATH_DEFAULT', 1);
+define('LANGUAGE_NEGOTIATION_CONTENT', 1);
 
 /**
- * Path based negotiation with fallback to user preferences
- * and browser language detection if no defined path prefix
- * identified.
+ * The UI language is set basing on the user preferences. Request
+ * parameters are checked, then session parameters, and eventually
+ * the user language settings. 
  */
-define('LANGUAGE_NEGOTIATION_PATH', 2);
+define('LANGUAGE_NEGOTIATION_USER', 2);
 
 /**
- * Domain based negotiation with fallback to default language
- * if no language identified by domain.
+ * The UI language is set basing on the browser language settings.
  */
-define('LANGUAGE_NEGOTIATION_DOMAIN', 3);
+define('LANGUAGE_NEGOTIATION_BROWSER', 3);
+
+/**
+ * Content language negotiation: use the path prefix as URL language
+ * indicator.
+ */
+define('LANGUAGE_NEGOTIATION_URL_PREFIX', 0);
+
+/**
+ * Content language negotiation: use the domain as URL language
+ * indicator.
+ */
+define('LANGUAGE_NEGOTIATION_URL_DOMAIN', 1);
 
 /**
  * Language written left to right. Possible value of $language->direction.
@@ -1641,16 +1652,22 @@ 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, $language_ui, $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 = $language_ui = language_default();
   }
   else {
     include_once DRUPAL_ROOT . '/includes/language.inc';
-    $language = language_initialize();
+    // Content language should be explicitly identified by its URL, if we cannot find
+    // a proper language indicator we assume the content has the default language.
+    $language = language_from_url();
+    if (!$language) {
+      $language = language_default();
+    }
+    $language_ui = language_ui_initialize();
   }
 }
 
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.977
diff -u -p -r1.977 common.inc
--- includes/common.inc	26 Aug 2009 15:00:17 -0000	1.977
+++ includes/common.inc	27 Aug 2009 22:22:39 -0000
@@ -1193,12 +1193,12 @@ function fix_gpc_magic() {
  *   The translated string.
  */
 function t($string, array $args = array(), array $options = array()) {
-  global $language;
+  global $language_ui;
   static $custom_strings;
 
   // Merge in default.
   if (empty($options['langcode'])) {
-    $options['langcode'] = isset($language->language) ? $language->language : 'en';
+    $options['langcode'] = isset($language_ui->language) ? $language_ui->language : 'en';
   }
   if (empty($options['context'])) {
     $options['context'] = '';
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	28 Aug 2009 00:27:17 -0000
@@ -7,95 +7,177 @@
  */
 
 /**
- *  Choose a language for the page, based on language negotiation settings.
+ * Return the current main language negotiation mode.
  */
-function language_initialize() {
-  global $user;
+function language_negotiation() {
+  $negotiation = variable_get('language_negotiation');
+  if (empty($negotiation)) {
+    return LANGUAGE_NEGOTIATION_DEFAULT;
+  }
+  return key($negotiation);
+}
+
+/**
+ * Return all the defined language providers.
+ */
+function language_providers() {
+  $language_providers = &drupal_static(__FUNCTION__);
+  if (!isset($language_providers)) {
+    $language_providers = array();
+    foreach (module_implements('language_providers') as $module) {
+      $language_providers += module_invoke($module, 'language_providers');
+    }
+    // Let other modules alter the list of language providers.
+    drupal_alter('language_providers', $language_providers);
+  }
+  return $language_providers;
+}
 
-  // Configured presentation language mode.
-  $mode = variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE);
-  // Get a list of enabled languages.
+/**
+ * Choose a language for the interface, based on the language negotiation settings.
+ */
+function language_ui_initialize() {
   $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;
-        }
+  // Execute the language providers in the order they were set up and return the first 
+  // valid language found.
+  $negotiation = variable_get('language_negotiation', array());
+  foreach ($negotiation as $provider) {
+    $callback = $provider['callback'];
+
+    if (isset($provider['file'])) {
+      require_once DRUPAL_ROOT .'/'. $provider['file'];
+    }
+
+    if (function_exists($callback)) {
+      $language = $callback();
+      if (isset($language->language) && isset($languages[$language->language])) {
+        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 no other language was found use the default one.
+  return language_default();
+}
+
+/**
+ * Identify language from the Accept-language HTTP header we got.
+ * 
+ * We perform browser accept-language parsing only if page cache is disabled, 
+ * otherwise we would cache an user-specific preference.
+ */
+function language_from_browser() {
+  if (variable_get('cache', CACHE_DISABLED) == CACHE_DISABLED) {
+    // Specified by the user via the browser's Accept Language setting
+    // Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5"
+    $browser_langs = array();
+  
+    if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
+      $browser_accept = explode(",", $_SERVER['HTTP_ACCEPT_LANGUAGE']);
+      for ($i = 0; $i < count($browser_accept); $i++) {
+        // The language part is either a code or a code with a quality.
+        // We cannot do anything with a * code, so it is skipped.
+        // If the quality is missing, it is assumed to be 1 according to the RFC.
+        if (preg_match("!([a-z-]+)(;q=([0-9\\.]+))?!", trim($browser_accept[$i]), $found)) {
+          $browser_langs[$found[1]] = (isset($found[3]) ? (float) $found[3] : 1.0);
         }
       }
-      if ($mode == LANGUAGE_NEGOTIATION_PATH_DEFAULT) {
-        // If we did not found the language by prefix, choose the default.
-        return language_default();
+    }
+  
+    // Order the codes by quality
+    arsort($browser_langs);
+  
+    // Try to find the first preferred language we have
+    $languages = language_list('enabled');
+    foreach ($browser_langs as $langcode => $q) {
+      if (isset($languages['1'][$langcode])) {
+        return $languages['1'][$langcode];
       }
-      break;
+    }
   }
 
-  // User language.
+  return FALSE;
+}
+
+/**
+ * Identify language from the user preferences.
+ */
+function language_from_user() {
+  $languages = language_list('enabled');
+  $languages = $languages[1];
+
+  // Request parameter.
+  if (isset($_GET['language_ui']) && isset($languages[$langcode = $_GET['language_ui']])) {
+    return $_SESSION['language_ui'] = $languages[$langcode];
+  }
+  // Session parameter.
+  if (isset($_SESSION['language_ui'])) {
+    return $_SESSION['language_ui'];
+  }
+  // User preference (only for logged users).
+  global $user;
   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();
+  // No language preference from the user.
+  return FALSE;
 }
 
 /**
- * Identify language from the Accept-language HTTP header we got.
+ * Identify language via URL prefix or domain.
  */
-function language_from_browser() {
-  // Specified by the user via the browser's Accept Language setting
-  // Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5"
-  $browser_langs = array();
-
-  if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
-    $browser_accept = explode(",", $_SERVER['HTTP_ACCEPT_LANGUAGE']);
-    for ($i = 0; $i < count($browser_accept); $i++) {
-      // The language part is either a code or a code with a quality.
-      // We cannot do anything with a * code, so it is skipped.
-      // If the quality is missing, it is assumed to be 1 according to the RFC.
-      if (preg_match("!([a-z-]+)(;q=([0-9\\.]+))?!", trim($browser_accept[$i]), $found)) {
-        $browser_langs[$found[1]] = (isset($found[3]) ? (float) $found[3] : 1.0);
-      }
+function language_from_url() {
+  $url_language = &drupal_static(__FUNCTION__);
+
+  if (!isset($url_language)) {
+    $url_language = FALSE;
+  
+    switch (variable_get('language_negotiation_url', LANGUAGE_NEGOTIATION_URL_PREFIX)) {
+      case LANGUAGE_NEGOTIATION_URL_PREFIX:
+        // $_GET['q'] might not be available at this time, because
+        // path initialization runs after the language bootstrap phase.
+        list($language, $_GET['q']) = language_split_prefix(isset($_GET['q']) ? $_GET['q'] : NULL);
+        if ($language !== FALSE) {
+          $url_language = $language;
+        }
+        break;
+  
+      case LANGUAGE_NEGOTIATION_URL_DOMAIN:
+        $languages = language_list('enabled');
+        $languages = $languages[1];
+        foreach ($languages as $language) {
+          $host = parse_url($language->domain, PHP_URL_HOST);
+          if ($host && ($_SERVER['HTTP_HOST'] == $host)) {
+            $url_language = $language;
+            break;
+          }
+        }
+        break;
     }
   }
 
-  // Order the codes by quality
-  arsort($browser_langs);
+  return $url_language;
+}
 
-  // Try to find the first preferred language we have
+/**
+ * Parse the given path and return the language identified by the
+ * prefix and the actual path.
+ */
+function language_split_prefix($path) {
   $languages = language_list('enabled');
-  foreach ($browser_langs as $langcode => $q) {
-    if (isset($languages['1'][$langcode])) {
-      return $languages['1'][$langcode];
+  $args = empty($path) ? array() : explode('/', $path);
+  $prefix = array_shift($args);
+  // Search prefix within enabled languages.
+  foreach ($languages[1] as $language) {
+    if (!empty($language->prefix) && $language->prefix == $prefix) {
+      // Rebuild $path with the language removed.
+      return array($language, implode('/', $args));
     }
   }
+  return array(FALSE, $path);
 }
 
 /**
@@ -113,13 +195,8 @@ function language_url_rewrite(&$path, &$
       $options['language'] = $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 +204,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.226
diff -u -p -r1.226 locale.inc
--- includes/locale.inc	22 Aug 2009 14:34:17 -0000	1.226
+++ includes/locale.inc	27 Aug 2009 23:25:14 -0000
@@ -476,33 +476,143 @@ function locale_languages_delete_form_su
  * Setting for language negotiation options
  */
 function locale_languages_configure_form() {
-  $form['language_negotiation'] = array(
-    '#title' => t('Language negotiation'),
+  $form['language_negotiation_url'] = array(
+    '#title' => t('Content 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.')),
-    '#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>")
+      LANGUAGE_NEGOTIATION_URL_PREFIX => t('Path prefix.'),
+      LANGUAGE_NEGOTIATION_URL_DOMAIN => t('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['weight'] = array('#tree' => TRUE);
+  $form['enabled'] = array('#tree' => TRUE);
+  $form['#show_config_path'] = FALSE;
+  $form['#language_providers'] = language_providers();
+
+  $enabled_providers = variable_get('language_providers_enabled', array());
+  $providers_weight = variable_get('language_providers_weight', array());
+  uksort($form['#language_providers'], '_language_providers_weight_compare');
+
+  foreach ($form['#language_providers'] as $id => $provider) {
+    $form['weight'][$id] = array(
+      '#type' => 'weight',
+      '#default_value' => @$providers_weight[$id],
+      '#attributes' => array('class' => array('language-provider-weight')),
+    );
+    $form['title'][$id] = array('#markup' => check_plain($provider['title']));
+    $form['enabled'][$id] = array('#type' => 'checkbox', '#default_value' => @$enabled_providers[$id]);
+    if ($id == LANGUAGE_NEGOTIATION_DEFAULT) {
+      $form['enabled'][$id]['#default_value'] = TRUE;
+      $form['enabled'][$id]['#attributes'] = array('disabled' => 'disabled');
+    }
+    $form['description'][$id] = array('#markup' => check_markup($provider['description']));
+    $config_op = '';
+    if (isset($provider['config_path'])) {
+      $config_op = l(t('Configure'), $provider['config_path']);
+      // If there is at least one operation enabled show the operation column.
+      $form['#show_config_path'] = TRUE;
+    }
+    $form['operation'][$id] = array('#markup' => $config_op);
+  }
+
   $form['submit'] = array(
     '#type' => 'submit',
     '#value' => t('Save settings')
   );
+
+  $form['#theme'] = 'locale_languages_configure_form';
+
   return $form;
 }
 
 /**
+ * Theme the language configure form.
+ *
+ * @ingroup themeable
+ */
+function theme_locale_languages_configure_form($form) {
+  $output = drupal_render($form['language_negotiation_url']);
+  $output .= '<div class="form-item"><label>' . t('Interface language negotiation') . '</label></div>';
+  
+  foreach ($form['title'] as $id => $element) {
+    // Do not take form control structures.
+    if (is_array($element) && element_child($id)) {
+      $row = array(
+        'data' => array(
+          '<strong>' . drupal_render($form['title'][$id]) . '</strong>',
+          drupal_render($form['description'][$id]),
+          drupal_render($form['enabled'][$id]),
+          drupal_render($form['weight'][$id]),
+        ),
+        'class' => array('draggable'),
+      );
+      if ($form['#show_config_path']) {
+        $row['data'][] = drupal_render($form['operation'][$id]);
+      }
+      $rows[] = $row;
+    }
+  }
+
+  $header = array(
+    array('data' => t('Detection method')), 
+    array('data' => t('Description')),
+    array('data' => t('Enabled')),
+    array('data' => t('Weight')),
+  );
+
+  // If there is at least one operation enabled show the operation column.
+  if ($form['#show_config_path']) {
+    $header[] = array('data' => t('Configure'));
+  }
+
+  $output .= theme('table', $header, $rows, array('id' => 'language-negotiation-providers'));
+  $output .= drupal_render_children($form);
+
+  drupal_add_tabledrag('language-negotiation-providers', 'order', 'sibling', 'language-provider-weight');
+
+  return $output;
+}
+
+/**
  * Submit function for language negotiation settings.
  */
 function locale_languages_configure_form_submit($form, &$form_state) {
-  variable_set('language_negotiation', $form_state['values']['language_negotiation']);
+  $negotiation = array();
+  $enabled_providers = $form_state['values']['enabled'];
+  $enabled_providers[LANGUAGE_NEGOTIATION_DEFAULT] = TRUE;
+  $providers_weight = $form_state['values']['weight'];
+  asort($providers_weight);
+
+  foreach ($providers_weight as $id => $weight) {
+    if ($enabled_providers[$id]) {
+      $provider = $form['#language_providers'][$id];
+      $negotiation[$id] = array('callback' => $provider['callback']);
+      if (isset($provider['file'])) {
+        $negotiation[$id]['file'] = $provider['file'];
+      }
+    }
+  }
+
+  variable_set('language_negotiation', $negotiation);
+  variable_set('language_providers_enabled', $enabled_providers);
+  variable_set('language_providers_weight', $providers_weight);
+  variable_set('language_negotiation_url', $form_state['values']['language_negotiation_url']);
   drupal_set_message(t('Language negotiation configuration saved.'));
   $form_state['redirect'] = 'admin/config/regional/language';
-  return;
 }
+
+/**
+ * Helper function used to compare the language providers' weights.
+ */
+function _language_providers_weight_compare($v1, $v2) {
+  $providers_weight = variable_get('language_providers_weight', array());
+  @$diff = $providers_weight[$v1] - $providers_weight[$v2];
+  return $diff ? $diff : strcmp($v1, $v2);
+}
+
 /**
  * @} End of "locale-languages-negotiation"
  */
Index: modules/locale/locale.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.install,v
retrieving revision 1.47
diff -u -p -r1.47 locale.install
--- modules/locale/locale.install	17 Aug 2009 19:14:40 -0000	1.47
+++ modules/locale/locale.install	27 Aug 2009 22:22:39 -0000
@@ -68,6 +68,9 @@ function locale_uninstall() {
   variable_del('language_default');
   variable_del('language_count');
   variable_del('language_negotiation');
+  variable_del('language_negotiation_url');
+  variable_del('language_providers_enabled');
+  variable_del('language_providers_weight');
   variable_del('javascript_parsed');
   variable_del('language_content_type_default');
   variable_del('language_content_type_negotiation');
Index: modules/locale/locale.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.module,v
retrieving revision 1.256
diff -u -p -r1.256 locale.module
--- modules/locale/locale.module	24 Aug 2009 00:14:20 -0000	1.256
+++ modules/locale/locale.module	27 Aug 2009 23:40:28 -0000
@@ -37,14 +37,6 @@ function locale_help($path, $arg) {
       return $output;
     case 'admin/config/regional/language/add':
       return '<p>' . t('Add all languages to be supported by your site. If your desired language is not available in the <em>Language name</em> drop-down, click <em>Custom language</em> and provide a language code and other details manually. When providing a language code manually, be sure to enter a standardized language code, since this code may be used by browsers to determine an appropriate display language.') . '</p>';
-    case 'admin/config/regional/language/configure':
-      $output = '<p>' . t("Language negotiation settings determine the site's presentation language. Available options include:") . '</p>';
-      $output .= '<ul><li>' . t('<strong>None.</strong> The default language is used for site presentation, though users may (optionally) select a preferred language on the <em>My Account</em> page. (User language preferences will be used for site e-mails, if available.)') . '</li>';
-      $output .= '<li>' . t('<strong>Path prefix only.</strong> The presentation language is determined by examining the path for a language code or other custom string that matches the path prefix (if any) specified for each language. If a suitable prefix is not identified, the default language is used. <em>Example: "example.com/de/contact" sets presentation language to German based on the use of "de" within the path.</em>') . '</li>';
-      $output .= '<li>' . t("<strong>Path prefix with language fallback.</strong> The presentation language is determined by examining the path for a language code or other custom string that matches the path prefix (if any) specified for each language. If a suitable prefix is not identified, the display language is determined by the user's language preferences from the <em>My Account</em> page, or by the browser's language settings. If a presentation language cannot be determined, the default language is used.") . '</li>';
-      $output .= '<li>' . t('<strong>Domain name only.</strong> The presentation language is determined by examining the domain used to access the site, and comparing it to the language domain (if any) specified for each language. If a match is not identified, the default language is used. <em>Example: "http://de.example.com/contact" sets presentation language to German based on the use of "http://de.example.com" in the domain.</em>') . '</li></ul>';
-      $output .= '<p>' . t('The path prefix or domain name for a language may be set by editing the <a href="@languages">available languages</a>. In the absence of an appropriate match, the site is displayed in the <a href="@languages">default language</a>.', array('@languages' => url('admin/config/regional/language'))) . '</p>';
-      return $output;
     case 'admin/config/regional/translate':
       $output = '<p>' . t('This page provides an overview of available translatable strings. Drupal displays translatable strings in text groups; modules may define additional text groups containing other translatable strings. Because text groups provide a method of grouping related strings, they are often used to focus translation efforts on specific areas of the Drupal interface.') . '</p>';
       $output .= '<p>' . t('Review the <a href="@languages">languages page</a> for more information on adding support for additional languages.', array('@languages' => url('admin/config/regional/language'))) . '</p>';
@@ -265,13 +257,13 @@ function locale_language_selector_form($
   );
 
   // Get language negotiation settings.
-  $mode = variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE);
+  $mode = language_negotiation() != LANGUAGE_NEGOTIATION_DEFAULT;
   $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;
 }
@@ -337,12 +329,48 @@ function locale_theme() {
     'locale_languages_overview_form' => array(
       'arguments' => array('form' => array()),
     ),
+    'locale_languages_configure_form' => array(
+      'arguments' => array('form' => array()),
+    ),
     'locale_translation_filters' => array(
       'arguments' => array('form' => array()),
     ),
   );
 }
 
+/**
+ * Implement hook_language_providers():
+ */
+function locale_language_providers() {
+  $providers = array();
+
+  $providers[LANGUAGE_NEGOTIATION_CONTENT] = array(
+    'title' => t('Content'),
+    'description' => t('The content language determined from the URL (Path prefix or domain) is used for the site presentation.'),
+    'callback' => 'language_from_url',
+  );
+
+  $providers[LANGUAGE_NEGOTIATION_USER] = array(
+    'title' => t('User'),
+    'description' => t('The site presentation is determined from the user language preferences.'),
+    'callback' => 'language_from_user',
+  );
+
+  $providers[LANGUAGE_NEGOTIATION_BROWSER] = array(
+    'title' => t('Browser'),
+    'description' => t('The site presentation is determined from the browser\'s language settings.'),
+    'callback' => 'language_from_browser',
+  );
+
+  $providers[LANGUAGE_NEGOTIATION_DEFAULT] = array(
+    'title' => t('Default'),
+    'description' => t('The default language is used for site presentation.'),
+    'callback' => 'language_default',
+  );
+
+  return $providers;
+}
+
 // ---------------------------------------------------------------------------------
 // Locale core functionality
 
@@ -646,7 +674,7 @@ function locale_block_list() {
  * web addresses, so we can actually link to other language versions.
  */
 function locale_block_view($delta = '') {
-  if (variable_get('language_count', 1) > 1 && variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE) != LANGUAGE_NEGOTIATION_NONE) {
+  if (variable_get('language_count', 1) > 1) {
     $path = drupal_is_front_page() ? '<front>' : $_GET['q'];
     $languages = language_list('enabled');
     $links = array();
Index: modules/locale/locale.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/locale/locale.test,v
retrieving revision 1.39
diff -u -p -r1.39 locale.test
--- modules/locale/locale.test	22 Aug 2009 00:58:53 -0000	1.39
+++ modules/locale/locale.test	28 Aug 2009 00:43:45 -0000
@@ -91,7 +91,8 @@ class LocaleConfigurationTest extends Dr
     // Ensure we can't delete the default language.
     $path = 'admin/config/regional/language/delete/' . $langcode;
     $this->drupalGet($path);
-    $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), t('Correct page redirection.'));
+    $url = url('admin/config/regional/language', array('absolute' => TRUE, 'prefix' => $langcode . '/'));
+    $this->assertEqual($this->getUrl(), $url, t('Correct page redirection.'));
     $this->assertText(t('The default language cannot be deleted.'), t('Failed to delete the default language.'));
 
     // Check if we can disable a language.
@@ -910,11 +911,11 @@ class LocaleUninstallFunctionalTest exte
   /**
    * The default language set for the UI before uninstall.
    */
-  protected $ui_language;
+  protected $language_ui;
 
   function setUp() {
     parent::setUp('locale');
-    $this->ui_language = 'en';
+    $this->language_ui = 'en';
   }
 
   /**
@@ -925,15 +926,16 @@ class LocaleUninstallFunctionalTest exte
 
     // Add a new language and optionally set it as default.
     require_once DRUPAL_ROOT . '/includes/locale.inc';
-    locale_add_language('fr', 'French', 'Français', LANGUAGE_LTR, '', '', TRUE, $this->ui_language == 'fr');
+    locale_add_language('fr', 'French', 'Français', LANGUAGE_LTR, '', '', TRUE, $this->language_ui == 'fr');
 
     // Check the UI language.
     drupal_language_initialize();
-    global $language;
-    $this->assertEqual($language->language, $this->ui_language, t('Current language: %lang', array('%lang' => $language->language)));
+    global $language_ui;
+    $this->assertEqual($language_ui->language, $this->language_ui, t('Current language: %lang', array('%lang' => $language_ui->language)));
 
     // Change language negotiation options.
-    variable_set('language_negotiation', LANGUAGE_NEGOTIATION_PATH_DEFAULT);
+    drupal_load('module', 'locale');
+    variable_set('language_negotiation', locale_language_providers());
 
     // Enable multilingual workflow option for articles.
     variable_set('language_content_type_article', 1);
@@ -968,7 +970,7 @@ class LocaleUninstallFunctionalTest exte
 
     // Check the init language logic.
     drupal_language_initialize();
-    $this->assertEqual($language->language, 'en', t('Language after uninstall: %lang', array('%lang' => $language->language)));
+    $this->assertEqual($language_ui->language, 'en', t('Language after uninstall: %lang', array('%lang' => $language_ui->language)));
 
     // Check JavaScript files deletion.
     $this->assertTrue($result = !file_exists($js_file), t('JavaScript file deleted: %file', array('%file' => $result ? $js_file : t('found'))));
@@ -978,7 +980,7 @@ class LocaleUninstallFunctionalTest exte
     $this->assertEqual($language_count, 1, t('Language count: %count', array('%count' => $language_count)));
 
     // Check language negotiation.
-    $language_negotiation = variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE) == LANGUAGE_NEGOTIATION_NONE;
+    $language_negotiation = language_negotiation() == LANGUAGE_NEGOTIATION_DEFAULT;
     $this->assertTrue($language_negotiation, t('Language negotiation: %setting', array('%setting' => t($language_negotiation ? 'none' : 'set'))));
 
     // Check JavaScript parsed.
@@ -1018,7 +1020,7 @@ class LocaleUninstallFrenchFunctionalTes
 
   function setUp() {
     parent::setUp();
-    $this->ui_language = 'fr';
+    $this->language_ui = 'fr';
   }
 }
 
@@ -1060,13 +1062,6 @@ class LanguageSwitchingFunctionalTest ex
     );
     $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
 
-    // Set language negotiation.
-    $edit = array(
-      'language_negotiation' => LANGUAGE_NEGOTIATION_PATH_DEFAULT,
-    );
-    $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
-    $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), t('Correct page redirection.'));
-
     // Assert that the language switching block is displayed on the frontpage.
     $this->drupalGet('');
     $this->assertText(t('Languages'), t('Language switcher block found.'));
@@ -1243,10 +1238,8 @@ class LocalePathFunctionalTest extends D
     $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
 
     // Set language negotiation.
-    $edit = array(
-      'language_negotiation' => LANGUAGE_NEGOTIATION_PATH_DEFAULT,
-    );
-    $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
+    drupal_load('module', 'locale');
+    variable_set('language_negotiation', locale_language_providers());
 
     // Create a node.
     $node = $this->drupalCreateNode(array('type' => 'page'));
@@ -1353,10 +1346,8 @@ class LocaleContentFunctionalTest extend
     $this->drupalPost($path, $edit, t('Save configuration'));
 
     // Set language negotiation.
-    $edit = array(
-      'language_negotiation' => LANGUAGE_NEGOTIATION_PATH_DEFAULT,
-    );
-    $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
+    drupal_load('module', 'locale');
+    variable_set('language_negotiation', locale_language_providers());
 
     // Set page content type to use multilingual support.
     $this->drupalGet('admin/structure/node-type/page');
@@ -1412,7 +1403,7 @@ class LocaleContentFunctionalTest extend
 
 /**
  * Test UI language negotiation
- * 1. LANGUAGE_NEGOTIATION_PATH_DEFAULT
+ * 1. CONTENT (PATH) > DEFAULT 
  *    UI Language base on URL prefix, browser language preference has no
  *    influence:
  *      admin/config
@@ -1421,7 +1412,7 @@ class LocaleContentFunctionalTest extend
  *        UI in Chinese
  *      blah-blah/admin/config
  *        404
- * 2. LANGUAGE_NEGOTIATION_PATH
+ * 2. CONTENT (PATH) > BROWSER > DEFAULT
  *        admin/config
  *          UI in user's browser language preference if the site has that
  *          language enabled, if not, the default language
@@ -1429,7 +1420,7 @@ class LocaleContentFunctionalTest extend
  *          UI in Chinese
  *        blah-blah/admin/config
  *          404
- * 3. LANGUAGE_NEGOTIATION_DOMAIN
+ * 3. CONTENT (DOMAIN) > DEFAULT
  *        http://example.com/admin/config
  *          UI language in site default
  *        http://example.cn/admin/config
@@ -1446,6 +1437,7 @@ class UILanguageNegotiationTest extends 
 
   function setUp() {
     parent::setUp('locale', 'locale_test');
+    drupal_load('module', 'locale');
   }
 
   /**
@@ -1475,9 +1467,9 @@ class UILanguageNegotiationTest extends 
     locale_add_language($language_browser_fallback);
     locale_add_language($language);
 
-    // We will look for this string in the admin/config screen to see if the
+    // We will look for this string in the default node page to see if the
     // corresponding translated string is shown.
-    $default_string = 'Configure languages for content and the user interface';
+    $default_string = 'Welcome to Drupal';
 
     // Set the default language in order for the translated string to be registered
     // into database when seen by t(). Without doing this, our target string
@@ -1495,8 +1487,9 @@ class UILanguageNegotiationTest extends 
     $language_browser_fallback_string = "In $language_browser_fallback In $language_browser_fallback In $language_browser_fallback";
     $language_string = "In $language In $language In $language";
     // Do a translate search of our target string.
-    $edit = array( 'string' => $default_string);
+    $edit = array('string' => 'Welcome to @site-name');
     $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Filter'));
+
     // Should find the string and now click edit to post translated string.
     $this->clickLink('edit');
     $edit = array(
@@ -1508,43 +1501,43 @@ class UILanguageNegotiationTest extends 
     $tests = array(
       // Default, browser preference should have no influence.
       array(
-        'language_negotiation' => LANGUAGE_NEGOTIATION_PATH_DEFAULT,
-        'path' => 'admin/config',
+        'language_negotiation' => array(LANGUAGE_NEGOTIATION_CONTENT, LANGUAGE_NEGOTIATION_DEFAULT),
+        'path' => 'node',
         'expect' => $default_string,
         'http_header' => $http_header_browser_fallback,
-        'message' => 'LANGUAGE_NEGOTIATION_PATH_DEFAULT: no language prefix, UI language is default and not the browser language preference setting is used.',
+        'message' => 'CONTENT (PATH) > DEFAULT: no language prefix, UI language is default and not the browser language preference setting is used.',
       ),
       // Language prefix.
       array(
-        'language_negotiation' => LANGUAGE_NEGOTIATION_PATH_DEFAULT,
-        'path' => "$language/admin/config",
+        'language_negotiation' => array(LANGUAGE_NEGOTIATION_CONTENT, LANGUAGE_NEGOTIATION_DEFAULT),
+        'path' => "$language/node",
         'expect' => $language_string,
         'http_header' => $http_header_browser_fallback,
-        'message' => 'LANGUAGE_NEGOTIATION_PATH_DEFAULT: with language prefix, UI language is switched based on path prefix',
+        'message' => 'CONTENT (PATH) > DEFAULT: with language prefix, UI language is switched based on path prefix',
       ),
       // Default, go by browser preference.
       array(
-        'language_negotiation' => LANGUAGE_NEGOTIATION_PATH,
-        'path' => 'admin/config',
+        'language_negotiation' => array(LANGUAGE_NEGOTIATION_CONTENT, LANGUAGE_NEGOTIATION_BROWSER),
+        'path' => 'node',
         'expect' => $language_browser_fallback_string,
         'http_header' => $http_header_browser_fallback,
-        'message' => 'LANGUAGE_NEGOTIATION_PATH: no language prefix, UI language is determined by browser language preference',
+        'message' => 'CONTENT (PATH) > BROWSER: no language prefix, UI language is determined by browser language preference',
       ),
       // Prefix, switch to the language.
       array(
-        'language_negotiation' => LANGUAGE_NEGOTIATION_PATH,
-        'path' => "$language/admin/config",
+        'language_negotiation' => array(LANGUAGE_NEGOTIATION_CONTENT, LANGUAGE_NEGOTIATION_BROWSER),
+        'path' => "$language/node",
         'expect' => $language_string,
         'http_header' => $http_header_browser_fallback,
-        'message' => 'LANGUAGE_NEGOTIATION_PATH: with langage prefix, UI language is based on path prefix',
+        'message' => 'CONTENT (PATH) > BROWSER: with langage prefix, UI language is based on path prefix',
       ),
       // Default, browser language preference is not one of site's lang.
       array(
-        'language_negotiation' => LANGUAGE_NEGOTIATION_PATH,
-        'path' => 'admin/config',
+        'language_negotiation' => array(LANGUAGE_NEGOTIATION_CONTENT, LANGUAGE_NEGOTIATION_BROWSER, LANGUAGE_NEGOTIATION_DEFAULT),
+        'path' => 'node',
         'expect' => $default_string,
         'http_header' => $http_header_blah,
-        'message' => 'LANGUAGE_NEGOTIATION_PATH: no language prefix and browser language preference set to unknown language should use default language',
+        'message' => 'CONTENT (PATH) > BROWSER > DEFAULT: no language prefix and browser language preference set to unknown language should use default language',
       ),
     );
 
@@ -1553,35 +1546,36 @@ class UILanguageNegotiationTest extends 
     }
 
     // Unknown language prefix should return 404.
-    foreach(array(LANGUAGE_NEGOTIATION_PATH_DEFAULT, LANGUAGE_NEGOTIATION_PATH) as $negotiation) {
-      variable_set('language_negotiation', $negotiation);
-      $this->drupalGet("$language_unknown/admin/config", array(), $http_header_browser_fallback);
-      $this->assertResponse(404, "Unknown language path prefix should return 404, code = $negotiation");
-    }
+    variable_set('language_negotiation', locale_language_providers());
+    $this->drupalGet("$language_unknown/admin/config", array(), $http_header_browser_fallback);
+    $this->assertResponse(404, "Unknown language path prefix should return 404");
 
     // Setup for domain negotiation, first configure the language to have domain
     // URL.
     $edit = array('prefix' => '', 'domain' => "http://$language_domain");
     $this->drupalPost("admin/config/regional/language/edit/$language", $edit, t('Save language'));
     // Set the site to use domain language negotiation.
-    variable_set('language_negotiation', LANGUAGE_NEGOTIATION_DOMAIN);
 
     $tests = array(
       // Default domain, browser preference should have no influence.
       array(
-        'path' => 'admin/config',
+        'language_negotiation' => array(LANGUAGE_NEGOTIATION_CONTENT, LANGUAGE_NEGOTIATION_DEFAULT),
+        'language_negotiation_url' => LANGUAGE_NEGOTIATION_URL_DOMAIN,
+        'path' => 'node',
         'expect' => $default_string,
         'http_header' => $http_header_browser_fallback,
-        'message' => 'LANGUAGE_NEGOTIATION_DOMAIN: default domain should get default language',
+        'message' => 'CONTENT (DOMAIN) > DEFAULT: default domain should get default language',
       ),
       // Language domain specific URL, we set the $_SERVER['HTTP_HOST'] in
       // locale_test.module hook_boot() to simulate this.
       array(
+        'language_negotiation' => array(LANGUAGE_NEGOTIATION_CONTENT, LANGUAGE_NEGOTIATION_DEFAULT),
+        'language_negotiation_url' => LANGUAGE_NEGOTIATION_URL_DOMAIN,
         'locale_test_domain' => $language_domain,
-        'path' => 'admin/config',
+        'path' => 'node',
         'expect' => $language_string,
         'http_header' => $http_header_browser_fallback,
-        'message' => 'LANGUAGE_NEGOTIATION_DOMAIN: domain example.cn should switch to Chinese',
+        'message' => 'CONTENT (DOMAIN) > DEFAULT: domain example.cn should switch to Chinese',
       ),
     );
 
@@ -1592,7 +1586,16 @@ class UILanguageNegotiationTest extends 
 
   private function runTest($test) {
     if (!empty($test['language_negotiation'])) {
-      variable_set('language_negotiation', $test['language_negotiation']);
+      $negotiation = array();
+      foreach (locale_language_providers() as $id => $provider) {
+        if (in_array($id, $test['language_negotiation'])) {
+          $negotiation[] = $provider;
+        }
+      }
+      variable_set('language_negotiation', $negotiation);
+    }
+    if (!empty($test['language_negotiation_url'])) {
+      variable_set('language_negotiation_url', $test['language_negotiation_url']);
     }
     if (!empty($test['locale_test_domain'])) {
       variable_set('locale_test_domain', $test['locale_test_domain']);
Index: modules/path/path.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/path/path.test,v
retrieving revision 1.19
diff -u -p -r1.19 path.test
--- modules/path/path.test	22 Aug 2009 00:58:54 -0000	1.19
+++ modules/path/path.test	28 Aug 2009 01:14:57 -0000
@@ -183,7 +183,9 @@ class PathLanguageTestCase extends Drupa
     drupal_static_reset('language_list');
 
     // Set language negotiation to "Path prefix with fallback".
-    variable_set('language_negotiation', LANGUAGE_NEGOTIATION_PATH);
+    drupal_load('module', 'locale');
+    variable_set('language_negotiation', locale_language_providers());
+    variable_set('language_negotiation_url', LANGUAGE_NEGOTIATION_URL_PREFIX);
 
     // Force inclusion of language.inc.
     drupal_language_initialize();
@@ -234,6 +236,10 @@ class PathLanguageTestCase extends Drupa
     $languages = language_list();
     $url = url('node/' . $french_node->nid, array('language' => $languages[$french_node->language]));
     $this->assertTrue(strpos($url, $edit['path']), t('URL contains the path alias.'));
+
+    // Restore the default language.
+    variable_set('language_count', 1);
+    drupal_language_initialize();
   }
 }
 
Index: modules/simpletest/drupal_web_test_case.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v
retrieving revision 1.145
diff -u -p -r1.145 drupal_web_test_case.php
--- modules/simpletest/drupal_web_test_case.php	27 Aug 2009 20:25:28 -0000	1.145
+++ modules/simpletest/drupal_web_test_case.php	28 Aug 2009 00:00:12 -0000
@@ -1797,6 +1797,9 @@ class DrupalWebTestCase extends DrupalTe
       $n = strlen($base_path);
       if (substr($path, 0, $n) == $base_path) {
         $path = substr($path, $n);
+        if (function_exists('language_split_prefix')) {
+          list(, $path) = language_split_prefix($path);
+        }
       }
       if (isset($parts['query'])) {
         $options['query'] = $parts['query'];
