From 1b9cde9d85b2c04df35b04cfbd12f68243a118bc Mon Sep 17 00:00:00 2001 From: Angie Byron Date: Fri, 9 Oct 2009 16:33:14 +0000 Subject: #282191 by plach, nedjo, catch, et al: TF #1: Allow different interface language for the same path. --- includes/bootstrap.inc | 55 ++-- includes/common.inc | 31 +- includes/language.inc | 454 ++++++++++++++++++++++------- includes/locale.inc | 518 ++++++++++++++++++++++++++++++++- includes/theme.inc | 4 +- modules/locale/locale.api.php | 101 ++++++- modules/locale/locale.css | 8 + modules/locale/locale.install | 55 +++- modules/locale/locale.module | 186 ++++++++++-- modules/locale/locale.test | 121 ++++---- modules/node/node.module | 46 +++ modules/path/path.test | 4 +- modules/translation/translation.module | 34 +-- 13 files changed, 1363 insertions(+), 254 deletions(-) diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc index cda1bb8b8..fe9a95471 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -154,27 +154,19 @@ define('DRUPAL_AUTHENTICATED_RID', 2); define('DRUPAL_KILOBYTE', 1024); /** - * No language negotiation. The default language is used. + * The type of language used to define the content language. */ -define('LANGUAGE_NEGOTIATION_NONE', 0); +define('LANGUAGE_TYPE_CONTENT', 'language'); /** - * Path based negotiation with fallback to default language if no defined path - * prefix identified. + * The type of language used to select the user interface. */ -define('LANGUAGE_NEGOTIATION_PATH_DEFAULT', 1); +define('LANGUAGE_TYPE_INTERFACE', 'language_interface'); /** - * Path based negotiation with fallback to user preferences and browser - * language detection if no defined path prefix identified. + * The type of language used for URLs. */ -define('LANGUAGE_NEGOTIATION_PATH', 2); - -/** - * Domain based negotiation with fallback to default language if no language - * identified by domain. - */ -define('LANGUAGE_NEGOTIATION_DOMAIN', 3); +define('LANGUAGE_TYPE_URL', 'language_url'); /** * Language written left to right. Possible value of $language->direction. @@ -1628,22 +1620,49 @@ function get_t() { } /** - * Choose a language for the current page, based on site and user preferences. + * Initialize all the defined language types. */ function drupal_language_initialize() { - global $language, $user; + $types = language_types(); // Ensure the language is correctly returned, even without multilanguage support. // Useful for eg. XML/HTML 'lang' attributes. if (variable_get('language_count', 1) == 1) { - $language = language_default(); + $default = language_default(); + foreach ($types as $type) { + $GLOBALS[$type] = $default; + } } else { include_once DRUPAL_ROOT . '/includes/language.inc'; - $language = language_initialize(); + foreach ($types as $type) { + $GLOBALS[$type] = language_initialize($type); + } } } +/** + * The built-in language types. + * + * @return + * An array of key-values pairs where the key is the language type and the + * value is its configurability. + */ +function drupal_language_types() { + return array( + LANGUAGE_TYPE_CONTENT => TRUE, + LANGUAGE_TYPE_INTERFACE => TRUE, + LANGUAGE_TYPE_URL => FALSE, + ); +} + +/** + * Return an array of the available language types. + */ +function language_types() { + return array_keys(variable_get('language_types', drupal_language_types())); +} + /** * Get a list of languages set up indexed by the specified key * diff --git a/includes/common.inc b/includes/common.inc index 3f26c4949..d8661100c 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -392,6 +392,26 @@ function drupal_get_query_parameters(array $query = NULL, array $exclude = array return $params; } +/** + * Split an URL-encoded query string into an array. + * + * @param $query + * The query string to split. + * + * @return + * An array of url decoded couples $param_name => $value. + */ +function drupal_get_query_array($query) { + $result = array(); + if (!empty($query)) { + foreach (explode('&', $query) as $param) { + $param = explode('=', $param); + $result[$param[0]] = isset($param[1]) ? rawurldecode($param[1]) : ''; + } + } + return $result; +} + /** * Parse an array into a valid, rawurlencoded query string. * @@ -1455,12 +1475,12 @@ function fix_gpc_magic() { * The translated string. */ function t($string, array $args = array(), array $options = array()) { - global $language; + global $language_interface; static $custom_strings; // Merge in default. if (empty($options['langcode'])) { - $options['langcode'] = isset($language->language) ? $language->language : 'en'; + $options['langcode'] = isset($language_interface->language) ? $language_interface->language : 'en'; } if (empty($options['context'])) { $options['context'] = ''; @@ -2438,7 +2458,8 @@ function url($path = NULL, array $options = array()) { $path = ''; } elseif (!empty($path) && !$options['alias']) { - $path = drupal_get_path_alias($path, isset($options['language']) ? $options['language']->language : ''); + $language = isset($options['language']) && isset($options['language']->language) ? $options['language']->language : ''; + $path = drupal_get_path_alias($path, $language); } if (function_exists('custom_url_rewrite_outbound')) { @@ -2546,7 +2567,7 @@ function drupal_attributes(array $attributes = array()) { * an HTML string containing a link to the given path. */ function l($text, $path, array $options = array()) { - global $language; + global $language_url; // Merge in defaults. $options += array( @@ -2556,7 +2577,7 @@ function l($text, $path, array $options = array()) { // Append active class. if (($path == $_GET['q'] || ($path == '' && drupal_is_front_page())) && - (empty($options['language']) || $options['language']->language == $language->language)) { + (empty($options['language']) || $options['language']->language == $language_url->language)) { $options['attributes']['class'][] = 'active'; } diff --git a/includes/language.inc b/includes/language.inc index 9a990a722..f170cf9a0 100644 --- a/includes/language.inc +++ b/includes/language.inc @@ -7,138 +7,392 @@ */ /** - * Choose a language for the page, based on language negotiation settings. - */ -function language_initialize() { - global $user; - - // Configured presentation language mode. - $mode = variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE); - // Get a list of enabled languages. - $languages = language_list('enabled'); - $languages = $languages[1]; - - switch ($mode) { - case LANGUAGE_NEGOTIATION_NONE: - return language_default(); - - case LANGUAGE_NEGOTIATION_DOMAIN: - foreach ($languages as $language) { - $host = parse_url($language->domain, PHP_URL_HOST); - if ($host && ($_SERVER['HTTP_HOST'] == $host)) { - return $language; - } - } - return language_default(); - - case LANGUAGE_NEGOTIATION_PATH_DEFAULT: - case LANGUAGE_NEGOTIATION_PATH: - // $_GET['q'] might not be available at this time, because - // path initialization runs after the language bootstrap phase. - $args = isset($_GET['q']) ? explode('/', $_GET['q']) : array(); - $prefix = array_shift($args); - // Search prefix within enabled languages. - foreach ($languages as $language) { - if (!empty($language->prefix) && $language->prefix == $prefix) { - // Rebuild $GET['q'] with the language removed. - $_GET['q'] = implode('/', $args); - return $language; - } + * No language negotiation. The default language is used. + */ +define('LANGUAGE_NEGOTIATION_DEFAULT', 'language-default'); + +/** + * Return all the defined language types. + * + * @return + * An array of language type names. The name will be used as the global + * variable name the language value will be stored in. + */ +function language_types_info() { + $language_types = &drupal_static(__FUNCTION__); + + if (!isset($language_types)) { + $language_types = module_invoke_all('language_types_info'); + // Let other modules alter the list of language types. + drupal_alter('language_types_info', $language_types); + } + + return $language_types; +} + +/** + * Return only the configurable language types. + * + * A language type maybe configurable or fixed. A fixed language type is a type + * whose negotiation values are unchangable and defined while defining the + * language type itself. + * + * @return + * An array of language type names. + */ +function language_types_configurable() { + $configurable = &drupal_static(__FUNCTION__); + + if (!isset($configurable)) { + $types = variable_get('language_types', drupal_language_types()); + $configurable = array_keys(array_filter($types)); + } + + return $configurable; +} + +/** + * Disable the given language types. + * + * @param $types + * An array of language types. + */ +function language_types_disable($types) { + $enabled_types = variable_get('language_types', drupal_language_types()); + + foreach ($types as $type) { + unset($enabled_types[$type]); + } + + variable_set('language_types', $enabled_types); +} + +/** + * Check if a language provider is enabled. + * + * This has two possible behaviors: + * - If $provider_id is given return its ID if enabled, FALSE otherwise. + * - If no ID is passed the first enabled language provider is returned. + * + * @param $type + * The language negotiation type. + * @param $provider_id + * The language provider ID. + * + * @return + * The provider ID if it is enabled, FALSE otherwise. + */ +function language_negotiation_get($type, $provider_id = NULL) { + $negotiation = variable_get("language_negotiation_$type", array()); + + if (empty($negotiation)) { + return empty($provider_id) ? LANGUAGE_NEGOTIATION_DEFAULT : FALSE; + } + + if (empty($provider_id)) { + return key($negotiation); + } + + if (isset($negotiation[$provider_id])) { + return $provider_id; + } + + return FALSE; +} + +/** + * Check if the given language provider is enabled for any configurable language + * type. + * + * @param $provider_id + * The language provider ID. + * + * @return + * TRUE if there is at least one language type for which the give language + * provider is enabled, FALSE otherwise. + */ +function language_negotiation_get_any($provider_id) { + foreach (language_types_configurable() as $type) { + if (language_negotiation_get($type, $provider_id)) { + return TRUE; + } + } + + return FALSE; +} + +/** + * Return the language switch links for the given language. + * + * @param $type + * The language negotiation type. + * @param $path + * The internal path the switch links will be relative to. + * + * @return + * A keyed array of links ready to be themed. + */ +function language_negotiation_get_switch_links($type, $path) { + $links = FALSE; + $negotiation = variable_get("language_negotiation_$type", array()); + + foreach ($negotiation as $id => $provider) { + if (isset($provider['callbacks']['switcher'])) { + if (isset($provider['file'])) { + require_once DRUPAL_ROOT . '/' . $provider['file']; } - if ($mode == LANGUAGE_NEGOTIATION_PATH_DEFAULT) { - // If we did not found the language by prefix, choose the default. - return language_default(); + + $callback = $provider['callbacks']['switcher']; + $result = $callback($type, $path); + + if (!empty($result)) { + // Allow modules to provide translations for specific links. + drupal_alter('language_switch_links', $result, $type, $path); + $links = (object) array('links' => $result, 'provider' => $id); + break; } - break; + } + } + + return $links; +} + + +/** + * Save a list of language providers. + * + * @param $type + * The language negotiation type. + * @param $language_providers + * An array of language provider ids. + */ +function language_negotiation_set($type, $language_providers) { + // Save only the necessary fields. + $provider_fields = array('callbacks', 'file', 'cache'); + + $negotiation = array(); + $providers_weight = array(); + $defined_providers = language_negotiation_info(); + $default_types = language_types_configurable(); + + // Initialize the providers weight list. + foreach ($language_providers as $id => $provider) { + $providers_weight[$id] = language_provider_weight($provider); } - // User language. - if ($user->uid && isset($languages[$user->language])) { - return $languages[$user->language]; + // Order providers list by weight. + asort($providers_weight); + + foreach ($providers_weight as $id => $weight) { + if (isset($defined_providers[$id])) { + $provider = $defined_providers[$id]; + // If the provider does not express any preference about types, make it + // available for any configurable type. + $types = array_flip(isset($provider['types']) ? $provider['types'] : $default_types); + // Check if the provider is defined and has the right type. + if (isset($types[$type])) { + $provider_data = array(); + foreach ($provider_fields as $field) { + if (isset($provider[$field])) { + $provider_data[$field] = $provider[$field]; + } + } + $negotiation[$id] = $provider_data; + } + } } - // Browser accept-language parsing. - if ($language = language_from_browser()) { - return $language; + variable_set("language_negotiation_$type", $negotiation); +} + +/** + * Return all the defined language providers. + * + * @return + * An array of language providers. + */ +function language_negotiation_info() { + $language_providers = &drupal_static(__FUNCTION__); + + if (!isset($language_providers)) { + // Collect all the module-defined language negotiation providers. + $language_providers = module_invoke_all('language_negotiation_info'); + + // Add the default language provider. + $language_providers[LANGUAGE_NEGOTIATION_DEFAULT] = array( + 'callbacks' => array('language' => 'language_from_default'), + 'weight' => 10, + 'name' => t('Default'), + 'description' => t('The default site language (@language_name) is used.', array('@language_name' => language_default()->native)), + ); + + // Let other modules alter the list of language providers. + drupal_alter('language_negotiation_info', $language_providers); } - // Fall back on the default if everything else fails. - return language_default(); + return $language_providers; } /** - * Identify language from the Accept-language HTTP header we got. + * Helper function used to cache the language providers results. + * + * @param $provider_id + * The language provider ID. + * @param $provider + * The language provider to be invoked. If not passed it will be explicitly + * loaded through language_negotiation_info(). + * + * @return + * The language provider's return value. */ -function language_from_browser() { - // Specified by the user via the browser's Accept Language setting - // Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5" - $browser_langs = array(); +function language_provider_invoke($provider_id, $provider = NULL) { + $results = &drupal_static(__FUNCTION__); - if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { - $browser_accept = explode(",", $_SERVER['HTTP_ACCEPT_LANGUAGE']); - for ($i = 0; $i < count($browser_accept); $i++) { - // The language part is either a code or a code with a quality. - // We cannot do anything with a * code, so it is skipped. - // If the quality is missing, it is assumed to be 1 according to the RFC. - if (preg_match("!([a-z-]+)(;q=([0-9\\.]+))?!", trim($browser_accept[$i]), $found)) { - $browser_langs[$found[1]] = (isset($found[3]) ? (float) $found[3] : 1.0); - } + if (!isset($results[$provider_id])) { + global $user; + + // Get languages grouped by status and select only the enabled ones. + $languages = language_list('enabled'); + $languages = $languages[1]; + + if (!isset($provider)) { + $providers = language_negotiation_info(); + $provider = $providers[$provider_id]; + } + + if (isset($provider['file'])) { + require_once DRUPAL_ROOT . '/' . $provider['file']; } + + // If the language provider has no cache preference or this is satisified + // we can execute the callback. + $cache = !isset($provider['cache']) || $user->uid || $provider['cache'] == variable_get('cache', CACHE_DISABLED); + $callback = isset($provider['callbacks']['language']) ? $provider['callbacks']['language'] : FALSE; + $langcode = $cache && function_exists($callback) ? $callback($languages) : FALSE; + $results[$provider_id] = isset($languages[$langcode]) ? $languages[$langcode] : FALSE; } - // Order the codes by quality - arsort($browser_langs); + return $results[$provider_id]; +} + +/** + * Return the passed language provider weight or a default value. + * + * @param $provider + * A language provider data structure. + * + * @return + * A numeric weight. + */ +function language_provider_weight($provider) { + $default = is_numeric($provider) ? $provider : 0; + return isset($provider['weight']) && is_numeric($provider['weight']) ? $provider['weight'] : $default; +} - // Try to find the first preferred language we have - $languages = language_list('enabled'); - foreach ($browser_langs as $langcode => $q) { - if (isset($languages['1'][$langcode])) { - return $languages['1'][$langcode]; +/** + * Choose a language for the given type based on language negotiation settings. + * + * @param $type + * The language type. + * + * @return + * The negotiated language object. + */ +function language_initialize($type) { + // Execute the language providers in the order they were set up and return the + // first valid language found. + $negotiation = variable_get("language_negotiation_$type", array()); + + foreach ($negotiation as $id => $provider) { + $language = language_provider_invoke($id, $provider); + if ($language) { + return $language; } } + + // If no other language was found use the default one. + return language_default(); } /** - * Rewrite URLs with language based prefix. Parameters are the same - * as those of the url() function. + * Default language provider. + * + * @return + * The default language code. */ -function language_url_rewrite(&$path, &$options) { - global $language; +function language_from_default() { + return language_default()->language; +} +/** + * Rewrite URLs allowing modules to hook in. + * + * @param $path + * The path to rewrite. + * @param $options + * An associative array of additional options as in url(). + */ +function language_url_rewrite(&$path, &$options) { // Only modify relative (insite) URLs. if (!$options['external']) { + static $callbacks; - // Language can be passed as an option, or we go for current language. - if (!isset($options['language'])) { - $options['language'] = $language; - } + if (!isset($callbacks)) { + $callbacks = array(); - switch (variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE)) { - case LANGUAGE_NEGOTIATION_NONE: - // No language dependent path allowed in this mode. - unset($options['language']); - break; + foreach (language_types_configurable() as $type) { + // Get url rewriter callbacks only from enabled language providers. + $negotiation = variable_get("language_negotiation_$type", array()); - case LANGUAGE_NEGOTIATION_DOMAIN: - if ($options['language']->domain) { - // Ask for an absolute URL with our modified base_url. - $options['absolute'] = TRUE; - $options['base_url'] = $options['language']->domain; - } - break; + foreach ($negotiation as $id => $provider) { + if (isset($provider['file'])) { + require_once DRUPAL_ROOT . '/' . $provider['file']; + } - case LANGUAGE_NEGOTIATION_PATH_DEFAULT: - $default = language_default(); - if ($options['language']->language == $default->language) { - break; + // Avoid duplicate callback entries. + if (isset($provider['callbacks']['url_rewrite'])) { + $callbacks[$provider['callbacks']['url_rewrite']] = NULL; + } } - // Intentionally no break here. + } - case LANGUAGE_NEGOTIATION_PATH: - if (!empty($options['language']->prefix)) { - $options['prefix'] = $options['language']->prefix . '/'; - } - break; + $callbacks = array_keys($callbacks); + } + + foreach ($callbacks as $callback) { + $callback($path, $options); } } } + +/** + * Split the given path into prefix and actual path. + * + * Parse the given path and return the language object identified by the + * prefix and the actual path. + * + * @param $path + * The path to split. + * @param $languages + * An array of valid languages. + * + * @return + * An array composed of: + * - A language object corresponding to the identified prefix on success, + * FALSE otherwise. + * - The path without the prefix on success, the given path otherwise. + */ +function language_url_split_prefix($path, $languages) { + $args = empty($path) ? array() : explode('/', $path); + $prefix = array_shift($args); + + // Search prefix within enabled languages. + foreach ($languages as $language) { + if (!empty($language->prefix) && $language->prefix == $prefix) { + // Rebuild $path with the language removed. + return array($language, implode('/', $args)); + } + } + + return array(FALSE, $path); +} diff --git a/includes/locale.inc b/includes/locale.inc index 85e056ce1..cc95ef7f4 100644 --- a/includes/locale.inc +++ b/includes/locale.inc @@ -23,6 +23,18 @@ define('LOCALE_IMPORT_OVERWRITE', 0); */ define('LOCALE_IMPORT_KEEP', 1); +/** + * URL language negotiation: use the path prefix as URL language + * indicator. + */ +define('LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX', 0); + +/** + * URL language negotiation: use the domain as URL language + * indicator. + */ +define('LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN', 1); + /** * @defgroup locale-language-overview Language overview functionality * @{ @@ -473,7 +485,7 @@ function locale_languages_delete_form_submit($form, &$form_state) { */ /** - * @defgroup locale-languages-negotiation Language negotiation options screen + * @defgroup locale-languages-negotiation Language negotiation options * @{ */ @@ -481,33 +493,507 @@ function locale_languages_delete_form_submit($form, &$form_state) { * Setting for language negotiation options */ function locale_languages_configure_form() { - $form['language_negotiation'] = array( - '#title' => t('Language negotiation'), - '#type' => 'radios', - '#options' => array( - LANGUAGE_NEGOTIATION_NONE => t('None.'), - LANGUAGE_NEGOTIATION_PATH_DEFAULT => t('Path prefix only.'), - LANGUAGE_NEGOTIATION_PATH => t('Path prefix with language fallback.'), - LANGUAGE_NEGOTIATION_DOMAIN => t('Domain name only.')), - '#default_value' => variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE), - '#description' => t("Select the mechanism used to determine your site's presentation language. Modifying this setting may break all incoming URLs and should be used with caution in a production environment.") + include_once DRUPAL_ROOT . '/includes/language.inc'; + + $form = array( + '#submit' => array('locale_languages_configure_form_submit'), + '#theme' => 'locale_languages_configure_form', + '#language_types' => language_types_configurable(), + '#language_types_info' => language_types_info(), + '#language_providers' => language_negotiation_info(), ); + + foreach ($form['#language_types'] as $type) { + _locale_languages_configure_form_language_table($form, $type); + } + $form['submit'] = array( '#type' => 'submit', - '#value' => t('Save settings') + '#value' => t('Save settings'), ); + return $form; } /** - * Submit function for language negotiation settings. + * Helper function to build a language provider table. + */ +function _locale_languages_configure_form_language_table(&$form, $type) { + $info = $form['#language_types_info'][$type]; + + $table_form = array( + '#title' => t('@type language', array('@type' => $info['name'])), + '#tree' => TRUE, + '#description' => $info['description'], + '#language_providers' => array(), + '#show_operations' => FALSE, + 'weight' => array('#tree' => TRUE), + 'enabled' => array('#tree' => TRUE), + ); + + $language_providers = $form['#language_providers']; + $enabled_providers = variable_get("locale_language_providers_enabled_$type", array()); + $providers_weight = variable_get("locale_language_providers_weight_$type", array()); + + // Add missing data to the providers lists. + foreach ($language_providers as $id => $provider) { + if (!isset($providers_weight[$id])) { + $providers_weight[$id] = language_provider_weight($provider); + } + if (!isset($enabled_providers[$id])) { + $enabled_providers[$id] = FALSE; + } + } + + // Order providers list by weight. + asort($providers_weight); + + foreach ($providers_weight as $id => $weight) { + $enabled = $enabled_providers[$id]; + $provider = $language_providers[$id]; + + // List the provider only if the current type is defined in its 'types' key. + // If it is not defined default to all the configurabe language types. + $types = array_flip(isset($provider['types']) ? $provider['types'] : $form['#language_types']); + + if (isset($types[$type])) { + $table_form['#language_providers'][$id] = $provider; + + $table_form['weight'][$id] = array( + '#type' => 'weight', + '#default_value' => $weight, + '#attributes' => array('class' => array("language-provider-weight-$type")), + ); + + $table_form['title'][$id] = array('#markup' => check_plain($provider['name'])); + + $table_form['enabled'][$id] = array('#type' => 'checkbox', '#default_value' => $enabled); + if ($id === LANGUAGE_NEGOTIATION_DEFAULT) { + $table_form['enabled'][$id]['#default_value'] = TRUE; + $table_form['enabled'][$id]['#attributes'] = array('disabled' => 'disabled'); + } + + $table_form['description'][$id] = array('#markup' => filter_xss_admin($provider['description'])); + + $config_op = ''; + if (isset($provider['config'])) { + $config_op = l(t('Configure'), $provider['config']); + // If there is at least one operation enabled show the operation column. + $table_form['#show_operations'] = TRUE; + } + $table_form['operation'][$id] = array('#markup' => $config_op); + } + } + + $form[$type] = $table_form; +} + +/** + * Theme the language configure form. + * + * @ingroup themeable + */ +function theme_locale_languages_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-providers-$type"), + ); + $table = theme('table', $variables); + $table .= drupal_render_children($form[$type]); + + drupal_add_tabledrag("language-negotiation-providers-$type", 'order', 'sibling', "language-provider-weight-$type"); + + $output .= '
' . $title . $description . $table . '
'; + } + + $output .= drupal_render_children($form); + return $output; +} + +/** + * Submit handler for language negotiation settings. */ function locale_languages_configure_form_submit($form, &$form_state) { - variable_set('language_negotiation', $form_state['values']['language_negotiation']); - drupal_set_message(t('Language negotiation configuration saved.')); + $language_types = array(); + $configurable_types = $form['#language_types']; + + foreach ($configurable_types as $type) { + $negotiation = array(); + $enabled_providers = $form_state['values'][$type]['enabled']; + $enabled_providers[LANGUAGE_NEGOTIATION_DEFAULT] = TRUE; + $providers_weight = $form_state['values'][$type]['weight']; + $language_types[$type] = TRUE; + + foreach ($providers_weight as $id => $weight) { + if ($enabled_providers[$id]) { + $provider = $form[$type]['#language_providers'][$id]; + $provider['weight'] = $weight; + $negotiation[$id] = $provider; + } + } + + language_negotiation_set($type, $negotiation); + variable_set("locale_language_providers_enabled_$type", $enabled_providers); + variable_set("locale_language_providers_weight_$type", $providers_weight); + } + + // Save non-configurable language types negotiation. + $language_types_info = language_types_info(); + $defined_providers = $form['#language_providers']; + foreach ($language_types_info as $type => $info) { + if (isset($info['fixed'])) { + $language_types[$type] = FALSE; + $negotiation = array(); + foreach ($info['fixed'] as $id) { + if (isset($defined_providers[$id])) { + $negotiation[$id] = $defined_providers[$id]; + } + } + language_negotiation_set($type, $negotiation); + } + } + + // Save language types. + variable_set('language_types', $language_types); + $form_state['redirect'] = 'admin/config/regional/language'; - return; + drupal_set_message(t('Language negotiation configuration saved.')); +} + +/** + * The URL language provider configuration form. + */ +function locale_language_providers_url_form() { + $form = array(); + + $form['locale_language_negotiation_url_part'] = array( + '#title' => t('URL language indicator'), + '#type' => 'radios', + '#options' => array( + LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX => t('Path prefix'), + LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN => t('Domain'), + ), + '#default_value' => variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX), + '#description' => t('Select which part of the URL will determine the language.'), + ); + + $form['#redirect'] = 'admin/config/regional/language/configure'; + + return system_settings_form($form); +} + +/** + * The URL language provider configuration form. + */ +function locale_language_providers_session_form() { + $form = array(); + + $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('This value will be the name of the request/session parameter which will be used to determine the desired language.'), + ); + + $form['#redirect'] = 'admin/config/regional/language/configure'; + + return system_settings_form($form); +} + +/** + * Identify the language from the current content language. + * + * @return + * The current content language code. + */ +function locale_language_from_content() { + global $language; + return isset($language->language) ? $language->language : 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 valid language objects. + * + * @return + * A valid language code on success, FALSE otherwise. + */ +function locale_language_from_browser($languages) { + // Specified by the user via the browser's Accept Language setting + // Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5" + $browser_langs = array(); + + if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { + $browser_accept = explode(",", $_SERVER['HTTP_ACCEPT_LANGUAGE']); + foreach ($browser_accept as $langpart) { + // The language part is either a code or a code with a quality. + // We cannot do anything with a * code, so it is skipped. + // If the quality is missing, it is assumed to be 1 according to the RFC. + if (preg_match("!([a-z-]+)(;q=([0-9\\.]+))?!", trim($langpart), $found)) { + $browser_langs[$found[1]] = (isset($found[3]) ? (float) $found[3] : 1.0); + } + } + } + + // Order the codes by quality + arsort($browser_langs); + + // Try to find the first preferred language we have + foreach ($browser_langs as $langcode => $q) { + if (isset($languages[$langcode])) { + return $langcode; + } + } + + return FALSE; +} + +/** + * Identify language from the user preferences. + * + * @param $languages + * An array of valid language objects. + * + * @return + * A valid language code on succes, FALSE otherwise. + */ +function locale_language_from_user($languages) { + // User preference (only for logged users). + global $user; + + if ($user->uid) { + return $user->language; + } + + // 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 succes, FALSE otherwise. + */ +function locale_language_from_session($languages) { + $param = variable_get('locale_language_negotiation_session_param', 'language'); + + // Request parameter. + if (isset($_GET[$param]) && isset($languages[$langcode = $_GET[$param]])) { + return $_SESSION[$param] = $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 succes, FALSE otherwise. + */ +function locale_language_from_url($languages) { + $language_url = FALSE; + + switch (variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX)) { + case LOCALE_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->language; + } + break; + + case LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN: + foreach ($languages as $language) { + $host = parse_url($language->domain, PHP_URL_HOST); + if ($host && ($_SERVER['HTTP_HOST'] == $host)) { + $language_url = $language->language; + break; + } + } + break; + } + + return $language_url; +} + +/** + * Return the URL language switcher block. Translation links may be provided by + * other modules. + */ +function locale_language_switcher_url($type, $path) { + $languages = language_list('enabled'); + $links = array(); + + foreach ($languages[1] as $language) { + $links[$language->language] = array( + 'href' => $path, + 'title' => $language->native, + '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]->language; + + $languages = language_list('enabled'); + $links = array(); + + $query = $_GET; + unset($query['q']); + + foreach ($languages[1] as $language) { + $langcode = $language->language; + $links[$langcode] = array( + 'href' => $path, + 'title' => $language->native, + '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 provider. + */ +function locale_language_url_rewrite_url(&$path, &$options) { + // 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; + } + + if (isset($options['language'])) { + switch (variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX)) { + case LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN: + if ($options['language']->domain) { + // Ask for an absolute URL with our modified base_url. + $options['absolute'] = TRUE; + $options['base_url'] = $options['language']->domain; + } + break; + + case LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX: + if (!empty($options['language']->prefix)) { + $options['prefix'] = $options['language']->prefix . '/'; + } + break; + } + } +} + +/** + * Rewrite URLs for the Session language provider. + */ +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) { + $languages = language_list('enabled'); + $languages = $languages[1]; + $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_get_any(LOCALE_LANGUAGE_NEGOTIATION_SESSION); + } + else { + $query_rewrite = FALSE; + } + } + + // If the user is anonymous, the user language provider 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" */ diff --git a/includes/theme.inc b/includes/theme.inc index b06f62c32..f17f10155 100644 --- a/includes/theme.inc +++ b/includes/theme.inc @@ -1397,7 +1397,7 @@ function theme_links($variables) { $links = $variables['links']; $attributes = $variables['attributes']; $heading = $variables['heading']; - global $language; + global $language_url; $output = ''; if (count($links) > 0) { @@ -1438,7 +1438,7 @@ function theme_links($variables) { $class[] = 'last'; } if (isset($link['href']) && ($link['href'] == $_GET['q'] || ($link['href'] == '' && drupal_is_front_page())) - && (empty($link['language']) || $link['language']->language == $language->language)) { + && (empty($link['language']) || $link['language']->language == $language_url->language)) { $class[] = 'active'; } $output .= ' $class)) . '>'; diff --git a/modules/locale/locale.api.php b/modules/locale/locale.api.php index 5d0852b51..b1805e2e0 100644 --- a/modules/locale/locale.api.php +++ b/modules/locale/locale.api.php @@ -25,26 +25,117 @@ function hook_locale($op = 'groups') { } /** - * Perform alterations on translation links. + * Perform alterations on language switcher links. * - * A translation link may need to point to a different path or use a translated - * link text before going through l(), which will just handle the path aliases. + * A language switcher link may need to point to a different path or use a + * translated link text before going through l(), which will just handle the + * path aliases. * * @param $links * Nested array of links keyed by language code. + * @param $type + * The language type the links will switch. * @param $path * The current path. */ -function hook_translation_link_alter(array &$links, $path) { +function hook_language_switch_link_alter(array &$links, $type, $path) { global $language; - if (isset($links[$language])) { + if ($type == LANGUAGE_TYPE_CONTENT && isset($links[$language])) { foreach ($links[$language] as $link) { $link['attributes']['class'][] = 'active-language'; } } } +/** + * Allow modules to define their own language types. + * + * @return + * An array of language type definitions. Each language type has an identifier + * key. The language type definition is an associative array that may contain + * the following key-value pairs: + * - "name": The human-readable language type identifier. + * - "description": A description of the language type. + */ +function hook_language_types_info() { + return array( + 'custom_language_type' => array( + 'name' => t('Custom language'), + 'description' => t('A custom language type.'), + ), + ); +} + +/** + * Perform alterations on language types. + * + * @param $language_types + * Array of language type definitions. + */ +function hook_language_types_info_alter(array &$language_types) { + if (isset($language_types['custom_language_type'])) { + $language_types['custom_language_type_custom']['description'] = t('A far better description.'); + } +} + +/** + * Allow modules to define their own language providers. + * + * @return + * An array of language provider definitions. Each language provider has an + * identifier key. The language provider definition is an associative array + * that may contain the following key-value pairs: + * - "types": An array of allowed language types. If a language provider does + * not specify which language types it should be used with, it will be + * available for all the configurable language types. + * - "callbacks": An array of functions that will be called to perform various + * tasks. Possible key-value pairs are: + * - "language": Required. The callback that will determine the language + * value. + * - "switcher": The callback that will determine the language switch links + * associated to the current language provider. + * - "url_rewrite": The callback that will provide URL rewriting. + * - "file": A file that will be included before the callback is invoked; this + * allows callback functions to be in separate files. + * - "weight": The default weight the language provider has. + * - "name": A human-readable identifier. + * - "description": A description of the language provider. + * - "config": An internal path pointing to the language provider + * configuration page. + * - "cache": The value Drupal's page cache should be set to for the current + * language provider to be invoked. + */ +function hook_language_negotiation_info() { + return array( + 'custom_language_provider' => array( + 'callbacks' => array( + 'language' => 'custom_language_provider_callback', + 'switcher' => 'custom_language_switcher_callback', + 'url_rewrite' => 'custom_language_url_rewrite_callback', + ), + 'file' => drupal_get_path('module', 'custom') . '/custom.module', + 'weight' => -4, + 'types' => array('custom_language_type'), + 'name' => t('Custom language provider'), + 'description' => t('This is a custom language provider.'), + 'cache' => CACHE_DISABLED, + ), + ); +} + +/** + * Perform alterations on language providers. + * + * @param $language_providers + * Array of language provider definitions. + */ +function hook_language_negotiation_info_alter(array &$language_providers) { + if (isset($language_providers['custom_language_provider'])) { + $language_providers['custom_language_provider']['config'] = 'admin/config/regional/language/configure/custom-language-provider'; + } +} + /** * @} End of "addtogroup hooks". */ diff --git a/modules/locale/locale.css b/modules/locale/locale.css index 3923f9ef3..b571a20d1 100644 --- a/modules/locale/locale.css +++ b/modules/locale/locale.css @@ -19,3 +19,11 @@ #locale-translation-filter-form .form-item select.form-select { width: 100%; } + +.language-switcher-locale-session .active a.active { + color: #0062A0; +} + +.language-switcher-locale-session .active a.session-active { + color: #000000; +} diff --git a/modules/locale/locale.install b/modules/locale/locale.install index 73d8814e1..7800cc0f8 100644 --- a/modules/locale/locale.install +++ b/modules/locale/locale.install @@ -40,6 +40,47 @@ function locale_update_7000() { db_add_index('locales_source', 'source_context', array(array('source', 30), 'context')); } +/** + * Upgrade language negotiation settings. + */ +function locale_update_7001() { + require_once DRUPAL_ROOT . '/includes/language.inc'; + + switch (variable_get('language_negotiation', 0)) { + // LANGUAGE_NEGOTIATION_NONE. + case 0: + $negotiation = array(); + break; + + // LANGUAGE_NEGOTIATION_PATH_DEFAULT. + case 1: + $negotiation = array(LOCALE_LANGUAGE_NEGOTIATION_URL); + break; + + // LANGUAGE_NEGOTIATION_PATH. + case 2: + $negotiation = array(LOCALE_LANGUAGE_NEGOTIATION_URL, LOCALE_LANGUAGE_NEGOTIATION_USER, LOCALE_LANGUAGE_NEGOTIATION_BROWSER); + break; + + // LANGUAGE_NEGOTIATION_DOMAIN. + case 3: + variable_set('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN); + $negotiation = array(LOCALE_LANGUAGE_NEGOTIATION_URL); + break; + } + + // Save new language negotiation options: UI language is tied to content + // language as this was Drupal 6 behavior. + language_negotiation_set(LANGUAGE_TYPE_CONTENT, array_flip($negotiation)); + language_negotiation_set(LANGUAGE_TYPE_INTERFACE, array(LOCALE_LANGUAGE_NEGOTIATION_CONTENT => 0)); + language_negotiation_set(LANGUAGE_TYPE_URL, array(LOCALE_LANGUAGE_NEGOTIATION_URL => 0)); + + // Unset the old language negotiation system variable. + variable_del('language_negotiation'); + + return array(); +} + /** * @} End of "defgroup updates-6.x-to-7.x" */ @@ -62,15 +103,23 @@ function locale_uninstall() { // Clear variables. variable_del('language_default'); variable_del('language_count'); - variable_del('language_negotiation'); - variable_del('javascript_parsed'); + variable_del('language_types'); + variable_del('locale_language_negotiation_url_part'); + 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'); + + foreach (language_types() as $type) { + variable_del("language_negotiation_$type"); + variable_del("locale_language_providers_enabled_$type"); + variable_del("locale_language_providers_weight_$type"); + } foreach (node_type_get_types() as $type => $content_type) { - $setting = variable_del('language_content_type_' . $type); + $setting = variable_del("language_content_type_$type"); } // Switch back to English: with a $language->language value different from 'en' diff --git a/modules/locale/locale.module b/modules/locale/locale.module index f6968c093..e73eaf87b 100644 --- a/modules/locale/locale.module +++ b/modules/locale/locale.module @@ -12,6 +12,32 @@ * Gettext portable object files are supported. */ +/** + * The language is determined using a URL language indicator: + * path prefix or domain according to the configuration. + */ +define('LOCALE_LANGUAGE_NEGOTIATION_URL', 'locale-url'); + +/** + * The language is set based on the browser language settings. + */ +define('LOCALE_LANGUAGE_NEGOTIATION_BROWSER', 'locale-browser'); + +/** + * The language is determined using the current content language. + */ +define('LOCALE_LANGUAGE_NEGOTIATION_CONTENT', 'locale-content'); + +/** + * The language is set based on the user language settings. + */ +define('LOCALE_LANGUAGE_NEGOTIATION_USER', 'locale-user'); + +/** + * The language is set based on the request/session parameters. + */ +define('LOCALE_LANGUAGE_NEGOTIATION_SESSION', 'locale-session'); + // --------------------------------------------------------------------------------- // Hook implementations @@ -38,12 +64,10 @@ function locale_help($path, $arg) { case 'admin/config/regional/language/add': return '

' . t('Add all languages to be supported by your site. If your desired language is not available in the Language name drop-down, click Custom language and provide a language code and other details manually. When providing a language code manually, be sure to enter a standardized language code, since this code may be used by browsers to determine an appropriate display language.') . '

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

' . t("Language negotiation settings determine the site's presentation language. Available options include:") . '

'; - $output .= '
  • ' . t('None. The default language is used for site presentation, though users may (optionally) select a preferred language on the My Account page. (User language preferences will be used for site e-mails, if available.)') . '
  • '; - $output .= '
  • ' . t('Path prefix only. The presentation language is determined by examining the path for a language code or other custom string that matches the path prefix (if any) specified for each language. If a suitable prefix is not identified, the default language is used. Example: "example.com/de/contact" sets presentation language to German based on the use of "de" within the path.') . '
  • '; - $output .= '
  • ' . t("Path prefix with language fallback. The presentation language is determined by examining the path for a language code or other custom string that matches the path prefix (if any) specified for each language. If a suitable prefix is not identified, the display language is determined by the user's language preferences from the My Account page, or by the browser's language settings. If a presentation language cannot be determined, the default language is used.") . '
  • '; - $output .= '
  • ' . t('Domain name only. The presentation language is determined by examining the domain used to access the site, and comparing it to the language domain (if any) specified for each language. If a match is not identified, the default language is used. Example: "http://de.example.com/contact" sets presentation language to German based on the use of "http://de.example.com" in the domain.') . '
'; - $output .= '

' . t('The path prefix or domain name for a language may be set by editing the available languages. In the absence of an appropriate match, the site is displayed in the default language.', array('@languages' => url('admin/config/regional/language'))) . '

'; + $output = '

' . t("Language negotiation settings determine the site's content and presentation languages. For both language types there is a list of language detection methods which can be used to configure the desired language negotiation logic. Each detection method can be dragged to gain a higher priority, but it must be enabled to affect the language negotiation process. If a language detection method is applied then all the lower ones are ignored, otherwise the following one will be taken into account. Some lanaguage detection methods provide a configuration page to further specify their behavior. The default detection method is always applied, so anything below it is always ignored. Modifying this setting may break all incoming URLs and should be used with caution in a production environment.") . '

'; + $output .= '

' . t('Available options include:') .'

'; + $output .= '
  • ' . t('URL. The language is determined by examining the URL for a language code, a custom string, or a domain, that matches the ones (if any) specified for each language. The path prefix or domain name for a language may be set by editing the available languages. In the absence of an appropriate match, the site is displayed in the default language. A configuration is available to choose whether use the path prefix or the domain. Example: "example.com/de/contact" sets language to German based on the use of "de" within the path. "http://de.example.com/contact" sets presentation language to German based on the use of "http://de.example.com" in the domain.', array('@languages' => url('admin/config/regional/language'))) . '
  • '; + $output .= '
  • ' . t('Session. The language is determined from a request/session parameter. A configuration is available to choose the URL parameter name to be used. Example: "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 provides an overview of available translatable strings. Drupal displays translatable strings in text groups; modules may define additional text groups containing other translatable strings. Because text groups provide a method of grouping related strings, they are often used to focus translation efforts on specific areas of the Drupal interface.') . '

'; @@ -103,6 +127,22 @@ function locale_menu() { 'file path' => 'includes', 'type' => MENU_LOCAL_TASK, ); + $items['admin/config/regional/language/configure/url'] = array( + 'title' => 'URL language provider configuration', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('locale_language_providers_url_form'), + 'access arguments' => array('administer languages'), + 'file' => 'locale.inc', + 'file path' => 'includes', + ); + $items['admin/config/regional/language/configure/session'] = array( + 'title' => 'Session language provider configuration', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('locale_language_providers_session_form'), + 'access arguments' => array('administer languages'), + 'file' => 'locale.inc', + 'file path' => 'includes', + ); $items['admin/config/regional/language/edit/%'] = array( 'title' => 'Edit language', 'page callback' => 'drupal_get_form', @@ -249,13 +289,13 @@ function locale_language_selector_form(&$form, &$form_state, $user) { ); // Get language negotiation settings. - $mode = variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE); + $mode = language_negotiation_get(LANGUAGE_TYPE_INTERFACE) != LANGUAGE_NEGOTIATION_DEFAULT; $form['locale']['language'] = array( '#type' => (count($names) <= 5 ? 'radios' : 'select'), '#title' => t('Language'), '#default_value' => $user_preferred_language->language, '#options' => $names, - '#description' => ($mode == LANGUAGE_NEGOTIATION_PATH) ? t("This account's default language for e-mails, and preferred language for site presentation.") : t("This account's default language for e-mails."), + '#description' => $mode ? t("This account's default language for e-mails, and preferred language for site presentation.") : t("This account's default language for e-mails."), ); } @@ -330,12 +370,97 @@ function locale_theme() { 'locale_languages_overview_form' => array( 'arguments' => array('form' => array()), ), + 'locale_languages_configure_form' => array( + 'arguments' => array('form' => array()), + ), 'locale_translation_filters' => array( 'arguments' => array('form' => array()), ), ); } +/** + * Implement hook_language_types_info(). + */ +function locale_language_types_info() { + return array( + LANGUAGE_TYPE_CONTENT => array( + 'name' => t('Content'), + 'description' => t('If a piece of content is available in multiple languages, the one matching the content language will be used.'), + ), + LANGUAGE_TYPE_INTERFACE => array( + 'name' => t('Interface'), + 'description' => t('The interface labels will be displayed in the interface language.'), + ), + LANGUAGE_TYPE_URL => array( + 'fixed' => array(LOCALE_LANGUAGE_NEGOTIATION_URL), + ), + ); +} + +/** + * Implement hook_language_negotiation_info(). + */ +function locale_language_negotiation_info() { + $file = 'includes/locale.inc'; + $providers = array(); + + $providers[LOCALE_LANGUAGE_NEGOTIATION_URL] = array( + 'types' => array(LANGUAGE_TYPE_CONTENT, LANGUAGE_TYPE_INTERFACE, LANGUAGE_TYPE_URL), + 'callbacks' => array( + 'language' => 'locale_language_from_url', + 'switcher' => 'locale_language_switcher_url', + 'url_rewrite' => 'locale_language_url_rewrite_url', + ), + 'file' => $file, + 'weight' => -8, + 'name' => t('URL'), + 'description' => t('The language is determined from the URL (Path prefix or domain).'), + 'config' => 'admin/config/regional/language/configure/url', + ); + + $providers[LOCALE_LANGUAGE_NEGOTIATION_SESSION] = array( + 'callbacks' => array( + 'language' => 'locale_language_from_session', + 'switcher' => 'locale_language_switcher_session', + 'url_rewrite' => 'locale_language_url_rewrite_session', + ), + 'file' => $file, + 'weight' => -6, + 'name' => t('Session'), + 'description' => t('The language is determined from a request/session parameter.'), + 'config' => 'admin/config/regional/language/configure/session', + ); + + $providers[LOCALE_LANGUAGE_NEGOTIATION_USER] = array( + 'callbacks' => array('language' => 'locale_language_from_user'), + 'file' => $file, + 'weight' => -4, + 'name' => t('User'), + 'description' => t('The language is determined from the language preference set in the user account.'), + ); + + $providers[LOCALE_LANGUAGE_NEGOTIATION_BROWSER] = array( + 'callbacks' => array('language' => 'locale_language_from_browser'), + 'name' => $file, + 'weight' => -2, + 'cache' => CACHE_DISABLED, + 'name' => t('Browser'), + 'description' => t('The language is determined from the browser\'s language settings.'), + ); + + $providers[LOCALE_LANGUAGE_NEGOTIATION_CONTENT] = array( + 'types' => array(LANGUAGE_TYPE_INTERFACE), + 'callbacks' => array('language' => 'locale_language_from_content'), + 'file' => $file, + 'weight' => 8, + 'name' => t('Content'), + 'description' => t('The interface language is the same as the negotiated content language.'), + ); + + return $providers; +} + // --------------------------------------------------------------------------------- // Locale core functionality @@ -625,39 +750,36 @@ function locale_css_alter(&$css) { * Implement hook_block_info(). */ function locale_block_info() { - $block['language-switcher']['info'] = t('Language switcher'); - // Not worth caching. - $block['language-switcher']['cache'] = DRUPAL_NO_CACHE; + include_once DRUPAL_ROOT . '/includes/language.inc'; + $block = array(); + $info = language_types_info(); + foreach (language_types_configurable() as $type) { + $block[$type] = array( + 'info' => t('Language switcher (@type)', array('@type' => $info[$type]['name'])), + // Not worth caching. + 'cache' => DRUPAL_NO_CACHE, + ); + } return $block; } /** * Implement hook_block_view(). * - * Displays a language switcher. Translation links may be provided by other modules. - * Only show if we have at least two languages and language dependent - * web addresses, so we can actually link to other language versions. + * Displays a language switcher. Only show if we have at least two languages. */ -function locale_block_view($delta = '') { - if (variable_get('language_count', 1) > 1 && variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE) != LANGUAGE_NEGOTIATION_NONE) { +function locale_block_view($type) { + if (variable_get('language_count', 1) > 1) { $path = drupal_is_front_page() ? '' : $_GET['q']; - $languages = language_list('enabled'); - $links = array(); - foreach ($languages[1] as $language) { - $links[$language->language] = array( - 'href' => $path, - 'title' => $language->native, - 'language' => $language, - 'attributes' => array('class' => array('language-link')), - ); + $links = language_negotiation_get_switch_links($type, $path); + + if (isset($links->links) && count($links->links > 1)) { + $class = "language-switcher-{$links->provider}"; + $variables = array('links' => $links->links, 'attributes' => array('class' => array($class))); + $block['content'] = theme('links', $variables); + $block['subject'] = t('Languages'); + return $block; } - - // Allow modules to provide translations for specific links. - drupal_alter('translation_link', $links, $path); - - $block['subject'] = t('Languages'); - $block['content'] = theme('links', array('links' => $links, 'attributes' => array())); - return $block; } } diff --git a/modules/locale/locale.test b/modules/locale/locale.test index 0078b2eb3..fc598e296 100644 --- a/modules/locale/locale.test +++ b/modules/locale/locale.test @@ -910,11 +910,11 @@ class LocaleUninstallFunctionalTest extends DrupalWebTestCase { /** * The default language set for the UI before uninstall. */ - protected $ui_language; + protected $language_interface; function setUp() { parent::setUp('locale'); - $this->ui_language = 'en'; + $this->language_interface = 'en'; } /** @@ -925,15 +925,12 @@ class LocaleUninstallFunctionalTest extends DrupalWebTestCase { // Add a new language and optionally set it as default. require_once DRUPAL_ROOT . '/includes/locale.inc'; - locale_add_language('fr', 'French', 'Français', LANGUAGE_LTR, '', '', TRUE, $this->ui_language == 'fr'); + locale_add_language('fr', 'French', 'Français', LANGUAGE_LTR, '', '', TRUE, $this->language_interface == 'fr'); // Check the UI language. drupal_language_initialize(); - global $language; - $this->assertEqual($language->language, $this->ui_language, t('Current language: %lang', array('%lang' => $language->language))); - - // Change language negotiation options. - variable_set('language_negotiation', LANGUAGE_NEGOTIATION_PATH_DEFAULT); + global $language_interface; + $this->assertEqual($language_interface->language, $this->language_interface, t('Current language: %lang', array('%lang' => $language_interface->language))); // Enable multilingual workflow option for articles. variable_set('language_content_type_article', 1); @@ -959,6 +956,17 @@ class LocaleUninstallFunctionalTest extends DrupalWebTestCase { // Disable string caching. variable_set('locale_cache_strings', 0); + // Change language negotiation options. + drupal_load('module', 'locale'); + variable_set('language_types', drupal_language_types() + 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()); + + // Change language providers settings. + variable_set('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX); + variable_set('locale_language_negotiation_session_param', TRUE); + // Uninstall Locale. module_disable($locale_module); drupal_uninstall_modules($locale_module); @@ -968,7 +976,7 @@ class LocaleUninstallFunctionalTest extends DrupalWebTestCase { // Check the init language logic. drupal_language_initialize(); - $this->assertEqual($language->language, 'en', t('Language after uninstall: %lang', array('%lang' => $language->language))); + $this->assertEqual($language_interface->language, 'en', t('Language after uninstall: %lang', array('%lang' => $language_interface->language))); // Check JavaScript files deletion. $this->assertTrue($result = !file_exists($js_file), t('JavaScript file deleted: %file', array('%file' => $result ? $js_file : t('found')))); @@ -978,8 +986,17 @@ class LocaleUninstallFunctionalTest extends DrupalWebTestCase { $this->assertEqual($language_count, 1, t('Language count: %count', array('%count' => $language_count))); // Check language negotiation. - $language_negotiation = variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE) == LANGUAGE_NEGOTIATION_NONE; - $this->assertTrue($language_negotiation, t('Language negotiation: %setting', array('%setting' => t($language_negotiation ? 'none' : 'set')))); + $this->assertTrue(count(language_types()) == count(drupal_language_types()), t('Language types reset')); + $language_negotiation = language_negotiation_get(LANGUAGE_TYPE_INTERFACE) == LANGUAGE_NEGOTIATION_DEFAULT; + $this->assertTrue($language_negotiation, t('Interface language negotiation: %setting', array('%setting' => t($language_negotiation ? 'none' : 'set')))); + $language_negotiation = language_negotiation_get(LANGUAGE_TYPE_CONTENT) == LANGUAGE_NEGOTIATION_DEFAULT; + $this->assertTrue($language_negotiation, t('Content language negotiation: %setting', array('%setting' => t($language_negotiation ? 'none' : 'set')))); + $language_negotiation = language_negotiation_get(LANGUAGE_TYPE_URL) == LANGUAGE_NEGOTIATION_DEFAULT; + $this->assertTrue($language_negotiation, t('URL language negotiation: %setting', array('%setting' => t($language_negotiation ? 'none' : 'set')))); + + // Check language providers settings. + $this->assertFalse(variable_get('locale_language_negotiation_url_part', FALSE), t('URL language provider indicator settings cleared.')); + $this->assertFalse(variable_get('locale_language_negotiation_session_param', FALSE), t('Visit language provider settings cleared.')); // Check JavaScript parsed. $javascript_parsed_count = count(variable_get('javascript_parsed', array())); @@ -1011,14 +1028,14 @@ class LocaleUninstallFrenchFunctionalTest extends LocaleUninstallFunctionalTest public static function getInfo() { return array( 'name' => 'Locale uninstall (FR)', - 'description' => 'Tests the uninstall process using French as UI language.', + 'description' => 'Tests the uninstall process using French as interface language.', 'group' => 'Locale', ); } function setUp() { parent::setUp(); - $this->ui_language = 'fr'; + $this->language_interface = 'fr'; } } @@ -1050,7 +1067,7 @@ class LanguageSwitchingFunctionalTest extends DrupalWebTestCase { function testLanguageBlock() { // Enable the language switching block. $edit = array( - 'locale_language-switcher[region]' => 'sidebar_first', + 'locale_language[region]' => 'sidebar_first', ); $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); @@ -1061,18 +1078,16 @@ class LanguageSwitchingFunctionalTest extends DrupalWebTestCase { $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); // Set language negotiation. - $edit = array( - 'language_negotiation' => LANGUAGE_NEGOTIATION_PATH_DEFAULT, - ); - $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings')); - $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), t('Correct page redirection.')); + drupal_load('module', 'locale'); + include_once DRUPAL_ROOT . '/includes/language.inc'; + language_negotiation_set(LANGUAGE_TYPE_CONTENT, locale_language_negotiation_info()); // Assert that the language switching block is displayed on the frontpage. $this->drupalGet(''); $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="block-locale-language-switcher"]'); + list($language_switcher) = $this->xpath('//div[@id="block-locale-language"]'); $links = array( 'active' => array(), 'inactive' => array(), @@ -1243,10 +1258,8 @@ class LocalePathFunctionalTest extends DrupalWebTestCase { $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); // Set language negotiation. - $edit = array( - 'language_negotiation' => LANGUAGE_NEGOTIATION_PATH_DEFAULT, - ); - $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings')); + drupal_load('module', 'locale'); + variable_set('language_negotiation_' . LANGUAGE_TYPE_CONTENT, locale_language_negotiation_info()); // Create a node. $node = $this->drupalCreateNode(array('type' => 'page')); @@ -1352,12 +1365,6 @@ class LocaleContentFunctionalTest extends DrupalWebTestCase { ); $this->drupalPost($path, $edit, t('Save configuration')); - // Set language negotiation. - $edit = array( - 'language_negotiation' => LANGUAGE_NEGOTIATION_PATH_DEFAULT, - ); - $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings')); - // Set page content type to use multilingual support. $this->drupalGet('admin/structure/types/manage/page'); $this->assertText(t('Multilingual support'), t('Multilingual support fieldset present on content type configuration form.')); @@ -1412,7 +1419,7 @@ class LocaleContentFunctionalTest extends DrupalWebTestCase { /** * Test UI language negotiation - * 1. LANGUAGE_NEGOTIATION_PATH_DEFAULT + * 1. URL (PATH) > DEFAULT * UI Language base on URL prefix, browser language preference has no * influence: * admin/config @@ -1421,7 +1428,7 @@ class LocaleContentFunctionalTest extends DrupalWebTestCase { * UI in Chinese * blah-blah/admin/config * 404 - * 2. LANGUAGE_NEGOTIATION_PATH + * 2. URL (PATH) > BROWSER > DEFAULT * admin/config * UI in user's browser language preference if the site has that * language enabled, if not, the default language @@ -1429,7 +1436,7 @@ class LocaleContentFunctionalTest extends DrupalWebTestCase { * UI in Chinese * blah-blah/admin/config * 404 - * 3. LANGUAGE_NEGOTIATION_DOMAIN + * 3. URL (DOMAIN) > DEFAULT * http://example.com/admin/config * UI language in site default * http://example.cn/admin/config @@ -1446,6 +1453,8 @@ class UILanguageNegotiationTest extends DrupalWebTestCase { function setUp() { parent::setUp('locale', 'locale_test'); + require_once DRUPAL_ROOT . '/includes/language.inc'; + drupal_load('module', 'locale'); } /** @@ -1505,46 +1514,49 @@ class UILanguageNegotiationTest extends DrupalWebTestCase { ); $this->drupalPost(NULL, $edit, t('Save translations')); + // Configure URL language rewrite. + variable_set('locale_language_negotiation_url_type', LANGUAGE_TYPE_INTERFACE); + $tests = array( // Default, browser preference should have no influence. array( - 'language_negotiation' => LANGUAGE_NEGOTIATION_PATH_DEFAULT, + 'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT), 'path' => 'admin/config', 'expect' => $default_string, 'http_header' => $http_header_browser_fallback, - 'message' => 'LANGUAGE_NEGOTIATION_PATH_DEFAULT: no language prefix, UI language is default and not the browser language preference setting is used.', + 'message' => 'URL (PATH) > DEFAULT: no language prefix, UI language is default and the browser language preference setting is not used.', ), // Language prefix. array( - 'language_negotiation' => LANGUAGE_NEGOTIATION_PATH_DEFAULT, + 'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT), 'path' => "$language/admin/config", 'expect' => $language_string, 'http_header' => $http_header_browser_fallback, - 'message' => 'LANGUAGE_NEGOTIATION_PATH_DEFAULT: with language prefix, UI language is switched based on path prefix', + 'message' => 'URL (PATH) > DEFAULT: with language prefix, UI language is switched based on path prefix', ), // Default, go by browser preference. array( - 'language_negotiation' => LANGUAGE_NEGOTIATION_PATH, + 'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LOCALE_LANGUAGE_NEGOTIATION_BROWSER), 'path' => 'admin/config', 'expect' => $language_browser_fallback_string, 'http_header' => $http_header_browser_fallback, - 'message' => 'LANGUAGE_NEGOTIATION_PATH: no language prefix, UI language is determined by browser language preference', + 'message' => 'URL (PATH) > BROWSER: no language prefix, UI language is determined by browser language preference', ), // Prefix, switch to the language. array( - 'language_negotiation' => LANGUAGE_NEGOTIATION_PATH, + 'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LOCALE_LANGUAGE_NEGOTIATION_BROWSER), 'path' => "$language/admin/config", 'expect' => $language_string, 'http_header' => $http_header_browser_fallback, - 'message' => 'LANGUAGE_NEGOTIATION_PATH: with langage prefix, UI language is based on path prefix', + 'message' => 'URL (PATH) > BROWSER: with langage prefix, UI language is based on path prefix', ), // Default, browser language preference is not one of site's lang. array( - 'language_negotiation' => LANGUAGE_NEGOTIATION_PATH, + 'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LOCALE_LANGUAGE_NEGOTIATION_BROWSER, LANGUAGE_NEGOTIATION_DEFAULT), 'path' => 'admin/config', 'expect' => $default_string, 'http_header' => $http_header_blah, - 'message' => 'LANGUAGE_NEGOTIATION_PATH: no language prefix and browser language preference set to unknown language should use default language', + 'message' => 'URL (PATH) > BROWSER > DEFAULT: no language prefix and browser language preference set to unknown language should use default language', ), ); @@ -1553,35 +1565,36 @@ class UILanguageNegotiationTest extends DrupalWebTestCase { } // Unknown language prefix should return 404. - foreach(array(LANGUAGE_NEGOTIATION_PATH_DEFAULT, LANGUAGE_NEGOTIATION_PATH) as $negotiation) { - variable_set('language_negotiation', $negotiation); - $this->drupalGet("$language_unknown/admin/config", array(), $http_header_browser_fallback); - $this->assertResponse(404, "Unknown language path prefix should return 404, code = $negotiation"); - } + variable_set('language_negotiation_' . LANGUAGE_TYPE_INTERFACE, locale_language_negotiation_info()); + $this->drupalGet("$language_unknown/admin/config", array(), $http_header_browser_fallback); + $this->assertResponse(404, "Unknown language path prefix should return 404"); // Setup for domain negotiation, first configure the language to have domain // URL. $edit = array('prefix' => '', 'domain' => "http://$language_domain"); $this->drupalPost("admin/config/regional/language/edit/$language", $edit, t('Save language')); // Set the site to use domain language negotiation. - variable_set('language_negotiation', LANGUAGE_NEGOTIATION_DOMAIN); $tests = array( // Default domain, browser preference should have no influence. array( + 'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT), + 'locale_language_negotiation_url_part' => LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN, 'path' => 'admin/config', 'expect' => $default_string, 'http_header' => $http_header_browser_fallback, - 'message' => 'LANGUAGE_NEGOTIATION_DOMAIN: default domain should get default language', + 'message' => 'URL (DOMAIN) > DEFAULT: default domain should get default language', ), // Language domain specific URL, we set the $_SERVER['HTTP_HOST'] in // locale_test.module hook_boot() to simulate this. array( + 'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT), + 'locale_language_negotiation_url_part' => LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN, 'locale_test_domain' => $language_domain, 'path' => 'admin/config', 'expect' => $language_string, 'http_header' => $http_header_browser_fallback, - 'message' => 'LANGUAGE_NEGOTIATION_DOMAIN: domain example.cn should switch to Chinese', + 'message' => 'URL (DOMAIN) > DEFAULT: domain example.cn should switch to Chinese', ), ); @@ -1592,7 +1605,11 @@ class UILanguageNegotiationTest extends DrupalWebTestCase { private function runTest($test) { if (!empty($test['language_negotiation'])) { - variable_set('language_negotiation', $test['language_negotiation']); + $negotiation = array_flip($test['language_negotiation']); + language_negotiation_set(LANGUAGE_TYPE_INTERFACE, $negotiation); + } + if (!empty($test['locale_language_negotiation_url_part'])) { + variable_set('locale_language_negotiation_url_part', $test['locale_language_negotiation_url_part']); } if (!empty($test['locale_test_domain'])) { variable_set('locale_test_domain', $test['locale_test_domain']); diff --git a/modules/node/node.module b/modules/node/node.module index ad30fba0d..2e6a3800c 100644 --- a/modules/node/node.module +++ b/modules/node/node.module @@ -1137,6 +1137,52 @@ function node_build_content($node, $build_mode = 'full') { drupal_alter('node_build', $node, $build_mode); } +/** + * Implement hook_language_negotiation_info(). + */ +function node_language_negotiation_info() { + $providers = array(); + + $providers['node-language'] = array( + 'types' => array(LANGUAGE_TYPE_CONTENT), + 'callbacks' => array('language' => 'node_language_provider'), + 'file' => drupal_get_path('module', 'node') . '/node.module', + 'name' => t('Node'), + 'description' => t('The current node language is used.'), + ); + + return $providers; +} + +/** + * Return the language of the current node. + * + * @param $languages + * An array of valid language objects. + * + * @return + * A valid language code on succes, FALSE otherwise. + */ +function node_language_provider($languages) { + require_once DRUPAL_ROOT . '/includes/path.inc'; + + $path = isset($_GET['q']) ? $_GET['q'] : ''; + list($language, $path) = language_url_split_prefix($path, $languages); + $language = $language ? $language : language_default(); + $path = drupal_get_normal_path($path, $language->language); + + // We cannot use args now. + $path = explode('/', $path); + // Act only if we are in a node page. + if ($path[0] == 'node' && $nid = intval($path[1])) { + // We cannot perform a node load here. + $result = db_query('SELECT n.language FROM {node} n WHERE n.nid = :nid', array(':nid' => $nid))->fetchAssoc(); + return $result['language']; + } + + return FALSE; +} + /** * Generate an array which displays a node detail page. * diff --git a/modules/path/path.test b/modules/path/path.test index d0abc0a2c..21cbb5ab9 100644 --- a/modules/path/path.test +++ b/modules/path/path.test @@ -183,7 +183,9 @@ class PathLanguageTestCase extends DrupalWebTestCase { drupal_static_reset('language_list'); // Set language negotiation to "Path prefix with fallback". - variable_set('language_negotiation', LANGUAGE_NEGOTIATION_PATH); + include_once DRUPAL_ROOT . '/includes/locale.inc'; + variable_set('language_negotiation_' . LANGUAGE_TYPE_CONTENT, locale_language_negotiation_info()); + variable_set('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX); // Force inclusion of language.inc. drupal_language_initialize(); diff --git a/modules/translation/translation.module b/modules/translation/translation.module index 4684c7f2f..a35dfb86a 100644 --- a/modules/translation/translation.module +++ b/modules/translation/translation.module @@ -170,23 +170,17 @@ function translation_form_alter(&$form, &$form_state, $form_id) { */ function translation_node_view($node, $build_mode) { if (isset($node->tnid) && $translations = translation_node_get_translations($node->tnid)) { - // Do not show link to the same node. - unset($translations[$node->language]); - $languages = language_list(); - foreach ($languages as $langcode => $language) { - if (isset($translations[$langcode])) { - $links["node_translation_$langcode"] = array( - 'title' => $language->native, - 'href' => 'node/' . $translations[$langcode]->nid, - 'language' => $language, - 'attributes' => array('title' => $translations[$langcode]->title, 'class' => array('translation-link')), - ); - $node->content['links']['translation'] = array( - '#theme' => 'links', - '#links' => $links, - '#attributes' => array('class' => array('links', 'inline')), - ); - } + $path = 'node/' . $node->nid; + $links = language_negotiation_get_switch_links(LANGUAGE_TYPE_CONTENT, $path); + if (is_object($links)) { + $links = $links->links; + // Do not show link to the same node. + unset($links[$node->language]); + $node->content['links']['translation'] = array( + '#theme' => 'links', + '#links' => $links, + '#attributes' => array('class' => array('links', 'inline')), + ); } } } @@ -407,12 +401,12 @@ function translation_path_get_translations($path) { } /** - * Implement hook_translation_link_alter(). + * Implement hook_language_switch_link_alter(). * * Replaces links with pointers to translated versions of the content. */ -function translation_translation_link_alter(array &$links, $path) { - if ($paths = translation_path_get_translations($path)) { +function translation_language_switch_links_alter(array &$links, $type, $path) { + if ($type == LANGUAGE_TYPE_CONTENT && $paths = translation_path_get_translations($path)) { foreach ($links as $langcode => $link) { if (isset($paths[$langcode])) { // Translation in a different node. -- cgit v1.2.3