Sometimes it can be very useful to temporarily change the current locale to perform an action and then directly switch back to the original locale. For example you need to deliver an email to a user in its own preferred locale.

Rails I18n ships with the I18n.with_locale method and is of course fully compatible with it.

Here is a basic example of usage :

I18n.locale #=> :en

I18n.with_locale(:fr) do
  I18n.locale #=> :fr

I18n.locale #=> :en

And a real world example in a mailer :

class UserMailer < ActionMailer::Base
  default from: ''

  def invitation(user)
    I18n.with_locale(user.locale) do
        :subject => _("You have been invited"),
        :to      =>

In this article, we will give you a quick overview of how to translate your Rails application with the GetText syntax. There is only a few (short-named) methods to remember, we will make you love underscores!


Singular _("text")
Plural n_("singular text", "plural text", number)
Singular with context p_("context", "text")
Plural with context np_("context", "singular text", "plural text", number)

Simple Texts

_ is the base method you will use everywhere.

In a controller :

class PostsController < ActionController::Base
  def create
    flash[:notice] = _('Post successfully saved')
    redirect_to posts_path

In a view :

<h1><%= _('Create a New Post') %></h1>
<%= f.label :title, _('Title') %>

In a model validation :

class Post < ApplicationRecord
  validates :title, :presence => {
                      :message => lambda { _('Title is mandatory.') }

Note that the call was wrapped into a lambda because otherwhise it would have been statically evaluated.


Interpolations can be easily performed using the standard % operator. Find some examples below.

_('Hi %{name}, do you like violence?') % { name: "Steve" }
_('%{city1} is bigger than %{city2}') % { city1: "NYC", city2: "BXL" }

Plural Forms

If the translatable string is depending on the number of items, use _n. It takes three arguments : the singular form, the plural form, and the cardinality.

For example :

n_("There is a mouse in our house.", "There are some mice in our house.", @mice.size)

And you can of course combine with interpolations :

n_("There is a mouse.", "There are %{num} mice.", @mice.size) % { num: @mice.size }

Translation Contexts

Sometimes you would need to scope a given translation to a given part of you application. For instance in order to be able to translate a given string differently depending on the context.

There are two dedicated methods for that : p_ (singular with context) and np_ (plural with context). They take the same arguments as _ and n_ (respectively) with an extra string argument defining the context added before.

Here is an example :

p_("Menu", "Open file")
p_("Menu", "Close file")

With plurals and interpolations :

np_("Menu", "Open a file", "Open %{n} files", 4) % { n: 4 }

If you are wondering why it is p for defining a context, that is because it stands for prefix.

Other Syntaxes

Some other special usages of GetText can also be used but won’t be detailed here because they are very specific (and not so useful) : s_, ns_, N_ and Nn_.

This article is about the way deals with YAML files. Whether you prefer YAML over GetText or you use GetText but dealing with a few YAML keys is inevitable, this page is for you!

Since YAML doesn’t guarantee that only a language is contained in a single file, we unfortunately are not able to mirror your source YAML structure.

Init & sync

Initializing your project implies :

  • All .yml files containing keys of your source language will be kept.
  • All .yml files containing keys of your target languages will be grouped into one named translation.LANGUAGE.yml.
  • All localization keys will be separated into localization-specific files. These files are named localization.LANGUAGE.yml.

Let’s see this in details.

translation.xx.yml files contain all the “textual” YAML keys from your project. Once your translators translated them in, you can sync and they will be updated. You cannot edit them directly: they will be overridden at each sync.

localization.xx.yml files contain all the non-textual (date formats, booleans, variables) keys that your translators will not understand. We populate them with the correct values for the target languages when initializing the project. These files are not synced with So you can update them manually if you need to. You can also configure other keys to be treated as localization.

Adding new keys

When a new key is needed:

  • Add it to one of your source files.
  • The next sync will send this new key to and the key will be added to all target translation.xx.yml files.
  • After the key is translated in, one more sync and your application translations will be up-to-date.


Let’s say your project is in en and is translated to fr and es. And you have these files at the moment :

  • config/locales/en.yml
  • config/locales/stuff.en.yml
  • config/locales/en/other.en.yml
  • config/locales/fr.yml
  • config/locales/
  • config/locales/es.yml
  • config/locales/

After an init, you will have these files :

  • config/locales/en.yml
  • config/locales/stuff.en.yml
  • config/locales/en/other.en.yml
  • config/locales/
  • config/locales/
  • config/locales/
  • config/locales/

Same file struture for source language and two files per target language for localization.

We have a strong opinion about how an application in general, and more especially a Rails application, should be internationalized. A lot of people use the default Rails Internationalization API (I18n) but we don’t really think this is an ideal solution for big projects.

Our reasons.

1. It’s painful to “make up” keys.

The welcome title of the main page may be as simple as t("home.title") but you can end up with very verbose things like t("on_call_schedules.contact_form.on_call_schedule_comment_placeholder").

2. Not trivial to search text in code.

When you want to change a text, you have to look for its key in the codebase, then find the text itself in the corresponding YAML file. It doesn’t sound very practical to us.

With GetText, editing a text is just a matter of finding it and updating it, right in place. No need to switch between many files.

3. Difficult coevolution between keys and associated text.

You’ve found a nice, short and descriptive key name for a given text. A few weeks later the customer asks for a significant change in the source language of that text. In such a situation you also need to change the key according to its new signification. Sometimes you won’t, or you will forget. Then you will probably end up with a key name not representative of its content.

4. Difficult to know if all the keys are really used in the application.

All the translation keys are located at the same place: in YAML file(s). They are also organized as a tree. When the key is used in the code, it is formatted with a dot syntax like my.key. When you are in a YAML file, it’s not easy to perform a “find in project” with your editor to see if it is still used or not. Because you have to convert the path in the tree to dot syntax before.

With the GetText paradigm, unused strings will be automatically detected at POT file generation. And with you can safely remove all unused strings with the rake translation:sync_and_purge task.

5. YAML can contain values that are not translations.

Rails YAML files contain not only translation strings but also localization values (integers, arrays, booleans) in the same place and that’s bad. For example : date formats, number separators, default currency or measure units, etc.

A translator is supposed to translate, not localize. That’s not his role to choose how you want your dates or numbers to be displayed, right? Moreover, this special keys often contain special constructions (e.g., with percent signs or spaces) that he might break.

We think localization is part of the configuration of the app and it should not reach the translator UI at all. That’s why these localization keys are detected and separated on a dedicated YAML file with

6. GetText is a standard in the Open-Source community.

GNU GetText has been around since 1995 and is the de-facto translation solution for many projects. So why not using it in Rails applications?

7. GetText syntax is nice.

GetText syntax looks optimal to us: short method names because used everywhere in the codebase and easy to remember.

However, YAML has some advantages too.

1. Pre-translated YAML files for Gems.

It’s easier to ship the translatable strings of a Gem as a YAML file. No dependency at all because Rails ships with YAML. But if you don’t code a gem, this argument is weak.

2. Allows to copywrite text in the source language.

Certainly the biggest argument in favor of YAML. You can use YAML keys in your code as placeholders for text and then allow some people (clients? marketing team?) to edit the text in all languages, including the source one, without changing the source code.

We allow this feature in It’s called Copywriting


We propose two ways for translating your applications and both ways can be used at the same time without any issue. But we strongly encourage you to use GetText as your main workflow since your translation process will be way smoother.

The only exception is when you need to delegate the source edition of the text to an external person or team. In that specific case, use a well-named YAML key and use our copywriting feature.

If you are currently using FastGettext and/or gettext_i18n_rails to translate your Rails application and you would like to switch to, this article will help you with the process of updating your codebase to make it work.

If you are not sure what you are doing, consider working in branch :-)

The Gemfile

The Gemfile should only contain the translation gem for all environments :

gem 'translation'

Make sure to remove any references to other gems like :

gem 'gettext_i18n_rails'
gem 'gettext',     :require => false
gem 'ruby_parser', :require => false
gem 'gettext'

You don’t have to include gettext neither, translation already depends on its right version and will therefore include it for you.


You probably have a fast_gettext.rb or a gettext.rb initializer file containing this kind of configuration :

FastGettext.add_text_domain 'app', :path => 'config/gettext_locales',
                                   :type => :po
FastGettext.default_available_locales = ['en', 'fr', 'de', 'nl']
FastGettext.default_text_domain       = 'app'

You won’t of course need it anymore.

New project

When you create a project on, you will be asked to create an initializer named translation.rb :

TranslationIO.configure do |config|
  config.api_key        = 'xxxxxxxxxxxxxxxxxxxxxxxx'
  config.source_locale  = 'en'
  config.target_locales = ['fr', 'de', 'nl']

  # Uncomment this if you don't want to use gettext
  # config.disable_gettext = true

  # Uncomment this if you already use gettext or fast_gettext
  # config.locales_path = File.join('path', 'to', 'gettext_locale')

There you have to correclty set config.target_locales to the former target locales.

It is very likely that your existing POT/PO files are not in config/locales/gettext. In that case, just move them there (keeping the right structure) or set config.locales_path to the correct base location used by FastGettext (usually Rails.root.join('locale')). only supports one unique textdomain named app. If you were using multiple textdomains, you will have to merge them. If your domain was not named app you will have to rename all the PO/POT files to app.xx.

Controllers and before filter

You probably have a before_action somewhere in your controller hierarchy to set the current locale (I18n.locale) according to the URL, the session, a cookie, etc.

translation ships with a generic implementation that can be called directly from all ActionController::Base descendants :

before_action :set_locale

But you can of course continue to use your own method if you need custom behaviour by defining it. For instance :

before_action :set_locale

def set_locale
  I18n.locale = current_user.locale || I18n.default_locale

That’s it

After performing this set of changes, you should be ready to translate your rails applications with Don’t hesitate to contact us if you think something is missing or not clear enough.