Rails: Including HTML in your i18n locales

Updated . Posted . Visible to the public. Repeats.

TL;DR

I18n keys ending with _html are automatically marked as HTML-safe when translating with t('.your_key_html').

When you're localizing a Rails application, some localized texts need to contain HTML. Be it some localized link, or some tags for formatting that you wantto include in your translated text. Example:

# e.g. config/locales/en.yml
en:
  page:
    text: 'Please visit our <a href="https://www.example.com/">corporate website</a> to learn more about <strong>the corporation</strong>.'

When your view translates through ...

<%= t('.text') %>

... it will not render as a text containing a link or <strong> tag. Instead, the HTML control characters from the text entry are escaped, and your HTML looks like this:

Please visit our &lt;a href=&quot;https://www.corporate.com/en&quot;&gt;corporate website&lt;/a&gt; to learn more about &lt;strong&gt;the corporation&lt;/strong&gt;.

Alright. Rails is being helpful here and saves you from accidentally injecting HTML into the page. But how could you insert the desired HTML intentionally?

You might be tempted to render t('.text').html_safe. While this renders as desired, the calling view vouches for it to be safe. Don't do it. There is a better way.

You might also be tempted to split your text into multiple fragments, like so:

<%= t('.please') %>
<%= link_to t('.website_label'), t('.website_url') %>
<%= t('.learn_more') %>
<strong><%= t('.corporation') %></strong>.

This is annoying to use, and often won't work because different languages work differently. Don't do it.

The solution

Rails ships with a solution for this: The t() helper (but not I18n.t()) will mark translations as .html_safe if their key ends with _html.

# e.g. config/locales/en.yml
en:
  page:
    text_html: 'Please visit our <a href="https://www.example.com/">corporate website</a> to learn more about <strong>the corporation</strong>.'

You render it as before.

<%= t('.text_html') %>

But for this entry, HTML entities are not sanitized:

Please visit our <a href="https://www.example.com/">corporate website</a> to learn more about <strong>the corporation</strongm>.

The output will contain a link, as expected:

Please visit our corporate website Show archive.org snapshot to learn more about the corporation.

This will also properly escape interpolations:

Assuming your locale file contains a format placeholder for the link:

en:
  page:
    text_html: 'Please visit our %{link} to learn more about <strong>the corporation</strong>.'

You can then pass an HTML-safe string and it ends up as expected in your HTML:

<%= t('.text_html', link: link_to('corporate website', WEBSITE_URL)) %>
Please visit our <a href="https://www.example.com/">corporate website</a> to learn more about <strong>the corporation</strong>.

But any unsafe input is escaped:

<%= t('.text_html', link: '<script>alert("hacked")</script>') %>
Please visit our &lt;script&gt;alert(&quot;hacked&quot;)&lt;/script&gt; to learn more about <strong>the corporation</strong>.
Profile picture of Dominik Schöler
Dominik Schöler
Last edit
Arne Hartherz
License
Source code in this card is licensed under the MIT License.
Posted by Dominik Schöler to makandra dev (2018-05-02 10:17)