Using FastGettext to translate a Rails application

Ruby on Rails is shipped with i18n gem, which provides an internationalization and localization system. It basically allows the developer to abstract all the locale specific elements, mainly strings and date formats, out of the application.

The i18n gem is split into two parts: 1) the public API and 2) the default backend, intentionally named Simple.

As the Rails guide suggests, the Simple backend can be replaced, if needed, with a more powerful one. And that’s where FastGettext comes into play.


FastGettext is an implementation of Gettext for Ruby. It has many benefits over Gettext, primarily in performance and support for multiple backends:

Since translations are cached after the first use, performance is almost the same for all the backends. Although we found that using the database as the backend offers the most flexible solution.

The great thing about FastGettext, If you are working Rails, is that is has a called gettext_i8n_rails for integrating it into the application.


If you plan on using the database as the backend, here’s how you can set it up:

1) Add the gettext_i18n_rails gem your Gemfile:

gem 'gettext_i18n_rails'

And of course run bundle install.

2) Initialize FastGettext by adding the following into config/initializers/fast_gettext.rb (adapt to your target locales):

require "fast_gettext/translation_repository/db"
FastGettext.add_text_domain "app_name", :type => :db, :model => TranslationKey

FastGettext.default_available_locales = ["sr-Latn",en]
FastGettext.default_text_domain = 'app_name'

3) Set up the locale by adding the following to app/controllers/application_controller.rb:

helper_method :locale
before_filter :set_gettext_locale


def locale
  default_locale = Rails.env.test? ? "en" : sr-Latn
  params[:locale] || session[:locale] || default_locale


def set_gettext_locale
  session[:locale] = I18n.locale = FastGettext.set_locale(locale)

4) Add a CRUD interface for the translations. You can either use translation_db_engine or roll your own. We decided to do the latter.

To roll your own interface, you first need to generate and run the following migration:

class CreateTranslationTables < ActiveRecord::Migration
  def self.up
    create_table :translation_keys do |t|
      t.string :key, :unique=>true, :null=>false
    add_index :translation_keys, :key

    create_table :translation_texts do |t| 
      t.text :text
      t.string :locale
      t.integer :translation_key_id, :null=>false

    add_index :translation_texts, :translation_key_id

  def self.down
    drop_table :translation_keys
    drop_table :translation_texts

There’s no need to create the models, since they are in the gettext_i8n_rails gem, and the controller and views are pretty standard Rails REST.

For example, here’s our ‘index’ action:

def index
  @translation_keys = TranslationKey.all(:order => "created_at DESC")

  if params[:sort_by] == "name"
    @translation_keys.sort! { |a, b| a.key <=> b.key }

Here’s a screenshot of our translation interface:


Translating text is a pretty straightforward process and the result doesn’t clutter up the code as one might expect. When translating copy which, for example contains links, it’s a bit more work but still simple.

One important thing to remember is to use “syntax.with.lots.of.dots” for keys, since it drastically increases the ability to find where the key is used.

For example, let’s translate a simple welcome page.

<h1><%= _("views.home.index.welcome") %></h1>

<p><%= (_("views.home.index.questions %{contact_link}") % {:contact_link => link_to(_(views.home.index.contact_us), home_contact_url)")}).html_safe} %></p>

Note that we had to mark contact copy as html safe.

Exporting and importing translations

Storing translations in the database is great, but in order to have them under version control they need to be in a file as well. For this purpose we created a rake task that exports them to a YAML file:

require 'ya2yaml'

namespace :app do
  namespace :i18n do

    desc "Dump translations from your db into config/translations.yml file."
    task :dump => :environment do
      translations = TranslationRepository.export"config", "translations.yml"), "w") do |f|

      puts "Wrote new translations into file. You may commit it now."


The TranslationRepository class loads and exports the translations as hashes:

class TranslationRepository

  def self.export
    locales = TranslationKey.available_locales
    translations = []

    TranslationKey.find_each do |key|
      locales.each do |locale|
        trans = TranslationKey.translation(key.key, locale)
        translations << {:key => key.key, :translation => trans, :locale => locale}



  def self.create_or_update_translation(key, translation, locale)
    translation_key = TranslationKey.find_or_create_by_key(key)
    translation_text = translation_key.translations.find_by_locale(locale)
    return TranslationText.create(:translation_key_id =>, :locale => locale, :text => translation) if translation_text.nil?
    translation_text.update_attribute(:text, translation)


Note that we are using the ya2yaml gem here, since we found it to be working better than the built-in ‘yaml’ with multiple Ruby versions. If you wish to do the same don’t forget to add it to your Gemfile.

Apart from being able to put the translations under version control, this allows us to import the translations into another database (i.e. production) in a simple and convenient way. For that purpose, we created another rake task:

namespace :app do
  namespace :i18n do
    desc "Load translations from config/translations.yml into your db."
    task :load => :environment do
      translations = YAML::load_file(Rails.root.join("config", "translations.yml"))
class TranslationRepository

  def self.load_translations(translations)

    translations.each do |t|
      TranslationRepository.create_or_update_translation(t[:key], t[:translation], t[:locale])


  def self.create_or_update_translation(key, translation, locale)
    translation_key = TranslationKey.find_or_create_by_key(key)
    translation_text = translation_key.translations.find_by_locale(locale)

    if translation_text.nil?
      return TranslationText.create(:translation_key_id =>, :locale => locale, :text => translation)

    translation_text.update_attribute(:text, translation)
  def self.remove_existing_translations


The import code can, of course, be improved not to remove all the translation files and create new ones, but instead to check which translations are missing or need updating.


When using this method for translating a Rails application we encountered a few issues.

XSS / html_safe

As can be seen in the above example (translating a welcome page), when interpolating html elements you need to mark the translated strings as html safe, this is fine in some cases, but when you are the one that’s translating, or have a trusted translator, it’s just time better spent. gettext_i18n_rails documentation recommends a couple of solutions for this, but they did not work for us.

Date format translations

When loading date format translations from config/locales/en.yml we encountered an issue with date helpers like date_select.

The date helpers expect to get an array ([:year, :month, :day]) and date format translations are correctly stored in the YAML file, using the YAML array format:

- :year
- :month
- :day

But FastGettext was returning a string instead and we were getting an exception.

We managed to solve this with a monkey patch by detecting a YAML array (be sure to put something like this in an initializer):

class TranslationKey

  class << self
    alias_method :original_translation, :translation

  def self.translation(key, locale)
    text = original_translation(key, locale)
    return text if text.nil?
    return YAML::load(text) if text.match /^---.*/ #detect YAML array via ---



FastGettext can of course do a lot more than outlined here. It’s very powerful and certainly a big improvement over i18n’s Simple backend, which isn’t meant to be a complete internationalization engine and that’s just fine.

FastGettext isn’t too complicated to set up, but with all the options it can be very daunting. It’s obvious that a lot of work has been done so far, but at the same time it deserves a lot more attention from the community. With this post we would like to help at least a little bit with that and also help others to get started.

It is, of course, possible that we made some mistakes and missed a few things, so if you spot anything please let us know. Also, we would love to hear your experiences with FastGettext and internationalization in Ruby and Rails in general.

Comments powered by Disqus