Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Translating strings

Drupal can show a site in several languages. To make module texts translatable, they are not written as plain strings but passed through the translation function. It looks up the translation for the current language and returns it, or the source text if there is none.

The t function

Classes that extend ControllerBase, FormBase and BlockBase have the $this->t() method:

// web/modules/custom/blog/src/Controller/BlogController.php
public function about(): array
{
    return [
        '#markup' => $this->t('A blog about Drupal development.'),
    ];
}

The /blog/about page looks the same as before, but this string now shows up in the list for translation. Functions in the blog.module file use the t() function without $this.

Placeholders

Changing parts are not glued into the string but passed as the second argument:

// web/modules/custom/blog/src/Controller/BlogController.php
$text = $this->t('Author: @name, articles: @count', [
    '@name' => $authorName,
    '@count' => 12,
]);

The translator gets the string with the placeholders and can change the word order. The kind of placeholder decides how the value is treated.

PlaceholderWhat happens to the value
@nameIt is escaped
%nameIt is escaped and shown in italics
:urlIt is escaped and checked as an address — for the href attribute

Plurals

// web/modules/custom/blog/src/Controller/BlogController.php
$text = $this->formatPlural($count, '1 article', '@count articles');

The first argument is the number, the second the form for one, the third the form for many. For languages with several plural forms the translator enters all of them in the translation interface.

Strings outside methods

In plugin attributes and other places where $this->t() is not available, the object is created directly:

// web/modules/custom/blog/src/Plugin/Block/LatestArticlesBlock.php
use Drupal\Core\StringTranslation\TranslatableMarkup;
 
$label = new TranslatableMarkup('Latest articles');

t() returns the same kind of TranslatableMarkup object. The translation is not done at once but when the object is turned into a string on output.

In Twig templates

{# web/modules/custom/blog/templates/blog-card.html.twig #}
<a href="{{ path('blog.list') }}">{{ 'All articles'|t }}</a>
 
<p>{{ 'Author: @name'|t({'@name': author}) }}</p>

The t filter does the same as the function in PHP. Placeholders are passed in the parentheses.

In YAML files

The title and description values in blog.routing.yml, blog.links.menu.yml, blog.permissions.yml and name in blog.info.yml are translated automatically — there is no need to wrap them.

Where translations are entered

Enable the core modules Language and Interface Translation and add a language at Configuration → Region and language → Languages. Module strings are translated on the User interface translation page, the address /admin/config/regional/translate. A string appears in the list after it has been output on the site at least once.

Source strings are written in English: Drupal treats English as the language of code, and other languages are added as translations.

Common mistake

Do not pass a variable or a concatenated string to t(): t($message) or t('Author: ' . $name). String extraction tools read the code without running it and do not find such text, and every name produces a separate untranslated string. Pass only a constant string to t() and the changing parts as placeholders.

Thanks for reading!

Donate to the site — we work hard for you.