Language Negotiation API

Last updated on
22 July 2017

It is possible to customize the language negotiation process both for how we detect the user's language and also on what type of data they are requesting.

The language negotiation API is based on two major concepts:

  • Language types, which describe the possible types of translatable content
  • Language providers, which allow Drupal to detect which language it should serve to the user (note that language providers are called language methods in Drupal 8)

Defaults provided by Drupal

Language Types

Drupal core defines three built-in language types:

Interface language
It is the page's main language. It is used to present translated user interface elements such as titles, labels, help text, and messages.
Content language
This is used to choose in which language to display content that is available in more than one language (see the multilingual capabilities of the new Field API for details).
URL language
This is the language associated with URLs. When generating a URL, this value will be used by url() as a default if no explicit preference is provided.

Different language types often share the same values, but then they can have independent values if needed.

Language Providers

Core includes the following providers:

  • URL - Determine the language from the URL (path prefix or domain).
  • Session - Determine the language from a request/session parameter.
  • User - Follow the user's language preference.
  • Browser - Determine the language from the browser's language settings.
  • Default language - Use the default site language.

Here is an image which shows the relationship between types and providers:

Drupal language negotiation - possible tracks

Defining language types

Starting in Drupal 7, the language API allows contributed modules to define additional language types through hook_language_types_info() and alter existing language type definitions through hook_language_types_info_alter().

A language type may be configurable or fixed. A configurable language type appears in the Configuration > Regional and language > Languages > Detection and selection page, where the language providers for that language type can be configured. There are also fixed language types that have predetermined (module-defined) negotiation settings and, thus, do not appear in the configuration page. Here is a code snippet that makes the content language (which by default inherits the interface language's values) configurable:

<?php
/**
 * Implements hook_language_types_info_alter().
 */
function language_negotiation_language_types_info_alter(&$language_types) {
  $language_types[\Drupal\Core\Language\LanguageInterface::TYPE_CONTENT]['locked'] = FALSE;
}
?>

Every configurable language type will have its own (independent) language switcher block. Obviously, if two language types are configured the same way, their language switcher blocks will be functionally identical and will act on both language types.

In Drupal 6.x, there is only one language type, named just language. During language initialization the selected language negotiation settings are used to determine its value. In Drupal 7.x, the same process happens for each defined language type, see drupal_language_initialize() for details.

Defining custom language providers

Every language type can have different language negotiation settings, i.e. every language type can have a different set of language detection methods, or providers, assigned to it.

Language providers are simple callback functions that implement a particular logic to return a language code. For instance, the locale_language_from_url() searches for a valid path prefix or domain name in the current request URL. If a language provider does not return a valid language code, the next provider associated to the language type (based on provider weight) is invoked. This way the concept of fallback is generalized and untied from the fixed path prefix > user preference > browser settings > default language scheme used in Drupal 6.x.

Also language providers are module-definable through hook_language_negotiation_info() and language providers definitions can be altered through hook_language_negotiation_info_alter(). Here is an example snippet that lets path prefixes be ignored for administrative paths:

<?php
/**
 * Implements hook_language_negotiation_info_alter().
 */
function language_negotiation_language_negotiation_info_alter(&$providers) {
  $providers[LOCALE_LANGUAGE_NEGOTIATION_URL]['callbacks']['language']
= 'language_negotiation_from_url';
  $providers[LOCALE_LANGUAGE_NEGOTIATION_URL]['file'] =
drupal_get_path('module', 'language_negotiation') .
'/language_negotiation.module';
}

/**
 * Identify language via URL prefix or domain excluding administrative paths.
 */
function language_negotiation_from_url($languages) {
  // Use the core URL language provider to get a valid language code.
  require DRUPAL_ROOT . '/includes/locale.inc';
  $langcode = locale_language_from_url($languages);

  // If we have an administrative path just return the default language.
  if (isset($_GET['q']) && strtok($_GET['q'], '/') == 'admin') {
    return language_default()->language;
  }

  return $langcode;
}
?>

In order for the above changes to be executed, the "Language detection and selection" form needs to be submitted.

Language provider definitions may include two more callbacks besides the language provider itself:

  • If the language provider can take advantage of a language switcher block, the switcher callback will allow it to return the language switch links that suit its logic, see locale_language_switcher_url() for an example.
  • If the language provider needs to rewrite URLs, it can specify an url_rewrite callback which will provide the rewriting logic.