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 == '' && 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. Modifying this setting may break all incoming URLs and should be used with caution in a production environment.") + '#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.
Modifying this setting may break all incoming URLs and should be used with caution in a production environment.") ); $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'] == '' && drupal_is_front_page())) - && (empty($link['language']) || $link['language']->language == $language->language)) { + && (empty($link['language']) || $link['language']->language == $content_language->language)) { $class .= ' active'; } $output .= ' $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; }