Drupal 8 multilingual tidbits 16: configuration translation development

We learned how configuration translation works on the conceptual level and through the Drupal 8 built-in user interfaces in the previous article of the series. In this article, we'll cover how can developers integrate with configuration translation.

Explaining the structure of configuration

We used your main site configuration file as an example in the previous article. Let's review that again (system.site.yml):

uuid: ''
name: Drupal
mail: ''
slogan: ''
page:
  403: ''
  404: ''
  front: user
admin_compact_mode: false
weight_select_max: 100
langcode: en

There are clearly some translatable elements here. Which ones? Well, at least the site name and slogan would be. How would Drupal know though? There is nothing in this file to tell Drupal about that. There is also no code in handling this configuration that needs to deal with that. We wanted to introduce language support in the most transparent way. Instead Drupal supports a static description format to describe the structure of configuration, that we call configuration schema.

The corresponding configuration schema snippet for this configuration is as follows:

system.site:
  type: mapping
  label: 'Site information'
  mapping:
    uuid:
      type: string
      label: 'Site UUID'
    name:
      type: label
      label: 'Site name'
    mail:
      type: email
      label: 'E-mail address'
    slogan:
      type: label
      label: 'Slogan'
    page:
      type: mapping
      label: 'Pages'
      mapping:
        403:
          type: path
          label: 'Default 403 (access denied) page'
        404:
          type: path
          label: 'Default 404 (not found) page'
        front:
          type: path
          label: 'Default front page'
    admin_compact_mode:
      type: boolean
      label: 'Compact mode'
    weight_select_max:
      type: integer
      label: 'Weight element maximum value'
    langcode:
      type: string
      label: 'Default language'

While that looks like a bit too much, it is pretty simple if you look at the pieces. The top level defines the file as a mapping of key-value pairs and then each key is defined with their type and label. The site name and slogan are both defined as using the label type which is defined as translatable. The other elements use string, path, boolean, integer, etc. types. The pages key is in fact defined as a mapping in itself that has three nested keys.

Applying this schema to your configuration, we can tell that the name and slogan keys are translatable and can deal with that for configuration translation. Why have such a complete description of the configuration if we only use a little portion of the information? Well, configuration schema is not only used for translation. For example, when you edit configuration through forms and save the changes, the same schema is used to type-cast values to the right types. That ensures that although forms on the web are all strings, we still end up with the right boolean, integer, etc. values in the configuration file. This is really valuable when it comes to deploying configuration changes, since type changes are not going to happen accidentally. Finally, its not just the types used from the schema. The core configuration translation module in fact generates forms dynamically based on the schema to offer translation forms.

This means that although configuration schemas are optional, if you implement them, you not only integrate with the configuration translation backend, provide all the necessary elements for configuration translation frontends to be generated but even play nice with the deployment system. That is a lot of value for some static YAML files.

Configuration schema can describe dynamic structures as well, for example it is able to describe field settings which may be type specific or views which contain arbitrary plugins nested within each other. Drupal.org has some great documentation on configuration schema. There is also the Configuration inspector module which helps to debug schema as applied to configuration.

The final question is why is it a static format, why is this not encoded within the PHP code handling configuration? First its used for so many different things that it needed its own definition. Second, it is also going to be used on localize.drupal.org to read default configuration in projects and make translatable default configuration elements available for community translation. That requires that no code should be necessary to run from your project to be able to tell what parts are translatable.

Integrating with the configuration translation user interface

As I explained in the introduction to content and configuration configuration now covers both global settings like site information (as shown above) and configuration entities, where multiple instances of configuration may be present. Examples of those are input formats, user roles, views, menus, fields, and so on. Integration of either of these with the configuration translation system starts with writing your configuration schema. Providing the schema itself is not enough though, the system needs to be able to integrate the translation tab and generate the form for you at the right path.

Global configuration

If you are providing your own global configuration, then you need to tell the configuration translation system which (administration) pages belong to your configuration. Provide those in a <em>modulename</em>.config_translation.yml file. For example, system module provides an item for the site level settings as follows:

system.site_information_settings:
  title: 'System information'
  base_route_name: system.site_information_settings
  names:
    - system.site

In this file, the key is by convention the same as the base route name, the route belongs to the page where this configuration is edited. (Routing is a new distinct subsystem to map paths to controllers in Drupal 8 that we are not delving into here). Finally, it lists all the configuration keys that are edited on that page. This is enough for configuration translation module to be able to attach a new translation tab to that page and generate all the rest of the pages from there, the translation overview, the translation forms, saving of translations, and so on.

It is worth reiterating, the only thing needed to make your global configuration translatable is to describe its structure with schemas and map its configuration key to a page, so the module can do the rest of the job itself. You only need to add some static YAML files to your module, no code to be written. I think its hard if not impossible to make this easier.

Configuration entities

The situation for configuration entities is just slightly different. You define configuration entities with an annotated class (which we are also not going to delve into here). You are very likely going to provide an edit-form route for your configuration entity type, which is the only thing needed for configuration translation to integrate with the user interface of your configuration entity. An example could be the contact category configuration entity:

<?php
/**
* Defines the contact category entity.
*
* @ConfigEntityType(
*   id = "contact_category",
*   label = @Translation("Contact category"),
*   [...]
*   links = {
*     "edit-form" = "contact.category_edit"
*   }
* )
*/
class Category extends ConfigEntityBundleBase implements CategoryInterface {
}
?>

This ensures the configuration translation module can attach the tab and add its overview and form on the right pages. See the relevant code in the module file itself. Once again all the configuration translation management is done transparently, no form or tab or path or anything else needs to be defined other then what you would do anyway for the configuration entity. In this case, you don't even need to ship with a code>modulename.config_translation.yml file.

Structure and placement of translations

As explained in the previous article, translations are not represented by storing the entire configuration file in a different language but instead only the translated pieces. When you export your configuration, translations are located in subdirectories per language under a language directory in the export, so for site settings in three languages with Hungarian and Spanish as translations, the following files would appear on export:

system.site.yml
language/es/system.site.yml
language/hu/system.site.yml

Both translation files would only contain the name and slogan keys and no other information, because those were the only ones set up for translation. The Spanish file may contain:

name: 'Noticias de última hora'
slogan: 'Información fresca'

It is also possible to ship default configuration translation with your project. Just place your translation files the same way into your modulename/config/install directory in subdirectories. This may be a way to ship some translations with custom projects you built for a client. Note that shipping with translations is absolutely discouraged for drupal.org projects, because for those, translation is happening on localize.drupal.org and the Interface translation module downloads and manages those translations already, giving control to translators.

Accessing translated configuration

Drupal by default always uses the language selected for the page to load configuration with. So if you are viewing a Spanish page, all configuration is loaded with Spanish translations merged in. If you are viewing the same page in Hungarian, the same code will now receive Hungarian translated configuration. This means normally you don't need to do anything special to access configuration in the language needed.

However, there are cases, when you want to load the original copy of the configuration or ask for a specific language. Such as when sending emails to users, you will need configuration values in the right language. The following code is a slightly adapted excerpt from user_mail() to illustrate loading configuration in the preferred language of $account to compose an email:

<?php
  $language_manager
= \Drupal::languageManager();
 
$language = $language_manager->getLanguage($account->getPreferredLangcode());
 
$original_language = $language_manager->getConfigOverrideLanguage();
 
$language_manager->setConfigOverrideLanguage($language);
 
$mail_config = \Drupal::config('user.mail');

 

// Compose email.

 

$language_manager->setConfigOverrideLanguage($original_language);
?>

Note that you are settings values on the language manager and not the configuration system directly. The configuration system is override-agnostic and cansupport overrides of different kinds with various conditions. It is the job of the overrides to manage their conditions, in this case to allow changing the language used. The same pattern can be used to load configuration entities in specific languages as well.

Just loading the original copy of the configuration without any overrides (include skipping non-language overrides) is a bit simpler:

<?php
  $config_factory
= \Drupal::configFactory();
 
$old_state = $config_factory->getOverrideState();
 
$config_factory->setOverrideState(FALSE);

 

// Load configuration without overrides.

 

$config_actory->setOverrideState($old_state);
?>

In this case, we directly tell the configuration system to skip all overrides, instead of needing to know about the kinds of overrides that may be in effect. Overrides are not only used for language but also for global value enforcement and may be dependent on group, domain or any other conditions. While these code examples don't look very trivial, good news is you very rarely need to use these patterns. Drupal attempts to do its best to make good assumptions for you in the right environment. For example admin pages set up to edit configuration entities get the entity loaded without overrides.

Read more about the override system in the drupal.org documentation.

Issues to work on

  • We still need to make localize.drupal.org expose default configuration for translation. That is unfortunately a non-trivial problem, but we need to solve before translators are asked to translate for the release. See and help at https://drupal.org/node/1933988
  • You may have noticed overrides can entirely be disabled by code. The enforced application of global overrides especially when important for security is still debated in https://drupal.org/node/1934152.
  • The configuration schema system is in theory extensible, but not all applications are currently implemented in a generic enough way. See https://drupal.org/node/1928868 for making it return to and fully embrace its original Typed data roots.
  • While schemas are not required, Drupal core attempts to provide complete schemas for all its module settings. There are still some small gaps and bugs, see https://drupal.org/project/issues/search?projects=&project_issue_followe...

Tags: 

Comments

Add new comment

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • To post pieces of code, surround them with <code>...</code> tags. For PHP code, you can use <?php ... ?>, which will also colour it based on syntax.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.