Drupal 8 multilingual tidbits 16: configuration translation development

Up to date as of October 29th, 2015.

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: ''
mail: ''
slogan: ''
  403: ''
  404: ''
  front: /user/login
admin_compact_mode: false
weight_select_max: 100
langcode: en
default_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 snippets for this configuration are as follows:

# Snippet from core.data_types.schema.yml
  type: mapping
      type: string
      label: 'Language code'

# Snippet from system.schema.yml
  type: config_object
  label: 'Site information'
      type: string
      label: 'Site UUID'
      type: label
      label: 'Site name'
      type: email
      label: 'Email address'
      type: label
      label: 'Slogan'
      type: mapping
      label: 'Pages'
          type: path
          label: 'Default 403 (access denied) page'
          type: path
          label: 'Default 404 (not found) page'
          type: path
          label: 'Default front page'
      type: boolean
      label: 'Compact mode'
      type: integer
      label: 'Weight element maximum value'
      type: string
      label: 'Site default language code'
      type: string
      label: 'Notification email address'

While that looks like a bit too much, it is pretty simple if you look at the pieces. The first snippet is from the core data types definition to define a base type for configuration objects. These are defined as mappings with a default langcode key. We covered in the previous article that this is a requirement of the configuration system and we track language on each file this way.

Then the actual site information entry defines the file as a configuration object with additional key-value pairs defining each key with their type and label. The site name and slogan are both defined using the label type which is defined as translatable among the core data types. The other elements use string, path, boolean, integer, etc. types. The page 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 string based, 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, it is 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. I created a fancy cheat sheet and 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 it is used for so many different things that it needed its own definition. Second, it is also 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 MODULENAME.config_translation.yml file. For example, system module provides an item for the site level settings in system.config_translation.yml as follows:

  title: 'System information'
  base_route_name: system.site_information_settings
    - 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 distinct subsystem to map paths to controllers in Drupal 8 that we are not diving 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 core 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 dive 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 form configuration entity:

 * Defines the contact form entity.
 * @ConfigEntityType(
 *   id = "contact_form",
 *   label = @Translation("Contact form"),
 *   [...]
 *   links = {
 *     "delete-form" = "/admin/structure/contact/manage/{contact_form}/delete",
 *     "edit-form" = "/admin/structure/contact/manage/{contact_form}",
 *     "collection" = "/admin/structure/contact",
 *     "canonical" = "/contact/{contact_form}",
 *   }
 * )
class ContactForm extends ConfigEntityBundleBase implements ContactFormInterface {

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 config_translation_entity_type_alter(). 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 multiple copies. Rather, the translation of installed configuration from projects is maintained within active configuration (if the site is not English) and translations are maintained as overrides. When you export your configuration, translation overrides are located in subdirectories per language under a language directory in the export, so for site settings in three languages, originally French with Hungarian and Spanish as translations, the following files would appear on export:


The system.site.yml would contain French settings (with all the setting values as explained above), while 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 (integrating the translation into the active configuration instead of overrides based on the site configuration), so your way of shipping overrides would not necessarily work.

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:

= \Drupal::languageManager();
$language = $language_manager->getLanguage($account->getPreferredLangcode());
$original_language = $language_manager->getConfigOverrideLanguage();
$mail_config = \Drupal::config('user.mail');
// ...
  // ...Compose (and send) email here...
  // ...

Note that you are setting values on the language manager and not the configuration system directly. The configuration system is override-agnostic and can support 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:

= \Drupal::config('system.site');
$config_without_overrides = \Drupal::configFactory()->getEditable('system.site');

This code example points at another clear difference. While the configuration system applies overrides appropriate for the current context when you get configuration, that means that saving back to that may lead to overrides ending up in your original configuration. That could lead to various problems including security concerns. Therefore only configuration accessed as editable (which does not have overrides) supports modifying values. If you try to modify values on non-editable configuration, you will immediately get an exception.

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 in an editable form without overrides.

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

Issues to work on

  • PARTLY DONE: 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
  • DONE! 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.
  • DONE! 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.
  • DONE! 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...



Add new comment