Blog post image with i18n Ruby on Rails logo

Rails i18n: How to Easily Localize & Translate a Multilingual App

by Denis Augsburger

Ruby on Rails is a framework that has built-in support for internationalization (i18n). If you use Ruby, you can install the same gem that Ruby on Rails has already included. As a result it is easy to start internationalizing your software in both Ruby & Ruby on Rails and reach more people. That's why I want to give you a quick guide on how to localize & translate rails i18n projects.

Table of Contents

Prepare New Project

Install Rails i18n for Ruby

If you want to localize your Ruby application without Rails just install the following gem and configure ruby:

gem 'i18n'

Configure Locales

The default path for your locales is config/locales. They do not include any subdirectories.

Let's configure where your locale files are located in your project:

# In File config/application.rb

# Load from subdirectories as well with path '**'
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]

# Set locale to spanish, default is en -> english
config.i18n.default_locale = :es

Get Default Locales or Install rails-i18n

It is not mandatory but the rails-i18n gem helps you with suitable defaults for pluralization rules, formats and translations of months, weekdays, errors, numbers and more. To save time, check if there is already a locale for your target language.

Install gem or see existing locale

gem 'rails-i18n', '~> 6.0.0' # For 6.0.0 or higher

Alternative to YAML: Configure PO-Files

If you like the gettext format in PO-Files more than YAML, you can optionally use the pluggable PO parser. Just insert the provided Gettext backend and load your PO files.

require "i18n"

include I18n::Gettext::Helpers

I18n.load_path << Dir["*.po"]  # Load all PO files in current directory

An example of a PO file looks like this:

msgid: "welcome"
msgstr: "Welcome %{usename}"

msgid: "change_language"
msgstr: "Change language"

Get Ready to Use Rails i18n Locales

Organize Locale Files

By default the translations are stored in config/locales/en.yaml. Even for smaller projects it is hard to maintain all translations in one file. With the config you can load your locales from subdirectories and organize them in multiple folders i.e. per feature.

┗ 📂config
┃ ┣ 📂locales
┃ ┣ ┣ 📂common
┃ ┃ ┃ ┣ 📜en.yml
┃ ┃ ┃ ┗ 📜fr.yml
┃ ┣ ┣ 📂product
┃ ┃ ┃ ┣ 📜en.yml
┃ ┃ ┃ ┗ 📜fr.yml
┃ ┣ ┣ 📂profile
┃ ┃ ┃ ┣ 📜en.yml
┃ ┃ ┃ ┗ 📜fr.yml
    welcome: 'Welcome %{usename}'
    bio: '<p>Describe your bio here<br/>This will be shown in your public profile</p>'
    bio_html: '<p>Describe your bio here<br/>This will be shown in your public profile</p>'
    changeLanguage: 'Change language'
      invalidProfile: 'Invalid Profile'

The scope/namespace is defined in the yaml file, i.e. the welcome message in the example above is in the profile scope and referenced as `profile.welcome'.

You can combine multiple scopes from your rails files. Be aware of naming collisions in your structures.

Translate I18n.t

The translations are organized in different scopes. In our example there are the namespaces/scopes common, product and profile, which are included in a seperate yaml file.

You can use your translations with the provided I18n.t function directly in your ruby code. There are different ways to reference your locale keys and the corresponding scope. Choose whatever suits your coding style best.

# Lookup translation in scope profile.errors
I18n.t 'invalidProfile', scope: ["profile.errors"]
# same as above
I18n.t :invalidProfile, scope: [:profile, :errors]

# Lookup deeply
I18n.t '.invalidProfile', scope: ["profile"]

# Lookup deeply without scope
I18n.t '.invalidProfile'

Use Translation Function in Views

You can use the t function directly in your views. You can also use the lazy lookup with the point notation, i.e. t('.welcome')

<!-- app/views/profile/index.html.erb -->
<h1><%= t('profile.welcome', username: @current_user.username) %></h1>  
<!-- Use a suffix or name them html to mark them as HTML safe. 
     Used in views => no escaping 
     Variables/Interpolations still escaped     
<div><%= t('profile.bio_html') %></div>

<!-- use raw for complicate html structures in your translations, use it reasonably -->
<div><%= raw t('') %></div>

<!-- Important: HTML safe conversion is only available in the view helper of translate -->

Use HTML mostly to emphasize parts of your text, i.e. bold and italic tags. See also the official guide for more information.

Localize Dates, Numbers & Currencies

To format data appropriately use the I18n.l function with the data as a parameter.

The next example localizes a timestamp with the short format:

I18n.l, format: :short

You can further configure your formats in your locale files, but the defaults from i18n-rails are suitable in most cases. See rails-i18n for reasonable defaults for your target languages. A corresponding YAML file with the short format could look like this for French:

    am: am
      default: "%d %B %Y %Hh %Mmin %Ss"
      long: "%A %d %B %Y %Hh%M"
      short: "%d %b %Hh%M"
    pm: pm

Here, the time format is taken. The French locale file configures the time format short as ""%d %b %Hh%M". The result for French becomes 16 fév. 18h30.

Localize Currencies

There are many currencies and depending on the region they are differently presented. Depending on your target locale there exists a sign like $ for your currency or there's a letter code like USD.

You can configure that format in your yaml files per locale.

      delimiter: " "
      format: "%n %u"
      precision: 2
      separator: ","
      significant: false
      strip_insignificant_zeros: false
      unit: "€"

Now You Can Translate Your Locale Files

There are many translation agencies that accept yaml files. Because the translation process tends to be expensive and slow, most companies try to postpone the translation of locales. This leads to the discovery of i18n problems at a later project stage, which results in additional implementation costs. Another challenge is the cost, especially for small and early stage projects.

We've created Simpleen to speed up the translation process using machine translation. We support ruby's variable/interpolations ${} and provide an easy to start online translator as well as a CLI to automatically translate multiple locales.

Check it out and start translating your YAML or PO files.