diff --git a/includes/install.core.inc b/includes/install.core.inc index 9364bcc..c80331c 100644 --- a/includes/install.core.inc +++ b/includes/install.core.inc @@ -1381,11 +1381,21 @@ function install_import_locales(&$install_state) { if (!isset($predefined[$install_locale])) { // Drupal does not know about this language, so we prefill its values with // our best guess. The user will be able to edit afterwards. - locale_add_language($install_locale, $install_locale, $install_locale, LANGUAGE_LTR, '', '', TRUE, TRUE); + $language = (object) array( + 'language' => $install_locale, + 'name' => $install_locale, + 'native' => $install_locale, + 'default' => TRUE, + ); + locale_language_save($language); } else { // A known predefined language, details will be filled in properly. - locale_add_language($install_locale, NULL, NULL, NULL, '', '', TRUE, TRUE); + $language = (object) array( + 'language' => $install_locale, + 'default' => TRUE, + ); + locale_language_save($language); } // Collect files to import for this language. diff --git a/includes/locale.inc b/includes/locale.inc index f667d30..36c5be4 100644 --- a/includes/locale.inc +++ b/includes/locale.inc @@ -423,85 +423,70 @@ function locale_string_is_safe($string) { } /** - * @defgroup locale-api-add Language addition API - * @{ - * Add a language. + * API function to add or update a language. * - * The language addition API is used to create languages and store them. + * @param $language + * Language object with properties corresponding to 'languages' table columns. */ - -/** - * API function to add a language. - * - * @param $langcode - * Language code. - * @param $name - * English name of the language - * @param $native - * Native name of the language - * @param $direction - * LANGUAGE_LTR or LANGUAGE_RTL - * @param $domain - * Optional custom domain name with protocol, without - * trailing slash (eg. http://de.example.com). - * @param $prefix - * Optional path prefix for the language. Defaults to the - * language code if omitted. - * @param $enabled - * Optionally TRUE to enable the language when created or FALSE to disable. - * @param $default - * Optionally set this language to be the default. - */ -function locale_add_language($langcode, $name = NULL, $native = NULL, $direction = LANGUAGE_LTR, $domain = '', $prefix = '', $enabled = TRUE, $default = FALSE) { - // Default prefix on language code. - if (empty($prefix)) { - $prefix = $langcode; +function locale_language_save($language) { + $language->is_new = !(bool) db_query_range('SELECT 1 FROM {languages} WHERE language = :language', 0, 1, array(':language' => $language->language))->fetchField(); + // Default prefix on language code if not provided otherwise. + if (!isset($language->prefix)) { + $language->prefix = $language->language; } // If name was not set, we add a predefined language. - if (!isset($name)) { + if (!isset($language->name)) { include_once DRUPAL_ROOT . '/includes/iso.inc'; $predefined = _locale_get_predefined_list(); - $name = $predefined[$langcode][0]; - $native = isset($predefined[$langcode][1]) ? $predefined[$langcode][1] : $predefined[$langcode][0]; - $direction = isset($predefined[$langcode][2]) ? $predefined[$langcode][2] : LANGUAGE_LTR; + $language->name = $predefined[$language->language][0]; + $language->native = isset($predefined[$language->language][1]) ? $predefined[$language->language][1] : $predefined[$language->language][0]; + $language->direction = isset($predefined[$language->language][2]) ? $predefined[$language->language][2] : LANGUAGE_LTR; } - db_insert('languages') - ->fields(array( - 'language' => $langcode, - 'name' => $name, - 'native' => $native, - 'direction' => $direction, - 'domain' => $domain, - 'prefix' => $prefix, - 'enabled' => $enabled, - )) - ->execute(); + // Set to enabled for the default language and unless specified otherwise. + if (!empty($language->default) || !isset($language->enabled)) { + $language->enabled = TRUE; + } + // Let other modules modify $language before saved. + module_invoke_all('locale_language_presave', $language); - // Only set it as default if enabled. - if ($enabled && $default) { - variable_set('language_default', (object) array('language' => $langcode, 'name' => $name, 'native' => $native, 'direction' => $direction, 'enabled' => (int) $enabled, 'plurals' => 0, 'formula' => '', 'domain' => '', 'prefix' => $prefix, 'weight' => 0, 'javascript' => '')); + // Save the record and inform others about the change. + if ($language->is_new) { + drupal_write_record('languages', $language); + module_invoke_all('locale_language_insert', $language); + watchdog('locale', 'The %language (%langcode) language has been created.', array('%language' => $language->name, '%langcode' => $language->language)); } + else { + drupal_write_record('languages', $language, array('language')); + module_invoke_all('locale_language_update', $language); + watchdog('locale', 'The %language (%langcode) language has been updated.', array('%language' => $language->name, '%langcode' => $language->language)); + } + + if (!empty($language->default)) { + // Set the new version of this language as default in a variable. + $default_language = language_default(); + variable_set('language_default', $language); - if ($enabled) { - // Increment enabled language count if we are adding an enabled language. - variable_set('language_count', variable_get('language_count', 1) + 1); + // If this is different to the previous default language, save that one + // as non-default too. + if ($language->language != $default_language->language) { + $default_language->default = FALSE; + locale_language_save($default_language); + } } - // Kill the static cache in language_list(). - drupal_static_reset('language_list'); + // Update language count based on enabled language count. + variable_set('language_count', db_query('SELECT COUNT(language) FROM {languages} WHERE enabled = 1')->fetchField()); - // Force JavaScript translation file creation for the newly added language. - _locale_invalidate_js($langcode); + // Kill the static cache in language_list(). + drupal_static_reset('language_list'); - watchdog('locale', 'The %language language (%code) has been created.', array('%language' => $name, '%code' => $langcode)); + // Force JavaScript translation file re-creation for the modified language. + _locale_invalidate_js($language->language); - module_invoke_all('multilingual_settings_changed'); + return $language; } -/** - * @} End of "locale-api-add" - */ /** * Parses a JavaScript file, extracts strings wrapped in Drupal.t() and diff --git a/modules/field/tests/field.test b/modules/field/tests/field.test index 669fc37..f2b3550 100644 --- a/modules/field/tests/field.test +++ b/modules/field/tests/field.test @@ -2644,7 +2644,12 @@ class FieldTranslationsTestCase extends FieldTestCase { require_once DRUPAL_ROOT . '/includes/locale.inc'; for ($i = 0; $i < 3; ++$i) { - locale_add_language('l' . $i, $this->randomString(), $this->randomString()); + $language = (object) array( + 'language' => 'l' . $i, + 'name' => $this->randomString(), + 'native' => $this->randomString(), + ); + locale_language_save($language); } } diff --git a/modules/locale/locale.admin.inc b/modules/locale/locale.admin.inc index 79ec3a1..6370326 100644 --- a/modules/locale/locale.admin.inc +++ b/modules/locale/locale.admin.inc @@ -350,14 +350,24 @@ function locale_languages_predefined_form_submit($form, &$form_state) { $langcode = $form_state['values']['langcode']; if (isset($form_state['values']['name'])) { // Custom language form. - locale_add_language($langcode, $form_state['values']['name'], $form_state['values']['native'], $form_state['values']['direction'], $form_state['values']['domain'], $form_state['values']['prefix']); - drupal_set_message(t('The language %language has been created and can now be used. More information is available on the help screen.', array('%language' => t($form_state['values']['name']), '@locale-help' => url('admin/help/locale')))); + $language = (object) array( + 'language' => $langcode, + 'name' => $form_state['values']['name'], + 'native' => $form_state['values']['native'], + 'direction' => $form_state['values']['direction'], + 'domain' => $form_state['values']['domain'], + 'prefix' => $form_state['values']['prefix'], + ); + locale_language_save($language); } else { // Predefined language selection. include_once DRUPAL_ROOT . '/includes/iso.inc'; $predefined = _locale_get_predefined_list(); - locale_add_language($langcode); + $language = (object) array( + 'language' => $langcode, + ); + locale_language_save($language); drupal_set_message(t('The language %language has been created and can now be used. More information is available on the help screen.', array('%language' => t($predefined[$langcode][0]), '@locale-help' => url('admin/help/locale')))); } diff --git a/modules/locale/locale.api.php b/modules/locale/locale.api.php index e327401..8111dc0 100644 --- a/modules/locale/locale.api.php +++ b/modules/locale/locale.api.php @@ -162,6 +162,11 @@ function hook_language_negotiation_info_alter(array &$language_providers) { * elsewhere. The typical implementation would trigger some kind of rebuilding, * this way system components could properly react to the change of the enabled * languages number. + * + * @todo + * To be deprecated by hook_locale_language_presave(), + * hook_locale_language_insert() and hook_locale_language_update() once all + * the language admin workflow uses locale_language_save(). */ function hook_multilingual_settings_changed() { field_info_cache_clear(); @@ -179,5 +184,38 @@ function hook_language_fallback_candidates_alter(array &$fallback_candidates) { } /** + * React to a language about to be added or updated in the system. + * + * @param $language + * A language object. + */ +function hook_locale_language_presave($language) { + if ($language->default) { + // React to a new default language. + example_new_default_language($language); + } +} + +/** + * React to a language that was just added to the system. + * + * @param $language + * A language object. + */ +function hook_locale_language_insert($language) { + example_refresh_permissions(); +} + +/** + * React to a language that was just updated in the system. + * + * @param $language + * A language object. + */ +function hook_locale_language_update($language) { + example_refresh_permissions(); +} + +/** * @} End of "addtogroup hooks". */ diff --git a/modules/locale/locale.test b/modules/locale/locale.test index 6dad7e0..e94b4af 100644 --- a/modules/locale/locale.test +++ b/modules/locale/locale.test @@ -1098,12 +1098,18 @@ class LocaleUninstallFunctionalTest extends DrupalWebTestCase { // Add a new language and optionally set it as default. require_once DRUPAL_ROOT . '/includes/locale.inc'; - locale_add_language('fr', 'French', 'Français', LANGUAGE_LTR, '', '', TRUE, $this->language == 'fr'); + + $language = (object) array( + 'language' => 'fr', + 'name' => 'French', + 'native' => 'Français', + 'default' => $this->language == 'fr', + ); + locale_language_save($language); // Check the UI language. drupal_language_initialize(); - global $language; - $this->assertEqual($language->language, $this->language, t('Current language: %lang', array('%lang' => $language->language))); + $this->assertEqual($GLOBALS['language']->language, $this->language, t('Current language: %lang', array('%lang' => $GLOBALS['language']->language))); // Enable multilingual workflow option for articles. variable_set('language_content_type_article', 1); @@ -1148,7 +1154,7 @@ class LocaleUninstallFunctionalTest extends DrupalWebTestCase { // Check the init language logic. drupal_language_initialize(); - $this->assertEqual($language->language, 'en', t('Language after uninstall: %lang', array('%lang' => $language->language))); + $this->assertEqual($GLOBALS['language']->language, 'en', t('Language after uninstall: %lang', array('%lang' => $GLOBALS['language']->language))); // Check JavaScript files deletion. $this->assertTrue($result = !file_exists($js_file), t('JavaScript file deleted: %file', array('%file' => $result ? $js_file : t('found')))); @@ -1271,19 +1277,19 @@ class LocaleLanguageSwitchingFunctionalTest extends DrupalWebTestCase { ); foreach ($language_switcher->ul->li as $link) { $classes = explode(" ", (string) $link['class']); - list($language) = array_intersect($classes, array('en', 'fr')); + list($langcode) = array_intersect($classes, array('en', 'fr')); if (in_array('active', $classes)) { - $links['active'][] = $language; + $links['active'][] = $langcode; } else { - $links['inactive'][] = $language; + $links['inactive'][] = $langcode; } $anchor_classes = explode(" ", (string) $link->a['class']); if (in_array('active', $anchor_classes)) { - $anchors['active'][] = $language; + $anchors['active'][] = $langcode; } else { - $anchors['inactive'][] = $language; + $anchors['inactive'][] = $langcode; } } $this->assertIdentical($links, array('active' => array('en'), 'inactive' => array('fr')), t('Only the current language list item is marked as active on the language switcher block.')); @@ -1796,13 +1802,13 @@ class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { function testUILanguageNegotiation() { // A few languages to switch to. // This one is unknown, should get the default lang version. - $language_unknown = 'blah-blah'; + $langcode_unknown = 'blah-blah'; // For testing browser lang preference. - $language_browser_fallback = 'vi'; + $langcode_browser_fallback = 'vi'; // For testing path prefix. - $language = 'zh-hans'; + $langcode = 'zh-hans'; // For setting browser language preference to 'vi'. - $http_header_browser_fallback = array("Accept-Language: $language_browser_fallback;q=1"); + $http_header_browser_fallback = array("Accept-Language: $langcode_browser_fallback;q=1"); // For setting browser language preference to some unknown. $http_header_blah = array("Accept-Language: blah;q=1"); @@ -1811,8 +1817,14 @@ class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { // Setup the site languages by installing two languages. require_once DRUPAL_ROOT . '/includes/locale.inc'; - locale_add_language($language_browser_fallback); - locale_add_language($language); + $language = (object) array( + 'language' => $langcode_browser_fallback, + ); + locale_language_save($language); + $language = (object) array( + 'language' => $langcode, + ); + locale_language_save($language); // We will look for this string in the admin/config screen to see if the // corresponding translated string is shown. @@ -1831,16 +1843,16 @@ class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { variable_del('language_default'); // Translate the string. - $language_browser_fallback_string = "In $language_browser_fallback In $language_browser_fallback In $language_browser_fallback"; - $language_string = "In $language In $language In $language"; + $language_browser_fallback_string = "In $langcode_browser_fallback In $langcode_browser_fallback In $langcode_browser_fallback"; + $language_string = "In $langcode In $langcode In $langcode"; // Do a translate search of our target string. $edit = array( 'string' => $default_string); $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Filter')); // Should find the string and now click edit to post translated string. $this->clickLink('edit'); $edit = array( - "translations[$language_browser_fallback]" => $language_browser_fallback_string, - "translations[$language]" => $language_string, + "translations[$langcode_browser_fallback]" => $language_browser_fallback_string, + "translations[$langcode]" => $language_string, ); $this->drupalPost(NULL, $edit, t('Save translations')); @@ -1859,7 +1871,7 @@ class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { // Language prefix. array( 'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT), - 'path' => "$language/admin/config", + 'path' => "$langcode/admin/config", 'expect' => $language_string, 'http_header' => $http_header_browser_fallback, 'message' => 'URL (PATH) > DEFAULT: with language prefix, UI language is switched based on path prefix', @@ -1875,7 +1887,7 @@ class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { // Prefix, switch to the language. array( 'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LOCALE_LANGUAGE_NEGOTIATION_BROWSER), - 'path' => "$language/admin/config", + 'path' => "$langcode/admin/config", 'expect' => $language_string, 'http_header' => $http_header_browser_fallback, 'message' => 'URL (PATH) > BROWSER: with langage prefix, UI language is based on path prefix', @@ -1896,13 +1908,13 @@ class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { // Unknown language prefix should return 404. variable_set('language_negotiation_' . LANGUAGE_TYPE_INTERFACE, locale_language_negotiation_info()); - $this->drupalGet("$language_unknown/admin/config", array(), $http_header_browser_fallback); + $this->drupalGet("$langcode_unknown/admin/config", array(), $http_header_browser_fallback); $this->assertResponse(404, "Unknown language path prefix should return 404"); // Setup for domain negotiation, first configure the language to have domain // URL. $edit = array('prefix' => '', 'domain' => "http://$language_domain"); - $this->drupalPost("admin/config/regional/language/edit/$language", $edit, t('Save language')); + $this->drupalPost("admin/config/regional/language/edit/$langcode", $edit, t('Save language')); // Set the site to use domain language negotiation. $tests = array( @@ -1953,8 +1965,11 @@ class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { */ function testUrlLanguageFallback() { // Add the Italian language. - $language_browser_fallback = 'it'; - locale_add_language($language_browser_fallback); + $langcode_browser_fallback = 'it'; + $language = (object) array( + 'language' => $langcode_browser_fallback, + ); + locale_language_save($language); $languages = language_list(); // Enable the path prefix for the default language: this way any unprefixed @@ -1978,14 +1993,14 @@ class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { // Access the front page without specifying any valid URL language prefix // and having as browser language preference a non-default language. - $http_header = array("Accept-Language: $language_browser_fallback;q=1"); + $http_header = array("Accept-Language: $langcode_browser_fallback;q=1"); $this->drupalGet('', array(), $http_header); // Check that the language switcher active link matches the given browser // language. - $args = array(':url' => base_path() . (!empty($GLOBALS['conf']['clean_url']) ? $language_browser_fallback : "?q=$language_browser_fallback")); + $args = array(':url' => base_path() . (!empty($GLOBALS['conf']['clean_url']) ? $langcode_browser_fallback : "?q=$langcode_browser_fallback")); $fields = $this->xpath('//div[@id="block-locale-language"]//a[@class="language-link active" and @href=:url]', $args); - $this->assertTrue($fields[0] == $languages[$language_browser_fallback]->native, t('The browser language is the URL active language')); + $this->assertTrue($fields[0] == $languages[$langcode_browser_fallback]->native, t('The browser language is the URL active language')); // Check that URLs are rewritten using the given browser language. $fields = $this->xpath('//div[@id="site-name"]//a[@rel="home" and @href=:url]//span', $args); @@ -2095,7 +2110,12 @@ class LocaleMultilingualFieldsFunctionalTest extends DrupalWebTestCase { // Add a new language. require_once DRUPAL_ROOT . '/includes/locale.inc'; - locale_add_language('it', 'Italian', 'Italiano', LANGUAGE_LTR, '', '', TRUE, FALSE); + $language = (object) array( + 'language' => 'it', + 'name' => 'Italian', + 'native' => 'Italiano', + ); + locale_language_save($language); // Enable URL language detection and selection. $edit = array('language[enabled][locale-url]' => '1'); @@ -2253,13 +2273,13 @@ class LocaleCommentLanguageFunctionalTest extends DrupalWebTestCase { // language and interface language do not influence comment language, as // only content language has to. foreach (language_list() as $node_langcode => $node_language) { - $language_none = LANGUAGE_NONE; + $langcode_none = LANGUAGE_NONE; // Create "Article" content. $title = $this->randomName(); $edit = array( "title" => $title, - "body[$language_none][0][value]" => $this->randomName(), + "body[$langcode_none][0][value]" => $this->randomName(), "language" => $node_langcode, ); $this->drupalPost("node/add/article", $edit, t('Save')); @@ -2268,7 +2288,7 @@ class LocaleCommentLanguageFunctionalTest extends DrupalWebTestCase { foreach (language_list() as $langcode => $language) { // Post a comment with content language $langcode. $prefix = empty($language->prefix) ? '' : $language->prefix . '/'; - $edit = array("comment_body[$language_none][0][value]" => $this->randomName()); + $edit = array("comment_body[$langcode_none][0][value]" => $this->randomName()); $this->drupalPost("{$prefix}node/{$node->nid}", $edit, t('Save')); // Check that comment language matches the current content language. diff --git a/modules/system/system.test b/modules/system/system.test index 846653b..fb22660 100644 --- a/modules/system/system.test +++ b/modules/system/system.test @@ -1215,7 +1215,11 @@ class DateTimeFunctionalTest extends DrupalWebTestCase { $this->assertFalse($format, 'Unlocalized date format resides not in localized table.'); // Enable German language - locale_add_language('de', NULL, NULL, LANGUAGE_LTR, '', '', TRUE, 'en'); + $language = (object) array( + 'language' => 'de', + 'default' => TRUE, + ); + locale_language_save($language); $date_format = array( 'type' => 'short',