Internationalization and localization - Rails

By Russell Taga

This is part two of our eight part series of posts on our first foray into internationalization and localization.

All of our server side apps are currently written in Ruby and use the Rails 3 framework. So where did we start? The first thing we did was to identify the major parts of the system that we needed to ensure were internationalized. Here are the big ticket items that we focused on:

Remember the golden rule

When it comes to internationalization, the simplest and most important rule is to separate code and content. If you’re using the Rails framework, the place to start is to read the Rails Internationalization API guide. Out of the box, you get a lot for free. Here are the basic things that we put in place:

1) Drop in the i18n gem

2) Locale is set based on the Accept-Language header that is passed in from a client. We leveraged Iain Hecker’s httpacceptlanguage gem for this.

if http_accept_language.header.present?
 I18n.locale = http_accept_language.compatible_language_from(I18n.available_locales)
  I18n.locale = :en

3) We centralized all localizable content for our API responses, emails, and web pages into .yml files. We chose to use multiple .yml files to make it easier to manage different types of content. E.g., legal vs. API responses. We created .yml files in config/locales with a key for each string and the string. For example:

  disabled_account: "Votre compte a été désactivé"

when the view renders its content, it’ll pull out the correct string based on the locale that’s set based on the Accept-Language header passed in by the client.

I18n.t :disabled_account, scope: 'customer'

As mentioned in our previous post, once you have the strings centralized in .yml files, we strongly recommend dropping in pseudo translations to ensure that you’ve got all of your strings properly internationalized and to clearly see what kind of layout issues you’ve got.

Screenshot of missing

4) For content in our database (e.g., the profile for each hotel), we ended up creating a custom solution. This isn’t in shape to open source yet, but here’s the general strategy:

Note that our database (we use MySQL) was set up to use UTF-8 from the get-go. If you are not using UTF-8, you’ll likely need to take on additional work to convert the data to UTF-8 so that you can store and sort content for multiple locales properly.

5) Ensure that strings and API responses format dates, numbers, and currencies properly. Be sure that I18n.locale is set (or explicitly pass the locale as an option to the method) and leverage the following methods.

Screenshot of bad date

6) When it comes to sorting, databases can handle sorting text for multiple locales properly, but Ruby struggles. We suggest checking out ICU4R and/or TwitterCLDR for help with sorting in Ruby, as well as, to solve many of the problems above.

Layout strategies

Variable length and namely longer strings (e.g., German tends to be 25% longer than English) will most likely cause you at least some layout problems. We’ll talk more about strategies for dealing with variable string lengths in our upcoming posts for design/UX, iOS, and Android, but here are a few tips.

Screenshot of truncation

Screenshot of overlap

Add some tests

Add more test scenarios to sanity check that translations are rendering properly. Pick content that doesn’t change frequently otherwise you’ll be doing a lot of maintenance on these checks. Don’t go too crazy particularly when you are leveraging things like the Rails i18n API which are already tested. Just do enough to give yourself confidence that things are working properly and that no one made a mistake like checking in a .yml file for the wrong locale under the wrong name.

Payment/currency support

We’ve got a custom abstraction layer for managing payments and currency so we’re going to go a little light on the guidance here. At a minimum you need to ensure that you’re working with a payment gateway that supports multiple currencies (we use Braintree) and that a particular form of payment supports a given currency. For example, we weren’t able to set up AMEX support with Mexican Pesos.

Other miscellaneous things to factor in

Key things to remember

Here are our top two pieces of advice.

1) Keep your minimum size string to a sentence - Often times people are tempted to concatenate sub-strings together to get more reuse. DRY is heavily emphasized in Rails, but this is one time where it can cause you problems. A couple simple examples of this are that in different languages words have gender and things may be phrased differently based on quantity.


base: "A city tax of %{tax_amount} will be collected by the hotel at check-in."
per_person: "A city tax of %{tax_amount} <strong>per person</strong> will be collected by the hotel at check-in."
per_night: "A city tax of %{tax_amount} <strong>per night</strong> will be collected by the hotel at check-in."
per_person_per_night: "A city tax of %{tax_amount} <strong>per person per night</strong> will be collected by the hotel at check-in."


base: "A city tax of %{tax_amount}"
per_person: "per person"
per_night: "per night"
collected_at_checking: "will be collected by the hotel at check-in."

With legal strings (e.g., privacy policy and terms of service) we went with the largest contiguous chunks of content (typically paragraph) that we could get away with.

2) Keep layout in your views versus your strings - This is less true for web views given the way that browsers handle whitespace, but we certainly had cases where emails and the mobile apps relied on spaces in strings for formatting. When you localize the length of your strings will vary and relying on spaces in specific positions is very brittle. It sounds simple, but our reality was that the aesthetic issues due to spaces made up approximately 10% of the issues logged by QA.

Screenshot of format problems due to spacing

More helpful resources

As always please let us know if you have more specific questions that we can try to help with. Up next, we’ll offer up some specifics about what we had to do for out native Android and iOS apps.

Written by Russell Taga

Read more posts by Russell, and follow Russell on Twitter.

Interested in building something great?

Join us in building the worlds most loved hotel app.
View our open engineering positions.