diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc index cda1bb8b81e1f3babb9e65de7043f370771de982..fe9a95471fbc215822e221cbf9ac2ff2778c826b 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -154,27 +154,19 @@ 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 3f26c4949ff246f1ac827891d1293078d9f0aa04..d8661100c86ae893b99941ce66d528aa615e31a5 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 == '<front>' && 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 9a990a72230cca0f09c5514dcbaa1a9268c70ace..f170cf9a064c545618de7825b65043b97b84ea4e 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 85e056ce1f7667b60d7259e5d2460a80b5625556..cc95ef7f4d9f2a5bdd970fd8fbb9df9a17988982 100644 --- a/includes/locale.inc +++ b/includes/locale.inc @@ -23,6 +23,18 @@ */ 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. <strong>Modifying this setting may break all incoming URLs and should be used with caution in a production environment.</strong>") + 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 = '<label>' . $form[$type]['#title'] . '</label>'; + $description = '<div class="description">' . $form[$type]['#description'] . '</div>'; + + foreach ($form[$type]['title'] as $id => $element) { + // Do not take form control structures. + if (is_array($element) && element_child($id)) { + $row = array( + 'data' => array( + '<strong>' . drupal_render($form[$type]['title'][$id]) . '</strong>', + 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 .= '<div class="form-item">' . $title . $description . $table . '</div>'; + } + + $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 b06f62c32e5be671957efdeeacaabb1cf5268ccd..f17f10155bef58755099dc708fe01f6f7490be52 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'] == '<front>' && drupal_is_front_page())) - && (empty($link['language']) || $link['language']->language == $language->language)) { + && (empty($link['language']) || $link['language']->language == $language_url->language)) { $class[] = 'active'; } $output .= '<li' . drupal_attributes(array('class' => $class)) . '>'; diff --git a/modules/locale/locale.api.php b/modules/locale/locale.api.php index 5d0852b51aee4edce5a8305cdd87999c3ad31089..b1805e2e0da5418051e2c91b095fb20e6d6b0ef1 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 3923f9ef3fb7c927f0a8393ac028dd16c361f815..b571a20d1751f6b1fe6168bd783339e88f432b48 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 73d8814e1f3af19bef9ffb26852ad218c806574b..7800cc0f864d0346ac7f7226c15e5b30dda98669 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 f6968c093a9a4c9b2e98fe2ca83cfc05a7a4ba76..e73eaf87bfed74d8b3c022b477b55847e90bbc9d 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 '<p>' . t('Add all languages to be supported by your site. If your desired language is not available in the <em>Language name</em> drop-down, click <em>Custom language</em> 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.') . '</p>'; case 'admin/config/regional/language/configure': - $output = '<p>' . t("Language negotiation settings determine the site's presentation language. Available options include:") . '</p>'; - $output .= '<ul><li>' . t('<strong>None.</strong> The default language is used for site presentation, though users may (optionally) select a preferred language on the <em>My Account</em> page. (User language preferences will be used for site e-mails, if available.)') . '</li>'; - $output .= '<li>' . t('<strong>Path prefix only.</strong> 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. <em>Example: "example.com/de/contact" sets presentation language to German based on the use of "de" within the path.</em>') . '</li>'; - $output .= '<li>' . t("<strong>Path prefix with language fallback.</strong> 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 <em>My Account</em> page, or by the browser's language settings. If a presentation language cannot be determined, the default language is used.") . '</li>'; - $output .= '<li>' . t('<strong>Domain name only.</strong> 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. <em>Example: "http://de.example.com/contact" sets presentation language to German based on the use of "http://de.example.com" in the domain.</em>') . '</li></ul>'; - $output .= '<p>' . t('The path prefix or domain name for a language may be set by editing the <a href="@languages">available languages</a>. In the absence of an appropriate match, the site is displayed in the <a href="@languages">default language</a>.', array('@languages' => url('admin/config/regional/language'))) . '</p>'; + $output = '<p>' . t("Language negotiation settings determine the site's content and presentation languages. For both <em>language types</em> there is a list of <em>language detection methods</em> which can be used to configure the desired language negotiation logic. Each detection method can be <em>dragged</em> to gain a higher priority, but it must be <em>enabled</em> to affect the language negotiation process. If a language detection method is applied then all the lower ones are <em>ignored</em>, otherwise the following one will be taken into account. Some lanaguage detection methods provide a configuration page to further specify their behavior. The <em>default</em> detection method is always applied, so anything below it is always ignored. <strong>Modifying this setting may break all incoming URLs and should be used with caution in a production environment.</strong>") . '</p>'; + $output .= '<p>' . t('Available options include:') .'</p>'; + $output .= '<ul><li>' . t('<strong>URL.</strong> 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 <a href="@languages">available languages</a>. In the absence of an appropriate match, the site is displayed in the <a href="@languages">default language</a>. A configuration is available to choose whether use the path prefix or the domain. <em>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.</em>', array('@languages' => url('admin/config/regional/language'))) . '</li>'; + $output .= '<li>' . t('<strong>Session.</strong> The language is determined from a request/session parameter. A configuration is available to choose the URL parameter name to be used. <em>Example: "example.com?language=de" sets language to German based on the use of "de" within the "language" parameter.</em>') . '</li></ul>'; return $output; case 'admin/config/regional/translate': $output = '<p>' . 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.') . '</p>'; @@ -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 <em>content</em> language will be used.'), + ), + LANGUAGE_TYPE_INTERFACE => array( + 'name' => t('Interface'), + 'description' => t('The interface labels will be displayed in the <em>interface</em> 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() ? '<front>' : $_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 0078b2eb371ad618964807f3731ec0605cf813a2..fc598e2964842c3ce12fe215c97099cd6ef4202c 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 ad30fba0d460a435639eee53ebffa7d312ec3a8d..2e6a3800cae7256729721f06efcd42c94e28c2fd 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 d0abc0a2caf5e06e8a5a6b5134c3f30099e62cff..21cbb5ab94782c279d32127e3c5da2f80b9bf383 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 4684c7f2fda61c5ac39abf203ba539d376ae06d2..a35dfb86a77f984b301f8cebd4646188e9ec25c9 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.