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 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 have a look at the following example:

<%= "#{t('currency')}#{@product.price}" %>
en:
  currency: "$"
de:
  currency: "€"

This translation makes the assumption that currency is always shown in front of the price.

However, a correct translation for German would be 10 € instead of €10.

A better abstraction would be:

<%= t("product_price", price: @product.price) %>
en:
  product_price: "$%{price}"
de:
  product_price: "%{price} €"

Adding Formatting

So far so good! But what happens if we now need to make the currency 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:

<%= t("product_price_html", price: @product.price) %>
en:
  product_price_html: "<b>$</b>%{price}"
de:
  product_price_html: "%{price} <b>€</b>"

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:

<%= t "product_price_html",
  price: @product.price,
  currency: tag.b(t("currency"))
%>
en:
  currency: $
  product_price_html: "%{currency}%{price}"
de:
  currency: 
  product_price_html: "%{price} %{currency}"

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!