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 15:57:43 -0000
@@ -159,28 +159,54 @@ 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 determined using a URL language indicator:
+ * Path prefix or domain according to the configuration.
*/
-define('LANGUAGE_NEGOTIATION_PATH_DEFAULT', 1);
+define('LANGUAGE_NEGOTIATION_URL', 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);
+
+/**
+ * The type of language used to select the user interface.
+ */
+define('LANGUAGE_TYPE_UI', 0x1);
+
+/**
+ * The type of language used to define the content language.
+ */
+define('LANGUAGE_TYPE_CONTENT', 0x2);
+
+/**
+ * Any type of language.
+ */
+define('LANGUAGE_TYPE_ANY', LANGUAGE_TYPE_UI | LANGUAGE_TYPE_CONTENT);
/**
* Language written left to right. Possible value of $language->direction.
@@ -1641,16 +1667,17 @@ 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();
+ $language = language_initialize(LANGUAGE_TYPE_CONTENT);
+ $language_ui = language_initialize(LANGUAGE_TYPE_UI);
}
}
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 28 Aug 2009 11:52:01 -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 15:49:04 -0000
@@ -7,69 +7,88 @@
*/
/**
- * Choose a language for the page, based on language negotiation settings.
+ * Return the current main language negotiation mode.
*/
-function language_initialize() {
- global $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();
+function language_negotiation($type) {
+ $negotiation = variable_get("language_negotiation_$type");
+ if (empty($negotiation)) {
+ return LANGUAGE_NEGOTIATION_DEFAULT;
+ }
+ return key($negotiation);
+}
- 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();
+/**
+ * Return all the defined language providers.
+ */
+function language_providers() {
+ $language_providers = &drupal_static(__FUNCTION__);
- 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 ($mode == LANGUAGE_NEGOTIATION_PATH_DEFAULT) {
- // If we did not found the language by prefix, choose the default.
- return language_default();
- }
- break;
+ 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);
}
- // User language.
- if ($user->uid && isset($languages[$user->language])) {
- return $languages[$user->language];
- }
+ return $language_providers;
+}
+
+/**
+ * Helper function used to cache the language providers results.
+ */
+function language_provider_invoke($provider_id, $provider = NULL) {
+ $results = &drupal_static(__FUNCTION__);
+
+ if (!isset($result[$provider_id])) {
+ $languages = language_list('enabled');
+ $languages = $languages[1];
+
+ if (!isset($provider)) {
+ $providers = language_providers();
+ $provider = $providers[$provider_id];
+ }
+
+ if (isset($provider['file'])) {
+ require_once DRUPAL_ROOT .'/'. $provider['file'];
+ }
- // Browser accept-language parsing.
- if ($language = language_from_browser()) {
- return $language;
+ // If the language provider has no cache preference or this is satisified
+ // we can execute the callback.
+ $cache = !isset($provider['cache']) || $provider['cache'] = variable_get('cache', CACHE_DISABLED);
+ $callback = $provider['callback'];
+ $language = $cache && function_exists($callback) ? $callback($languages) : FALSE;
+ $results[$provider_id] = isset($language->language) && isset($languages[$language->language]) ? $result : FALSE;
}
- // Fall back on the default if everything else fails.
+ return $results[$provider_id];
+}
+
+/**
+ * Choose a language for the give type, based on the language negotiation settings.
+ */
+function language_initialize($type) {
+ // Execute the language providers in the order they were set up and return the first
+ // valid language found.
+ $negotiation = variable_get("language_negotiation_$type", array());
+ foreach ($negotiation as $id => $provider) {
+ $language = language_provider_invoke($id, $provider);
+ if ($language) {
+ 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() {
+function language_from_browser($languages) {
// 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();
@@ -90,12 +109,85 @@ function language_from_browser() {
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];
+ if (isset($languages[$langcode])) {
+ return $langcode;
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ * Identify language from the user preferences.
+ */
+function language_from_user($languages) {
+ // Request parameter.
+ if (isset($_GET['language_ui']) && isset($languages[$langcode = $_GET['language_ui']])) {
+ return $_SESSION['language_ui'] = $langcode;
+ }
+ // Session parameter.
+ if (isset($_SESSION['language_ui'])) {
+ return $_SESSION['language_ui'];
+ }
+ // User preference (only for logged users).
+ global $user;
+ if ($user->uid) {
+ return $user->language;
+ }
+ // No language preference from the user.
+ return FALSE;
+}
+
+/**
+ * Identify language via URL prefix or domain.
+ */
+function language_from_url($languages) {
+ $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, $languages);
+ if ($language !== FALSE) {
+ $url_language = $language->language;
+ }
+ break;
+
+ case LANGUAGE_NEGOTIATION_URL_DOMAIN:
+ foreach ($languages as $language) {
+ $host = parse_url($language->domain, PHP_URL_HOST);
+ if ($host && ($_SERVER['HTTP_HOST'] == $host)) {
+ $url_language = $language->language;
+ break;
+ }
+ }
+ break;
+ }
+
+ return $url_language;
+}
+
+/**
+ * Parse the given path and return the language identified by the
+ * prefix and the actual path.
+ */
+function language_split_prefix($path, $languages = NULL) {
+ if (empty($languages)) {
+ $languages = language_list('enabled');
+ $languages = $languages[1];
+ }
+ $args = empty($path) ? array() : explode('/', $path);
+ $prefix = array_shift($args);
+ // Search prefix within enabled languages.
+ foreach ($languages 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 +205,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 +214,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 28 Aug 2009 15:53:55 -0000
@@ -476,33 +476,196 @@ 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 = array();
+
+ $title = t('Content language negotiation');
+ $description = '';
+ $table_form = _locale_languages_configure_form_language_table(LANGUAGE_TYPE_CONTENT, $title, $description);
+ $form[LANGUAGE_TYPE_CONTENT] = array('#tree' => TRUE) + $table_form;
+
+ $title = t('Site presentation language negotiation');
+ $description = '';
+ $table_form = _locale_languages_configure_form_language_table(LANGUAGE_TYPE_UI, $title, $description);
+ $form[LANGUAGE_TYPE_UI] = array('#tree' => TRUE) + $table_form;
+
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Save settings')
);
+
+ $form['#theme'] = 'locale_languages_configure_form';
+
return $form;
}
/**
+ * Helper function to build a language provider table.
+ */
+function _locale_languages_configure_form_language_table($type, $title, $description) {
+ require_once DRUPAL_ROOT . '/includes/language.inc';
+
+ $table_form = array();
+ $table_form['weight'] = array('#tree' => TRUE);
+ $table_form['enabled'] = array('#tree' => TRUE);
+ $table_form['#language_providers'] = array();
+ $table_form['#title'] = $title;
+ $table_form['#description'] = $description;
+ $table_form['#show_operations'] = FALSE;
+
+ $language_providers = language_providers();
+ $enabled_providers = variable_get("language_providers_enabled_$type", array());
+ $providers_weight = variable_get("language_providers_weight_$type", array());
+ $ordered_list = $providers_weight + $language_providers;
+
+ foreach ($ordered_list as $id => $value) {
+ $provider = $language_providers[$id];
+ if (!isset($provider['type']) || $provider['type'] & $type) {
+ $table_form['#language_providers'][$id] = $provider;
+ $table_form['weight'][$id] = array(
+ '#type' => 'weight',
+ '#default_value' => @$providers_weight[$id],
+ '#attributes' => array('class' => array("language-provider-weight-$type")),
+ );
+ $table_form['title'][$id] = array('#markup' => check_plain($provider['title']));
+ $table_form['enabled'][$id] = array('#type' => 'checkbox', '#default_value' => @$enabled_providers[$id]);
+ if ($id === LANGUAGE_NEGOTIATION_DEFAULT) {
+ $table_form['enabled'][$id]['#default_value'] = TRUE;
+ $table_form['enabled'][$id]['#attributes'] = array('disabled' => 'disabled');
+ }
+ $table_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.
+ $table_form['#show_operations'] = TRUE;
+ }
+ $table_form['operation'][$id] = array('#markup' => $config_op);
+ }
+ }
+
+ return $table_form;
+}
+
+/**
+ * Theme the language configure form.
+ *
+ * @ingroup themeable
+ */
+function theme_locale_languages_configure_form($form) {
+ $output = '';
+
+ foreach (array(LANGUAGE_TYPE_CONTENT, LANGUAGE_TYPE_UI) as $type) {
+ $rows = array();
+ $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'))) . '
'; @@ -103,6 +95,15 @@ function locale_menu() { 'file path' => 'includes', 'type' => MENU_LOCAL_TASK, ); + // Manage languages + $items['admin/config/regional/language/configure/url'] = array( + 'title' => 'URL Language provider configuration', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('locale_language_providers_url_form'), + 'access arguments' => array('administer languages'), + 'file' => 'locale.inc', + 'file path' => 'includes', + ); $items['admin/config/regional/language/edit/%'] = array( 'title' => 'Edit language', 'page callback' => 'drupal_get_form', @@ -265,13 +266,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 +338,53 @@ 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_URL] = array( + 'title' => t('URL'), + 'description' => t('The language is determined from the URL (Path prefix or domain).'), + 'callback' => 'language_from_url', + 'config_path' => 'admin/config/regional/language/configure/url', + ); + + $providers[LANGUAGE_NEGOTIATION_USER] = array( + 'title' => t('User'), + 'description' => t('The language is determined from the language preference set in the user account.'), + 'callback' => 'language_from_user', + 'type' => LANGUAGE_TYPE_UI, + ); + + $providers[LANGUAGE_NEGOTIATION_BROWSER] = array( + 'title' => t('Browser'), + 'description' => t('The language is determined from the browser\'s language settings.'), + 'callback' => 'language_from_browser', + 'type' => LANGUAGE_TYPE_UI, + 'cache' => CACHE_DISABLED, + ); + + $language = language_default(); + $providers[LANGUAGE_NEGOTIATION_DEFAULT] = array( + 'title' => t('Default'), + 'description' => t('The default language (@language_name) is used.', array('@language_name' => $language->native)), + 'callback' => '', + ); + + return $providers; +} + // --------------------------------------------------------------------------------- // Locale core functionality @@ -646,10 +688,11 @@ 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() ? '