Oct 14, 2014

Sprechen Sie I18n?

blogpost author photo
Marek Stanisz
blogpost cover image
Many apps you’re working on stick to one language. Most of the time it will be English or your national language, but there may be times when it’s not enough. Let it be the need to launch the site in more languages, or to convert it to another locale. Face it – every app has the potential to become international.

The main purpose of this guide is to gather as much useful information about Rails I18n API as possible – all in one place. You can say some of these topics are covered in the Rails Guides tutorial to I18n API and the Rails Style Guide has many useful hints. That’s fine – but I think that they tell you how to do things, failing to provide the purpose. Thus they don’t show you the full power lying behind the I18n.

So… Shall we begin?

It’s dangerous to go alone

The gem i18n should be included in every new Rails project by default. That’s nice. It includes some basic methods used in translation and localization, and as far as I’m concerned – supports basic english locale. That’s all. Though you still can work this out with some locales, you may encounter problems with pluralization rules, for example. Some languages, like Polish, can be very tricky when it comes to that matter. To make this work, you’ll need to get rails-i18n gem. Piece of cake – put it in yout Gemfile and run bundle installEt voilà.

Now we have to set the default locale. Open the config/application.rb file and uncomment the following line:

config.I18n.default_locale = :en

Then, just replace :en with a symbol for your desired locale. It may be :pl for Polish, :pt for Portuguese, :de for German… Your choice.

Set up? So go and get YAML file corresponding to you locale from I18n locale repository. This will load you up with some basic i18n translations, responsible mostly for date and time. That’s neat – should be enough for a good start.

New Model Army

YAML files are pretty useless while empty – just like our apps. So let’s make a basic model with some attributes and put accurate translation into our YAML. For example, suppose we have a User model with the following fields: nickfirst_namelast_name and password.

Corresponding yaml scopes may look like this:

        zero: "0 użytkowników"
        one: "Użytkownik"
        few: "%{count} użytkownicy"
        many: "%{count} użytkowników"
        nick: "Nick"
          zero: "0 imion"
          one: "Imię"
          few: "%{count} imiona"
          many: "%{count} imion"
          other: "%{count} imienia"
        last_name: "Nazwisko"
        password: "Hasło"

OK, so we’ve got it. Now here’s the question – why are we doing that? First of all – I18n provides us with two helper methods. One of them is model_name.human, the second is human_attribute_name. Usage is simple:

  # => "Użytkownik"

  # => "Nazwisko"

Pluralizing them is also a piece of cake.

User.model_name.human(count: 3)
  # => "3 użytkownicy"

  User.human_attribute_name('first_name', count: 7)
  # => "7 imion"

The method just takes the count parameter, searches for accurate translation (by the language pluralization rules – a brief overview can be found here; note that in some cases Rails requires you to be much more speciic than described above – so it may be safer to provide translations for all pluralizations; you have to find it out by yourself) and displays it as a string.

Second – when displaying error messages, the model and atribute names are also taken and translated.

There is also one more reason for structuring your YAML this way. When you write your forms using the basic form helper, you don’t need to add anything more to your form labels. Just write

= form_for :user do |f|
    = f.label :first_name
    = f.text_field :first_name

And it will already be replaced with translation in your view. I find it very convenient.

A Room With A View

But let’s face it. For the most of the time we’ll use I18n to translate our views. Let’s assume that each user has his dashboard, with a profile view. Our YAML would look like that:

      greeting: "Witaj w swoim panelu kontrolnym"
        deeper: "We're going deeper!"

So we’ve got a scope for the controller, then the view, and then whatever you may want to include. You may also want to nest the scopes even deeper, if you really want to.

And calling it inside a view… Nothing more simple. To call the translation inside a view, use the thelper.

= t('.greeting')
  = t('.dig.deeper')

t is a special helper for I18n.t. It does nothing else than calling the mentioned method, except it’s a just a shorter way. The ‘dot’ approach is called ‘the lazy lookup’ – also a very convenient tool.

Back in Control

Next – the controllers. There’s not much we can do here actually. All we have to think about is basically displaying flash messages – notices and alerts.

Let’s take a look at the YAML structure:

      notice: "Utworzono kartę"
      other_message: "Inna wiadomość"

So we’ve got the controller name, action name and then desired messages. Calling this is as simple as in views – we still can use the lazy lookup!

class CardsController < ApplicationController
    def create
      # some code
      redirect_to profile_path, notice: t('.notice') + t('.other_message')

If you want more, check plataformatec’s responders gem. It provides some neat options for automating and customizing flash messages.


We’re going back to our model bit for a while. One other thing worth mentioning is customizing error messages. We can customize them while defining our validations, but wait! We can also define them in our YAML, and Rails will look them up automatically. To sum up the information from RailsGuides on I18n:

Rails I18n looks up error translations in following order:


It makes sense, when you think about it. Rails searches for most specific entry, and if it’s not found, it goes searching some less and less detailed scopes. So you should fill in the top scope only when you need to declare special cases, using others for more common examples. Simple and clever.

Remember that you have to define the type of your error message, depending on validations you do on certain fields. A list of all the types and possibilities can be found in the guide. Search for ‘5.1.2’ or ‘Error Message Interoplation’, and you’ll find a lovely table explaining… well, everything.

Testing, testing, testing!

Your code is worth only as much as your tests. There’s not much testing here, though. Just remember two things:

First – notice the short t helper doesn’t work here. You should use I18n.t instead (which is basically the same, as stated before).

Second – the ‘lazy lookup’ also doesn’t work here. You need to provide the full path to your translation – which also makes some sense. You check if that exact translation is really on it’s place.

What’s more?

I think that I’ve provided you with some basic information about working with I18n. You don’t need to use it from the start – though it may save you some time later, if your app happens to go international.

Some popular tools also have help for working with I18n. For example, for Devise – devise-i18n, where you can find locale files mostly for flash messages, and devise-i18n-views, providing you with upgraded views and locale files for them.

There is more to I18n. Setting the locale depending on various conditions, translating routes… I’ll try to cover some of these in the future.

But for now… It’s time to go back to coding. Cheers! :)