Sometimes, a translated message needs some HTML inside it, such as a link
in the middle of a sentence. The usual solution is to put the HTML in the
translation and render it with the raw filter:
1 2 3 4
{# translations/messages.en.yaml:
auth_prompt: 'Please <a href="/login">sign in</a> to continue.' #}
{{ 'auth_prompt'|trans|raw }}
This has several problems: URLs are hardcoded in the translation files, any HTML added by translators is rendered as is, and the message parameters are no longer escaped. If one of them contains user input, you have an XSS vulnerability.
The trans_html Filter
Symfony 8.2 adds a trans_html Twig filter to solve this. Translations no
longer contain real HTML, but pseudo-tags (<name>...</name>) without attributes:
1 2
# translations/messages+intl-icu.en.yaml
auth_prompt: 'Hi {name}! Please <signin_link>sign in</signin_link> or <signup_link>create an account</signup_link>.'
In the template, you map each pseudo-tag to the HTML element to render. Elements are defined as data (the tag name and its attributes), and you can pass a list of elements to nest them (from the outermost to the innermost):
1 2 3 4
{{ 'auth_prompt'|trans_html(tags: {
signin_link: { tag: 'a', attr: { href: path('login') } },
signup_link: [{ tag: 'strong' }, { tag: 'a', attr: { href: path('register') } }],
}, arguments: { name: user.name }) }}
Symfony renders the declared tags and escapes everything else: the message text,
the parameters, the attribute values and any tag that you didn't declare. If
the translation contains unbalanced tags, Symfony escapes the entire message.
Tags such as script and style are not allowed.
The output of this filter is already safe, so you don't need raw. The filter
also works with translatable messages created with the t() function.
Translating HTML Messages in PHP
You can also translate these messages in PHP with the new HtmlTranslator
class of the Translation component. For example, you could use it when
building HTML emails:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Translation\HtmlTranslator;
class AuthPromptRenderer
{
public function __construct(
private HtmlTranslator $htmlTranslator,
private UrlGeneratorInterface $urlGenerator,
) {
}
public function render(User $user): string
{
return $this->htmlTranslator->trans('auth_prompt', [
'signin_link' => ['tag' => 'a', 'attr' => ['href' => $this->urlGenerator->generate('login')]],
'signup_link' => ['tag' => 'a', 'attr' => ['href' => $this->urlGenerator->generate('register')]],
], ['name' => $user->getName()]);
}
}
Thanks Hugo for the job you do everyday <3
Awesome! Thank you.
Is there any plan to make this work with Twig Components?