This is the fifth post in a series about coding standards. In this post we’ll talk about how to use translation functions in both Drupal 7 and 8. This is so essential that it deserves its own post!
Other posts in this series:
- Drupal Code Standards: What Are They?
- Drupal Code Standards: How Do We Implement Them?
- Drupal Code Standards: Formatting
- Drupal Code Standards: Documentation
- Drupal Code Standards: The t() function
- Drupal Code Standards: Object Oriented Coding & Drupal 8
- Drupal Code Standards: Twig in Drupal 8
What is the t() function?
User interface text that is written in code must be prepared in a special way in order for the interface text to be identified and translated properly. Drupal 8 uses the Translation API whereas Drupal 7 and earlier versions use the Localization API
In procedural code, the t()
function allows for localization and translates a given string to a given language at run-time. When coding, you wrap your user-facing strings in this function so that they can be translated. While this function is still available in Drupal 8, with the switch to a services architecture, it is now best practice to use the TranslationManager
class (the string_translation
service). The StringTranslationTrait
is also available to simplify the inclusion of the t()
function and other translation methods in classes.
Often times the t()
and formatPlural()
methods are often already available when extending common Drupal base classes for controllers and plugins, reducing the need to add traits or inject the service in custom classes. Additional details can be found in the Translation API documentation.
When do I use it?
In just about every user-facing string. This ensures that your site can be localized! When in doubt, translate everything. You might be thinking "oh, this string will never need to be translated," but two years later, when you’re trying to find this string that’s showing up untranslated on the site, and your stakeholders want it changed, you’ll be much happier if it’s already ready to translate. I may be speaking from experience here.
What does it do?
Firstly, t()
translates text at runtime if you have more than one language enabled. Depending on which placeholder you use, it runs different sanitization functions.
The t()
function takes three parameters, two of which are optional. The first is the string to be translated, the second is the array of replacements, if any. The third is an array of options.
From Drupal.org:
$options: An associative array of additional options, with the following elements:
* 'langcode' (defaults to the current language): The language code to translate to a language other than what is used to display the page.
* 'context' (defaults to the empty context): A string giving the context that the source string belongs to.
String context (or translation context) is a way to organize translations when words have one to many translations.
From the handbook page:
What is string context?
When translating Drupal’s user interface to other languages, each original (English) string can have only one translation. This is a problem when one English word has several meanings, like "Order", which can mean the order of elements in a list, to order something in a shop, or an order someone has placed in a shop. For many languages, the string "Order" needs a different translation for each of these meanings.
You can read more about string context Drupal.org’s String Context documentation.
Using Placeholders
You use a placeholder to insert a value into the translated text, like in this example from the Advanced Forum contrib module: Placeholders come from the placeholderFormat
method, which is called during the translation render()
method.
The most common placeholder is probably @variable
. This placeholder runs static::placeholderEscape()
on the text before replacing it.
$block->title = t(
'Most active poster in @forum', array('@forum' => $forum->name)
);
You may also use %variable
, which runs placeholderEscape()
on the text similar to @
placeholders. Using this placeholder will also create <em>
tags around the placeholder values.
In Drupal 7, !variable
inserts your value exactly as-is, without running any sanitization functions, so you would never want to use this on user-entered text that has not been sanitized. Only use this on text that has already been properly escaped.
In Drupal 8, !variable
is deprecated, as there is no longer a placeholder for unsanitized text.
New in Drupal 8 is :variable
, for use specifically with URLs. The :variable
placeholder is escaped with \Drupal\Component\Utility\Html::escape()
and filtered for dangerous protocols using UrlHelper::stripDangerousProtocols()
.
When shouldn’t I use it?
In Drupal 7, there are some instances where t()
is not available.
During the installation phase, t()
isn’t available, so you must use get_t()
. You can do something like this:
$t = get_t();
$t(‘my string’);
Translation is also not used inside of hook_schema()
or hook_menu()
.
In Drupal 8, t()
is always available, so you can always use it.
Additionally, never pass a variable through t()
directly - only string literals. The short explanation for this is that the string to be translated needs to be available at runtime, and a variable may not be available and may change its value. You can find an in-depth explanation in this StackExchange question.
t() and Links
There are many times when you may want to translate the text in a link. There are many ways to do this, and most of them aren’t great. Here are some bad examples, and a good (and simple!) one.
Bad:
$do_not_do_this = t('Do not ')."<a href="api.drupal.org">" . t('link ') . "</a>" .t('to something like this.');
$bad = t('This is not a good way to make a @link.', array('@link' => '<a href="https://api.drupal.org">'. t('link') .'</a>'));
$dreadful = t('This is a dreadful way to make a link pointing to the <a href="https://api.drupal.org">Drupal API t() documentation</a>.');
$awful = t('This may seem good, but it’s an awful way to link to this @doc.', array('@doc => l(t(‘documentation'), 'https://api.drupal.org'));
Good:
$good = t('Read about the t() function <a href=":api">here</a>', array(':api' => 'https://api.drupal.org'));
Here is an example from Drupal 8 Core, in the function install_check_translations()
in install.core.inc
:
if (!$readable) {
$requirements['translations directory readable'] = array(
'title' => t('Translations directory'),
'value' => t('The translations directory is not readable.'),
'severity' => REQUIREMENT_ERROR,
'description' => t('The installer requires read permissions to %translations_directory at all times. The <a href=":handbook_url">webhosting issues</a> documentation section offers help on this and other topics.', array('%translations_directory' => $translations_directory, ':handbook_url' => 'https://www.drupal.org/server-permissions')),
);
}
In this code you can see that the <a href>
tags are inside the t()
function, and the URL is escaped using the :variable
placeholder.
It’s okay to put a little HTML in your t()
function to simplify like this. The element can easily be moved around if the translation requires it, without needing to know any code other than which word is being linked. Our next section will talk more about keeping your text translatable.
Translation Best Practices
Writing your code and content to be translatable isn’t just a best practice, it may very well be used to actually translate your site, so sometimes you need to think from the point of view of a translator. Try not to abstract out pieces of content too much. Here’s an example. In English, you may have a blog title "Bob’s Homepage." You could want to abstract that into the following:
$username . "‘s " . t(‘Homepage.’);
What’s the problem here? In other languages, this phrase may be re-arranged. For example, in French or Spanish, it would be "Homepage de Bob." The above example would require a translator to change code. We don’t want that. So we write this:
t(‘@user’s Homepage.’, array(‘@username’ => ‘Bob’));
Now, this string can easily be changed to:
t(‘Homepage de @user.’, array(‘@username’ => ‘Bob’));
Concatenation Dos and Don’ts
In the example in the previous section, we showed where concatenating a translated string with another string can make trouble. There are some other things you want to avoid.
Don’t concatenate strings within t()
. For example, don’t do this:
t(‘Don’t try to join’ . ‘ ‘ . @num . ‘ ‘ . ‘strings.’, array(‘@num’ => ‘multiple’));
Even if you think you have to, there is a better way.
And don’t concatenate t()
strings and variables - you don’t need to!
Don’t do this:
t(‘This is a complicated way to join ’) . $mystring . t(‘ and translated strings’);
Additionally, the above would give you a codesniffer error because you should not have leading or trailing whitespace in a translatable string.
Do this:
t(‘This is a simple way to join @mystring and translated strings’, array(‘@mystring’ => ‘whatever my string is’));
This is how the t()
function is designed to be used! Going around it defeats the purpose of using it at all.
Drupal 8 & Twig
In Drupal 8, the essential function and its use are the same. Wrap text in your module code in t(‘’)
, with the same optional placeholder and options arrays. As noted in the placeholders section, !variable
has been deprecated and replaced with :variable
.
With Drupal 8, we have the introduction of the Twig templating engine, and with that, new ways format our text for translation.
The simplest way is to pipe your text through |t
. Here’s an example from the Devel contrib module:
<thead>
<tr>
<th>___TWIG0___</th>
<th>___TWIG1___</th>
<th>___TWIG2___</th>
</tr>
</thead>
In the above code, we can see the text in the table headers piped into the translation function, just as it would be passed through t()
in Drupal 7. You can also use |trans
interchangeably with |t
. You can use a {% trans %}
block to translate a larger chunk of text or use placeholders. These blocks can also handle logic for plurals. Here’s an example from Drupal 8 Core:
<h3 class="views-ui-view-title" data-drupal-selector="views-table-filter-text-source">___TWIG0___</h3>
<div class="views-ui-view-displays">
___TWIG1___
___TWIG2___
Display
___TWIG3___
Displays
___TWIG4___:
<em>___TWIG5___</em>
___TWIG6___
___TWIG7___
___TWIG8___
</div>
Here we can see that the template appropriately shows the translated text for “Display” or the plural, “Displays.”
Values are escaped by default. The
passthrough
filter can be used to skip escaping. Theplaceholder
filter can be used to form a placeholder. The default behavior is equivalent to@
int()
, while 'passthrough' matches!
and 'placeholder' matches%
.
This comes from Twig, and is not yet commonly used in Drupal, but its usage with placeholders is similar to t()
. Here’s an example from Drupal 8 Core:
___TWIG0___
___TWIG1___
___TWIG2___
<ul>
<li>
___TWIG3___
Enabled: ___TWIG4___
___TWIG5___
</li>
<li>
___TWIG6___
___TWIG7___
Disabled: ___TWIG8___
___TWIG9___
</li>
</ul>
___TWIG10___
___TWIG11___
Includes: ___TWIG12___
___TWIG13___
___TWIG14___
In the above code, we can see the use of the placeholder in Twig. The set
lines set the placeholders to be used later in the code. The |placeholder
filter indicates that a replacement is to be made.
[Update January 10, 2018]
Here's a little Twig translation trick that may come in handy to some folks!
I needed to translate a piece of text in Twig along with a piece of content ("View as" myFileExtension) from a node and make that translated string available to users via the translation interface. Using a placeholder or wrapping in a {% trans %}
block worked fine for display, but it would result in something like this in the translation interface: "View as @item.content.#fileExtension." Not ideal.
So how to get around Drupal not loading that array? Concatenate the strings:
___TWIG0___
Using the concat operator, ~
, forces Drupal to load the item.content
contents before translating them, and I get what I wanted in the translation interface, which is the actual file extension! Hooray!
Wrapping Up
This post has a lot of what not to do, but you’ll run into a lot of creatively incorrect code when it comes to translation, and now you’ll know it when you see it. Simple is best. Remember that this function exists to give translators a list of strings to translate, and you’ll be in the right frame of mind when assembling these strings to keep them flexible and translatable!
Have some questions/comments/concerns? Experience you’d like to share? Talk to us on Twitter: @ChromaticHQ!
Keep an eye out for our next post in our Drupal Code Standards series, on Object Oriented programming in Drupal 8!
Editor's Note: When we first published this post, we included a work-around for placeholders in Drupal 8. This caught the attention of the Drupal security team and was considered a critical risk, resulting in this issue, and this update. We've removed the section and are happy to have helped bring this issue to light!