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.
Note it's also compatible with translation extraction:
php bin/console translation:update
(sot('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.