diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index e7da6ec..7b6421a 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -236,6 +236,11 @@ const LANGUAGE_LOCKED = 2; const LANGUAGE_ALL = 3; /** + * The language state used when referring to the site's default language. + */ +const LANGUAGE_DEFAULT = 4; + +/** * The type of language used to define the content language. */ const LANGUAGE_TYPE_CONTENT = 'language_content'; @@ -2807,6 +2812,15 @@ function language_list($flags = LANGUAGE_CONFIGURABLE) { // default we remove the locked languages, but the caller may request for // those languages to be added as well. $filtered_languages = array(); + + // Add the site's default language if flagged as allowed value. + if ($flags & LANGUAGE_DEFAULT) { + $default = isset($default) ? $default : language_default(); + // Rename the default language. + $default->name = t("Site's default language (@lang_name)", array('@lang_name' => $default->name)); + $filtered_languages['site_default'] = $default; + } + foreach ($languages as $langcode => $language) { if (($language->locked && !($flags & LANGUAGE_LOCKED)) || (!$language->locked && !($flags & LANGUAGE_CONFIGURABLE))) { continue; diff --git a/core/includes/language.inc b/core/includes/language.inc index 2a0defa..122670c 100644 --- a/core/includes/language.inc +++ b/core/includes/language.inc @@ -10,7 +10,7 @@ /** * No language negotiation. The default language is used. */ -const LANGUAGE_NEGOTIATION_DEFAULT = 'language-default'; +const LANGUAGE_NEGOTIATION_SELECTED = 'language-selected'; /** * @defgroup language_negotiation Language Negotiation API functionality @@ -133,7 +133,7 @@ function language_types_initialize($type, $request = NULL) { // If no other language was found use the default one. $language = language_default(); - $language->method_id = LANGUAGE_NEGOTIATION_DEFAULT; + $language->method_id = LANGUAGE_NEGOTIATION_SELECTED; return $language; } @@ -263,7 +263,7 @@ function language_types_set() { */ function language_negotiation_method_get_first($type) { $negotiation = variable_get("language_negotiation_$type", array()); - return empty($negotiation) ? LANGUAGE_NEGOTIATION_DEFAULT : key($negotiation); + return empty($negotiation) ? LANGUAGE_NEGOTIATION_SELECTED : key($negotiation); } /** @@ -404,14 +404,20 @@ function language_negotiation_info() { if (!isset($negotiation_info)) { // Collect all the module-defined language negotiation methods. $negotiation_info = module_invoke_all('language_negotiation_info'); - + $languages = language_list(); + $selected_language = $languages[language_from_selected($languages)]; + $description = 'Language based on a selected language. '; + $description .= ($selected_language->langcode == language_default()->langcode) ? "(Site's default language (@language_name))" : '(@language_name)'; // Add the default language negotiation method. - $negotiation_info[LANGUAGE_NEGOTIATION_DEFAULT] = array( - 'callbacks' => array('language' => 'language_from_default'), - 'weight' => 10, - 'name' => t('Default language'), - 'description' => t('Use the default site language (@language_name).', array('@language_name' => language_default()->name)), - 'config' => 'admin/config/regional/language', + $negotiation_info[LANGUAGE_NEGOTIATION_SELECTED] = array( + 'callbacks' => array( + 'language' => 'language_from_default', + 'negotiation' => 'language_from_selected', + ), + 'weight' => 12, + 'name' => t('Selected language'), + 'description' => t($description, array('@language_name' => $selected_language->name)), + 'config' => 'admin/config/regional/language/detection/selected', ); // Let other modules alter the list of language negotiation methods. @@ -478,6 +484,24 @@ function language_from_default() { return language_default()->langcode; } + /** + * Identifies language from configuration. + * + * @param $languages + * An array of valid language objects. + * + * @return + * A valid language code on success, FALSE otherwise. + */ +function language_from_selected($languages) { + $langcode = (string) config('language.negotiation')->get('selected_langcode'); + // Replace the site's default langcode by its real value. + if ($langcode == 'site_default') { + $langcode = language_default()->langcode; + } + return isset($languages[$langcode]) ? $langcode : language_default()->langcode; +} + /** * Splits the given path into prefix and actual path. * diff --git a/core/modules/language/config/language.detection.yml b/core/modules/language/config/language.detection.yml new file mode 100644 index 0000000..1299603 --- /dev/null +++ b/core/modules/language/config/language.detection.yml @@ -0,0 +1 @@ +selected_langcode: site_default diff --git a/core/modules/language/language.admin.inc b/core/modules/language/language.admin.inc index af2b3b1..8affe0a 100644 --- a/core/modules/language/language.admin.inc +++ b/core/modules/language/language.admin.inc @@ -519,6 +519,7 @@ function language_negotiation_configure_form_table(&$form, $type) { '#title_display' => 'invisible', '#default_value' => $weight, '#attributes' => array('class' => array("language-method-weight-$type")), + '#delta' => 20, ); $table_form['title'][$method_id] = array('#markup' => $method_name); @@ -529,7 +530,7 @@ function language_negotiation_configure_form_table(&$form, $type) { '#title_display' => 'invisible', '#default_value' => $enabled, ); - if ($method_id === LANGUAGE_NEGOTIATION_DEFAULT) { + if ($method_id === LANGUAGE_NEGOTIATION_SELECTED) { $table_form['enabled'][$method_id]['#default_value'] = TRUE; $table_form['enabled'][$method_id]['#attributes'] = array('disabled' => 'disabled'); } @@ -624,7 +625,7 @@ function language_negotiation_configure_form_submit($form, &$form_state) { foreach ($configurable_types as $type) { $method_weights = array(); $enabled_methods = $form_state['values'][$type]['enabled']; - $enabled_methods[LANGUAGE_NEGOTIATION_DEFAULT] = TRUE; + $enabled_methods[LANGUAGE_NEGOTIATION_SELECTED] = TRUE; $method_weights_input = $form_state['values'][$type]['weight']; foreach ($method_weights_input as $method_id => $weight) { @@ -815,6 +816,27 @@ function language_negotiation_configure_session_form($form, &$form_state) { } /** + * Builds the selected language negotiation method configuration form. + */ +function language_negotiation_configure_selected_form($form, &$form_state) { + $form['selected_langcode'] = array( + '#type' => 'language_select', + '#title' => t('Language'), + '#languages' => LANGUAGE_CONFIGURABLE | LANGUAGE_DEFAULT, + '#default_value' => config('language.negotiation')->get('selected_langcode'), + ); + + return system_config_form($form, $form_state); +} + +/** + * Form submission handler for language_negotiation_configure_selected_form(). + */ +function language_negotiation_configure_selected_form_submit($form, &$form_state) { + config('language.negotiation')->set('selected_langcode', $form_state['values']['selected_langcode'])->save(); +} + +/** * Builds the browser language negotiation method configuration form. */ function language_negotiation_configure_browser_form($form, &$form_state) { diff --git a/core/modules/language/language.module b/core/modules/language/language.module index 5b62382..1cd89a8 100644 --- a/core/modules/language/language.module +++ b/core/modules/language/language.module @@ -41,6 +41,12 @@ function language_help($path, $arg) { $output = '

' . t('Browsers use different language codes to refer to the same languages. You can add and edit mappings from browser language codes to the languages used by Drupal.', array('@configure-languages' => url('admin/config/regional/language'))) . '

'; return $output; + case 'admin/config/regional/language/detection/selected': + $output = '

' . t("Determine which language is used to display page elements (primarily text provided by Drupal and modules, such as field labels and help text), from a selected language or the site's default language.") . '

'; + $output = $output . '

' . t("Selecting a different language here is preferred over changing your site's default language directly. Changing the site's default language can cause undesired side effects. A lot of things don't have language selectors. When you go add a view or contact form or specify the site name, there is no language selector, so Drupal can only assume you create all of those in the site's default language. If the site default langague is then switched, all the data entered earlier is now assumed to be in another language. Selecting a language here can avoid those problems.") . '

'; + $output = $output . '

' . t("Alternatively, change your site's default language directly when you need the change to ripple down through everything, for example in content creation forms if a content type or vocabulary is set to default to the site default language.", array('@default-lang-configuration' => url('admin/config/regional/language'))) . '

'; + return $output; + case 'admin/structure/block/manage/%/%': if ($arg[4] == 'language' && $arg[5] == 'language_interface') { return '

' . t('With multiple languages enabled, registered users can select their preferred language and authors can assign a specific language to content.') . '

'; @@ -135,6 +141,14 @@ function language_menu() { 'access arguments' => array('administer languages'), 'file' => 'language.admin.inc', ); + $items['admin/config/regional/language/detection/selected'] = array( + 'title' => 'Selected language detection configuration', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('language_negotiation_configure_selected_form'), + 'access arguments' => array('administer languages'), + 'file' => 'language.admin.inc', + 'type' => MENU_VISIBLE_IN_BREADCRUMB, + ); return $items; } diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php index 0b87a09..6de762d 100644 --- a/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php +++ b/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php @@ -140,19 +140,56 @@ class LanguageUILanguageNegotiationTest extends WebTestBase { // Configure URL language rewrite. variable_set('language_negotiation_url_type', LANGUAGE_TYPE_INTERFACE); + // Configure selected language negotiation to use zh-hans. + $edit = array('selected_langcode' => $langcode); + $this->drupalPost('admin/config/regional/language/detection/selected', $edit, t('Save configuration')); + $test = array( + 'language_negotiation' => array(LANGUAGE_NEGOTIATION_SELECTED), + 'path' => 'admin/config', + 'expect' => $language_string, + 'expected_method_id' => LANGUAGE_NEGOTIATION_SELECTED, + 'http_header' => $http_header_browser_fallback, + 'message' => 'SELECTED: UI language is switched based on selected language.', + ); + $this->runTest($test); + + // An invalid language is selected. + config('language.negotiation')->set('selected_langcode', NULL)->save(); + $test = array( + 'language_negotiation' => array(LANGUAGE_NEGOTIATION_SELECTED), + 'path' => 'admin/config', + 'expect' => $default_string, + 'expected_method_id' => LANGUAGE_NEGOTIATION_SELECTED, + 'http_header' => $http_header_browser_fallback, + 'message' => 'SELECTED > DEFAULT: UI language is switched based on selected language.', + ); + $this->runTest($test); + + // No selected language is available. + config('language.negotiation')->set('selected_langcode', $langcode_unknown)->save(); + $test = array( + 'language_negotiation' => array(LANGUAGE_NEGOTIATION_SELECTED), + 'path' => 'admin/config', + 'expect' => $default_string, + 'expected_method_id' => LANGUAGE_NEGOTIATION_SELECTED, + 'http_header' => $http_header_browser_fallback, + 'message' => 'SELECTED > DEFAULT: UI language is switched based on selected language.', + ); + $this->runTest($test); + $tests = array( // Default, browser preference should have no influence. array( - 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT), + 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_SELECTED), 'path' => 'admin/config', 'expect' => $default_string, - 'expected_method_id' => LANGUAGE_NEGOTIATION_DEFAULT, + 'expected_method_id' => LANGUAGE_NEGOTIATION_SELECTED, 'http_header' => $http_header_browser_fallback, 'message' => 'URL (PATH) > DEFAULT: no language prefix, UI language is default and the browser language preference setting is not used.', ), // Language prefix. array( - 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT), + 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_SELECTED), 'path' => "$langcode/admin/config", 'expect' => $language_string, 'expected_method_id' => LANGUAGE_NEGOTIATION_URL, @@ -179,10 +216,10 @@ class LanguageUILanguageNegotiationTest extends WebTestBase { ), // Default, browser language preference is not one of site's lang. array( - 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_BROWSER, LANGUAGE_NEGOTIATION_DEFAULT), + 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_BROWSER, LANGUAGE_NEGOTIATION_SELECTED), 'path' => 'admin/config', 'expect' => $default_string, - 'expected_method_id' => LANGUAGE_NEGOTIATION_DEFAULT, + 'expected_method_id' => LANGUAGE_NEGOTIATION_SELECTED, 'http_header' => $http_header_blah, 'message' => 'URL (PATH) > BROWSER > DEFAULT: no language prefix and browser language preference set to unknown language should use default language', ), @@ -204,10 +241,10 @@ class LanguageUILanguageNegotiationTest extends WebTestBase { $tests = array( array( - 'language_negotiation' => array(LANGUAGE_NEGOTIATION_USER, LANGUAGE_NEGOTIATION_DEFAULT), + 'language_negotiation' => array(LANGUAGE_NEGOTIATION_USER, LANGUAGE_NEGOTIATION_SELECTED), 'path' => 'admin/config', 'expect' => $default_string, - 'expected_method_id' => LANGUAGE_NEGOTIATION_DEFAULT, + 'expected_method_id' => LANGUAGE_NEGOTIATION_SELECTED, 'http_header' => array(), 'message' => 'USER > DEFAULT: no preferred user language setting, the UI language is default', ), @@ -220,10 +257,10 @@ class LanguageUILanguageNegotiationTest extends WebTestBase { $tests = array( array( - 'language_negotiation' => array(LANGUAGE_NEGOTIATION_USER, LANGUAGE_NEGOTIATION_DEFAULT), + 'language_negotiation' => array(LANGUAGE_NEGOTIATION_USER, LANGUAGE_NEGOTIATION_SELECTED), 'path' => 'admin/config', 'expect' => $default_string, - 'expected_method_id' => LANGUAGE_NEGOTIATION_DEFAULT, + 'expected_method_id' => LANGUAGE_NEGOTIATION_SELECTED, 'http_header' => array(), 'message' => 'USER > DEFAULT: invalid preferred user language setting, the UI language is default', ), @@ -236,7 +273,7 @@ class LanguageUILanguageNegotiationTest extends WebTestBase { $tests = array( array( - 'language_negotiation' => array(LANGUAGE_NEGOTIATION_USER, LANGUAGE_NEGOTIATION_DEFAULT), + 'language_negotiation' => array(LANGUAGE_NEGOTIATION_USER, LANGUAGE_NEGOTIATION_SELECTED), 'path' => 'admin/config', 'expect' => $language_string, 'expected_method_id' => LANGUAGE_NEGOTIATION_USER, @@ -255,10 +292,10 @@ class LanguageUILanguageNegotiationTest extends WebTestBase { $tests = array( array( - 'language_negotiation' => array(LANGUAGE_NEGOTIATION_USER_ADMIN, LANGUAGE_NEGOTIATION_DEFAULT), + 'language_negotiation' => array(LANGUAGE_NEGOTIATION_USER_ADMIN, LANGUAGE_NEGOTIATION_SELECTED), 'path' => 'admin/config', 'expect' => $default_string, - 'expected_method_id' => LANGUAGE_NEGOTIATION_DEFAULT, + 'expected_method_id' => LANGUAGE_NEGOTIATION_SELECTED, 'http_header' => array(), 'message' => 'USER ADMIN > DEFAULT: no preferred user admin language setting, the UI language is default', ), @@ -270,10 +307,10 @@ class LanguageUILanguageNegotiationTest extends WebTestBase { $tests = array( array( - 'language_negotiation' => array(LANGUAGE_NEGOTIATION_USER_ADMIN, LANGUAGE_NEGOTIATION_DEFAULT), + 'language_negotiation' => array(LANGUAGE_NEGOTIATION_USER_ADMIN, LANGUAGE_NEGOTIATION_SELECTED), 'path' => 'admin/config', 'expect' => $default_string, - 'expected_method_id' => LANGUAGE_NEGOTIATION_DEFAULT, + 'expected_method_id' => LANGUAGE_NEGOTIATION_SELECTED, 'http_header' => array(), 'message' => 'USER ADMIN > DEFAULT: invalid preferred user admin language setting, the UI language is default', ), @@ -285,7 +322,7 @@ class LanguageUILanguageNegotiationTest extends WebTestBase { $tests = array( array( - 'language_negotiation' => array(LANGUAGE_NEGOTIATION_USER_ADMIN, LANGUAGE_NEGOTIATION_DEFAULT), + 'language_negotiation' => array(LANGUAGE_NEGOTIATION_USER_ADMIN, LANGUAGE_NEGOTIATION_SELECTED), 'path' => 'admin/config', 'expect' => $language_string, 'expected_method_id' => LANGUAGE_NEGOTIATION_USER_ADMIN, @@ -307,18 +344,18 @@ class LanguageUILanguageNegotiationTest extends WebTestBase { $tests = array( // Default domain, browser preference should have no influence. array( - 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT), + 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_SELECTED), 'language_negotiation_url_part' => LANGUAGE_NEGOTIATION_URL_DOMAIN, 'path' => 'admin/config', 'expect' => $default_string, - 'expected_method_id' => LANGUAGE_NEGOTIATION_DEFAULT, + 'expected_method_id' => LANGUAGE_NEGOTIATION_SELECTED, 'http_header' => $http_header_browser_fallback, 'message' => 'URL (DOMAIN) > DEFAULT: default domain should get default language', ), // Language domain specific URL, we set the $_SERVER['HTTP_HOST'] in // language_test.module hook_boot() to simulate this. array( - 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT), + 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_SELECTED), 'language_negotiation_url_part' => LANGUAGE_NEGOTIATION_URL_DOMAIN, 'language_test_domain' => $language_domain . ':88', 'path' => 'admin/config', diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallTest.php index 7591f67..9f63420 100644 --- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallTest.php +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallTest.php @@ -118,11 +118,11 @@ class LocaleUninstallTest extends WebTestBase { // Check language negotiation. require_once DRUPAL_ROOT . '/core/includes/language.inc'; $this->assertTrue(count(language_types_get_all()) == count(language_types_get_default()), t('Language types reset')); - $language_negotiation = language_negotiation_method_get_first(LANGUAGE_TYPE_INTERFACE) == LANGUAGE_NEGOTIATION_DEFAULT; + $language_negotiation = language_negotiation_method_get_first(LANGUAGE_TYPE_INTERFACE) == LANGUAGE_NEGOTIATION_SELECTED; $this->assertTrue($language_negotiation, t('Interface language negotiation: %setting', array('%setting' => t($language_negotiation ? 'none' : 'set')))); - $language_negotiation = language_negotiation_method_get_first(LANGUAGE_TYPE_CONTENT) == LANGUAGE_NEGOTIATION_DEFAULT; + $language_negotiation = language_negotiation_method_get_first(LANGUAGE_TYPE_CONTENT) == LANGUAGE_NEGOTIATION_SELECTED; $this->assertTrue($language_negotiation, t('Content language negotiation: %setting', array('%setting' => t($language_negotiation ? 'none' : 'set')))); - $language_negotiation = language_negotiation_method_get_first(LANGUAGE_TYPE_URL) == LANGUAGE_NEGOTIATION_DEFAULT; + $language_negotiation = language_negotiation_method_get_first(LANGUAGE_TYPE_URL) == LANGUAGE_NEGOTIATION_SELECTED; $this->assertTrue($language_negotiation, t('URL language negotiation: %setting', array('%setting' => t($language_negotiation ? 'none' : 'set')))); // Check language negotiation method settings. diff --git a/core/modules/user/lib/Drupal/user/AccountFormController.php b/core/modules/user/lib/Drupal/user/AccountFormController.php index a5545f4..b79e983 100644 --- a/core/modules/user/lib/Drupal/user/AccountFormController.php +++ b/core/modules/user/lib/Drupal/user/AccountFormController.php @@ -210,7 +210,7 @@ abstract class AccountFormController extends EntityFormController { // Is default the interface language? include_once DRUPAL_ROOT . '/core/includes/language.inc'; - $interface_language_is_default = language_negotiation_method_get_first(LANGUAGE_TYPE_INTERFACE) != LANGUAGE_NEGOTIATION_DEFAULT; + $interface_language_is_default = language_negotiation_method_get_first(LANGUAGE_TYPE_INTERFACE) != LANGUAGE_NEGOTIATION_SELECTED; $form['language'] = array( '#type' => language_multilingual() ? 'fieldset' : 'container', '#title' => t('Language settings'),