Index: includes/bootstrap.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v retrieving revision 1.308 diff -u -p -r1.308 bootstrap.inc --- includes/bootstrap.inc 3 Oct 2009 19:27:44 -0000 1.308 +++ includes/bootstrap.inc 9 Oct 2009 09:30:05 -0000 @@ -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,23 +1620,50 @@ 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 * * @param $field The field to index the list with. Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.1010 diff -u -p -r1.1010 common.inc --- includes/common.inc 9 Oct 2009 08:08:55 -0000 1.1010 +++ includes/common.inc 9 Oct 2009 09:30:05 -0000 @@ -393,6 +393,26 @@ function drupal_get_query_parameters(arr } /** + * 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. * * This differs from http_build_query() as we need to rawurlencode() (instead of @@ -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 $option $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 $attrib * 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 // 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'; } Index: includes/language.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/language.inc,v retrieving revision 1.19 diff -u -p -r1.19 language.inc --- includes/language.inc 1 Feb 2009 16:45:53 -0000 1.19 +++ includes/language.inc 9 Oct 2009 09:32:47 -0000 @@ -7,138 +7,392 @@ */ /** - * Choose a language for the page, based on language negotiation settings. + * No language negotiation. The default language is used. */ -function language_initialize() { - global $user; +define('LANGUAGE_NEGOTIATION_DEFAULT', 'language-default'); - // 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(); +/** + * 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__); - 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; - } + 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; + } } - // User language. - if ($user->uid && isset($languages[$user->language])) { - return $languages[$user->language]; + 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); } - // Browser accept-language parsing. - if ($language = language_from_browser()) { - return $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; + } + } } - // Fall back on the default if everything else fails. - return language_default(); + variable_set("language_negotiation_$type", $negotiation); } /** - * Identify language from the Accept-language HTTP header we got. + * Return all the defined language providers. + * + * @return + * An array of language providers. */ -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(); - - 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); - } +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); + } + + return $language_providers; +} + +/** + * 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_provider_invoke($provider_id, $provider = NULL) { + $results = &drupal_static(__FUNCTION__); + + 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; - } - - switch (variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE)) { - case LANGUAGE_NEGOTIATION_NONE: - // No language dependent path allowed in this mode. - unset($options['language']); - break; + if (!isset($callbacks)) { + $callbacks = 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; + foreach (language_types_configurable() as $type) { + // Get url rewriter callbacks only from enabled language providers. + $negotiation = variable_get("language_negotiation_$type", array()); + + foreach ($negotiation as $id => $provider) { + if (isset($provider['file'])) { + require_once DRUPAL_ROOT . '/' . $provider['file']; + } + + // Avoid duplicate callback entries. + if (isset($provider['callbacks']['url_rewrite'])) { + $callbacks[$provider['callbacks']['url_rewrite']] = NULL; + } } - break; + } - case LANGUAGE_NEGOTIATION_PATH_DEFAULT: - $default = language_default(); - if ($options['language']->language == $default->language) { - break; - } - // Intentionally no break here. + $callbacks = array_keys($callbacks); + } - case LANGUAGE_NEGOTIATION_PATH: - if (!empty($options['language']->prefix)) { - $options['prefix'] = $options['language']->prefix . '/'; - } - break; + 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); } Index: includes/locale.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/locale.inc,v retrieving revision 1.230 diff -u -p -r1.230 locale.inc --- includes/locale.inc 9 Oct 2009 00:59:54 -0000 1.230 +++ includes/locale.inc 9 Oct 2009 09:30:05 -0000 @@ -24,6 +24,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_su */ /** - * @defgroup locale-languages-negotiation Language negotiation options screen + * @defgroup locale-languages-negotiation Language negotiation options * @{ */ @@ -481,33 +493,507 @@ function locale_languages_delete_form_su * 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" */ Index: includes/theme.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/theme.inc,v retrieving revision 1.532 diff -u -p -r1.532 theme.inc --- includes/theme.inc 9 Oct 2009 00:59:54 -0000 1.532 +++ includes/theme.inc 9 Oct 2009 09:30:05 -0000 @@ -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)) . '>'; Index: modules/locale/locale.api.php =================================================================== RCS file: /cvs/drupal/drupal/modules/locale/locale.api.php,v retrieving revision 1.4 diff -u -p -r1.4 locale.api.php --- modules/locale/locale.api.php 22 Aug 2009 14:34:20 -0000 1.4 +++ modules/locale/locale.api.php 9 Oct 2009 09:30:05 -0000 @@ -25,20 +25,23 @@ 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'; } @@ -46,5 +49,93 @@ function hook_translation_link_alter(arr } /** + * 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". */ Index: modules/locale/locale.css =================================================================== RCS file: /cvs/drupal/drupal/modules/locale/locale.css,v retrieving revision 1.4 diff -u -p -r1.4 locale.css --- modules/locale/locale.css 24 Aug 2009 03:11:34 -0000 1.4 +++ modules/locale/locale.css 9 Oct 2009 09:30:05 -0000 @@ -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; +} Index: modules/locale/locale.install =================================================================== RCS file: /cvs/drupal/drupal/modules/locale/locale.install,v retrieving revision 1.49 diff -u -p -r1.49 locale.install --- modules/locale/locale.install 29 Sep 2009 15:13:55 -0000 1.49 +++ modules/locale/locale.install 9 Oct 2009 09:30:05 -0000 @@ -41,6 +41,47 @@ function locale_update_7000() { } /** + * 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' Index: modules/locale/locale.module =================================================================== RCS file: /cvs/drupal/drupal/modules/locale/locale.module,v retrieving revision 1.260 diff -u -p -r1.260 locale.module --- modules/locale/locale.module 9 Oct 2009 01:00:00 -0000 1.260 +++ modules/locale/locale.module 9 Oct 2009 09:30:05 -0000 @@ -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(& ); // 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')), - ); - } - - // Allow modules to provide translations for specific links. - drupal_alter('translation_link', $links, $path); + $links = language_negotiation_get_switch_links($type, $path); - $block['subject'] = t('Languages'); - $block['content'] = theme('links', array('links' => $links, 'attributes' => array())); - return $block; + 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; + } } } Index: modules/locale/locale.test =================================================================== RCS file: /cvs/drupal/drupal/modules/locale/locale.test,v retrieving revision 1.41 diff -u -p -r1.41 locale.test --- modules/locale/locale.test 28 Sep 2009 22:14:30 -0000 1.41 +++ modules/locale/locale.test 9 Oct 2009 09:30:05 -0000 @@ -910,11 +910,11 @@ class LocaleUninstallFunctionalTest exte /** * 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 exte // 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 exte // 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 exte // 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 exte $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 LocaleUninstallFrenchFunctionalTes 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 ex 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 ex $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 D $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 extend ); $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 extend /** * 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 extend * 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 extend * 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 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 ); $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 } // 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 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']); Index: modules/node/node.module =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.module,v retrieving revision 1.1143 diff -u -p -r1.1143 node.module --- modules/node/node.module 9 Oct 2009 07:28:53 -0000 1.1143 +++ modules/node/node.module 9 Oct 2009 09:30:05 -0000 @@ -1138,6 +1138,52 @@ function node_build_content($node, $buil } /** + * 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. * * @param $node Index: modules/path/path.test =================================================================== RCS file: /cvs/drupal/drupal/modules/path/path.test,v retrieving revision 1.20 diff -u -p -r1.20 path.test --- modules/path/path.test 28 Aug 2009 14:40:12 -0000 1.20 +++ modules/path/path.test 9 Oct 2009 09:30:05 -0000 @@ -183,7 +183,9 @@ class PathLanguageTestCase extends Drupa 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(); Index: modules/translation/translation.module =================================================================== RCS file: /cvs/drupal/drupal/modules/translation/translation.module,v retrieving revision 1.60 diff -u -p -r1.60 translation.module --- modules/translation/translation.module 10 Sep 2009 06:32:54 -0000 1.60 +++ modules/translation/translation.module 9 Oct 2009 09:30:05 -0000 @@ -170,23 +170,17 @@ function translation_form_alter(&$form, */ 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_translatio } /** - * 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.