At Cookpad we build a global product to not only make cooking fun every day but also everywhere. With the help of the Rails Internationalization (i18n) API we have brought Cookpad to over 30 languages around the world.

However, translating a product in 30 languages is not easy, and we’re constantly learning and improving.

In the following article we will introduce one approach we’ve adopted for handling formatting of translated phrases.

Our Workflow

  • Working on a new feature, our development team uploads all new phrases in English to OneSky.
  • Our colleagues around the world use the OneSky UI to translate new phrases.
  • Once a day an automated background job imports any new translations back into our repository.

Passing Variables to Translations

As stated in the Rails i18n guide, a key consideration for successfully internationalizing an application is to avoid making incorrect assumptions about grammar rules when abstracting localized code. Let’s say we want to display when a user joined Cookpad. A naive implementation could look like this:

# app/views/users/show.html.erb
<%= t("joined") %>  <%= @user.created_at.year %>
# config/locales/en.yml
en: 
  joined: “Joined”

This translation makes the assumption that ‘Joined’ is always in front of the date. However, a correct translation for German would be ‘2020 beigetreten’. A better abstraction therefore would be:

# app/views/users/show.html.erb
<%= t("joined", year: @user.created_at.year) %>
# config/locales/en.yml
en:  
  joined: "Joined %{year}"

# config/locales/de.yml
de:  
  joined: "%{year} beigetreten"

Adding Formatting

So far so good! But what happens if we now need to make the year bold?

Luckily you can use HTML in translations by adding a _html suffix to the key.

We can now simply add a <b> tag in our translation:

# app/views/users/show.html.erb
<%= t("joined_html", year: @user.created_at.year) %>
# config/locales/en.yml
en:  
  joined_html: "Joined <b>%{year}</b>"

# config/locales/de.yml
de:  
  joined_html: "<b>%{year}</b> beigetreten"

Problem solved, right?

Not quite, because we’re now mixing content and formatting which leads to other problems:

  • Our translators are not developers, and may get confused by or make mistakes when editing the translation.
  • If we want to change the formatting, we now have to change all the translations
  • If the markup is even slightly more involved than a simple <b> (ie links, HTML classes etc), it quickly becomes a maintenance nightmare, that’s also very prone mistakes.

Solution

The solution we’ve adopted is to pass in any formatting to the translation:

# app/views/users/show.html.erb
<%= t "joined_html",
  year: tag.b(@user.created_at.year)
%>
# config/locales/en.yml
en:  
  joined_html: "Joined %{year}"
# config/locales/de.yml

de:  
  joined_html: "%{year} beigetreten"

This can scale to accomodate even complicated markup, by combining strings:

<%= t "have_account_html",
  link: link_to(t("login"), logout_path, class: "link-primary")
%>
en:
  have_account_html: "Already have an account? %{link}"
  login: "Login here."

Summary

While it may take a bit of extra work and the erb markup grow slightly more than we’d ideally wish for, ultimately we think it’s worth it for the problems it saves us from in the long run.

Your translators (and future you) will thank you!

(Originally shared on Source Diving)