diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index cfbdffb..4009c0e 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -1324,7 +1324,7 @@ function install_select_language(&$install_state) { */ function install_select_language_form($form, &$form_state, $files) { include_once DRUPAL_ROOT . '/core/includes/standard.inc'; - include_once DRUPAL_ROOT . '/core/includes/locale.inc'; + include_once DRUPAL_ROOT . '/core/modules/language/language.negotiation.inc'; $standard_languages = standard_language_list(); $select_options = array(); @@ -1345,7 +1345,7 @@ function install_select_language_form($form, &$form_state, $files) { ); } - $browser_langcode = locale_language_from_browser($languages); + $browser_langcode = language_from_browser($languages); $form['langcode'] = array( '#type' => 'select', '#options' => $select_options, @@ -1477,7 +1477,6 @@ function install_profile_modules(&$install_state) { * The batch definition, if there are language files to import. */ function install_import_translations(&$install_state) { - include_once DRUPAL_ROOT . '/core/includes/locale.inc'; include_once drupal_get_path('module', 'locale') . '/locale.bulk.inc'; $langcode = $install_state['parameters']['langcode']; @@ -1773,8 +1772,6 @@ function install_check_requirements($install_state) { * Forms API array definition for site configuration. */ function _install_configure_form($form, &$form_state, &$install_state) { - include_once DRUPAL_ROOT . '/core/includes/locale.inc'; - $form['site_information'] = array( '#type' => 'fieldset', '#title' => st('Site information'), diff --git a/core/includes/language.inc b/core/includes/language.inc index 76c4318..2c46c76 100644 --- a/core/includes/language.inc +++ b/core/includes/language.inc @@ -397,7 +397,7 @@ function language_url_split_prefix($path, $languages) { $prefix = array_shift($args); // Search prefix within enabled languages. - $prefixes = locale_language_negotiation_url_prefixes(); + $prefixes = language_negotiation_url_prefixes(); foreach ($languages as $language) { if (isset($prefixes[$language->langcode]) && $prefixes[$language->langcode] == $prefix) { // Rebuild $path with the language removed. diff --git a/core/includes/locale.inc b/core/includes/locale.inc deleted file mode 100644 index edfa4d3..0000000 --- a/core/includes/locale.inc +++ /dev/null @@ -1,923 +0,0 @@ -langcode) ? $language_interface->langcode : FALSE; -} - -/** - * 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 a user-specific preference. - * - * @param $languages - * An array of language objects for enabled languages ordered by weight. - * - * @return - * A valid language code on success, FALSE otherwise. - */ -function locale_language_from_browser($languages) { - if (empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { - return FALSE; - } - - // The Accept-Language header contains information about the language - // preferences configured in the user's browser / operating system. - // RFC 2616 (section 14.4) defines the Accept-Language header as follows: - // Accept-Language = "Accept-Language" ":" - // 1#( language-range [ ";" "q" "=" qvalue ] ) - // language-range = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" ) - // Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5" - $browser_langcodes = array(); - if (preg_match_all('@(?<=[, ]|^)([a-zA-Z-]+|\*)(?:;q=([0-9.]+))?(?:$|\s*,\s*)@', trim($_SERVER['HTTP_ACCEPT_LANGUAGE']), $matches, PREG_SET_ORDER)) { - foreach ($matches as $match) { - // We can safely use strtolower() here, tags are ASCII. - // RFC2616 mandates that the decimal part is no more than three digits, - // so we multiply the qvalue by 1000 to avoid floating point comparisons. - $langcode = strtolower($match[1]); - $qvalue = isset($match[2]) ? (float) $match[2] : 1; - $browser_langcodes[$langcode] = (int) ($qvalue * 1000); - } - } - - // We should take pristine values from the HTTP headers, but Internet Explorer - // from version 7 sends only specific language tags (eg. fr-CA) without the - // corresponding generic tag (fr) unless explicitly configured. In that case, - // we assume that the lowest value of the specific tags is the value of the - // generic language to be as close to the HTTP 1.1 spec as possible. - // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 and - // http://blogs.msdn.com/b/ie/archive/2006/10/17/accept-language-header-for-internet-explorer-7.aspx - asort($browser_langcodes); - foreach ($browser_langcodes as $langcode => $qvalue) { - $generic_tag = strtok($langcode, '-'); - if (!isset($browser_langcodes[$generic_tag])) { - $browser_langcodes[$generic_tag] = $qvalue; - } - } - - // Find the enabled language with the greatest qvalue, following the rules - // of RFC 2616 (section 14.4). If several languages have the same qvalue, - // prefer the one with the greatest weight. - $best_match_langcode = FALSE; - $max_qvalue = 0; - foreach ($languages as $langcode => $language) { - // Language tags are case insensitive (RFC2616, sec 3.10). - $langcode = strtolower($langcode); - - // If nothing matches below, the default qvalue is the one of the wildcard - // language, if set, or is 0 (which will never match). - $qvalue = isset($browser_langcodes['*']) ? $browser_langcodes['*'] : 0; - - // Find the longest possible prefix of the browser-supplied language - // ('the language-range') that matches this site language ('the language tag'). - $prefix = $langcode; - do { - if (isset($browser_langcodes[$prefix])) { - $qvalue = $browser_langcodes[$prefix]; - break; - } - } - while ($prefix = substr($prefix, 0, strrpos($prefix, '-'))); - - // Find the best match. - if ($qvalue > $max_qvalue) { - $best_match_langcode = $language->langcode; - $max_qvalue = $qvalue; - } - } - - return $best_match_langcode; -} - -/** - * Identify language from the user preferences. - * - * @param $languages - * An array of valid language objects. - * - * @return - * A valid language code on success, FALSE otherwise. - */ -function locale_language_from_user($languages) { - // User preference (only for logged users). - global $user; - - if ($user->uid && !empty($user->preferred_langcode)) { - return $user->preferred_langcode; - } - - // No language preference from the user. - return FALSE; -} - -/** - * Identify language from a request/session parameter. - * - * @param $languages - * An array of valid language objects. - * - * @return - * A valid language code on success, FALSE otherwise. - */ -function locale_language_from_session($languages) { - $param = variable_get('locale_language_negotiation_session_param', 'language'); - - // Request parameter: we need to update the session parameter only if we have - // an authenticated user. - if (isset($_GET[$param]) && isset($languages[$langcode = $_GET[$param]])) { - global $user; - if ($user->uid) { - $_SESSION[$param] = $langcode; - } - return $langcode; - } - - // Session parameter. - if (isset($_SESSION[$param])) { - return $_SESSION[$param]; - } - - return FALSE; -} - -/** - * Identify language via URL prefix or domain. - * - * @param $languages - * An array of valid language objects. - * - * @return - * A valid language code on success, FALSE otherwise. - */ -function locale_language_from_url($languages) { - $language_url = FALSE; - - if (!language_negotiation_method_enabled(LANGUAGE_NEGOTIATION_URL)) { - return $language_url; - } - - switch (variable_get('locale_language_negotiation_url_part', 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_url_split_prefix(isset($_GET['q']) ? $_GET['q'] : NULL, $languages); - if ($language !== FALSE) { - $language_url = $language->langcode; - } - break; - - case LANGUAGE_NEGOTIATION_URL_DOMAIN: - $domains = locale_language_negotiation_url_domains(); - foreach ($languages as $language) { - // Skip check if the language doesn't have a domain. - if (!empty($domains[$language->langcode])) { - // Only compare the domains not the protocols or ports. - // Remove protocol and add http:// so parse_url works - $host = 'http://' . str_replace(array('http://', 'https://'), '', $domains[$language->langcode]); - $host = parse_url($host, PHP_URL_HOST); - if ($_SERVER['HTTP_HOST'] == $host) { - $language_url = $language->langcode; - break; - } - } - } - break; - } - - return $language_url; -} - -/** - * Determines the language to be assigned to URLs when none is detected. - * - * The language negotiation process has a fallback chain that ends with the - * default language negotiation method. Each built-in language type has a - * separate initialization: - * - Interface language, which is the only configurable one, always gets a valid - * value. If no request-specific language is detected, the default language - * will be used. - * - Content language merely inherits the interface language by default. - * - URL language is detected from the requested URL and will be used to rewrite - * URLs appearing in the page being rendered. If no language can be detected, - * there are two possibilities: - * - If the default language has no configured path prefix or domain, then the - * default language is used. This guarantees that (missing) URL prefixes are - * preserved when navigating through the site. - * - If the default language has a configured path prefix or domain, a - * requested URL having an empty prefix or domain is an anomaly that must be - * fixed. This is done by introducing a prefix or domain in the rendered - * page matching the detected interface language. - * - * @param $languages - * (optional) An array of valid language objects. This is passed by - * language_negotiation_method_invoke() to every language method callback, - * but it is not actually needed here. Defaults to NULL. - * @param $language_type - * (optional) The language type to fall back to. Defaults to the interface - * language. - * - * @return - * A valid language code. - */ -function locale_language_url_fallback($language = NULL, $language_type = LANGUAGE_TYPE_INTERFACE) { - $default = language_default(); - $prefix = (variable_get('locale_language_negotiation_url_part', LANGUAGE_NEGOTIATION_URL_PREFIX) == LANGUAGE_NEGOTIATION_URL_PREFIX); - - // If the default language is not configured to convey language information, - // a missing URL language information indicates that URL language should be - // the default one, otherwise we fall back to an already detected language. - $domains = locale_language_negotiation_url_domains(); - $prefixes = locale_language_negotiation_url_prefixes(); - if (($prefix && empty($prefixes[$default->langcode])) || (!$prefix && empty($domains[$default->langcode]))) { - return $default->langcode; - } - else { - return $GLOBALS[$language_type]->langcode; - } -} - -/** - * Return links for the URL language switcher block. - * - * Translation links may be provided by other modules. - */ -function locale_language_switcher_url($type, $path) { - // Get the enabled languages only. - $languages = language_list(TRUE); - $links = array(); - - foreach ($languages as $language) { - $links[$language->langcode] = array( - 'href' => $path, - 'title' => $language->name, - 'language' => $language, - 'attributes' => array('class' => array('language-link')), - ); - } - - return $links; -} - -/** - * Return the session language switcher block. - */ -function locale_language_switcher_session($type, $path) { - drupal_add_css(drupal_get_path('module', 'locale') . '/locale.css'); - - $param = variable_get('locale_language_negotiation_session_param', 'language'); - $language_query = isset($_SESSION[$param]) ? $_SESSION[$param] : $GLOBALS[$type]->langcode; - - // Get the enabled languages only. - $languages = language_list(TRUE); - $links = array(); - - $query = $_GET; - unset($query['q']); - - foreach ($languages as $language) { - $langcode = $language->langcode; - $links[$langcode] = array( - 'href' => $path, - 'title' => $language->name, - 'attributes' => array('class' => array('language-link')), - 'query' => $query, - ); - if ($language_query != $langcode) { - $links[$langcode]['query'][$param] = $langcode; - } - else { - $links[$langcode]['attributes']['class'][] = ' session-active'; - } - } - - return $links; -} - -/** - * Rewrite URLs for the URL language negotiation method. - */ -function locale_language_url_rewrite_url(&$path, &$options) { - static $drupal_static_fast; - if (!isset($drupal_static_fast)) { - $drupal_static_fast['languages'] = &drupal_static(__FUNCTION__); - } - $languages = &$drupal_static_fast['languages']; - - if (!isset($languages)) { - // Get the enabled languages only. - $languages = language_list(TRUE); - $languages = array_flip(array_keys($languages)); - } - - // Language can be passed as an option, or we go for current URL language. - if (!isset($options['language'])) { - global $language_url; - $options['language'] = $language_url; - } - // We allow only enabled languages here. - elseif (!isset($languages[$options['language']->langcode])) { - unset($options['language']); - return; - } - - if (isset($options['language'])) { - switch (variable_get('locale_language_negotiation_url_part', LANGUAGE_NEGOTIATION_URL_PREFIX)) { - case LANGUAGE_NEGOTIATION_URL_DOMAIN: - $domains = locale_language_negotiation_url_domains(); - if (!empty($domains[$options['language']->langcode])) { - // Ask for an absolute URL with our modified base_url. - global $is_https; - $url_scheme = ($is_https) ? 'https://' : 'http://'; - $options['absolute'] = TRUE; - $options['base_url'] = $url_scheme . $domains[$options['language']->langcode]; - if (isset($options['https']) && variable_get('https', FALSE)) { - if ($options['https'] === TRUE) { - $options['base_url'] = str_replace('http://', 'https://', $options['base_url']); - } - elseif ($options['https'] === FALSE) { - $options['base_url'] = str_replace('https://', 'http://', $options['base_url']); - } - } - } - break; - - case LANGUAGE_NEGOTIATION_URL_PREFIX: - $prefixes = locale_language_negotiation_url_prefixes(); - if (!empty($prefixes[$options['language']->langcode])) { - $options['prefix'] = $prefixes[$options['language']->langcode] . '/'; - } - break; - } - } -} - -/** - * Reads language prefixes and uses the langcode if no prefix is set. - */ -function locale_language_negotiation_url_prefixes() { - return variable_get('locale_language_negotiation_url_prefixes', array()); -} - -/** - * Saves language prefix settings. - */ -function locale_language_negotiation_url_prefixes_save(array $prefixes) { - variable_set('locale_language_negotiation_url_prefixes', $prefixes); -} - -/** - * Reads language domains. - */ -function locale_language_negotiation_url_domains() { - return variable_get('locale_language_negotiation_url_domains', array()); -} - -/** - * Saves the language domain settings. - */ -function locale_language_negotiation_url_domains_save(array $domains) { - variable_set('locale_language_negotiation_url_domains', $domains); -} - -/** - * Rewrite URLs for the Session language negotiation method. - */ -function locale_language_url_rewrite_session(&$path, &$options) { - static $query_rewrite, $query_param, $query_value; - - // The following values are not supposed to change during a single page - // request processing. - if (!isset($query_rewrite)) { - global $user; - if (!$user->uid) { - // Get the enabled languages only. - $languages = language_list(TRUE); - $query_param = check_plain(variable_get('locale_language_negotiation_session_param', 'language')); - $query_value = isset($_GET[$query_param]) ? check_plain($_GET[$query_param]) : NULL; - $query_rewrite = isset($languages[$query_value]) && language_negotiation_method_enabled(LANGUAGE_NEGOTIATION_SESSION); - } - else { - $query_rewrite = FALSE; - } - } - - // If the user is anonymous, the user language negotiation method is enabled, - // and the corresponding option has been set, we must preserve any explicit - // user language preference even with cookies disabled. - if ($query_rewrite) { - if (is_string($options['query'])) { - $options['query'] = drupal_get_query_array($options['query']); - } - if (!isset($options['query'][$query_param])) { - $options['query'][$query_param] = $query_value; - } - } -} - -/** - * @} End of "locale-languages-negotiation" - */ - -/** - * Check that a string is safe to be added or imported as a translation. - * - * This test can be used to detect possibly bad translation strings. It should - * not have any false positives. But it is only a test, not a transformation, - * as it destroys valid HTML. We cannot reliably filter translation strings - * on import because some strings are irreversibly corrupted. For example, - * a & in the translation would get encoded to &amp; by filter_xss() - * before being put in the database, and thus would be displayed incorrectly. - * - * The allowed tag list is like filter_xss_admin(), but omitting div and img as - * not needed for translation and likely to cause layout issues (div) or a - * possible attack vector (img). - */ -function locale_string_is_safe($string) { - return decode_entities($string) == decode_entities(filter_xss($string, array('a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'ins', 'kbd', 'li', 'ol', 'p', 'pre', 'q', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'ul', 'var'))); -} - -/** - * Parses a JavaScript file, extracts strings wrapped in Drupal.t() and - * Drupal.formatPlural() and inserts them into the database. - */ -function _locale_parse_js_file($filepath) { - // The file path might contain a query string, so make sure we only use the - // actual file. - $parsed_url = drupal_parse_url($filepath); - $filepath = $parsed_url['path']; - // Load the JavaScript file. - $file = file_get_contents($filepath); - - // Match all calls to Drupal.t() in an array. - // Note: \s also matches newlines with the 's' modifier. - preg_match_all('~ - [^\w]Drupal\s*\.\s*t\s* # match "Drupal.t" with whitespace - \(\s* # match "(" argument list start - (' . LOCALE_JS_STRING . ')\s* # capture string argument - (?:,\s*' . LOCALE_JS_OBJECT . '\s* # optionally capture str args - (?:,\s*' . LOCALE_JS_OBJECT_CONTEXT . '\s*) # optionally capture context - ?)? # close optional args - [,\)] # match ")" or "," to finish - ~sx', $file, $t_matches); - - // Match all Drupal.formatPlural() calls in another array. - preg_match_all('~ - [^\w]Drupal\s*\.\s*formatPlural\s* # match "Drupal.formatPlural" with whitespace - \( # match "(" argument list start - \s*.+?\s*,\s* # match count argument - (' . LOCALE_JS_STRING . ')\s*,\s* # match singular string argument - ( # capture plural string argument - (?: # non-capturing group to repeat string pieces - (?: - \' # match start of single-quoted string - (?:\\\\\'|[^\'])* # match any character except unescaped single-quote - @count # match "@count" - (?:\\\\\'|[^\'])* # match any character except unescaped single-quote - \' # match end of single-quoted string - | - " # match start of double-quoted string - (?:\\\\"|[^"])* # match any character except unescaped double-quote - @count # match "@count" - (?:\\\\"|[^"])* # match any character except unescaped double-quote - " # match end of double-quoted string - ) - (?:\s*\+\s*)? # match "+" with possible whitespace, for str concat - )+ # match multiple because we supports concatenating strs - )\s* # end capturing of plural string argument - (?:,\s*' . LOCALE_JS_OBJECT . '\s* # optionally capture string args - (?:,\s*' . LOCALE_JS_OBJECT_CONTEXT . '\s*)? # optionally capture context - )? - [,\)] - ~sx', $file, $plural_matches); - - $matches = array(); - - // Add strings from Drupal.t(). - foreach ($t_matches[1] as $key => $string) { - $matches[] = array( - 'string' => $string, - 'context' => $t_matches[2][$key], - ); - } - - // Add string from Drupal.formatPlural(). - foreach ($plural_matches[1] as $key => $string) { - $matches[] = array( - 'string' => $string, - 'context' => $plural_matches[3][$key], - ); - - // If there is also a plural version of this string, add it to the strings array. - if (isset($plural_matches[2][$key])) { - $matches[] = array( - 'string' => $plural_matches[2][$key], - 'context' => $plural_matches[3][$key], - ); - } - } - - // Loop through all matches and process them. - foreach ($matches as $key => $match) { - - // Remove the quotes and string concatenations from the string and context. - $string = implode('', preg_split('~(? $string, ':context' => $context))->fetchObject(); - if ($source) { - // We already have this source string and now have to add the location - // to the location column, if this file is not yet present in there. - $locations = preg_split('~\s*;\s*~', $source->location); - - if (!in_array($filepath, $locations)) { - $locations[] = $filepath; - $locations = implode('; ', $locations); - - // Save the new locations string to the database. - db_update('locales_source') - ->fields(array( - 'location' => $locations, - )) - ->condition('lid', $source->lid) - ->execute(); - } - } - else { - // We don't have the source string yet, thus we insert it into the database. - db_insert('locales_source') - ->fields(array( - 'location' => $filepath, - 'source' => $string, - 'context' => $context, - )) - ->execute(); - } - } -} - -/** - * Force the JavaScript translation file(s) to be refreshed. - * - * This function sets a refresh flag for a specified language, or all - * languages except English, if none specified. JavaScript translation - * files are rebuilt (with locale_update_js_files()) the next time a - * request is served in that language. - * - * @param $langcode - * The language code for which the file needs to be refreshed. - * - * @return - * New content of the 'javascript_parsed' variable. - */ -function _locale_invalidate_js($langcode = NULL) { - $parsed = variable_get('javascript_parsed', array()); - - if (empty($langcode)) { - // Invalidate all languages. - $languages = language_list(); - if (!locale_translate_english()) { - unset($languages['en']); - } - foreach ($languages as $lcode => $data) { - $parsed['refresh:' . $lcode] = 'waiting'; - } - } - else { - // Invalidate single language. - $parsed['refresh:' . $langcode] = 'waiting'; - } - - variable_set('javascript_parsed', $parsed); - return $parsed; -} - -/** - * (Re-)Creates the JavaScript translation file for a language. - * - * @param $langcode - * The language, the translation file should be (re)created for. - */ -function _locale_rebuild_js($langcode = NULL) { - if (!isset($langcode)) { - global $language_interface; - $language = $language_interface; - } - else { - // Get information about the locale. - $languages = language_list(); - $language = $languages[$langcode]; - } - - // Construct the array for JavaScript translations. - // Only add strings with a translation to the translations array. - $result = db_query("SELECT s.lid, s.source, s.context, t.translation FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.location LIKE '%.js%'", array(':language' => $language->langcode)); - - $translations = array(); - foreach ($result as $data) { - $translations[$data->context][$data->source] = $data->translation; - } - - // Construct the JavaScript file, if there are translations. - $data_hash = NULL; - $data = $status = ''; - if (!empty($translations)) { - - $data = "Drupal.locale = { "; - - $locale_plurals = variable_get('locale_translation_plurals', array()); - if (!empty($locale_plurals[$language->langcode])) { - $data .= "'pluralFormula': function (\$n) { return Number({$locale_plurals[$language->langcode]['formula']}); }, "; - } - - $data .= "'strings': " . drupal_json_encode($translations) . " };"; - $data_hash = drupal_hash_base64($data); - } - - // Construct the filepath where JS translation files are stored. - // There is (on purpose) no front end to edit that variable. - $dir = 'public://' . variable_get('locale_js_directory', 'languages'); - - // Delete old file, if we have no translations anymore, or a different file to be saved. - $locale_javascripts = variable_get('locale_translation_javascript', array()); - $changed_hash = !isset($locale_javascripts[$language->langcode]) || ($locale_javascripts[$language->langcode] != $data_hash); - if (!empty($locale_javascripts[$language->langcode]) && (!$data || $changed_hash)) { - file_unmanaged_delete($dir . '/' . $language->langcode . '_' . $locale_javascripts[$language->langcode] . '.js'); - $locale_javascripts[$language->langcode] = ''; - $status = 'deleted'; - } - - // Only create a new file if the content has changed or the original file got - // lost. - $dest = $dir . '/' . $language->langcode . '_' . $data_hash . '.js'; - if ($data && ($changed_hash || !file_exists($dest))) { - // Ensure that the directory exists and is writable, if possible. - file_prepare_directory($dir, FILE_CREATE_DIRECTORY); - - // Save the file. - if (file_unmanaged_save_data($data, $dest)) { - $locale_javascripts[$language->langcode] = $data_hash; - // If we deleted a previous version of the file and we replace it with a - // new one we have an update. - if ($status == 'deleted') { - $status = 'updated'; - } - // If the file did not exist previously and the data has changed we have - // a fresh creation. - elseif ($changed_hash) { - $status = 'created'; - } - // If the data hash is unchanged the translation was lost and has to be - // rebuilt. - else { - $status = 'rebuilt'; - } - } - else { - $locale_javascripts[$language->langcode] = ''; - $status = 'error'; - } - } - - // Save the new JavaScript hash (or an empty value if the file just got - // deleted). Act only if some operation was executed that changed the hash - // code. - if ($status && $changed_hash) { - variable_set('locale_translation_javascript', $locale_javascripts); - } - - // Log the operation and return success flag. - switch ($status) { - case 'updated': - watchdog('locale', 'Updated JavaScript translation file for the language %language.', array('%language' => $language->name)); - return TRUE; - case 'rebuilt': - watchdog('locale', 'JavaScript translation file %file.js was lost.', array('%file' => $locale_javascripts[$language->langcode]), WATCHDOG_WARNING); - // Proceed to the 'created' case as the JavaScript translation file has - // been created again. - case 'created': - watchdog('locale', 'Created JavaScript translation file for the language %language.', array('%language' => $language->name)); - return TRUE; - case 'deleted': - watchdog('locale', 'Removed JavaScript translation file for the language %language because no translations currently exist for that language.', array('%language' => $language->name)); - return TRUE; - case 'error': - watchdog('locale', 'An error occurred during creation of the JavaScript translation file for the language %language.', array('%language' => $language->name), WATCHDOG_ERROR); - return FALSE; - default: - // No operation needed. - return TRUE; - } -} - -/** - * Get list of all predefined and custom countries. - * - * @return - * An array of all country code => country name pairs. - */ -function country_get_list() { - include_once DRUPAL_ROOT . '/core/includes/standard.inc'; - $countries = standard_country_list(); - // Allow other modules to modify the country list. - drupal_alter('countries', $countries); - return $countries; -} - -/** - * Save locale specific date formats to the database. - * - * @param $langcode - * Language code, can be 2 characters, e.g. 'en' or 5 characters, e.g. - * 'en-CA'. - * @param $type - * Date format type, e.g. 'short', 'medium'. - * @param $format - * The date format string. - */ -function locale_date_format_save($langcode, $type, $format) { - $locale_format = array(); - $locale_format['language'] = $langcode; - $locale_format['type'] = $type; - $locale_format['format'] = $format; - - $is_existing = (bool) db_query_range('SELECT 1 FROM {date_format_locale} WHERE language = :langcode AND type = :type', 0, 1, array(':langcode' => $langcode, ':type' => $type))->fetchField(); - if ($is_existing) { - $keys = array('type', 'language'); - drupal_write_record('date_format_locale', $locale_format, $keys); - } - else { - drupal_write_record('date_format_locale', $locale_format); - } -} - -/** - * Select locale date format details from database. - * - * @param $languages - * An array of language codes. - * - * @return - * An array of date formats. - */ -function locale_get_localized_date_format($languages) { - $formats = array(); - - // Get list of different format types. - $format_types = system_get_date_types(); - $short_default = variable_get('date_format_short', 'm/d/Y - H:i'); - - // Loop through each language until we find one with some date formats - // configured. - foreach ($languages as $language) { - $date_formats = system_date_format_locale($language); - if (!empty($date_formats)) { - // We have locale-specific date formats, so check for their types. If - // we're missing a type, use the default setting instead. - foreach ($format_types as $type => $type_info) { - // If format exists for this language, use it. - if (!empty($date_formats[$type])) { - $formats['date_format_' . $type] = $date_formats[$type]; - } - // Otherwise get default variable setting. If this is not set, default - // to the short format. - else { - $formats['date_format_' . $type] = variable_get('date_format_' . $type, $short_default); - } - } - - // Return on the first match. - return $formats; - } - } - - // No locale specific formats found, so use defaults. - $system_types = array('short', 'medium', 'long'); - // Handle system types separately as they have defaults if no variable exists. - $formats['date_format_short'] = $short_default; - $formats['date_format_medium'] = variable_get('date_format_medium', 'D, m/d/Y - H:i'); - $formats['date_format_long'] = variable_get('date_format_long', 'l, F j, Y - H:i'); - - // For non-system types, get the default setting, otherwise use the short - // format. - foreach ($format_types as $type => $type_info) { - if (!in_array($type, $system_types)) { - $formats['date_format_' . $type] = variable_get('date_format_' . $type, $short_default); - } - } - - return $formats; -} diff --git a/core/includes/update.inc b/core/includes/update.inc index 863c994..2715fdf 100644 --- a/core/includes/update.inc +++ b/core/includes/update.inc @@ -144,7 +144,7 @@ function update_prepare_stored_includes() { $negotiation = variable_get("language_negotiation_$language_type", array()); foreach ($negotiation as $method_id => &$method) { if (isset($method['file']) && $method['file'] == 'includes/locale.inc') { - $method['file'] = 'core/includes/locale.inc'; + $method['file'] = 'core/modules/language/language.negotiation.inc'; } } variable_set("language_negotiation_$language_type", $negotiation); diff --git a/core/modules/field/tests/field.test b/core/modules/field/tests/field.test index 61c7073..8f94063 100644 --- a/core/modules/field/tests/field.test +++ b/core/modules/field/tests/field.test @@ -2708,7 +2708,6 @@ class FieldTranslationsTestCase extends FieldTestCase { field_create_instance($instance); $this->instance = field_read_instance('test_entity', $this->field_name, 'test_bundle'); - require_once DRUPAL_ROOT . '/core/includes/locale.inc'; for ($i = 0; $i < 3; ++$i) { $language = (object) array( 'langcode' => 'l' . $i, diff --git a/core/modules/language/language.admin.inc b/core/modules/language/language.admin.inc index 33bc147..e341094 100644 --- a/core/modules/language/language.admin.inc +++ b/core/modules/language/language.admin.inc @@ -156,13 +156,19 @@ function language_admin_overview_form_submit($form, &$form_state) { $language->weight = $form_state['values']['languages'][$langcode]['weight']; if ($language->default || $old_default->langcode == $langcode) { - // Automatically enable the default language and the language - // which was default previously (because we will not get the - // value from that disabled checkbox). + // Automatically enable the default language and the language which was + // default previously (because we will not get the value from that + // disabled checkbox). $form_state['values']['languages'][$langcode]['enabled'] = 1; } $language->enabled = (int) !empty($form_state['values']['languages'][$langcode]['enabled']); + // If the interface language has been disabled make sure that the form + // redirect includes the new default language as a query parameter. + if ($language->enabled == FALSE && $langcode == $GLOBALS['language_interface']->langcode) { + $form_state['redirect'] = array('admin/config/regional/language', array('language' => $languages[$form_state['values']['site_default']])); + } + language_save($language); } @@ -279,7 +285,7 @@ function _language_admin_common_controls(&$form, $language = NULL) { '#required' => TRUE, '#description' => t('Direction that text in this language is presented.'), '#default_value' => @$language->direction, - '#options' => array(LANGUAGE_LTR => t('Left to right'), LANGUAGE_RTL => t('Right to left')) + '#options' => array(LANGUAGE_LTR => t('Left to right'), LANGUAGE_RTL => t('Right to left')), ); return $form; } @@ -367,7 +373,7 @@ function language_admin_edit_form_validate($form, &$form_state) { * Process the language editing form submission. */ function language_admin_edit_form_submit($form, &$form_state) { - // Prepare a language object for saving + // Prepare a language object for saving. $languages = language_list(); $langcode = $form_state['values']['langcode']; $language = $languages[$langcode]; @@ -388,7 +394,7 @@ function language_admin_delete_form($form, &$form_state, $language) { drupal_goto('admin/config/regional/language'); } - // For other languages, warn user that data loss is ahead. + // For other languages, warn the user that data loss is ahead. $languages = language_list(); if (!isset($languages[$langcode])) { @@ -436,3 +442,375 @@ function language_admin_predefined_list() { asort($predefined); return $predefined; } + +/** + * Builds the configuration form for language negotiation. + */ +function language_negotiation_configure_form() { + language_negotiation_include(); + + $form = array( + '#submit' => array('language_negotiation_configure_form_submit'), + '#theme' => 'language_negotiation_configure_form', + '#language_types' => language_types_get_configurable(FALSE), + '#language_types_info' => language_types_info(), + '#language_negotiation_info' => language_negotiation_info(), + ); + + foreach ($form['#language_types'] as $type) { + language_negotiation_configure_form_table($form, $type); + } + + $form['actions'] = array('#type' => 'actions'); + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save settings'), + ); + + return $form; +} + +/** + * Builds a language negotiation method configuration table. + */ +function language_negotiation_configure_form_table(&$form, $type) { + $info = $form['#language_types_info'][$type]; + + $table_form = array( + '#title' => t('@type language detection', array('@type' => $info['name'])), + '#tree' => TRUE, + '#description' => $info['description'], + '#language_negotiation_info' => array(), + '#show_operations' => FALSE, + 'weight' => array('#tree' => TRUE), + 'enabled' => array('#tree' => TRUE), + ); + + $negotiation_info = $form['#language_negotiation_info']; + $enabled_methods = variable_get("language_negotiation_$type", array()); + $methods_weight = variable_get("language_negotiation_methods_weight_$type", array()); + + // Add missing data to the methods lists. + foreach ($negotiation_info as $method_id => $method) { + if (!isset($methods_weight[$method_id])) { + $methods_weight[$method_id] = isset($method['weight']) ? $method['weight'] : 0; + } + } + + // Order methods list by weight. + asort($methods_weight); + + foreach ($methods_weight as $method_id => $weight) { + // A language method might be no more available if the defining module has + // been disabled after the last configuration saving. + if (!isset($negotiation_info[$method_id])) { + continue; + } + + $enabled = isset($enabled_methods[$method_id]); + $method = $negotiation_info[$method_id]; + + // List the method only if the current type is defined in its 'types' key. + // If it is not defined default to all the configurable language types. + $types = array_flip(isset($method['types']) ? $method['types'] : $form['#language_types']); + + if (isset($types[$type])) { + $table_form['#language_negotiation_info'][$method_id] = $method; + $method_name = check_plain($method['name']); + + $table_form['weight'][$method_id] = array( + '#type' => 'weight', + '#title' => t('Weight for !title language detection method', array('!title' => drupal_strtolower($method_name))), + '#title_display' => 'invisible', + '#default_value' => $weight, + '#attributes' => array('class' => array("language-method-weight-$type")), + ); + + $table_form['title'][$method_id] = array('#markup' => $method_name); + + $table_form['enabled'][$method_id] = array( + '#type' => 'checkbox', + '#title' => t('Enable !title language detection method', array('!title' => drupal_strtolower($method_name))), + '#title_display' => 'invisible', + '#default_value' => $enabled, + ); + if ($method_id === LANGUAGE_NEGOTIATION_DEFAULT) { + $table_form['enabled'][$method_id]['#default_value'] = TRUE; + $table_form['enabled'][$method_id]['#attributes'] = array('disabled' => 'disabled'); + } + + $table_form['description'][$method_id] = array('#markup' => filter_xss_admin($method['description'])); + + $config_op = array(); + if (isset($method['config'])) { + $config_op = array('#type' => 'link', '#title' => t('Configure'), '#href' => $method['config']); + // If there is at least one operation enabled show the operation column. + $table_form['#show_operations'] = TRUE; + } + $table_form['operation'][$method_id] = $config_op; + } + } + + $form[$type] = $table_form; +} + +/** + * Returns HTML for the language negotiation configuration form. + * + * @param $variables + * An associative array containing: + * - form: A render element representing the form. + * + * @ingroup themeable + */ +function theme_language_negotiation_configure_form($variables) { + $form = $variables['form']; + $output = ''; + + foreach ($form['#language_types'] as $type) { + $rows = array(); + $title = ''; + $description = '
' . $form[$type]['#description'] . '
'; + + foreach ($form[$type]['title'] as $id => $element) { + // Do not take form control structures. + if (is_array($element) && element_child($id)) { + $row = array( + 'data' => array( + '' . drupal_render($form[$type]['title'][$id]) . '', + drupal_render($form[$type]['description'][$id]), + drupal_render($form[$type]['enabled'][$id]), + drupal_render($form[$type]['weight'][$id]), + ), + 'class' => array('draggable'), + ); + if ($form[$type]['#show_operations']) { + $row['data'][] = drupal_render($form[$type]['operation'][$id]); + } + $rows[] = $row; + } + } + + $header = array( + array('data' => t('Detection method')), + array('data' => t('Description')), + array('data' => t('Enabled')), + array('data' => t('Weight')), + ); + + // If there is at least one operation enabled show the operation column. + if ($form[$type]['#show_operations']) { + $header[] = array('data' => t('Operations')); + } + + $variables = array( + 'header' => $header, + 'rows' => $rows, + 'attributes' => array('id' => "language-negotiation-methods-$type"), + ); + $table = theme('table', $variables); + $table .= drupal_render_children($form[$type]); + + drupal_add_tabledrag("language-negotiation-methods-$type", 'order', 'sibling', "language-method-weight-$type"); + + $output .= '
' . $title . $description . $table . '
'; + } + + $output .= drupal_render_children($form); + return $output; +} + +/** + * Submit handler for language negotiation settings. + */ +function language_negotiation_configure_form_submit($form, &$form_state) { + $configurable_types = $form['#language_types']; + + foreach ($configurable_types as $type) { + $method_weights = array(); + $enabled_methods = $form_state['values'][$type]['enabled']; + $enabled_methods[LANGUAGE_NEGOTIATION_DEFAULT] = TRUE; + $method_weights_input = $form_state['values'][$type]['weight']; + + foreach ($method_weights_input as $method_id => $weight) { + if ($enabled_methods[$method_id]) { + $method_weights[$method_id] = $weight; + } + } + + language_negotiation_set($type, $method_weights); + variable_set("language_negotiation_methods_weight_$type", $method_weights_input); + } + + // Update non-configurable language types and the related language negotiation + // configuration. + language_types_set(); + + $form_state['redirect'] = 'admin/config/regional/language/detection'; + drupal_set_message(t('Language negotiation configuration saved.')); +} + +/** + * Builds the URL language negotiation method configuration form. + */ +function language_negotiation_configure_url_form($form, &$form_state) { + global $base_url; + language_negotiation_include(); + + $form['language_negotiation_url_part'] = array( + '#title' => t('Part of the URL that determines language'), + '#type' => 'radios', + '#options' => array( + LANGUAGE_NEGOTIATION_URL_PREFIX => t('Path prefix'), + LANGUAGE_NEGOTIATION_URL_DOMAIN => t('Domain'), + ), + '#default_value' => variable_get('language_negotiation_url_part', LANGUAGE_NEGOTIATION_URL_PREFIX), + ); + + $form['prefix'] = array( + '#type' => 'fieldset', + '#tree' => TRUE, + '#title' => t('Path prefix configuration'), + '#description' => t('Language codes or other custom text to use as a path prefix for URL language detection. For the default language, this value may be left blank. Modifying this value may break existing URLs. Use with caution in a production environment. Example: Specifying "deutsch" as the path prefix code for German results in URLs like "example.com/deutsch/contact".'), + '#states' => array( + 'visible' => array( + ':input[name="language_negotiation_url_part"]' => array( + 'value' => (string) LANGUAGE_NEGOTIATION_URL_PREFIX, + ), + ), + ), + ); + $form['domain'] = array( + '#type' => 'fieldset', + '#tree' => TRUE, + '#title' => t('Domain configuration'), + '#description' => t('The domain names to use for these languages. Leave blank for the default language. Use with caution in a production environment.Modifying this value may break existing URLs. Use with caution in a production environment. Example: Specifying "de.example.com" as language domain for German will result in an URL like "http://de.example.com/contact".'), + '#states' => array( + 'visible' => array( + ':input[name="language_negotiation_url_part"]' => array( + 'value' => (string) LANGUAGE_NEGOTIATION_URL_DOMAIN, + ), + ), + ), + ); + + // Get the enabled languages only. + $languages = language_list(TRUE); + $prefixes = language_negotiation_url_prefixes(); + $domains = language_negotiation_url_domains(); + foreach ($languages as $langcode => $language) { + $form['prefix'][$langcode] = array( + '#type' => 'textfield', + '#title' => t('%language (%langcode) path prefix', array('%language' => $language->name, '%langcode' => $language->langcode)), + '#maxlength' => 64, + '#default_value' => isset($prefixes[$langcode]) ? $prefixes[$langcode] : '', + '#field_prefix' => $base_url . '/' . (variable_get('clean_url', 0) ? '' : '?q='), + ); + $form['domain'][$langcode] = array( + '#type' => 'textfield', + '#title' => t('%language (%langcode) domain', array('%language' => $language->name, '%langcode' => $language->langcode)), + '#maxlength' => 128, + '#default_value' => isset($domains[$langcode]) ? $domains[$langcode] : '', + ); + } + + $form_state['redirect'] = 'admin/config/regional/language/detection'; + + $form['actions']['#type'] = 'actions'; + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save configuration'), + ); + return $form; +} + +/** + * Validates the URL language negotiation method configuration. + * + * Validate that the prefixes and domains are unique, and make sure that + * the prefix and domain are only blank for the default. + */ +function language_negotiation_configure_url_form_validate($form, &$form_state) { + // Get the enabled languages only. + $languages = language_list(TRUE); + + // Count repeated values for uniqueness check. + $count = array_count_values($form_state['values']['prefix']); + foreach ($languages as $langcode => $language) { + $value = $form_state['values']['prefix'][$langcode]; + + if ($value === '') { + if (!$language->default && $form_state['values']['language_negotiation_url_part'] == LANGUAGE_NEGOTIATION_URL_PREFIX) { + // Throw a form error if the prefix is blank for a non-default language, + // although it is required for selected negotiation type. + form_error($form['prefix'][$langcode], t('The prefix may only be left blank for the default language.')); + } + } + elseif (isset($count[$value]) && $count[$value] > 1) { + // Throw a form error if there are two languages with the same + // domain/prefix. + form_error($form['prefix'][$langcode], t('The prefix for %language, %value, is not unique.', array('%language' => $language->name, '%value' => $value))); + } + } + + // Count repeated values for uniqueness check. + $count = array_count_values($form_state['values']['domain']); + foreach ($languages as $langcode => $language) { + $value = $form_state['values']['domain'][$langcode]; + + if ($value === '') { + if (!$language->default && $form_state['values']['language_negotiation_url_part'] == LANGUAGE_NEGOTIATION_URL_DOMAIN) { + // Throw a form error if the domain is blank for a non-default language, + // although it is required for selected negotiation type. + form_error($form['domain'][$langcode], t('The domain may only be left blank for the default language.')); + } + } + elseif (isset($count[$value]) && $count[$value] > 1) { + // Throw a form error if there are two languages with the same + // domain/domain. + form_error($form['domain'][$langcode], t('The domain for %language, %value, is not unique.', array('%language' => $language->name, '%value' => $value))); + } + } + + // Domain names should not contain protocol and/or ports. + foreach ($languages as $langcode => $name) { + $value = $form_state['values']['domain'][$langcode]; + if (!empty($value)) { + // Ensure we have exactly one protocol when checking the hostname. + $host = 'http://' . str_replace(array('http://', 'https://'), '', $value); + if (parse_url($host, PHP_URL_HOST) != $value) { + form_error($form['domain'][$langcode], t('The domain for %language may only contain the domain name, not a protocol and/or port.', array('%language' => $name))); + } + } + } +} + +/** + * Saves the URL language negotiation method settings. + */ +function language_negotiation_configure_url_form_submit($form, &$form_state) { + // Save selected format (prefix or domain). + variable_set('language_negotiation_url_part', $form_state['values']['language_negotiation_url_part']); + + // Save new domain and prefix values. + language_negotiation_url_prefixes_save($form_state['values']['prefix']); + language_negotiation_url_domains_save($form_state['values']['domain']); + + drupal_set_message(t('Configuration saved.')); +} + +/** + * Builds the session language negotiation method configuration form. + */ +function language_negotiation_configure_session_form($form, &$form_state) { + $form['language_negotiation_session_param'] = array( + '#title' => t('Request/session parameter'), + '#type' => 'textfield', + '#default_value' => variable_get('language_negotiation_session_param', 'language'), + '#description' => t('Name of the request/session parameter used to determine the desired language.'), + ); + + $form_state['redirect'] = 'admin/config/regional/language/detection'; + + return system_settings_form($form); +} diff --git a/core/modules/language/language.info b/core/modules/language/language.info index 699b283..b4b307a 100644 --- a/core/modules/language/language.info +++ b/core/modules/language/language.info @@ -1,5 +1,5 @@ name = Language -description = Lets you configure a number of languages to be used on your website. +description = Lets you configure a number of languages to be used on your website and provides language negotiation functionality. package = Core version = VERSION core = 8.x diff --git a/core/modules/language/language.install b/core/modules/language/language.install index ecf637d..92f61a0 100644 --- a/core/modules/language/language.install +++ b/core/modules/language/language.install @@ -7,10 +7,19 @@ /** * Implements hook_install(). + * + * Enable URL language negotiation by default in order to have a basic working + * system on multilingual sites without needing any preliminary configuration. */ function language_install() { // Add the default language to the database too. language_save(language_default()); + + // Enable URL language detection for each configurable language type. + require_once DRUPAL_ROOT . '/core/includes/language.inc'; + foreach (language_types_get_configurable(FALSE) as $type) { + language_negotiation_set($type, array(LANGUAGE_NEGOTIATION_URL => 0)); + } } /** @@ -21,6 +30,20 @@ function language_uninstall() { variable_del('language_default'); variable_del('language_count'); + // Clear variables. + variable_del('language_types'); + variable_del('language_negotiation_url_part'); + variable_del('language_negotiation_url_prefixes'); + variable_del('language_negotiation_url_domains'); + variable_del('language_negotiation_session_param'); + variable_del('language_content_type_default'); + variable_del('language_content_type_negotiation'); + + foreach (language_types_get_all() as $type) { + variable_del("language_negotiation_$type"); + variable_del("language_negotiation_methods_weight_$type"); + } + // Re-initialize the language system so successive calls to t() and other // functions will not expect languages to be present. drupal_language_initialize(); diff --git a/core/modules/language/language.module b/core/modules/language/language.module index 20f7c62..34f1a59 100644 --- a/core/modules/language/language.module +++ b/core/modules/language/language.module @@ -18,6 +18,8 @@ function language_help($path, $arg) { $output .= '
'; $output .= '
' . t('Configuring the list of languages') . '
'; $output .= '
' . t('Configure the list of languages either using the built-in language list or providing any custom languages you wish.', array('@configure-languages' => url('admin/config/regional/language'))) . '
'; + $output .= '
' . t('Configuring a multilingual site') . '
'; + $output .= '
' . t("Language negotiation allows your site to automatically change language based on the domain or path used for each request. Users may (optionally) select their preferred language on their My account page, and your site can be configured to honor a web browser's preferred language settings. Site content can be translated using the Content Translation module.", array('@content-help' => url('admin/help/translation'))) . '
'; $output .= '
'; return $output; @@ -26,6 +28,20 @@ function language_help($path, $arg) { case 'admin/config/regional/language/add': return '

' . t('Add a language to be supported by your site. If your desired language is not available, pick Custom language... at the end and provide a language code and other details manually.') . '

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

' . t("Define how to decide which language is used to display page elements (primarily text provided by Drupal and modules, such as field labels and help text). This decision is made by evaluating a series of detection methods for languages; the first detection method that gets a result will determine which language is used for that type of text. Define the order of evaluation of language detection methods on this page.") . '

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

' . t('Determine the language from a request/session parameter. Example: "http://example.com?language=de" sets language to German based on the use of "de" within the "language" parameter.') . '

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

' . t('This block is only shown if at least two languages are enabled and language negotiation is set to URL or Session.', array('@languages' => url('admin/config/regional/language'), '@configuration' => url('admin/config/regional/language/detection'))) . '

'; + } + break; } } @@ -33,6 +49,7 @@ function language_help($path, $arg) { * Implements hook_menu(). */ function language_menu() { + // Base language management and configuration. $items['admin/config/regional/language'] = array( 'title' => 'Languages', 'description' => 'Configure languages for content and the user interface.', @@ -70,6 +87,34 @@ function language_menu() { 'access arguments' => array('administer languages'), 'file' => 'language.admin.inc', ); + + // Language negotiation. + $items['admin/config/regional/language/detection'] = array( + 'title' => 'Detection and selection', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('language_negotiation_configure_form'), + 'access arguments' => array('administer languages'), + 'weight' => 10, + 'file' => 'language.admin.inc', + 'type' => MENU_LOCAL_TASK, + ); + $items['admin/config/regional/language/detection/url'] = array( + 'title' => 'URL language detection configuration', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('language_negotiation_configure_url_form'), + 'access arguments' => array('administer languages'), + 'file' => 'language.admin.inc', + 'type' => MENU_VISIBLE_IN_BREADCRUMB, + ); + $items['admin/config/regional/language/detection/session'] = array( + 'title' => 'Session language detection configuration', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('language_negotiation_configure_session_form'), + 'access arguments' => array('administer languages'), + 'file' => 'language.admin.inc', + 'type' => MENU_VISIBLE_IN_BREADCRUMB, + ); + return $items; } @@ -97,6 +142,9 @@ function language_theme() { 'render element' => 'elements', 'file' => 'language.admin.inc', ), + 'language_negotiation_configure_form' => array( + 'render element' => 'form', + ), ); } @@ -211,3 +259,283 @@ function language_css_alter(&$css) { } } } + +/** + * Implements hook_language_types_info(). + * + * Defines the three core language types: + * - Interface language is the only configurable language type in core. It is + * used by t() as the default language if none is specified. + * - Content language is by default non-configurable and inherits the interface + * language negotiated value. It is used by the Field API to determine the + * display language for fields if no explicit value is specified. + * - URL language is by default non-configurable and is determined through the + * URL language negotiation method or the URL fallback language negotiation + * method if no language can be detected. It is used by l() as the default + * language if none is specified. + */ +function language_language_types_info() { + language_negotiation_include(); + + return array( + LANGUAGE_TYPE_INTERFACE => array( + 'name' => t('User interface text'), + 'description' => t('Order of language detection methods for user interface text. If a translation of user interface text is available in the detected language, it will be displayed.'), + ), + LANGUAGE_TYPE_CONTENT => array( + 'name' => t('Content'), + 'description' => t('Order of language detection methods for content. If a version of content is available in the detected language, it will be displayed.'), + 'fixed' => array(LANGUAGE_NEGOTIATION_INTERFACE), + ), + LANGUAGE_TYPE_URL => array( + 'fixed' => array(LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_URL_FALLBACK), + ), + ); +} + +/** + * Implements hook_language_negotiation_info(). + */ +function language_language_negotiation_info() { + language_negotiation_include(); + $file = drupal_get_path('module', 'language') . '/language.negotiation.inc'; + + $negotiation_info = array(); + $negotiation_info[LANGUAGE_NEGOTIATION_URL] = array( + 'types' => array(LANGUAGE_TYPE_CONTENT, LANGUAGE_TYPE_INTERFACE, LANGUAGE_TYPE_URL), + 'callbacks' => array( + 'negotiation' => 'language_from_url', + 'language_switch' => 'language_switcher_url', + 'url_rewrite' => 'language_url_rewrite_url', + ), + 'file' => $file, + 'weight' => -8, + 'name' => t('URL'), + 'description' => t('Determine the language from the URL (Path prefix or domain).'), + 'config' => 'admin/config/regional/language/detection/url', + ); + + $negotiation_info[LANGUAGE_NEGOTIATION_SESSION] = array( + 'callbacks' => array( + 'negotiation' => 'language_from_session', + 'language_switch' => 'language_switcher_session', + 'url_rewrite' => 'language_url_rewrite_session', + ), + 'file' => $file, + 'weight' => -6, + 'name' => t('Session'), + 'description' => t('Determine the language from a request/session parameter.'), + 'config' => 'admin/config/regional/language/detection/session', + ); + + $negotiation_info[LANGUAGE_NEGOTIATION_USER] = array( + 'callbacks' => array('negotiation' => 'language_from_user'), + 'file' => $file, + 'weight' => -4, + 'name' => t('User'), + 'description' => t("Follow the user's language preference."), + ); + + $negotiation_info[LANGUAGE_NEGOTIATION_BROWSER] = array( + 'callbacks' => array('negotiation' => 'language_from_browser'), + 'file' => $file, + 'weight' => -2, + 'cache' => 0, + 'name' => t('Browser'), + 'description' => t("Determine the language from the browser's language settings."), + ); + + $negotiation_info[LANGUAGE_NEGOTIATION_INTERFACE] = array( + 'types' => array(LANGUAGE_TYPE_CONTENT), + 'callbacks' => array('negotiation' => 'language_from_interface'), + 'file' => $file, + 'weight' => 8, + 'name' => t('Interface'), + 'description' => t('Use the detected interface language.'), + ); + + $negotiation_info[LANGUAGE_NEGOTIATION_URL_FALLBACK] = array( + 'types' => array(LANGUAGE_TYPE_URL), + 'callbacks' => array('negotiation' => 'language_url_fallback'), + 'file' => $file, + 'weight' => 8, + 'name' => t('URL fallback'), + 'description' => t('Use an already detected language for URLs if none is found.'), + ); + + return $negotiation_info; +} + +/** + * Include negotiation backend functionality. + */ +function language_negotiation_include() { + include_once DRUPAL_ROOT . '/core/includes/language.inc'; + include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'language') . '/language.negotiation.inc'; +} + +/** + * Implements hook_modules_enabled(). + */ +function language_modules_enabled($modules) { + language_negotiation_include(); + language_types_set(); + language_negotiation_purge(); +} + +/** + * Implements hook_modules_disabled(). + */ +function language_modules_disabled($modules) { + language_modules_enabled($modules); +} + +/** + * Implements hook_language_insert(). + */ +function language_language_insert($language) { + language_negotiation_include(); + + // Add new language to the list of language prefixes. + $prefixes = language_negotiation_url_prefixes(); + $prefixes[$language->langcode] = (empty($language->default) ? $language->langcode : ''); + language_negotiation_url_prefixes_save($prefixes); + + // Add language to the list of language domains. + $domains = language_negotiation_url_domains(); + $domains[$language->langcode] = ''; + language_negotiation_url_domains_save($domains); +} + +/** + * Implements hook_language_update(). + */ +function language_language_update($language) { + language_negotiation_include(); + + // If the language is the default, then ensure that no other languages have + // blank prefix codes. + if (!empty($language->default)) { + $prefixes = language_negotiation_url_prefixes(); + foreach ($prefixes as $langcode => $prefix) { + if ($prefix == '' && $langcode != $language->langcode) { + $prefixes[$langcode] = $langcode; + } + } + language_negotiation_url_prefixes_save($prefixes); + } +} + +/** + * Implements hook_language_delete(). + */ +function language_language_delete($language) { + language_negotiation_include(); + + // Remove language from language prefix list. + $prefixes = language_negotiation_url_prefixes(); + unset($prefixes[$language->langcode]); + language_negotiation_url_prefixes_save($prefixes); + + // Remove language from language domain list. + $domains = language_negotiation_url_domains(); + unset($domains[$language->langcode]); + language_negotiation_url_domains_save($domains); +} + +/** + * Implements hook_block_info(). + */ +function language_block_info() { + include_once DRUPAL_ROOT . '/core/includes/language.inc'; + $block = array(); + $info = language_types_info(); + foreach (language_types_get_configurable(FALSE) as $type) { + $block[$type] = array( + // We do not need to escape the language type name since the block 'info' + // value is supposed not to be sanitized. It is escaped later, if needed. + 'info' => t('Language switcher (!type)', array('!type' => $info[$type]['name'])), + // Not worth caching. + 'cache' => DRUPAL_NO_CACHE, + ); + } + return $block; +} + +/** + * Implements hook_block_view(). + * + * Displays a language switcher. Only show if we have at least two languages. + */ +function language_block_view($type) { + if (language_multilingual()) { + $path = drupal_is_front_page() ? '' : $_GET['q']; + $links = language_negotiation_get_switch_links($type, $path); + + if (isset($links->links)) { + $class = "language-switcher-{$links->method_id}"; + $variables = array('links' => $links->links, 'attributes' => array('class' => array($class))); + $block['content'] = theme('links__language_block', $variables); + $block['subject'] = t('Languages'); + return $block; + } + } +} + +/** + * Implements hook_preprocess_block(). + */ +function language_preprocess_block(&$variables) { + if ($variables['block']->module == 'language') { + $variables['attributes_array']['role'] = 'navigation'; + } +} + +/** + * Implements hook_url_outbound_alter(). + * + * Rewrite outbound URLs with language based prefixes. + */ +function language_url_outbound_alter(&$path, &$options, $original_path) { + // Only modify internal URLs. + if (!$options['external'] && language_multilingual()) { + static $drupal_static_fast; + if (!isset($drupal_static_fast)) { + $drupal_static_fast['callbacks'] = &drupal_static(__FUNCTION__); + } + $callbacks = &$drupal_static_fast['callbacks']; + + if (!isset($callbacks)) { + $callbacks = array(); + include_once DRUPAL_ROOT . '/core/includes/language.inc'; + + foreach (language_types_get_configurable() as $type) { + // Get URL rewriter callbacks only from enabled language methods. + $negotiation = variable_get("language_negotiation_$type", array()); + + foreach ($negotiation as $method_id => $method) { + if (isset($method['callbacks']['url_rewrite'])) { + if (isset($method['file'])) { + require_once DRUPAL_ROOT . '/' . $method['file']; + } + // Avoid duplicate callback entries. + $callbacks[$method['callbacks']['url_rewrite']] = TRUE; + } + } + } + + $callbacks = array_keys($callbacks); + } + + foreach ($callbacks as $callback) { + if (function_exists($callback)) { + $callback($path, $options); + } + } + + // No language dependent path allowed in this mode. + if (empty($callbacks)) { + unset($options['language']); + } + } +} diff --git a/core/modules/language/language.negotiation.inc b/core/modules/language/language.negotiation.inc new file mode 100644 index 0000000..c90457f --- /dev/null +++ b/core/modules/language/language.negotiation.inc @@ -0,0 +1,465 @@ +langcode) ? $language_interface->langcode : FALSE; +} + +/** + * 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 a user-specific preference. + * + * @param $languages + * An array of language objects for enabled languages ordered by weight. + * + * @return + * A valid language code on success, FALSE otherwise. + */ +function language_from_browser($languages) { + if (empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { + return FALSE; + } + + // The Accept-Language header contains information about the language + // preferences configured in the user's browser / operating system. RFC 2616 + // (section 14.4) defines the Accept-Language header as follows: + // Accept-Language = "Accept-Language" ":" + // 1#( language-range [ ";" "q" "=" qvalue ] ) + // language-range = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" ) + // Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5" + $browser_langcodes = array(); + if (preg_match_all('@(?<=[, ]|^)([a-zA-Z-]+|\*)(?:;q=([0-9.]+))?(?:$|\s*,\s*)@', trim($_SERVER['HTTP_ACCEPT_LANGUAGE']), $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + // We can safely use strtolower() here, tags are ASCII. + // RFC2616 mandates that the decimal part is no more than three digits, + // so we multiply the qvalue by 1000 to avoid floating point comparisons. + $langcode = strtolower($match[1]); + $qvalue = isset($match[2]) ? (float) $match[2] : 1; + $browser_langcodes[$langcode] = (int) ($qvalue * 1000); + } + } + + // We should take pristine values from the HTTP headers, but Internet Explorer + // from version 7 sends only specific language tags (eg. fr-CA) without the + // corresponding generic tag (fr) unless explicitly configured. In that case, + // we assume that the lowest value of the specific tags is the value of the + // generic language to be as close to the HTTP 1.1 spec as possible. + // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 and + // http://blogs.msdn.com/b/ie/archive/2006/10/17/accept-language-header-for-internet-explorer-7.aspx + asort($browser_langcodes); + foreach ($browser_langcodes as $langcode => $qvalue) { + $generic_tag = strtok($langcode, '-'); + if (!isset($browser_langcodes[$generic_tag])) { + $browser_langcodes[$generic_tag] = $qvalue; + } + } + + // Find the enabled language with the greatest qvalue, following the rules of + // RFC 2616 (section 14.4). If several languages have the same qvalue, prefer + // the one with the greatest weight. + $best_match_langcode = FALSE; + $max_qvalue = 0; + foreach ($languages as $langcode => $language) { + // Language tags are case insensitive (RFC2616, sec 3.10). + $langcode = strtolower($langcode); + + // If nothing matches below, the default qvalue is the one of the wildcard + // language, if set, or is 0 (which will never match). + $qvalue = isset($browser_langcodes['*']) ? $browser_langcodes['*'] : 0; + + // Find the longest possible prefix of the browser-supplied language ('the + // language-range') that matches this site language ('the language tag'). + $prefix = $langcode; + do { + if (isset($browser_langcodes[$prefix])) { + $qvalue = $browser_langcodes[$prefix]; + break; + } + } + while ($prefix = substr($prefix, 0, strrpos($prefix, '-'))); + + // Find the best match. + if ($qvalue > $max_qvalue) { + $best_match_langcode = $language->langcode; + $max_qvalue = $qvalue; + } + } + + return $best_match_langcode; +} + +/** + * Identify language from the user preferences. + * + * @param $languages + * An array of valid language objects. + * + * @return + * A valid language code on success, FALSE otherwise. + */ +function language_from_user($languages) { + // User preference (only for logged users). + global $user; + + if ($user->uid && !empty($user->preferred_langcode)) { + return $user->preferred_langcode; + } + + // No language preference from the user. + return FALSE; +} + +/** + * Identify language from a request/session parameter. + * + * @param $languages + * An array of valid language objects. + * + * @return + * A valid language code on success, FALSE otherwise. + */ +function language_from_session($languages) { + $param = variable_get('language_negotiation_session_param', 'language'); + + // Request parameter: we need to update the session parameter only if we have + // an authenticated user. + if (isset($_GET[$param]) && isset($languages[$langcode = $_GET[$param]])) { + global $user; + if ($user->uid) { + $_SESSION[$param] = $langcode; + } + return $langcode; + } + + // Session parameter. + if (isset($_SESSION[$param])) { + return $_SESSION[$param]; + } + + return FALSE; +} + +/** + * Identify language via URL prefix or domain. + * + * @param $languages + * An array of valid language objects. + * + * @return + * A valid language code on success, FALSE otherwise. + */ +function language_from_url($languages) { + $language_url = FALSE; + + if (!language_negotiation_method_enabled(LANGUAGE_NEGOTIATION_URL)) { + return $language_url; + } + + switch (variable_get('language_negotiation_url_part', 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_url_split_prefix(isset($_GET['q']) ? $_GET['q'] : NULL, $languages); + if ($language !== FALSE) { + $language_url = $language->langcode; + } + break; + + case LANGUAGE_NEGOTIATION_URL_DOMAIN: + $domains = language_negotiation_url_domains(); + foreach ($languages as $language) { + // Skip the check if the language doesn't have a domain. + if (!empty($domains[$language->langcode])) { + // Ensure that there is exactly one protocol in the url when checking + // the hostname. + $host = 'http://' . str_replace(array('http://', 'https://'), '', $domains[$language->langcode]); + $host = parse_url($host, PHP_URL_HOST); + if ($_SERVER['HTTP_HOST'] == $host) { + $language_url = $language->langcode; + break; + } + } + } + break; + } + + return $language_url; +} + +/** + * Determines the language to be assigned to URLs when none is detected. + * + * The language negotiation process has a fallback chain that ends with the + * default language negotiation method. Each built-in language type has a + * separate initialization: + * - Interface language, which is the only configurable one, always gets a valid + * value. If no request-specific language is detected, the default language + * will be used. + * - Content language merely inherits the interface language by default. + * - URL language is detected from the requested URL and will be used to rewrite + * URLs appearing in the page being rendered. If no language can be detected, + * there are two possibilities: + * - If the default language has no configured path prefix or domain, then the + * default language is used. This guarantees that (missing) URL prefixes are + * preserved when navigating through the site. + * - If the default language has a configured path prefix or domain, a + * requested URL having an empty prefix or domain is an anomaly that must be + * fixed. This is done by introducing a prefix or domain in the rendered + * page matching the detected interface language. + * + * @param $languages + * (optional) An array of valid language objects. This is passed by + * language_negotiation_method_invoke() to every language method callback, + * but it is not actually needed here. Defaults to NULL. + * @param $language_type + * (optional) The language type to fall back to. Defaults to the interface + * language. + * + * @return + * A valid language code. + */ +function language_url_fallback($language = NULL, $language_type = LANGUAGE_TYPE_INTERFACE) { + $default = language_default(); + $prefix = (variable_get('language_negotiation_url_part', LANGUAGE_NEGOTIATION_URL_PREFIX) == LANGUAGE_NEGOTIATION_URL_PREFIX); + + // If the default language is not configured to convey language information, + // a missing URL language information indicates that URL language should be + // the default one, otherwise we fall back to an already detected language. + $domains = language_negotiation_url_domains(); + $prefixes = language_negotiation_url_prefixes(); + if (($prefix && empty($prefixes[$default->langcode])) || (!$prefix && empty($domains[$default->langcode]))) { + return $default->langcode; + } + else { + return $GLOBALS[$language_type]->langcode; + } +} + +/** + * Return links for the URL language switcher block. + * + * Translation links may be provided by other modules. + */ +function language_switcher_url($type, $path) { + // Get the enabled languages only. + $languages = language_list(TRUE); + $links = array(); + + foreach ($languages as $language) { + $links[$language->langcode] = array( + 'href' => $path, + 'title' => $language->name, + 'language' => $language, + 'attributes' => array('class' => array('language-link')), + ); + } + + return $links; +} + +/** + * Return the session language switcher block. + */ +function language_switcher_session($type, $path) { + $param = variable_get('language_negotiation_session_param', 'language'); + $language_query = isset($_SESSION[$param]) ? $_SESSION[$param] : $GLOBALS[$type]->langcode; + + // Get the enabled languages only. + $languages = language_list(TRUE); + $links = array(); + + $query = $_GET; + unset($query['q']); + + foreach ($languages as $language) { + $langcode = $language->langcode; + $links[$langcode] = array( + 'href' => $path, + 'title' => $language->name, + 'attributes' => array('class' => array('language-link')), + 'query' => $query, + ); + if ($language_query != $langcode) { + $links[$langcode]['query'][$param] = $langcode; + } + else { + $links[$langcode]['attributes']['class'][] = ' session-active'; + } + } + + return $links; +} + +/** + * Rewrite URLs for the URL language negotiation method. + */ +function language_url_rewrite_url(&$path, &$options) { + static $drupal_static_fast; + if (!isset($drupal_static_fast)) { + $drupal_static_fast['languages'] = &drupal_static(__FUNCTION__); + } + $languages = &$drupal_static_fast['languages']; + + if (!isset($languages)) { + // Get the enabled languages only. + $languages = language_list(TRUE); + $languages = array_flip(array_keys($languages)); + } + + // Language can be passed as an option, or we go for current URL language. + if (!isset($options['language'])) { + global $language_url; + $options['language'] = $language_url; + } + // We allow only enabled languages here. + elseif (!isset($languages[$options['language']->langcode])) { + unset($options['language']); + return; + } + + if (isset($options['language'])) { + switch (variable_get('language_negotiation_url_part', LANGUAGE_NEGOTIATION_URL_PREFIX)) { + case LANGUAGE_NEGOTIATION_URL_DOMAIN: + $domains = language_negotiation_url_domains(); + if (!empty($domains[$options['language']->langcode])) { + // Ask for an absolute URL with our modified base_url. + global $is_https; + $url_scheme = ($is_https) ? 'https://' : 'http://'; + $options['absolute'] = TRUE; + $options['base_url'] = $url_scheme . $domains[$options['language']->langcode]; + if (isset($options['https']) && variable_get('https', FALSE)) { + if ($options['https'] === TRUE) { + $options['base_url'] = str_replace('http://', 'https://', $options['base_url']); + } + elseif ($options['https'] === FALSE) { + $options['base_url'] = str_replace('https://', 'http://', $options['base_url']); + } + } + } + break; + + case LANGUAGE_NEGOTIATION_URL_PREFIX: + $prefixes = language_negotiation_url_prefixes(); + if (!empty($prefixes[$options['language']->langcode])) { + $options['prefix'] = $prefixes[$options['language']->langcode] . '/'; + } + break; + } + } +} + +/** + * Reads language prefixes and uses the langcode if no prefix is set. + */ +function language_negotiation_url_prefixes() { + return variable_get('language_negotiation_url_prefixes', array()); +} + +/** + * Saves language prefix settings. + */ +function language_negotiation_url_prefixes_save(array $prefixes) { + variable_set('language_negotiation_url_prefixes', $prefixes); +} + +/** + * Reads language domains. + */ +function language_negotiation_url_domains() { + return variable_get('language_negotiation_url_domains', array()); +} + +/** + * Saves the language domain settings. + */ +function language_negotiation_url_domains_save(array $domains) { + variable_set('language_negotiation_url_domains', $domains); +} + +/** + * Rewrite URLs for the Session language negotiation method. + */ +function language_url_rewrite_session(&$path, &$options) { + static $query_rewrite, $query_param, $query_value; + + // The following values are not supposed to change during a single page + // request processing. + if (!isset($query_rewrite)) { + global $user; + if (!$user->uid) { + // Get the enabled languages only. + $languages = language_list(TRUE); + $query_param = check_plain(variable_get('language_negotiation_session_param', 'language')); + $query_value = isset($_GET[$query_param]) ? check_plain($_GET[$query_param]) : NULL; + $query_rewrite = isset($languages[$query_value]) && language_negotiation_method_enabled(LANGUAGE_NEGOTIATION_SESSION); + } + else { + $query_rewrite = FALSE; + } + } + + // If the user is anonymous, the user language negotiation method is enabled, + // and the corresponding option has been set, we must preserve any explicit + // user language preference even with cookies disabled. + if ($query_rewrite) { + if (is_string($options['query'])) { + $options['query'] = drupal_get_query_array($options['query']); + } + if (!isset($options['query'][$query_param])) { + $options['query'][$query_param] = $query_value; + } + } +} diff --git a/core/modules/language/language.test b/core/modules/language/language.test index 0a59937..6c64c29 100644 --- a/core/modules/language/language.test +++ b/core/modules/language/language.test @@ -78,7 +78,7 @@ class LanguageListTest extends DrupalWebTestCase { $edit = array( 'languages[en][enabled]' => FALSE, ); - $this->drupalPost($path, $edit, t('Save configuration')); + $this->drupalPost(NULL, $edit, t('Save configuration')); $this->assertNoFieldChecked('edit-languages-en-enabled', t('Language disabled.')); // Set disabled language to be the default and ensure it is re-enabled. diff --git a/core/modules/locale/locale.admin.inc b/core/modules/locale/locale.admin.inc index 321b8ce..d74f714 100644 --- a/core/modules/locale/locale.admin.inc +++ b/core/modules/locale/locale.admin.inc @@ -6,379 +6,6 @@ */ /** - * Builds the configuration form for language negotiation. - */ -function language_negotiation_configure_form() { - include_once DRUPAL_ROOT . '/core/includes/language.inc'; - - $form = array( - '#submit' => array('language_negotiation_configure_form_submit'), - '#theme' => 'language_negotiation_configure_form', - '#language_types' => language_types_get_configurable(FALSE), - '#language_types_info' => language_types_info(), - '#language_negotiation_info' => language_negotiation_info(), - ); - - foreach ($form['#language_types'] as $type) { - language_negotiation_configure_form_table($form, $type); - } - - $form['actions'] = array('#type' => 'actions'); - $form['actions']['submit'] = array( - '#type' => 'submit', - '#value' => t('Save settings'), - ); - - return $form; -} - -/** - * Builds a language negotion method configuration table. - */ -function language_negotiation_configure_form_table(&$form, $type) { - $info = $form['#language_types_info'][$type]; - - $table_form = array( - '#title' => t('@type language detection', array('@type' => $info['name'])), - '#tree' => TRUE, - '#description' => $info['description'], - '#language_negotiation_info' => array(), - '#show_operations' => FALSE, - 'weight' => array('#tree' => TRUE), - 'enabled' => array('#tree' => TRUE), - ); - - $negotiation_info = $form['#language_negotiation_info']; - $enabled_methods = variable_get("language_negotiation_$type", array()); - $methods_weight = variable_get("locale_language_negotiation_methods_weight_$type", array()); - - // Add missing data to the methods lists. - foreach ($negotiation_info as $method_id => $method) { - if (!isset($methods_weight[$method_id])) { - $methods_weight[$method_id] = isset($method['weight']) ? $method['weight'] : 0; - } - } - - // Order methods list by weight. - asort($methods_weight); - - foreach ($methods_weight as $method_id => $weight) { - // A language method might be no more available if the defining module has - // been disabled after the last configuration saving. - if (!isset($negotiation_info[$method_id])) { - continue; - } - - $enabled = isset($enabled_methods[$method_id]); - $method = $negotiation_info[$method_id]; - - // List the method only if the current type is defined in its 'types' key. - // If it is not defined default to all the configurable language types. - $types = array_flip(isset($method['types']) ? $method['types'] : $form['#language_types']); - - if (isset($types[$type])) { - $table_form['#language_negotiation_info'][$method_id] = $method; - $method_name = check_plain($method['name']); - - $table_form['weight'][$method_id] = array( - '#type' => 'weight', - '#title' => t('Weight for !title language detection method', array('!title' => drupal_strtolower($method_name))), - '#title_display' => 'invisible', - '#default_value' => $weight, - '#attributes' => array('class' => array("language-method-weight-$type")), - ); - - $table_form['title'][$method_id] = array('#markup' => $method_name); - - $table_form['enabled'][$method_id] = array( - '#type' => 'checkbox', - '#title' => t('Enable !title language detection method', array('!title' => drupal_strtolower($method_name))), - '#title_display' => 'invisible', - '#default_value' => $enabled, - ); - if ($method_id === LANGUAGE_NEGOTIATION_DEFAULT) { - $table_form['enabled'][$method_id]['#default_value'] = TRUE; - $table_form['enabled'][$method_id]['#attributes'] = array('disabled' => 'disabled'); - } - - $table_form['description'][$method_id] = array('#markup' => filter_xss_admin($method['description'])); - - $config_op = array(); - if (isset($method['config'])) { - $config_op = array('#type' => 'link', '#title' => t('Configure'), '#href' => $method['config']); - // If there is at least one operation enabled show the operation column. - $table_form['#show_operations'] = TRUE; - } - $table_form['operation'][$method_id] = $config_op; - } - } - - $form[$type] = $table_form; -} - -/** - * Returns HTML for the language negotiation configuration form. - * - * @param $variables - * An associative array containing: - * - form: A render element representing the form. - * - * @ingroup themeable - */ -function theme_language_negotiation_configure_form($variables) { - $form = $variables['form']; - $output = ''; - - foreach ($form['#language_types'] as $type) { - $rows = array(); - $info = $form['#language_types_info'][$type]; - $title = ''; - $description = '
' . $form[$type]['#description'] . '
'; - - foreach ($form[$type]['title'] as $id => $element) { - // Do not take form control structures. - if (is_array($element) && element_child($id)) { - $row = array( - 'data' => array( - '' . drupal_render($form[$type]['title'][$id]) . '', - drupal_render($form[$type]['description'][$id]), - drupal_render($form[$type]['enabled'][$id]), - drupal_render($form[$type]['weight'][$id]), - ), - 'class' => array('draggable'), - ); - if ($form[$type]['#show_operations']) { - $row['data'][] = drupal_render($form[$type]['operation'][$id]); - } - $rows[] = $row; - } - } - - $header = array( - array('data' => t('Detection method')), - array('data' => t('Description')), - array('data' => t('Enabled')), - array('data' => t('Weight')), - ); - - // If there is at least one operation enabled show the operation column. - if ($form[$type]['#show_operations']) { - $header[] = array('data' => t('Operations')); - } - - $variables = array( - 'header' => $header, - 'rows' => $rows, - 'attributes' => array('id' => "language-negotiation-methods-$type"), - ); - $table = theme('table', $variables); - $table .= drupal_render_children($form[$type]); - - drupal_add_tabledrag("language-negotiation-methods-$type", 'order', 'sibling', "language-method-weight-$type"); - - $output .= '
' . $title . $description . $table . '
'; - } - - $output .= drupal_render_children($form); - return $output; -} - -/** - * Submit handler for language negotiation settings. - */ -function language_negotiation_configure_form_submit($form, &$form_state) { - $configurable_types = $form['#language_types']; - - foreach ($configurable_types as $type) { - $method_weights = array(); - $enabled_methods = $form_state['values'][$type]['enabled']; - $enabled_methods[LANGUAGE_NEGOTIATION_DEFAULT] = TRUE; - $method_weights_input = $form_state['values'][$type]['weight']; - - foreach ($method_weights_input as $method_id => $weight) { - if ($enabled_methods[$method_id]) { - $method_weights[$method_id] = $weight; - } - } - - language_negotiation_set($type, $method_weights); - variable_set("locale_language_negotiation_methods_weight_$type", $method_weights_input); - } - - // Update non-configurable language types and the related language negotiation - // configuration. - language_types_set(); - - $form_state['redirect'] = 'admin/config/regional/language/detection'; - drupal_set_message(t('Language negotiation configuration saved.')); -} - -/** - * Builds the URL language negotiation method configuration form. - */ -function language_negotiation_configure_url_form($form, &$form_state) { - $form['locale_language_negotiation_url_part'] = array( - '#title' => t('Part of the URL that determines language'), - '#type' => 'radios', - '#options' => array( - LANGUAGE_NEGOTIATION_URL_PREFIX => t('Path prefix'), - LANGUAGE_NEGOTIATION_URL_DOMAIN => t('Domain'), - ), - '#default_value' => variable_get('locale_language_negotiation_url_part', LANGUAGE_NEGOTIATION_URL_PREFIX), - ); - - $form['prefix'] = array( - '#type' => 'fieldset', - '#tree' => TRUE, - '#title' => t('Path prefix configuration'), - '#description' => t('Language codes or other custom text to use as a path prefix for URL language detection. For the default language, this value may be left blank. Modifying this value may break existing URLs. Use with caution in a production environment. Example: Specifying "deutsch" as the path prefix code for German results in URLs like "example.com/deutsch/contact".'), - '#states' => array( - 'visible' => array( - ':input[name="locale_language_negotiation_url_part"]' => array( - 'value' => (string) LANGUAGE_NEGOTIATION_URL_PREFIX, - ), - ), - ), - ); - $form['domain'] = array( - '#type' => 'fieldset', - '#tree' => TRUE, - '#title' => t('Domain configuration'), - '#description' => t('The domain names to use for these languages. Leave blank for the default language. Use with caution in a production environment.Modifying this value may break existing URLs. Use with caution in a production environment. Example: Specifying "de.example.com" as language domain for German will result in an URL like "http://de.example.com/contact".'), - '#states' => array( - 'visible' => array( - ':input[name="locale_language_negotiation_url_part"]' => array( - 'value' => (string) LANGUAGE_NEGOTIATION_URL_DOMAIN, - ), - ), - ), - ); - - // Get the enabled languages only. - $languages = language_list(TRUE); - $prefixes = locale_language_negotiation_url_prefixes(); - $domains = locale_language_negotiation_url_domains(); - foreach ($languages as $langcode => $language) { - $form['prefix'][$langcode] = array( - '#type' => 'textfield', - '#title' => t('%language (%langcode) path prefix', array('%language' => $language->name, '%langcode' => $language->langcode)), - '#maxlength' => 64, - '#default_value' => isset($prefixes[$langcode]) ? $prefixes[$langcode] : '', - '#field_prefix' => url('', array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q=') - ); - $form['domain'][$langcode] = array( - '#type' => 'textfield', - '#title' => t('%language (%langcode) domain', array('%language' => $language->name, '%langcode' => $language->langcode)), - '#maxlength' => 128, - '#default_value' => isset($domains[$langcode]) ? $domains[$langcode] : '', - ); - } - - $form_state['redirect'] = 'admin/config/regional/language/detection'; - - $form['actions']['#type'] = 'actions'; - $form['actions']['submit'] = array( - '#type' => 'submit', - '#value' => t('Save configuration'), - ); - return $form; -} - -/** - * Validates the URL language negotiation method configuration. - * - * Validate that the prefixes and domains are unique, and make sure that - * the prefix and domain are only blank for the default. - */ -function language_negotiation_configure_url_form_validate($form, &$form_state) { - // Get the enabled languages only. - $languages = language_list(TRUE); - $default = language_default(); - - // Count repeated values for uniqueness check. - $count = array_count_values($form_state['values']['prefix']); - foreach ($languages as $langcode => $language) { - $value = $form_state['values']['prefix'][$langcode]; - - if ($value === '') { - if (!$language->default && $form_state['values']['locale_language_negotiation_url_part'] == LANGUAGE_NEGOTIATION_URL_PREFIX) { - // Validation error if the prefix is blank for a non-default language, and value is for selected negotiation type. - form_error($form['prefix'][$langcode], t('The prefix may only be left blank for the default language.')); - } - } - else if (isset($count[$value]) && $count[$value] > 1) { - // Validation error if there are two languages with the same domain/prefix. - form_error($form['prefix'][$langcode], t('The prefix for %language, %value, is not unique.', array('%language' => $language->name, '%value' => $value))); - } - } - - // Count repeated values for uniqueness check. - $count = array_count_values($form_state['values']['domain']); - foreach ($languages as $langcode => $language) { - $value = $form_state['values']['domain'][$langcode]; - - if ($value === '') { - if (!$language->default && $form_state['values']['locale_language_negotiation_url_part'] == LANGUAGE_NEGOTIATION_URL_DOMAIN) { - // Validation error if the domain is blank for a non-default language, and value is for selected negotiation type. - form_error($form['domain'][$langcode], t('The domain may only be left blank for the default language.')); - } - } - else if (isset($count[$value]) && $count[$value] > 1) { - // Validation error if there are two languages with the same domain/domain. - form_error($form['domain'][$langcode], t('The domain for %language, %value, is not unique.', array('%language' => $language->name, '%value' => $value))); - } - } - - // Domain names should not contain protocol and/or ports. - foreach ($languages as $langcode => $name) { - $value = $form_state['values']['domain'][$langcode]; - if (!empty($value)) { - // Ensure we have a protocol but only one protocol in the setting for - // parse_url() checking against the hostname. - $host = 'http://' . str_replace(array('http://', 'https://'), '', $value); - if (parse_url($host, PHP_URL_HOST) != $value) { - form_error($form['domain'][$langcode], t('The domain for %language may only contain the domain name, not a protocol and/or port.', array( '%language' => $name))); - } - } - } -} - -/** - * Saves the URL language negotiation method settings. - */ -function language_negotiation_configure_url_form_submit($form, &$form_state) { - - // Save selected format (prefix or domain). - variable_set('locale_language_negotiation_url_part', $form_state['values']['locale_language_negotiation_url_part']); - - // Save new domain and prefix values. - locale_language_negotiation_url_prefixes_save($form_state['values']['prefix']); - locale_language_negotiation_url_domains_save($form_state['values']['domain']); - - drupal_set_message(t('Configuration saved.')); -} - -/** - * Builds the session language negotiation method configuration form. - */ -function language_negotiation_configure_session_form($form, &$form_state) { - $form['locale_language_negotiation_session_param'] = array( - '#title' => t('Request/session parameter'), - '#type' => 'textfield', - '#default_value' => variable_get('locale_language_negotiation_session_param', 'language'), - '#description' => t('Name of the request/session parameter used to determine the desired language.'), - ); - - $form_state['redirect'] = 'admin/config/regional/language/detection'; - - return system_settings_form($form); -} - -/** - * @} End of "locale-language-administration" - */ - -/** * Returns HTML for a locale date format form. * * @param $variables @@ -495,7 +122,6 @@ function locale_date_format_form($form, &$form_state, $langcode) { * Submit handler for configuring localized date formats on the locale_date_format_form. */ function locale_date_format_form_submit($form, &$form_state) { - include_once DRUPAL_ROOT . '/core/includes/locale.inc'; $langcode = $form_state['values']['langcode']; // Get list of date format types. diff --git a/core/modules/locale/locale.css b/core/modules/locale/locale.css index 96f08b9..6c03945 100644 --- a/core/modules/locale/locale.css +++ b/core/modules/locale/locale.css @@ -1,4 +1,3 @@ - .locale-untranslated { font-style: normal; text-decoration: line-through; @@ -24,9 +23,3 @@ float: left; /* LTR */ padding: 3ex 0 0 1em; /* LTR */ } -.language-switcher-locale-session a.active { - color: #0062a0; -} -.language-switcher-locale-session a.session-active { - color: #000000; -} diff --git a/core/modules/locale/locale.info b/core/modules/locale/locale.info index 28bbd03..8b3e5f7 100644 --- a/core/modules/locale/locale.info +++ b/core/modules/locale/locale.info @@ -1,5 +1,5 @@ name = Locale -description = Provides language negotiation functionality and user interface translation to languages other than English. +description = Provides user interface translation to languages other than English. package = Core version = VERSION core = 8.x diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install index 294fa4d..d740c67 100644 --- a/core/modules/locale/locale.install +++ b/core/modules/locale/locale.install @@ -6,62 +6,6 @@ */ /** - * Implements hook_install(). - * - * Enable URL language negotiation by default in order to have a basic working - * system on multilingual sites without needing any preliminary configuration. - */ -function locale_install() { - require_once DRUPAL_ROOT . '/core/includes/language.inc'; - - // We cannot rely on language negotiation hooks here, because locale module is - // not enabled yet. Therefore language_negotiation_set() cannot be used. - $info = locale_language_negotiation_info(); - $method = $info[LANGUAGE_NEGOTIATION_URL]; - $method_fields = array('callbacks', 'file', 'cache'); - $negotiation = array(); - - // Store only the needed data. - foreach ($method_fields as $field) { - if (isset($method[$field])) { - $negotiation[LANGUAGE_NEGOTIATION_URL][$field] = $method[$field]; - } - } - - // Enable URL language detection for each (core) configurable language type. - foreach (language_types_get_configurable() as $type) { - variable_set("language_negotiation_$type", $negotiation); - } -} - -/** - * Fill in the path prefixes and domains when enabled. - * - * Language module might change the list of languages, so we need to sync our - * configuration for domains and paths with the current language list. This - * should run every time the module is enabled. - */ -function locale_enable() { - require_once DRUPAL_ROOT . '/core/includes/locale.inc'; - - $languages = language_list(); - $prefixes_old = locale_language_negotiation_url_prefixes(); - $domains_old = locale_language_negotiation_url_domains(); - - $prefixes = array(); - $domains = array(); - foreach ($languages as $langcode => $language) { - // Keep the old prefix or fill in based on whether the language is default. - $prefixes[$langcode] = empty($prefixes_old[$langcode]) ? (empty($language->default) ? $langcode : '') : $prefixes_old[$langcode]; - // Keep the old domain or fill in empty value. - $domains[$langcode] = empty($domains_old[$langcode]) ? '' : $domains_old[$langcode]; - } - - locale_language_negotiation_url_prefixes_save($prefixes); - locale_language_negotiation_url_domains_save($domains); -} - -/** * Implements hook_uninstall(). */ function locale_uninstall() { @@ -82,13 +26,6 @@ function locale_uninstall() { } // Clear variables. - variable_del('language_types'); - variable_del('locale_language_negotiation_url_part'); - variable_del('locale_language_negotiation_url_prefixes'); - variable_del('locale_language_negotiation_url_domains'); - variable_del('locale_language_negotiation_session_param'); - variable_del('language_content_type_default'); - variable_del('language_content_type_negotiation'); variable_del('locale_cache_strings'); variable_del('locale_js_directory'); variable_del('javascript_parsed'); @@ -97,11 +34,6 @@ function locale_uninstall() { variable_del('locale_translation_plurals'); variable_del('locale_translation_javascript'); - foreach (language_types_get_all() as $type) { - variable_del("language_negotiation_$type"); - variable_del("locale_language_negotiation_methods_weight_$type"); - } - // Remove all node type language variables. Node module might have been // enabled, but may be disabled, so use a wildcard delete. db_delete('variable') @@ -364,7 +296,7 @@ function locale_update_8004() { // Rename the language negotiation methods weight variable. $weight = variable_get('locale_language_providers_weight_' . $type , NULL); if ($weight !== NULL) { - variable_set('locale_language_negotiation_methods_weight_' . $type , $weight); + variable_set('language_negotiation_methods_weight_' . $type , $weight); variable_del('locale_language_providers_weight_' . $type); } } @@ -394,9 +326,9 @@ function locale_update_8005() { if (!empty($plural_lids)) { // Look up all translations for these source strings. Ordering by language - // will group the strings by language, the 'plid' order will get the - // strings in singular/plural order and 'plural' will get them in precise - // sequential order needed. + // will group the strings by language, the 'plid' order will get the strings + // in singular/plural order and 'plural' will get them in precise sequential + // order needed. $results = db_query("SELECT s.lid, s.source, t.translation, t.plid, t.plural, t.language FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid WHERE s.lid IN (:lids) ORDER BY t.language, t.plid, t.plural", array(':lids' => $plural_lids)); // Collect the strings into an array and combine values as we go. @@ -426,9 +358,9 @@ function locale_update_8005() { // formulas. $utmost_parent['translation'][] = str_replace('@count[' . $child->plural .']', '@count', $child->translation); if (count($utmost_parent['source']) < 2) { - // Append source to the utmost parent's source string only if it is the - // plural variant. Further Drupal specific plural variants are not to be - // retained for source strings. + // Append source to the utmost parent's source string only if it is + // the plural variant. Further Drupal specific plural variants are not + // to be retained for source strings. $utmost_parent['source'][] = $child->source; } @@ -443,10 +375,10 @@ function locale_update_8005() { foreach ($strings as $langcode => $translations) { foreach($translations as $lid => $translation) { if (!in_array($lid, $updated_sources)) { - // Only update source string if not yet updated. We merged these within - // the translation lookups because plural information was only avilable - // with the translation, but we don't need to save it again for every - // language. + // Only update source string if not yet updated. We merged these + // within the translation lookups because plural information was only + // available with the translation, but we don't need to save it again + // for every language. db_update('locales_source') ->fields(array( 'source' => implode(LOCALE_PLURAL_DELIMITER, $translation['source']), @@ -504,6 +436,112 @@ function locale_update_8006() { } /** + * Convert language_negotiation_* variables to use the new callbacks. + */ +function locale_update_8007() { + $variable_names = array( + 'language_negotiation_language_interface', + 'language_negotiation_language_content', + 'language_negotiation_language_url', + ); + // Add all language type weight variables. As the function language_types() + // is not available its functionality is rebuild. + $language_types = variable_get('language_types', array( + LANGUAGE_TYPE_INTERFACE => TRUE, + LANGUAGE_TYPE_CONTENT => FALSE, + LANGUAGE_TYPE_URL => FALSE, + )); + foreach ($language_types as $language_type => $configurable) { + $variable_names[] = 'language_negotiation_methods_weight_' . $language_type; + } + $callback_map = array( + 'locale_language_from_url' => 'language_from_url', + 'locale_language_switcher_url' => 'language_switcher_url', + 'locale_language_url_rewrite_url' => 'language_url_rewrite_url', + 'locale_language_from_session' => 'language_from_session', + 'locale_language_switcher_session' => 'language_switcher_session', + 'locale_language_url_rewrite_session' => 'language_url_rewrite_session', + 'locale_language_from_user' => 'language_from_user', + 'locale_language_from_browser' => 'language_from_browser', + 'locale_language_url_fallback' => 'language_url_fallback', + 'locale_language_from_interface' => 'language_from_interface', + ); + $type_map = array( + 'locale-interface' => 'language-interface', + 'locale-url' => 'language-url', + 'locale-url-fallback' => 'language-url-fallback', + 'locale-browser' => 'language-browser', + 'locale-user' => 'language-user', + 'locale-session' => 'language-session', + ); + foreach ($variable_names as $variable_name) { + $value = variable_get($variable_name); + // Skip processing if the variable is not stored in the db. + if ($value === NULL) { + continue; + } + $new_value = $value; + foreach ($value as $type => $type_settings) { + // Convert the file. + if (isset($type_settings['file']) && (strpos($type_settings['file'], 'core/includes/locale.inc') !== FALSE)) { + $new_value[$type]['file'] = 'core/modules/language/language.negotiation.inc'; + } + // Convert the callbacks. + if (is_array($type_settings) && isset($type_settings['callbacks'])) { + foreach ($type_settings['callbacks'] as $key => $callback) { + if (isset($callback_map[$callback])) { + $new_value[$type]['callbacks'][$key] = $callback_map[$callback]; + } + } + } + // Convert the type. + if (isset($type_map[$type])) { + $new_value[$type_map[$type]] = $new_value[$type]; + unset($new_value[$type]); + } + } + // If necessary maintain the order of the values / keys of the variable. + if (stristr($variable_name, 'language_negotiation_methods_weight_') !== FALSE) { + asort($new_value); + } + variable_set($variable_name, $new_value); + } +} + +/** + * Rename the option variables of the locale language negotiation. + */ +function locale_update_8008() { + $variable_name_map = array( + 'locale_language_negotiation_url_part' => 'language_negotiation_url_part', + 'locale_language_negotiation_url_domains' => 'language_negotiation_url_domains', + 'locale_language_negotiation_url_prefixes' => 'language_negotiation_url_prefixes', + 'locale_language_negotiation_session_param' => 'language_negotiation_session_param', + ); + foreach ($variable_name_map as $deprecated_variable_name => $new_variable_name) { + // Check if this variable is stored in the db and if so rename it. + $value = variable_get($deprecated_variable_name); + if ($value !== NULL) { + variable_set($new_variable_name, $value); + variable_del($deprecated_variable_name); + } + } +} + +/** + * Convert locale blocks to language blocks. + */ +function locale_update_8009() { + $block_tables = array('block', 'block_node_type', 'block_role'); + foreach ($block_tables as $table) { + db_update($table) + ->fields(array('module' => 'language')) + ->condition('module', 'locale') + ->execute(); + } +} + +/** * @} End of "addtogroup updates-7.x-to-8.x" * The next series of updates should start at 9000. */ diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index 2afc147..6f81480 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -11,6 +11,57 @@ * Gettext portable object files are supported. */ +/** + * Regular expression pattern used to localize JavaScript strings. + */ +const LOCALE_JS_STRING = '(?:(?:\'(?:\\\\\'|[^\'])*\'|"(?:\\\\"|[^"])*")(?:\s*\+\s*)?)+'; + +/** + * Regular expression pattern used to match simple JS object literal. + * + * This pattern matches a basic JS object, but will fail on an object with + * nested objects. Used in JS file parsing for string arg processing. + */ +const LOCALE_JS_OBJECT = '\{.*?\}'; + +/** + * Regular expression to match an object containing a key 'context'. + * + * Pattern to match a JS object containing a 'context key' with a string value, + * which is captured. Will fail if there are nested objects. + */ +define('LOCALE_JS_OBJECT_CONTEXT', ' + \{ # match object literal start + .*? # match anything, non-greedy + (?: # match a form of "context" + \'context\' + | + "context" + | + context + ) + \s*:\s* # match key-value separator ":" + (' . LOCALE_JS_STRING . ') # match context string + .*? # match anything, non-greedy + \} # match end of object literal +'); + +/** + * Flag for locally not customized interface translation. + * + * Such translations are imported from .po files downloaded from + * localize.drupal.org for example. + */ +const LOCALE_NOT_CUSTOMIZED = 0; + +/** + * Flag for locally customized interface translation. + * + * Such translations are edited from their imported originals on the user + * interface or are imported as customized. + */ +const LOCALE_CUSTOMIZED = 1; + // --------------------------------------------------------------------------------- // Hook implementations @@ -32,22 +83,12 @@ function locale_help($path, $arg) { $output .= '
  • ' . t('Importing files from a set of existing translations, known as a translation package. A translation package enables the display of a specific version of Drupal in a specific language, and contains files in the Gettext Portable Object (.po) format. Although not all languages are available for every version of Drupal, translation packages for many languages are available for download from the Drupal translations page.', array('@translations' => 'http://localize.drupal.org')) . '
  • '; $output .= '
  • ' . t("If an existing translation package does not meet your needs, the Gettext Portable Object (.po) files within a package may be modified, or new .po files may be created, using a desktop Gettext editor. The Locale module's import feature allows the translated strings from a new or modified .po file to be added to your site. The Locale module's export feature generates files from your site's translated strings, that can either be shared with others or edited offline by a Gettext translation editor.", array('@import' => url('admin/config/regional/translate/import'), '@export' => url('admin/config/regional/translate/export'))) . '
  • '; $output .= ''; - $output .= '
    ' . t('Configuring a multilingual site') . '
    '; - $output .= '
    ' . t("Language negotiation allows your site to automatically change language based on the domain or path used for each request. Users may (optionally) select their preferred language on their My account page, and your site can be configured to honor a web browser's preferred language settings. Site content can be translated using the Content Translation module.", array('@content-help' => url('admin/help/translation'))) . '
    '; $output .= ''; return $output; case 'admin/config/regional/language': return '

    ' . t('Interface text can be translated. Download contributed translations from Drupal.org.', array('@translations' => 'http://localize.drupal.org')) . '

    '; - case 'admin/config/regional/language/detection': - $output = '

    ' . t("Define how to decide which language is used to display page elements (primarily text provided by Drupal and modules, such as field labels and help text). This decision is made by evaluating a series of detection methods for languages; the first detection method that gets a result will determine which language is used for that type of text. Define the order of evaluation of language detection methods on this page.") . '

    '; - return $output; - - case 'admin/config/regional/language/detection/session': - $output = '

    ' . t('Determine the language from a request/session parameter. Example: "http://example.com?language=de" sets language to German based on the use of "de" within the "language" parameter.') . '

    '; - return $output; - case 'admin/config/regional/translate': $output = '

    ' . t('This page allows a translator to search for specific translated and untranslated strings, and is used when creating or editing translations. (Note: For translation tasks involving many strings, it may be more convenient to export strings for offline editing in a desktop Gettext translation editor.) Searches may be limited to strings in a specific language.', array('@export' => url('admin/config/regional/translate/export'))) . '

    '; return $output; @@ -59,12 +100,6 @@ function locale_help($path, $arg) { case 'admin/config/regional/translate/export': return '

    ' . t('This page exports the translated strings used by your site. An export file may be in Gettext Portable Object (.po) form, which includes both the original string and the translation (used to share translations with others), or in Gettext Portable Object Template (.pot) form, which includes the original strings only (used to create new translations with a Gettext translation editor).') . '

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

    ' . t('This block is only shown if at least two languages are enabled and language negotiation is set to URL or Session.', array('@languages' => url('admin/config/regional/language'), '@configuration' => url('admin/config/regional/language/detection'))) . '

    '; - } - break; } } @@ -72,33 +107,6 @@ function locale_help($path, $arg) { * Implements hook_menu(). */ function locale_menu() { - // Language negotiation. - $items['admin/config/regional/language/detection'] = array( - 'title' => 'Detection and selection', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('language_negotiation_configure_form'), - 'access arguments' => array('administer languages'), - 'weight' => 10, - 'file' => 'locale.admin.inc', - 'type' => MENU_LOCAL_TASK, - ); - $items['admin/config/regional/language/detection/url'] = array( - 'title' => 'URL language detection configuration', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('language_negotiation_configure_url_form'), - 'access arguments' => array('administer languages'), - 'file' => 'locale.admin.inc', - 'type' => MENU_VISIBLE_IN_BREADCRUMB, - ); - $items['admin/config/regional/language/detection/session'] = array( - 'title' => 'Session language detection configuration', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('language_negotiation_configure_session_form'), - 'access arguments' => array('administer languages'), - 'file' => 'locale.admin.inc', - 'type' => MENU_VISIBLE_IN_BREADCRUMB, - ); - // Translation functionality. $items['admin/config/regional/translate'] = array( 'title' => 'User interface translation', @@ -183,7 +191,6 @@ function locale_menu() { */ function locale_init() { global $conf, $language_interface; - include_once DRUPAL_ROOT . '/core/includes/locale.inc'; // For each date type (e.g. long, short), get the localized date format // for the user's current language and override the default setting for it @@ -335,9 +342,6 @@ function locale_field_node_form_submit($form, &$form_state) { */ function locale_theme() { return array( - 'language_negotiation_configure_form' => array( - 'render element' => 'form', - ), 'locale_date_format_form' => array( 'render element' => 'form', ), @@ -407,140 +411,9 @@ function locale_entity_info_alter(&$entity_info) { } /** - * Implements hook_language_types_info(). - * - * Defines the three core language types: - * - Interface language is the only configurable language type in core. It is - * used by t() as the default language if none is specified. - * - Content language is by default non-configurable and inherits the interface - * language negotiated value. It is used by the Field API to determine the - * display language for fields if no explicit value is specified. - * - URL language is by default non-configurable and is determined through the - * URL language negotiation method or the URL fallback language negotiation - * method if no language can be detected. It is used by l() as the default - * language if none is specified. - */ -function locale_language_types_info() { - require_once DRUPAL_ROOT . '/core/includes/locale.inc'; - return array( - LANGUAGE_TYPE_INTERFACE => array( - 'name' => t('User interface text'), - 'description' => t('Order of language detection methods for user interface text. If a translation of user interface text is available in the detected language, it will be displayed.'), - ), - LANGUAGE_TYPE_CONTENT => array( - 'name' => t('Content'), - 'description' => t('Order of language detection methods for content. If a version of content is available in the detected language, it will be displayed.'), - 'fixed' => array(LANGUAGE_NEGOTIATION_INTERFACE), - ), - LANGUAGE_TYPE_URL => array( - 'fixed' => array(LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_URL_FALLBACK), - ), - ); -} - -/** - * Implements hook_language_negotiation_info(). - */ -function locale_language_negotiation_info() { - require_once DRUPAL_ROOT . '/core/includes/locale.inc'; - $file = '/core/includes/locale.inc'; - $negotiation_info = array(); - - $negotiation_info[LANGUAGE_NEGOTIATION_URL] = array( - 'types' => array(LANGUAGE_TYPE_CONTENT, LANGUAGE_TYPE_INTERFACE, LANGUAGE_TYPE_URL), - 'callbacks' => array( - 'negotiation' => 'locale_language_from_url', - 'language_switch' => 'locale_language_switcher_url', - 'url_rewrite' => 'locale_language_url_rewrite_url', - ), - 'file' => $file, - 'weight' => -8, - 'name' => t('URL'), - 'description' => t('Determine the language from the URL (Path prefix or domain).'), - 'config' => 'admin/config/regional/language/detection/url', - ); - - $negotiation_info[LANGUAGE_NEGOTIATION_SESSION] = array( - 'callbacks' => array( - 'negotiation' => 'locale_language_from_session', - 'language_switch' => 'locale_language_switcher_session', - 'url_rewrite' => 'locale_language_url_rewrite_session', - ), - 'file' => $file, - 'weight' => -6, - 'name' => t('Session'), - 'description' => t('Determine the language from a request/session parameter.'), - 'config' => 'admin/config/regional/language/detection/session', - ); - - $negotiation_info[LANGUAGE_NEGOTIATION_USER] = array( - 'callbacks' => array('negotiation' => 'locale_language_from_user'), - 'file' => $file, - 'weight' => -4, - 'name' => t('User'), - 'description' => t("Follow the user's language preference."), - ); - - $negotiation_info[LANGUAGE_NEGOTIATION_BROWSER] = array( - 'callbacks' => array('negotiation' => 'locale_language_from_browser'), - 'file' => $file, - 'weight' => -2, - 'cache' => 0, - 'name' => t('Browser'), - 'description' => t("Determine the language from the browser's language settings."), - ); - - $negotiation_info[LANGUAGE_NEGOTIATION_INTERFACE] = array( - 'types' => array(LANGUAGE_TYPE_CONTENT), - 'callbacks' => array('negotiation' => 'locale_language_from_interface'), - 'file' => $file, - 'weight' => 8, - 'name' => t('Interface'), - 'description' => t('Use the detected interface language.'), - ); - - $negotiation_info[LANGUAGE_NEGOTIATION_URL_FALLBACK] = array( - 'types' => array(LANGUAGE_TYPE_URL), - 'callbacks' => array('negotiation' => 'locale_language_url_fallback'), - 'file' => $file, - 'weight' => 8, - 'name' => t('URL fallback'), - 'description' => t('Use an already detected language for URLs if none is found.'), - ); - - return $negotiation_info; -} - -/** - * Implements hook_modules_enabled(). - */ -function locale_modules_enabled($modules) { - include_once DRUPAL_ROOT . '/core/includes/language.inc'; - language_types_set(); - language_negotiation_purge(); -} - -/** - * Implements hook_modules_disabled(). - */ -function locale_modules_disabled($modules) { - locale_modules_enabled($modules); -} - -/** * Implements hook_language_insert(). */ function locale_language_insert($language) { - // Add new language to the list of language prefixes. - $prefixes = locale_language_negotiation_url_prefixes(); - $prefixes[$language->langcode] = (empty($language->default) ? $language->langcode : ''); - locale_language_negotiation_url_prefixes_save($prefixes); - - // Add language to the list of language domains. - $domains = locale_language_negotiation_url_domains(); - $domains[$language->langcode] = ''; - locale_language_negotiation_url_domains_save($domains); - // @todo move these two cache clears out. See http://drupal.org/node/1293252 // Changing the language settings impacts the interface. cache('page')->flush(); @@ -552,19 +425,6 @@ function locale_language_insert($language) { * Implements hook_language_update(). */ function locale_language_update($language) { - - // If the language is the default, then ensure that no other languages have - // blank prefix codes. - if (!empty($language->default)) { - $prefixes = locale_language_negotiation_url_prefixes(); - foreach ($prefixes as $langcode => $prefix) { - if ($prefix == '' && $langcode != $language->langcode) { - $prefixes[$langcode] = $langcode; - } - } - locale_language_negotiation_url_prefixes_save($prefixes); - } - // @todo move these two cache clears out. See http://drupal.org/node/1293252 // Changing the language settings impacts the interface. cache('page')->flush(); @@ -576,16 +436,6 @@ function locale_language_update($language) { * Implements hook_language_delete(). */ function locale_language_delete($language) { - // Remove language from language prefix list. - $prefixes = locale_language_negotiation_url_prefixes(); - unset($prefixes[$language->langcode]); - locale_language_negotiation_url_prefixes_save($prefixes); - - // Remove language from language domain list. - $domains = locale_language_negotiation_url_domains(); - unset($domains[$language->langcode]); - locale_language_negotiation_url_domains_save($domains); - // Remove translations. db_delete('locales_target') ->condition('language', $language->langcode) @@ -824,9 +674,6 @@ function locale_js_alter(&$javascript) { $parsed = variable_get('javascript_parsed', array()); $files = $new_files = FALSE; - // Require because locale_js_alter() could be called without locale_init(). - require_once DRUPAL_ROOT . '/core/includes/locale.inc'; - foreach ($javascript as $item) { if ($item['type'] == 'file') { $files = TRUE; @@ -896,103 +743,6 @@ function locale_library_info_alter(&$libraries, $module) { } } -// --------------------------------------------------------------------------------- -// Language switcher block - -/** - * Implements hook_block_info(). - */ -function locale_block_info() { - include_once DRUPAL_ROOT . '/core/includes/language.inc'; - $block = array(); - $info = language_types_info(); - foreach (language_types_get_configurable(FALSE) as $type) { - $block[$type] = array( - 'info' => t('Language switcher (@type)', array('@type' => $info[$type]['name'])), - // Not worth caching. - 'cache' => DRUPAL_NO_CACHE, - ); - } - return $block; -} - -/** - * Implements hook_block_view(). - * - * Displays a language switcher. Only show if we have at least two languages. - */ -function locale_block_view($type) { - if (language_multilingual()) { - $path = drupal_is_front_page() ? '' : $_GET['q']; - $links = language_negotiation_get_switch_links($type, $path); - - if (isset($links->links)) { - drupal_add_css(drupal_get_path('module', 'locale') . '/locale.css'); - $class = "language-switcher-{$links->method_id}"; - $variables = array('links' => $links->links, 'attributes' => array('class' => array($class))); - $block['content'] = theme('links__locale_block', $variables); - $block['subject'] = t('Languages'); - return $block; - } - } -} - -/** - * Implements hook_preprocess_block(). - */ -function locale_preprocess_block(&$variables) { - if ($variables['block']->module == 'locale') { - $variables['attributes_array']['role'] = 'navigation'; - } -} - -/** - * Implements hook_url_outbound_alter(). - * - * Rewrite outbound URLs with language based prefixes. - */ -function locale_url_outbound_alter(&$path, &$options, $original_path) { - // Only modify internal URLs. - if (!$options['external'] && language_multilingual()) { - static $drupal_static_fast; - if (!isset($drupal_static_fast)) { - $drupal_static_fast['callbacks'] = &drupal_static(__FUNCTION__); - } - $callbacks = &$drupal_static_fast['callbacks']; - - if (!isset($callbacks)) { - $callbacks = array(); - include_once DRUPAL_ROOT . '/core/includes/language.inc'; - - foreach (language_types_get_configurable() as $type) { - // Get URL rewriter callbacks only from enabled language methods. - $negotiation = variable_get("language_negotiation_$type", array()); - - foreach ($negotiation as $method_id => $method) { - if (isset($method['callbacks']['url_rewrite'])) { - if (isset($method['file'])) { - require_once DRUPAL_ROOT . '/' . $method['file']; - } - // Avoid duplicate callback entries. - $callbacks[$method['callbacks']['url_rewrite']] = TRUE; - } - } - } - - $callbacks = array_keys($callbacks); - } - - foreach ($callbacks as $callback) { - $callback($path, $options); - } - - // No language dependent path allowed in this mode. - if (empty($callbacks)) { - unset($options['language']); - } - } -} - /** * Implements hook_form_FORM_ID_alter() for language_admin_overview_form(). */ @@ -1132,3 +882,379 @@ function locale_preprocess_node(&$variables) { } } } + +/** + * Check that a string is safe to be added or imported as a translation. + * + * This test can be used to detect possibly bad translation strings. It should + * not have any false positives. But it is only a test, not a transformation, + * as it destroys valid HTML. We cannot reliably filter translation strings + * on import because some strings are irreversibly corrupted. For example, + * a & in the translation would get encoded to &amp; by filter_xss() + * before being put in the database, and thus would be displayed incorrectly. + * + * The allowed tag list is like filter_xss_admin(), but omitting div and img as + * not needed for translation and likely to cause layout issues (div) or a + * possible attack vector (img). + */ +function locale_string_is_safe($string) { + return decode_entities($string) == decode_entities(filter_xss($string, array('a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'ins', 'kbd', 'li', 'ol', 'p', 'pre', 'q', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'ul', 'var'))); +} + +/** + * Parses a JavaScript file, extracts strings wrapped in Drupal.t() and + * Drupal.formatPlural() and inserts them into the database. + */ +function _locale_parse_js_file($filepath) { + // The file path might contain a query string, so make sure we only use the + // actual file. + $parsed_url = drupal_parse_url($filepath); + $filepath = $parsed_url['path']; + // Load the JavaScript file. + $file = file_get_contents($filepath); + + // Match all calls to Drupal.t() in an array. + // Note: \s also matches newlines with the 's' modifier. + preg_match_all('~ + [^\w]Drupal\s*\.\s*t\s* # match "Drupal.t" with whitespace + \(\s* # match "(" argument list start + (' . LOCALE_JS_STRING . ')\s* # capture string argument + (?:,\s*' . LOCALE_JS_OBJECT . '\s* # optionally capture str args + (?:,\s*' . LOCALE_JS_OBJECT_CONTEXT . '\s*) # optionally capture context + ?)? # close optional args + [,\)] # match ")" or "," to finish + ~sx', $file, $t_matches); + + // Match all Drupal.formatPlural() calls in another array. + preg_match_all('~ + [^\w]Drupal\s*\.\s*formatPlural\s* # match "Drupal.formatPlural" with whitespace + \( # match "(" argument list start + \s*.+?\s*,\s* # match count argument + (' . LOCALE_JS_STRING . ')\s*,\s* # match singular string argument + ( # capture plural string argument + (?: # non-capturing group to repeat string pieces + (?: + \' # match start of single-quoted string + (?:\\\\\'|[^\'])* # match any character except unescaped single-quote + @count # match "@count" + (?:\\\\\'|[^\'])* # match any character except unescaped single-quote + \' # match end of single-quoted string + | + " # match start of double-quoted string + (?:\\\\"|[^"])* # match any character except unescaped double-quote + @count # match "@count" + (?:\\\\"|[^"])* # match any character except unescaped double-quote + " # match end of double-quoted string + ) + (?:\s*\+\s*)? # match "+" with possible whitespace, for str concat + )+ # match multiple because we supports concatenating strs + )\s* # end capturing of plural string argument + (?:,\s*' . LOCALE_JS_OBJECT . '\s* # optionally capture string args + (?:,\s*' . LOCALE_JS_OBJECT_CONTEXT . '\s*)? # optionally capture context + )? + [,\)] + ~sx', $file, $plural_matches); + + $matches = array(); + + // Add strings from Drupal.t(). + foreach ($t_matches[1] as $key => $string) { + $matches[] = array( + 'string' => $string, + 'context' => $t_matches[2][$key], + ); + } + + // Add string from Drupal.formatPlural(). + foreach ($plural_matches[1] as $key => $string) { + $matches[] = array( + 'string' => $string, + 'context' => $plural_matches[3][$key], + ); + + // If there is also a plural version of this string, add it to the strings array. + if (isset($plural_matches[2][$key])) { + $matches[] = array( + 'string' => $plural_matches[2][$key], + 'context' => $plural_matches[3][$key], + ); + } + } + + // Loop through all matches and process them. + foreach ($matches as $key => $match) { + + // Remove the quotes and string concatenations from the string and context. + $string = implode('', preg_split('~(? $string, ':context' => $context))->fetchObject(); + if ($source) { + // We already have this source string and now have to add the location + // to the location column, if this file is not yet present in there. + $locations = preg_split('~\s*;\s*~', $source->location); + + if (!in_array($filepath, $locations)) { + $locations[] = $filepath; + $locations = implode('; ', $locations); + + // Save the new locations string to the database. + db_update('locales_source') + ->fields(array( + 'location' => $locations, + )) + ->condition('lid', $source->lid) + ->execute(); + } + } + else { + // We don't have the source string yet, thus we insert it into the database. + db_insert('locales_source') + ->fields(array( + 'location' => $filepath, + 'source' => $string, + 'context' => $context, + )) + ->execute(); + } + } +} + +/** + * Force the JavaScript translation file(s) to be refreshed. + * + * This function sets a refresh flag for a specified language, or all + * languages except English, if none specified. JavaScript translation + * files are rebuilt (with locale_update_js_files()) the next time a + * request is served in that language. + * + * @param $langcode + * The language code for which the file needs to be refreshed. + * + * @return + * New content of the 'javascript_parsed' variable. + */ +function _locale_invalidate_js($langcode = NULL) { + $parsed = variable_get('javascript_parsed', array()); + + if (empty($langcode)) { + // Invalidate all languages. + $languages = language_list(); + if (!locale_translate_english()) { + unset($languages['en']); + } + foreach ($languages as $lcode => $data) { + $parsed['refresh:' . $lcode] = 'waiting'; + } + } + else { + // Invalidate single language. + $parsed['refresh:' . $langcode] = 'waiting'; + } + + variable_set('javascript_parsed', $parsed); + return $parsed; +} + +/** + * (Re-)Creates the JavaScript translation file for a language. + * + * @param $langcode + * The language, the translation file should be (re)created for. + */ +function _locale_rebuild_js($langcode = NULL) { + if (!isset($langcode)) { + global $language_interface; + $language = $language_interface; + } + else { + // Get information about the locale. + $languages = language_list(); + $language = $languages[$langcode]; + } + + // Construct the array for JavaScript translations. + // Only add strings with a translation to the translations array. + $result = db_query("SELECT s.lid, s.source, s.context, t.translation FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.location LIKE '%.js%'", array(':language' => $language->langcode)); + + $translations = array(); + foreach ($result as $data) { + $translations[$data->context][$data->source] = $data->translation; + } + + // Construct the JavaScript file, if there are translations. + $data_hash = NULL; + $data = $status = ''; + if (!empty($translations)) { + + $data = "Drupal.locale = { "; + + $locale_plurals = variable_get('locale_translation_plurals', array()); + if (!empty($locale_plurals[$language->langcode])) { + $data .= "'pluralFormula': function (\$n) { return Number({$locale_plurals[$language->langcode]['formula']}); }, "; + } + + $data .= "'strings': " . drupal_json_encode($translations) . " };"; + $data_hash = drupal_hash_base64($data); + } + + // Construct the filepath where JS translation files are stored. + // There is (on purpose) no front end to edit that variable. + $dir = 'public://' . variable_get('locale_js_directory', 'languages'); + + // Delete old file, if we have no translations anymore, or a different file to be saved. + $locale_javascripts = variable_get('locale_translation_javascript', array()); + $changed_hash = !isset($locale_javascripts[$language->langcode]) || ($locale_javascripts[$language->langcode] != $data_hash); + if (!empty($locale_javascripts[$language->langcode]) && (!$data || $changed_hash)) { + file_unmanaged_delete($dir . '/' . $language->langcode . '_' . $locale_javascripts[$language->langcode] . '.js'); + $locale_javascripts[$language->langcode] = ''; + $status = 'deleted'; + } + + // Only create a new file if the content has changed or the original file got + // lost. + $dest = $dir . '/' . $language->langcode . '_' . $data_hash . '.js'; + if ($data && ($changed_hash || !file_exists($dest))) { + // Ensure that the directory exists and is writable, if possible. + file_prepare_directory($dir, FILE_CREATE_DIRECTORY); + + // Save the file. + if (file_unmanaged_save_data($data, $dest)) { + $locale_javascripts[$language->langcode] = $data_hash; + // If we deleted a previous version of the file and we replace it with a + // new one we have an update. + if ($status == 'deleted') { + $status = 'updated'; + } + // If the file did not exist previously and the data has changed we have + // a fresh creation. + elseif ($changed_hash) { + $status = 'created'; + } + // If the data hash is unchanged the translation was lost and has to be + // rebuilt. + else { + $status = 'rebuilt'; + } + } + else { + $locale_javascripts[$language->langcode] = ''; + $status = 'error'; + } + } + + // Save the new JavaScript hash (or an empty value if the file just got + // deleted). Act only if some operation was executed that changed the hash + // code. + if ($status && $changed_hash) { + variable_set('locale_translation_javascript', $locale_javascripts); + } + + // Log the operation and return success flag. + switch ($status) { + case 'updated': + watchdog('locale', 'Updated JavaScript translation file for the language %language.', array('%language' => $language->name)); + return TRUE; + case 'rebuilt': + watchdog('locale', 'JavaScript translation file %file.js was lost.', array('%file' => $locale_javascripts[$language->langcode]), WATCHDOG_WARNING); + // Proceed to the 'created' case as the JavaScript translation file has + // been created again. + case 'created': + watchdog('locale', 'Created JavaScript translation file for the language %language.', array('%language' => $language->name)); + return TRUE; + case 'deleted': + watchdog('locale', 'Removed JavaScript translation file for the language %language because no translations currently exist for that language.', array('%language' => $language->name)); + return TRUE; + case 'error': + watchdog('locale', 'An error occurred during creation of the JavaScript translation file for the language %language.', array('%language' => $language->name), WATCHDOG_ERROR); + return FALSE; + default: + // No operation needed. + return TRUE; + } +} + +/** + * Save locale specific date formats to the database. + * + * @param $langcode + * Language code, can be 2 characters, e.g. 'en' or 5 characters, e.g. + * 'en-CA'. + * @param $type + * Date format type, e.g. 'short', 'medium'. + * @param $format + * The date format string. + */ +function locale_date_format_save($langcode, $type, $format) { + $locale_format = array(); + $locale_format['language'] = $langcode; + $locale_format['type'] = $type; + $locale_format['format'] = $format; + + $is_existing = (bool) db_query_range('SELECT 1 FROM {date_format_locale} WHERE language = :langcode AND type = :type', 0, 1, array(':langcode' => $langcode, ':type' => $type))->fetchField(); + if ($is_existing) { + $keys = array('type', 'language'); + drupal_write_record('date_format_locale', $locale_format, $keys); + } + else { + drupal_write_record('date_format_locale', $locale_format); + } +} + +/** + * Select locale date format details from database. + * + * @param $languages + * An array of language codes. + * + * @return + * An array of date formats. + */ +function locale_get_localized_date_format($languages) { + $formats = array(); + + // Get list of different format types. + $format_types = system_get_date_types(); + $short_default = variable_get('date_format_short', 'm/d/Y - H:i'); + + // Loop through each language until we find one with some date formats + // configured. + foreach ($languages as $language) { + $date_formats = system_date_format_locale($language); + if (!empty($date_formats)) { + // We have locale-specific date formats, so check for their types. If + // we're missing a type, use the default setting instead. + foreach ($format_types as $type => $type_info) { + // If format exists for this language, use it. + if (!empty($date_formats[$type])) { + $formats['date_format_' . $type] = $date_formats[$type]; + } + // Otherwise get default variable setting. If this is not set, default + // to the short format. + else { + $formats['date_format_' . $type] = variable_get('date_format_' . $type, $short_default); + } + } + + // Return on the first match. + return $formats; + } + } + + // No locale specific formats found, so use defaults. + $system_types = array('short', 'medium', 'long'); + // Handle system types separately as they have defaults if no variable exists. + $formats['date_format_short'] = $short_default; + $formats['date_format_medium'] = variable_get('date_format_medium', 'D, m/d/Y - H:i'); + $formats['date_format_long'] = variable_get('date_format_long', 'l, F j, Y - H:i'); + + // For non-system types, get the default setting, otherwise use the short + // format. + foreach ($format_types as $type => $type_info) { + if (!in_array($type, $system_types)) { + $formats['date_format_' . $type] = variable_get('date_format_' . $type, $short_default); + } + } + + return $formats; +} diff --git a/core/modules/locale/locale.test b/core/modules/locale/locale.test index 46dbaf0..bb3c6e7 100644 --- a/core/modules/locale/locale.test +++ b/core/modules/locale/locale.test @@ -207,7 +207,7 @@ class LocaleTranslationFunctionalTest extends DrupalWebTestCase { // The English name for the language. This will be translated. $name = $this->randomName(16); // This is the language indicator on the translation search screen for - // untranslated strings. Copied straight from locale.inc. + // untranslated strings. $language_indicator = "$langcode "; // This will be the translation of $name. $translation = $this->randomName(16); @@ -371,7 +371,6 @@ class LocaleTranslationFunctionalTest extends DrupalWebTestCase { $this->drupalPost($url, $edit, t('Save translations')); // Trigger JavaScript translation parsing and building. - require_once DRUPAL_ROOT . '/core/includes/locale.inc'; _locale_rebuild_js($langcode); $locale_javascripts = variable_get('locale_translation_javascript', array()); @@ -399,7 +398,7 @@ class LocaleTranslationFunctionalTest extends DrupalWebTestCase { // The English name for the language. This will be translated. $name = $this->randomName(16); // This is the language indicator on the translation search screen for - // untranslated strings. Copied straight from locale.inc. + // untranslated strings. $language_indicator = "$langcode "; // These will be the invalid translations of $name. $key = $this->randomName(16); @@ -460,7 +459,7 @@ class LocaleTranslationFunctionalTest extends DrupalWebTestCase { // The English name for the language. This will be translated. $name = $this->randomName(16); // This is the language indicator on the translation search screen for - // untranslated strings. Copied straight from locale.inc. + // untranslated strings. $language_indicator = "$langcode "; // This will be the translation of $name. $translation = $this->randomName(16); @@ -1131,7 +1130,7 @@ class LocaleImportFunctionalTest extends DrupalWebTestCase { )); $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 0, '%update' => 0, '%delete' => 1)), t('The translation file was successfully imported.')); // This is the language indicator on the translation search screen for - // untranslated strings. Copied straight from locale.inc. + // untranslated strings. $language_indicator = "$langcode "; $str = "Operations"; $search = array( @@ -1577,9 +1576,6 @@ class LocaleUninstallFunctionalTest extends DrupalWebTestCase { function testUninstallProcess() { $locale_module = array('locale', 'language'); - // Add a new language and optionally set it as default. - require_once DRUPAL_ROOT . '/core/includes/locale.inc'; - $language = (object) array( 'langcode' => 'fr', 'name' => 'French', @@ -1617,13 +1613,13 @@ class LocaleUninstallFunctionalTest extends DrupalWebTestCase { // Change language negotiation options. drupal_load('module', 'locale'); variable_set('language_types', language_types_get_default() + array('language_custom' => TRUE)); - variable_set('language_negotiation_' . LANGUAGE_TYPE_INTERFACE, locale_language_negotiation_info()); - variable_set('language_negotiation_' . LANGUAGE_TYPE_CONTENT, locale_language_negotiation_info()); - variable_set('language_negotiation_' . LANGUAGE_TYPE_URL, locale_language_negotiation_info()); + variable_set('language_negotiation_' . LANGUAGE_TYPE_INTERFACE, language_language_negotiation_info()); + variable_set('language_negotiation_' . LANGUAGE_TYPE_CONTENT, language_language_negotiation_info()); + variable_set('language_negotiation_' . LANGUAGE_TYPE_URL, language_language_negotiation_info()); // Change language negotiation settings. - variable_set('locale_language_negotiation_url_part', LANGUAGE_NEGOTIATION_URL_PREFIX); - variable_set('locale_language_negotiation_session_param', TRUE); + variable_set('language_negotiation_url_part', LANGUAGE_NEGOTIATION_URL_PREFIX); + variable_set('language_negotiation_session_param', TRUE); // Uninstall Locale. module_disable($locale_module); @@ -1654,8 +1650,8 @@ class LocaleUninstallFunctionalTest extends DrupalWebTestCase { $this->assertTrue($language_negotiation, t('URL language negotiation: %setting', array('%setting' => t($language_negotiation ? 'none' : 'set')))); // Check language negotiation method settings. - $this->assertFalse(variable_get('locale_language_negotiation_url_part', FALSE), t('URL language negotiation method indicator settings cleared.')); - $this->assertFalse(variable_get('locale_language_negotiation_session_param', FALSE), t('Visit language negotiation method settings cleared.')); + $this->assertFalse(variable_get('language_negotiation_url_part', FALSE), t('URL language negotiation method indicator settings cleared.')); + $this->assertFalse(variable_get('language_negotiation_session_param', FALSE), t('Visit language negotiation method settings cleared.')); // Check JavaScript parsed. $javascript_parsed_count = count(variable_get('javascript_parsed', array())); @@ -1723,7 +1719,7 @@ class LocaleLanguageSwitchingFunctionalTest extends DrupalWebTestCase { // Enable the language switching block. $language_type = LANGUAGE_TYPE_INTERFACE; $edit = array( - "blocks[locale_{$language_type}][region]" => 'sidebar_first', + "blocks[language_{$language_type}][region]" => 'sidebar_first', ); $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); @@ -1734,7 +1730,7 @@ class LocaleLanguageSwitchingFunctionalTest extends DrupalWebTestCase { $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); // Enable URL language detection and selection. - $edit = array('language_interface[enabled][locale-url]' => '1'); + $edit = array('language_interface[enabled][language-url]' => '1'); $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); // Assert that the language switching block is displayed on the frontpage. @@ -1742,7 +1738,7 @@ class LocaleLanguageSwitchingFunctionalTest extends DrupalWebTestCase { $this->assertText(t('Languages'), t('Language switcher block found.')); // Assert that only the current language is marked as active. - list($language_switcher) = $this->xpath('//div[@id=:id]/div[@class="content"]', array(':id' => 'block-locale-' . str_replace('_', '-', $language_type))); + list($language_switcher) = $this->xpath('//div[@id=:id]/div[@class="content"]', array(':id' => 'block-language-' . str_replace('_', '-', $language_type))); $links = array( 'active' => array(), 'inactive' => array(), @@ -1787,11 +1783,11 @@ class LocaleBrowserDetectionTest extends DrupalUnitTestCase { } /** - * Unit tests for the locale_language_from_browser() function. + * Unit tests for the language_from_browser() function. */ function testLanguageFromBrowser() { // Load the required functions. - require_once DRUPAL_ROOT . '/core/includes/locale.inc'; + require_once DRUPAL_ROOT . '/core/modules/language/language.negotiation.inc'; $languages = array( // In our test case, 'en' has priority over 'en-US'. @@ -1888,7 +1884,7 @@ class LocaleBrowserDetectionTest extends DrupalUnitTestCase { foreach ($test_cases as $accept_language => $expected_result) { $_SERVER['HTTP_ACCEPT_LANGUAGE'] = $accept_language; - $result = locale_language_from_browser($languages); + $result = language_from_browser($languages); $this->assertIdentical($result, $expected_result, t("Language selection '@accept-language' selects '@result', result = '@actual'", array('@accept-language' => $accept_language, '@result' => $expected_result, '@actual' => isset($result) ? $result : 'none'))); } } @@ -2016,7 +2012,7 @@ class LocaleUserCreationTest extends DrupalWebTestCase { // Set language negotiation. $edit = array( - 'language_interface[enabled][locale-url]' => TRUE, + 'language_interface[enabled][language-url]' => TRUE, ); $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); $this->assertText(t('Language negotiation configuration saved.'), t('Set language negotiation.')); @@ -2506,7 +2502,6 @@ class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { $language_domain = 'example.cn'; // Setup the site languages by installing two languages. - require_once DRUPAL_ROOT . '/core/includes/locale.inc'; $language = (object) array( 'langcode' => $langcode_browser_fallback, ); @@ -2547,7 +2542,7 @@ class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { $this->drupalPost(NULL, $edit, t('Save translations')); // Configure URL language rewrite. - variable_set('locale_language_negotiation_url_type', LANGUAGE_TYPE_INTERFACE); + variable_set('language_negotiation_url_type', LANGUAGE_TYPE_INTERFACE); $tests = array( // Default, browser preference should have no influence. @@ -2602,7 +2597,7 @@ class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { } // Unknown language prefix should return 404. - variable_set('language_negotiation_' . LANGUAGE_TYPE_INTERFACE, locale_language_negotiation_info()); + variable_set('language_negotiation_' . LANGUAGE_TYPE_INTERFACE, language_language_negotiation_info()); $this->drupalGet("$langcode_unknown/admin/config", array(), $http_header_browser_fallback); $this->assertResponse(404, "Unknown language path prefix should return 404"); @@ -2616,7 +2611,7 @@ class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { // Default domain, browser preference should have no influence. array( 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT), - 'locale_language_negotiation_url_part' => LANGUAGE_NEGOTIATION_URL_DOMAIN, + 'language_negotiation_url_part' => LANGUAGE_NEGOTIATION_URL_DOMAIN, 'path' => 'admin/config', 'expect' => $default_string, 'expected_method_id' => LANGUAGE_NEGOTIATION_DEFAULT, @@ -2627,7 +2622,7 @@ class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { // locale_test.module hook_boot() to simulate this. array( 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT), - 'locale_language_negotiation_url_part' => LANGUAGE_NEGOTIATION_URL_DOMAIN, + 'language_negotiation_url_part' => LANGUAGE_NEGOTIATION_URL_DOMAIN, 'locale_test_domain' => $language_domain, 'path' => 'admin/config', 'expect' => $language_string, @@ -2647,8 +2642,8 @@ class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { $method_weights = array_flip($test['language_negotiation']); language_negotiation_set(LANGUAGE_TYPE_INTERFACE, $method_weights); } - if (!empty($test['locale_language_negotiation_url_part'])) { - variable_set('locale_language_negotiation_url_part', $test['locale_language_negotiation_url_part']); + if (!empty($test['language_negotiation_url_part'])) { + variable_set('language_negotiation_url_part', $test['language_negotiation_url_part']); } if (!empty($test['locale_test_domain'])) { variable_set('locale_test_domain', $test['locale_test_domain']); @@ -2677,16 +2672,16 @@ class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { // Enable browser and URL language detection. $edit = array( - 'language_interface[enabled][locale-browser]' => TRUE, - 'language_interface[enabled][locale-url]' => TRUE, - 'language_interface[weight][locale-browser]' => -8, - 'language_interface[weight][locale-url]' => -10, + 'language_interface[enabled][language-browser]' => TRUE, + 'language_interface[enabled][language-url]' => TRUE, + 'language_interface[weight][language-browser]' => -8, + 'language_interface[weight][language-url]' => -10, ); $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); $this->drupalGet('admin/config/regional/language/detection'); // Enable the language switcher block. - $edit = array('blocks[locale_language_interface][region]' => 'sidebar_first'); + $edit = array('blocks[language_language_interface][region]' => 'sidebar_first'); $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); // Access the front page without specifying any valid URL language prefix @@ -2698,7 +2693,7 @@ class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { // Check that the language switcher active link matches the given browser // language. $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-interface"]//a[@class="language-link active" and starts-with(@href, :url)]', $args); + $fields = $this->xpath('//div[@id="block-language-language-interface"]//a[@class="language-link active" and starts-with(@href, :url)]', $args); $this->assertTrue($fields[0] == $languages[$langcode_browser_fallback]->name, t('The browser language is the URL active language')); // Check that URLs are rewritten using the given browser language. @@ -2720,14 +2715,14 @@ class LocaleUILanguageNegotiationTest extends DrupalWebTestCase { // Enable browser and URL language detection. $edit = array( - 'language_interface[enabled][locale-url]' => TRUE, - 'language_interface[weight][locale-url]' => -10, + 'language_interface[enabled][language-url]' => TRUE, + 'language_interface[weight][language-url]' => -10, ); $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); // Change the domain for the Italian language. $edit = array( - 'locale_language_negotiation_url_part' => 1, + 'language_negotiation_url_part' => 1, 'domain[it]' => 'it.example.com', ); $this->drupalPost('admin/config/regional/language/detection/url', $edit, t('Save configuration')); @@ -2795,13 +2790,13 @@ class LocaleUrlRewritingTest extends DrupalWebTestCase { $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration')); // Enable URL language detection and selection. - $edit = array('language_interface[enabled][locale-url]' => 1); + $edit = array('language_interface[enabled][language-url]' => 1); $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); // Reset static caching. drupal_static_reset('language_list'); - drupal_static_reset('locale_url_outbound_alter'); - drupal_static_reset('locale_language_url_rewrite_url'); + drupal_static_reset('language_url_outbound_alter'); + drupal_static_reset('language_url_rewrite_url'); } /** @@ -2835,7 +2830,7 @@ class LocaleUrlRewritingTest extends DrupalWebTestCase { // If the rewritten URL has not a language prefix we pick a random prefix so // we can always check the prefixed URL. - $prefixes = locale_language_negotiation_url_prefixes(); + $prefixes = language_negotiation_url_prefixes(); $stored_prefix = isset($prefixes[$language->langcode]) ? $prefixes[$language->langcode] : $this->randomName(); if ($this->assertNotEqual($stored_prefix, $prefix, $message1)) { $prefix = $stored_prefix; @@ -2869,7 +2864,6 @@ class LocaleMultilingualFieldsFunctionalTest extends DrupalWebTestCase { $this->drupalLogin($admin_user); // Add a new language. - require_once DRUPAL_ROOT . '/core/includes/locale.inc'; $language = (object) array( 'langcode' => 'it', 'name' => 'Italian', @@ -2877,7 +2871,7 @@ class LocaleMultilingualFieldsFunctionalTest extends DrupalWebTestCase { language_save($language); // Enable URL language detection and selection. - $edit = array('language_interface[enabled][locale-url]' => '1'); + $edit = array('language_interface[enabled][language-url]' => '1'); $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); // Set "Basic page" content type to use multilingual support. @@ -3011,9 +3005,9 @@ class LocaleCommentLanguageFunctionalTest extends DrupalWebTestCase { // language will fall back to the default language if no URL language can be // detected. $edit = array( - 'language_interface[enabled][locale-user]' => TRUE, - 'language_content[enabled][locale-url]' => TRUE, - 'language_content[enabled][locale-interface]' => FALSE, + 'language_interface[enabled][language-user]' => TRUE, + 'language_content[enabled][language-url]' => TRUE, + 'language_content[enabled][language-interface]' => FALSE, ); $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); @@ -3048,7 +3042,7 @@ class LocaleCommentLanguageFunctionalTest extends DrupalWebTestCase { $this->drupalPost("node/add/article", $edit, t('Save')); $node = $this->drupalGetNodeByTitle($title); - $prefixes = locale_language_negotiation_url_prefixes(); + $prefixes = language_negotiation_url_prefixes(); foreach (language_list() as $langcode => $language) { // Post a comment with content language $langcode. $prefix = empty($prefixes[$langcode]) ? '' : $prefixes[$langcode] . '/'; @@ -3106,7 +3100,7 @@ class LocaleDateFormatsFunctionalTest extends DrupalWebTestCase { // Set language negotiation. $language_type = LANGUAGE_TYPE_INTERFACE; $edit = array( - "{$language_type}[enabled][locale-url]" => TRUE, + "{$language_type}[enabled][language-url]" => TRUE, ); $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); @@ -3269,7 +3263,7 @@ class LocaleLanguageNegotiationInfoFunctionalTest extends DrupalWebTestCase { drupal_static_reset('language_types_info'); drupal_static_reset('language_negotiation_info'); - $function = "locale_modules_{$op}d"; + $function = "language_modules_{$op}d"; if (function_exists($function)) { $function($modules); } diff --git a/core/modules/overlay/overlay.module b/core/modules/overlay/overlay.module index 58dcbab..144c2ab 100644 --- a/core/modules/overlay/overlay.module +++ b/core/modules/overlay/overlay.module @@ -639,10 +639,14 @@ function overlay_overlay_parent_initialize() { } drupal_add_js(array('overlay' => array('paths' => $paths)), 'setting'); $path_prefixes = array(); - if (module_exists('locale') && variable_get('locale_language_negotiation_url_part', LANGUAGE_NEGOTIATION_URL_PREFIX) == LANGUAGE_NEGOTIATION_URL_PREFIX) { - // Skip the empty string indicating the default language. We always accept - // paths without a prefix. - $path_prefixes = array_values(array_filter(locale_language_negotiation_url_prefixes())); + if (module_exists('language')) { + language_negotiation_include(); + if (variable_get('language_negotiation_url_part', LANGUAGE_NEGOTIATION_URL_PREFIX) == LANGUAGE_NEGOTIATION_URL_PREFIX) { + // Skip the empty string indicating the default language. We always accept + // paths without a prefix. + $path_prefixes = language_negotiation_url_prefixes(); + $path_prefixes = array_values(array_filter($path_prefixes)); + } } drupal_add_js(array('overlay' => array('pathPrefixes' => $path_prefixes)), 'setting'); // Pass along the Ajax callback for rerendering sections of the parent window. diff --git a/core/modules/path/path.test b/core/modules/path/path.test index 8ede9c0..4dcd611 100644 --- a/core/modules/path/path.test +++ b/core/modules/path/path.test @@ -311,7 +311,7 @@ class PathLanguageTestCase extends PathTestCase { $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); // Enable URL language detection and selection. - $edit = array('language_interface[enabled][locale-url]' => 1); + $edit = array('language_interface[enabled][language-url]' => 1); $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); } @@ -360,8 +360,8 @@ class PathLanguageTestCase extends PathTestCase { // Confirm that the alias is returned by url(). Languages are cached on // many levels, and we need to clear those caches. drupal_static_reset('language_list'); - drupal_static_reset('locale_url_outbound_alter'); - drupal_static_reset('locale_language_url_rewrite_url'); + drupal_static_reset('language_url_outbound_alter'); + drupal_static_reset('language_url_rewrite_url'); $languages = language_list(); $url = url('node/' . $french_node->nid, array('language' => $languages[$french_node->langcode])); $this->assertTrue(strpos($url, $edit['path[alias]']), t('URL contains the path alias.')); @@ -369,10 +369,10 @@ class PathLanguageTestCase extends PathTestCase { // Confirm that the alias works even when changing language negotiation // options. Enable User language detection and selection over URL one. $edit = array( - 'language_interface[enabled][locale-user]' => 1, - 'language_interface[weight][locale-user]' => -9, - 'language_interface[enabled][locale-url]' => 1, - 'language_interface[weight][locale-url]' => -8, + 'language_interface[enabled][language-user]' => 1, + 'language_interface[weight][language-user]' => -9, + 'language_interface[enabled][language-url]' => 1, + 'language_interface[weight][language-url]' => -8, ); $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); @@ -396,7 +396,7 @@ class PathLanguageTestCase extends PathTestCase { $this->assertText($french_node->title, 'Alias for French translation works.'); // Disable URL language negotiation. - $edit = array('language_interface[enabled][locale-url]' => FALSE); + $edit = array('language_interface[enabled][language-url]' => FALSE); $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); // Check that the English alias still works. @@ -454,7 +454,7 @@ class PathLanguageUITestCase extends PathTestCase { $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); // Enable URL language detection and selection. - $edit = array('language_interface[enabled][locale-url]' => 1); + $edit = array('language_interface[enabled][language-url]' => 1); $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); } @@ -541,7 +541,7 @@ class PathMonolingualTestCase extends PathTestCase { $this->assertEqual(language_default()->langcode, 'fr', t('French is the default language')); // Set language detection to URL. - $edit = array('language_interface[enabled][locale-url]' => TRUE); + $edit = array('language_interface[enabled][language-url]' => TRUE); $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); // Force languages to be initialized. diff --git a/core/modules/simpletest/tests/upgrade/upgrade.language.test b/core/modules/simpletest/tests/upgrade/upgrade.language.test index 192f00c..bcf73a1 100644 --- a/core/modules/simpletest/tests/upgrade/upgrade.language.test +++ b/core/modules/simpletest/tests/upgrade/upgrade.language.test @@ -55,7 +55,7 @@ class LanguageUpgradePathTestCase extends UpgradePathTestCase { // Ensure that the language switcher has been correctly upgraded. We need to // assert the expected HTML id because the block might appear even if the // language negotiation settings are not properly upgraded. - $this->assertTrue($this->xpath('//div[@id="block-locale-language-interface"]'), t('The language switcher block is being correctly showed.')); + $this->assertTrue($this->xpath('//div[@id="block-language-language-interface"]'), t('The language switcher block is being correctly showed.')); // Test that the 'language' property was properly renamed to 'langcode'. $language_none_nid = 38; @@ -99,10 +99,16 @@ class LanguageUpgradePathTestCase extends UpgradePathTestCase { // Check if language negotiation weights were renamed properly. This is a // reproduction of the previous weights from the dump. - $expected_weights = array('locale-url' => '-8', 'locale-session' => '-6', 'locale-user' => '-4', 'locale-browser' => '-2', 'language-default' => '10'); + $expected_weights = array( + 'language-url' => '-8', + 'language-session' => '-6', + 'language-user' => '-4', + 'language-browser' => '-2', + 'language-default' => '10', + ); // Check that locale_language_providers_weight_language is correctly // renamed. - $current_weights = variable_get('locale_language_negotiation_methods_weight_language_interface', array()); + $current_weights = variable_get('language_negotiation_methods_weight_language_interface', array()); $this->assertTrue(serialize($expected_weights) == serialize($current_weights), t('Language negotiation method weights upgraded.')); // Look up migrated plural string. @@ -133,7 +139,8 @@ class LanguageUpgradePathTestCase extends UpgradePathTestCase { $this->assertTrue($this->performUpgrade(), t('The upgrade was completed successfully.')); - $domains = locale_language_negotiation_url_domains(); + language_negotiation_include(); + $domains = language_negotiation_url_domains(); $this->assertTrue($domains['ca'] == $language_domain, t('Language domain for Catalan properly upgraded.')); } diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc index 978b0f4..aac0c3d 100644 --- a/core/modules/system/system.admin.inc +++ b/core/modules/system/system.admin.inc @@ -1899,7 +1899,6 @@ function system_rss_feeds_settings() { * @see system_regional_settings_submit() */ function system_regional_settings() { - include_once DRUPAL_ROOT . '/core/includes/locale.inc'; $countries = country_get_list(); // Date settings: diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 82013ce..a3594b9 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -4035,3 +4035,17 @@ function system_admin_paths() { ); return $paths; } + +/** + * Get list of all predefined and custom countries. + * + * @return + * An array of all country code => country name pairs. + */ +function country_get_list() { + include_once DRUPAL_ROOT . '/core/includes/standard.inc'; + $countries = standard_country_list(); + // Allow other modules to modify the country list. + drupal_alter('countries', $countries); + return $countries; +} diff --git a/core/modules/translation/translation.test b/core/modules/translation/translation.test index e2b8358..c0e158c 100644 --- a/core/modules/translation/translation.test +++ b/core/modules/translation/translation.test @@ -49,7 +49,7 @@ class TranslationTestCase extends DrupalWebTestCase { // Enable the language switcher block. $language_type = LANGUAGE_TYPE_INTERFACE; - $edit = array("blocks[locale_$language_type][region]" => 'sidebar_first'); + $edit = array("blocks[language_$language_type][region]" => 'sidebar_first'); $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); // Reset static caches in our local language environment. @@ -76,7 +76,7 @@ class TranslationTestCase extends DrupalWebTestCase { // Check that the "add translation" link uses a localized path. $languages = language_list(); - $prefixes = locale_language_negotiation_url_prefixes(); + $prefixes = language_negotiation_url_prefixes(); $this->drupalGet('node/' . $node->nid . '/translate'); $this->assertLinkByHref($prefixes['es'] . '/node/add/' . str_replace('_', '-', $node->type), 0, t('The "add translation" link for %language points to the localized path of the target language.', array('%language' => $languages['es']->name))); @@ -169,7 +169,7 @@ class TranslationTestCase extends DrupalWebTestCase { // Check that links to the displayed translation appear only in the language // switcher block. $this->assertLanguageSwitchLinks($node, $node, FALSE, 'node'); - $this->assertLanguageSwitchLinks($node, $node, TRUE, 'block-locale'); + $this->assertLanguageSwitchLinks($node, $node, TRUE, 'block-language'); // Unpublish the Spanish translation to check that the related language // switch link is not shown. @@ -182,7 +182,7 @@ class TranslationTestCase extends DrupalWebTestCase { // Check that content translation links are shown even when no language // negotiation is configured. $this->drupalLogin($this->admin_user); - $edit = array('language_interface[enabled][locale-url]' => FALSE); + $edit = array('language_interface[enabled][language-url]' => FALSE); $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); $this->resetCaches(); $edit = array('status' => TRUE); @@ -203,7 +203,7 @@ class TranslationTestCase extends DrupalWebTestCase { $this->drupalLogin($this->translator); // Create a Basic page in English. - $type = 'block-locale'; + $type = 'block-language'; $node = $this->createPage($this->randomName(), $this->randomName(), 'en'); $this->assertLanguageSwitchLinks($node, $node, TRUE, $type); $this->assertLanguageSwitchLinks($node, $this->emptyNode('es'), TRUE, $type); @@ -259,8 +259,8 @@ class TranslationTestCase extends DrupalWebTestCase { */ function resetCaches() { drupal_static_reset('language_list'); - drupal_static_reset('locale_url_outbound_alter'); - drupal_static_reset('locale_language_url_rewrite_url'); + drupal_static_reset('language_url_outbound_alter'); + drupal_static_reset('language_url_rewrite_url'); } /** @@ -422,7 +422,7 @@ class TranslationTestCase extends DrupalWebTestCase { */ function assertLanguageSwitchLinks($node, $translation, $find = TRUE, $types = NULL) { if (empty($types)) { - $types = array('node', 'block-locale'); + $types = array('node', 'block-language'); } elseif (is_string($types)) { $types = array($types);