Drupal 8 Twig Templates and Translations
Even though Twig has been in Drupal 8 for quite some time now, there are still a lot of things left to do! I recently started collaborating with the "Twig team" in an effort to help move Drupal in the right direction. I spent the past few weeks tackling one major issue for Twig templates: easier translations. Just a few days ago a huge win, in terms of simplicity and power, for front-end developers was made and a new Twig tag was committed to core!
Current Implementation In Drupal 7
For as long as PHPTemplate has been Drupal's theme engine, translating text (with variables) in template files has been no walk in the park. We currently have stuff like this lurking around in the code base:
<p class="submitted"><?php print $submitted; ?></p>
Oh, but wait... what is $submitted actually printing?? Us front-end developers despise this. We have to figure out where it's being preprocessed and the culprit would be template_preprocess_node():
$variables['submitted'] = t('Submitted by !username on !datetime', array('!username' => $variables['name'], '!datetime' => $variables['date']));
Great... so if we want to change the text to read something like "Published on June 2 by Mark Carver", we'd have to implement THEME_preprocess_node() and override $variables['submitted']. While seasoned Drupal theme developers are used to doing this on a very frequent basis, it still doesn't change the fact that it's a pain. Ugh!
Converting To Twig Drupal 8
Let's go ahead and start converting this into Twig for Drupal 8:
<p class="submitted">{{ submitted }}</p>
This is just a straight forward port from PHPTemplate's mess to Twig, so it still has the annoyance of not knowing what $submitted is. Let's try changing them to just use standard Twig variables:
<p class="submitted">Submitted by {{ author }} on {{ date }}</p>
Unfortunately, we found out that this doesn't really work either. The text around the Twig variables isn't translatable. So back to the drawing board we went. How about using the |t filter to help remedy this problem?
<p class="submitted">{{ "Submitted by !author on @date"|t({ '!author': author, '@date': date }) }}</p>
Sure, this technically works... but that syntax is extremely complex [and ugly] to scan with the eye. We also realized that front-end developers who are new to Drupal would be required to know how the "Drupalism" prefixes for format_string() worked, let alone that they existed. Ok, so the filter really isn't a good solution for bigger blocks of texts with lots of variables.
INTRODUCING THE TWIG {% TRANS %} TAG
Twig actually already has a better and simpler way to solve all of this. There is an extension that we could use (or at least base off of): i18n Extension, which makes the tag available for Twig templates. As it turns out, we actually did end up having to modify it rather heavily so it would work with t(), format_plural() and format_string() functions properly. We also debated whether or not to keep the name "trans" or rename it something more appropriate/readable. Given that this Twig tag extension wasn't originally created by the Drupal community and is, in fact, used widely by the existing Twig community, we decided that it was best to leave it simply as "trans". Despite the name, the end result for templates is a much cleaner and simpler approach for translating blocks of text using variables:
<p class="submitted"> {% trans %} Submitted by {{ author.username }} on {{ node.created }} {% endtrans %} </p>
Note: If you use variable attributes, the placeholder name used in the translation msgids will always be the ending attribute name and not the entire variable path. In the above example, the msgid would be:
Submitted by @username on @created, not Submitted by @author.username on @node.created.
{% trans %} & Twig Filters
Filtering Twig variables inside the {% trans %} tag will generally work. However, some of these filters may not work properly or at all. If you are not seeing the desired result or you are receiving fatal errors/WSOD you may need scale down what you are trying to do inside the {% trans %} tag. Create a new Twig variable outside of the tag with this filter applied:
{% set date = node.created|format_date('medium') %} {% trans %} Node was created on {{ date }}. {% endtrans %}
Plural Translations
Making plural translations are now also even easier, just implement a {% plural ... %} switch!
{% set count = comments|length %} {% trans %} {{ count }} comment was deleted successfully. {% plural count %} {{ count }} comments were deleted successfully. {% endtrans %}
Placeholder Equivalents
By default, all Twig variables detected in the {% trans %} tag are sent with the @ prefix so they can escaped safely. If the value of that variable needs to be passed through (!) or used as a placeholder (%), modify the variable with these following Twig filters |passthrough and |placeholder:
{% set string = '&"<>' %} <div> {% trans %} Escaped: {{ string }} {% endtrans %} </div> <div> {% trans %} Pass-through: {{ string|passthrough }} {% endtrans %} </div> <div> {% trans %} Placeholder: {{ string|placeholder }} {% endtrans %} </div>
{% trans %} Debugging
Even though it was kind of considered a stretch task, I had a simple and brilliant idea. Why not utilize when $conf['twig_debug'] is enabled to help debug {% trans %} tag translations?! Now when enabled, the translation for both the regular and plural msgids generated by the {% trans %} will displayed as inline HTML comments to use in your .po files. You never have to "guess" what translation string is being used.
Note: this only works with the {% trans %} tag inside Twig templates. This doesn't happen when just using the t() function.
<!-- TRANSLATION: "Hello star.", PLURAL: "Hello @count stars." -->
Conclusion
I'm very excited about this new Twig tag! Generally speaking, this wonderful enhancement relates mostly to front-end, however I would implore you back-end module maintainers to convert your templates to utilize this wonderful new syntax. Let's give some of the power back to the theme! Many thanks to the numerous people who have helped make this possible and welcomed me with open arms!
Commit issue: #1927584: Add support for the Twig {% trans %} tag extension
Change record: Added support for the Twig {% trans %} tag extension
Editor's update:
Since writing this post, a new issue has further expanded the syntax of this awesome new tag!
Language Options
The t() and format_plural() functions have an $options parameter that can provide additional context or allow a specific language to be chosen for translation. To pass these options in the {% trans %} tag, use the with { ... } syntax in the opening tag:
{% trans with {'context': 'Long month name', 'langcode': 'fr'} %} January {% endtrans %}
Example debug output using with { ... }:
<!-- TRANSLATION: "January", CONTEXT: "Long month name", LANGCODE: "fr" -->
Original photo: Untitled by Tau Zero