diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index de6a4ef..2a44307 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -238,6 +238,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'; @@ -2778,6 +2783,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..a2d1bac 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('Changing the selected language here (and leaving this option as the last among the detection and selection options) is the easiest way to change the fallback language for the website, if you need to change how your site works by default (eg. when using an empty path prefix or using the default domain). Changing the site\'s default language itself might have other undesired side effects. +', array('@admin-change-language' => 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/locale/locale.install b/core/modules/locale/locale.install index 4970242..c8777d4 100644 --- a/core/modules/locale/locale.install +++ b/core/modules/locale/locale.install @@ -732,6 +732,28 @@ function locale_update_8011() { } /** + * Renames language_default language negotiation method to language_selected. + */ +function locale_update_8013() { + $weight = update_variable_get('language_negotiation_methods_weight_language_interface', NULL); + if ($weight !== NULL) { + $weight[LANGUAGE_NEGOTIATION_SELECTED] = $weight['language-default']; + unset($weight['language-default']); + update_variable_set('language_negotiation_methods_weight_language_interface', $weight); + } + + $negotiation_interface = update_variable_get('language_negotiation_language_interface', NULL); + if ($negotiation_interface !== NULL) { + if (isset($negotiation_interface['language-default'])) { + $negotiation_interface[LANGUAGE_NEGOTIATION_SELECTED] = $negotiation_interface['language-default']; + $negotiation_interface[LANGUAGE_NEGOTIATION_SELECTED]['callbacks']['negotiation'] = 'language_from_selected'; + unset($negotiation_interface['language-default']); + update_variable_set('language_negotiation_language_interface', $negotiation_interface); + } + } +} + +/** * @} End of "addtogroup updates-7.x-to-8.x". * The next series of updates should start at 9000. */ diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/LanguageUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/LanguageUpgradePathTest.php index 7a30cb0..65c30de 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/LanguageUpgradePathTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/LanguageUpgradePathTest.php @@ -106,12 +106,18 @@ class LanguageUpgradePathTest extends UpgradePathTestBase { 'language-session' => '-6', 'language-user' => '-4', 'language-browser' => '-2', - 'language-default' => '10', + 'language-selected' => '10', ); // Check that locale_language_providers_weight_language is correctly // renamed. $current_weights = update_variable_get('language_negotiation_methods_weight_language_interface', array()); $this->assertTrue(serialize($expected_weights) == serialize($current_weights), t('Language negotiation method weights upgraded.')); + $this->assertTrue(isset($current_weights['language-selected']), 'Language-selected is present.'); + $this->assertFalse(isset($current_weights['language-default']), 'Language-default is not present.'); + + // Check that negotiation callback was added to language_negotiation_language_interface. + $language_negotiation_language_interface = update_variable_get('language_negotiation_language_interface', NULL); + $this->assertTrue(isset($language_negotiation_language_interface[LANGUAGE_NEGOTIATION_SELECTED]['callbacks']['negotiation']), 'Negotiation callback was added to language_negotiation_language_interface.'); // Look up migrated plural string. $source_string = db_query('SELECT * FROM {locales_source} WHERE lid = 22')->fetchObject(); 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'),