Drupal 8 — How to translate the Config API
Tags: php, drupal, i18n
Published: 2020-06-26 07:02:39

Before getting into the main subject, be aware that this article has been first write for the Blog of Antistatique — Web Agency in Lausanne, Switzerland. A place where I work as Full Stack Web Developer.

Feel free to read it here or check it out there: https://antistatique.net/en/we/blog/2018/05/01/drupal-8-how-to-translate-the-config-api

Retrieve all the code on Gist: https://gist.github.com/WengerK/5c7f3df9aa5c27a95cd8a29b72b28a19

**Happy reading.**

<hr>

As you probably already know, I live in Switzerland and - beside chocolate & cheese - this country has one another singularity: 4 official national languages. As result, more than 90% of our web creations need to be **localized in at least 2 languages**.   
This peculiarity makes us **experts in the creation of multilingual websites**.

When creating applications using Drupal 8, there are many cases when the **configuration forms containing data have to be managed by the client**. The most concrete case is - for example - all the links of the social networks must be different according to the language.

I will explain here the whole process to enable configuration translation with a `ConfigFormBase` form.
I will go through the [creation of the structure](#config-install) and the [schema generation](#config-schema) as well as the [activation of the translation system](#config-trans).

## Prerequisites

> Every solution to every problem is simple. It's the distance between the two where the mystery lies. <br/>
> — Derek Landy, Skulduggery Pleasant

If you do not know the Drupal 8 Config API system yet, check it out [Drupal 8 - Differences between Configuration API & State API](https://antistatique.net/en/we/blog/2016/06/14/drupal-8-differences-between-configuration-api-state-api). You may also find more help directly on the [official documentation](https://www.drupal.org/docs/8/api/configuration-api).

<div class="alert alert-info text-white">This article is for experienced Drupal 8 developers.</div>

## Assets

> Give me matter and I will build a world out of it. <br/>
> — Immanuel Kant


Before explaining how it all works, you can download all the sample files below.

This article example is testable via the [downloadable test module here](https://antistatique.net/sites/default/files/my_module.zip).

To help you understand this article, **2 translatable configuration forms** will be created in our example:

1. The first one is a simple form demonstrating the translation system of several types of fields:
  - Text - _translatable_
  - Select - _untranslatable_
  - Checkbox - _untranslatable_
  - Checkboxes - _untranslatable_
  - Radios - _untranslatable_
  - Textarea - _translatable_
  - CKeditor - _translatable_

<script src="https://gist.github.com/WengerK/5c7f3df9aa5c27a95cd8a29b72b28a19.js?file=SettingsForm.php" type="text/javascript"></script>

2. The second is a complex form, demonstrating the translation system via mapping and structure in sequence:
  - Nested fields with multiple children - _translatable_
  - Entity reference field - _untranslatable_
  - Textarea with preprocess in sequence (then stored as array) - _translatable_

<script src="https://gist.github.com/WengerK/5c7f3df9aa5c27a95cd8a29b72b28a19.js?file=SettingsAdvancedForm.php" type="text/javascript"></script>

As you can notice, several fields are "untranslatable". It's not a deliberate choice, but the Core Drupal requires this, and that's normal! Fields that are not translatable are fields called "key" -> "value". It is therefore normal to be unable to translate keys since they **have their own Business Logic**. As for the values, they are translatable via the [standard translation function exposed by the Core](https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21StringTranslation%21StringTranslationTrait.php/trait/StringTranslationTrait).

## Setup default values

We have 2 files because we have 2 different configurations.

- **SettingsForm** -> `my_module.settings.yml`
- **SettingsAdvancedForm** -> `my_module.settings_advanced.yml`

These structure files are always found in the `config/install` folder of the module.
They correspond to the **initialization of the configuration**, ie the default values. These files are not mandatory by default in Drupal 8, but are required once we want to make our configuration translatable.

These files must be named in a very specific way, depending on the name of the corresponding configuration. You can find this name **in the array of the `getEditableConfigNames`** method of your configuration forms.

Let's see what the definition of the structure of our 2 forms looks like:

**SettingsForm**

<script src="https://gist.github.com/WengerK/5c7f3df9aa5c27a95cd8a29b72b28a19.js?file=my_module.settings.yml" type="text/javascript"></script>

**SettingsAdvancedForm**

<script src="https://gist.github.com/WengerK/5c7f3df9aa5c27a95cd8a29b72b28a19.js?file=my_module.settings_advanced.yml" type="text/javascript"></script>

Did you notice **both content files correspond to what we save in database**?! All the fields saved in the database must be there.
In addition, the structure of your configuration is also represented in this file.

**SettingsForm**

    title: ''                # simple text field
    select: ''               # select field
    checkbox: FALSE          # boolean checkbox
    checkboxes: { }          # array of checkboxes
    radios: ''               # the value selected from radios
    message: ''              # content of textarea
    ckeditor:
      value: ''              # ckeditor content
      format: 'basic_html'   # ckeditor format

**SettingsAdvancedForm**

    site:                   # array containing our data
      title: ''             # simple text field (nested)
      content:
        value: ''           # ckeditor content
        format: basic_html  # ckeditor format
    mails: { }              # Values, formated as array, transformed from the textarea
    entity_reference: ''    # entity reference field

## Setup the schema

<div id="config-schema"></div>

The schema is **a single file**, regardless of how many configurations your module depends on.
This file contains the tree structure corresponding to the previous step, but this time we don't add the default value to each key, but its type and a label.

![Cheatsheet formats and types usable in a Drupal schema.yml 8.](https://dev-to-uploads.s3.amazonaws.com/i/hy5e9qm5mcbzq5wdokpx.png)
</a>
<figcaption>Cheatsheet formats and types usable in a Drupal schema.yml 8.</figcaption>

Thanks to the schema configuration, we can declare which key is translatable and which is its Label in the interface of translation for the configuration!

Are you wondering why we need to provide a complete schema when we do not use all the keys for translation? Well **the schema is not used only for translations** !
For example, when you edit a configuration, the forms will use the schema to "type-cast" the data, which ensures that a field defined as a boolean in your schema is a boolean and not a string when it's manipulated.
In addition, this file is used for the **automatic generation of the translation form**, hence the need to add a Label to each key.

The schema must be created directly in the `config/schema` folder and must be named following the module name:` my_module.schema.yml`.

Here is what the schema file of our 2 forms looks like:

<script src="https://gist.github.com/WengerK/5c7f3df9aa5c27a95cd8a29b72b28a19.js?file=my_module.schema.yml" type="text/javascript"></script>

In short, the schema system, just like the structure file, is **optional** as long as you do not need to translate your configuration.

## Activate the translation

<div id="config-trans"></div>

Once you have created your structure file and schema file, you just have to declare to the translation system which forms expose the translations.

You just need to create the `my_module.config_translation.yml` file.

<script src="https://gist.github.com/WengerK/5c7f3df9aa5c27a95cd8a29b72b28a19.js?file=my_module.config_translation.yml" type="text/javascript"></script>

This file contains **all the configuration exposed by the translation form**.
Each form must define a title, the path to access the form and finally all the configuration keys that it exposes.

You can now access the forms from the menu:
Configuration > Regional and language > Configuration translation.

## Add a translation tab

Now that your form is translatable, it is sometimes disconcerting to access it throught the "Configuration translation" page, hidden behind 3 levels of links. It would be much more convenient to access this page from a **"Local task"**, exactly like the other pages and settings that Core Drupal 8 offers.

![Local task - Basic Site Translation](https://dev-to-uploads.s3.amazonaws.com/i/6107gnkhzbcf1v0jt8q8.png)
<figcaption>Local task - Basic Site Translation </figcaption>

By default, the translation system will generate a "Local task" - or a tab - for the translations of your form, but you may not see it, because to see this action you need to have at least 1 "Local task". I admit it's a little far-fetched, but here's a workaround.

1. Create a new "Local task" in your module using the `my_module.links.task.yml` file at the root of your module.
2. Add an entry for each configuration form exposing translations.

<script src="https://gist.github.com/WengerK/5c7f3df9aa5c27a95cd8a29b72b28a19.js?file=my_module.links.task.yml" type="text/javascript"></script>

<hr>

## Sources

For the most curious, here are some sources of additional information that inspired the creation of this article.

**Gábor Hojtsy** (29 october 2015). Configuration translation development.
Retrieved from [hojtsy.hu](http://hojtsy.hu/blog/2014-may-26/drupal-8-multilingual-tidbits-16-configuration-translation-development)

**Alex Tkachev** (16 décembre 2014). Drupal 8 configuration management.
Retrieved from [amazeelabs.com](https://www.amazeelabs.com/en/blog/drupal8-configuration-management-part1)