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. Modifying this setting may break all incoming URLs and should be used with caution in a production environment.")
+ 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.
Modifying this setting may break all incoming URLs and should be used with caution in a production environment.')
);
+
+ $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 .= '
' . t('Add all languages to be supported by your site. If your desired language is not available in the Language name drop-down, click Custom language 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.') . '
'; - case 'admin/config/regional/language/configure': - $output = '' . t("Language negotiation settings determine the site's presentation language. Available options include:") . '
'; - $output .= '' . t('The path prefix or domain name for a language may be set by editing the available languages. In the absence of an appropriate match, the site is displayed in the default language.', array('@languages' => url('admin/config/regional/language'))) . '
'; - return $output; case 'admin/config/regional/translate': $output = '' . 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.') . '
'; $output .= '' . t('Review the languages page for more information on adding support for additional languages.', array('@languages' => url('admin/config/regional/language'))) . '
'; @@ -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() ? '