SymfonyWorld Online 2020
100% online
30+ talks + workshops
Live + Replay watch talks later

New in Symfony 5.2: Translatable objects

Contributed by
Nate Wiebe
in #37670.

Translating contents usually require more information than the original message, such as the translation parameters and the translation domain. In order to make templates simpler, you could translate the entire messages in the backend and pass the translations to the templates.

However, this is sometimes cumbersome and also requires injecting the translator service in several layers of your application. Another alternative is to pass all the translation data (message, parameters, domain) to the template:

1
2
3
4
5
6
7
return $this->render('...', [
    'order_status' => [
        'message' => 'order.status_message',
        'params' => ['%status%' => $order->getStatus(), '%order_id%' => $order->getId()],
        'domain' => 'admin',
    ],
]);

This no longer requires injecting the translator, but makes templates more complex:

1
{{ order_status.message|trans(order_status.params, order_status.domain) }}

In Symfony 5.2 we’re introducing a new TranslatableMessage class that solves this problem in an easier way. First, create a TranslatableMessage object and pass all the translation data to it:

1
2
3
4
5
6
7
8
9
use Symfony\Component\Translation\TranslatableMessage;

return $this->render('...', [
    'order_status' => new TranslatableMessage(
        'order.status_message',
        ['%status%' => $order->getStatus(), '%order_id%' => $order->getId()],
        'admin'
    ),
]);

Now you can translate this object in your template with the same trans filter as before, but in a much simpler way:

1
{{ order_status|trans }}

The translatable message holds all the translation data, so the trans filter can extract it automatically. This solution allows to simplify both the templates and the backend code, because you don’t need to inject the translator or mock it when writing unit tests.

This feature also introduces a new t() function, usable both in PHP and Twig templates, to quickly create this kind of objects. The following example uses that function to create a fallback translation which uses different parameters than the default translation:

1
2
3
4
5
{# Before #}
{{ message is defined ? message|trans : fallback|trans({'%param%': value}) }}

{# After #}
{{ message|default(t(fallback, {'%param%': value}))|trans }}

Kudos to the amazing folks at Symfony Slack chat who helped me better understand this feature so I could prepare this blog post.

Help the Symfony project!

As with any Open-Source project, contributing code or documentation is the most common way to help, but we also have a wide range of sponsoring opportunities.

Comments

Note it's also compatible with translation extraction: `php bin/console translation:update` (so `t('literal')` will be extracted from anywhere)
This seems like a very nice feature, but isn't "Translatable" an odd name for the class? Classes usually are named as nouns, whereas this is named as an adjective, which to me feels a bit weird (also considering the rest of the Symfony codebase). Perhaps "TranslatableMessage" (or another noun option) would be more appropriate?
t() makes me remember good old Drupal 7 :D
@Davide Borsatto it's named as nominal adjective. Which is quite common i'd say, e.g. PHPs `Stringable`.
@Roland Franssen That's different though. The use case you mention is with interfaces (and with traits, kind of), where you define behavior and attributes, whereas with classes you have a concrete "something". It's also I believe the one of this type in Symfony, IIRC.
@Davide Borsatto im not aware of any rule that says nominal adjective naming only applies to interfaces and traits. The thing is you can name "something" using nominal adjective :)
After two decades, this is probably the most sane solution to localization I've seen.
Login with SymfonyConnect to post a comment