Translation library for ActiveRecord models in Rails 2
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.


EfficientTranslations is a translation library for ActiveRecord models in Rails 2

I wrote EfficientTranslations because I'm working on several legacy rails apps with performance problems and I cannot migrate to Rails 3.

EfficientTranslations is inspired to both Globalize2 (for models architecture: translations are stored in a separated table) and Puret (for cache mechanics).


  • ActiveSupport 2.3.x
  • ActiveRecord 2.3.x


The idea is always the same. One table for model records, another table for model translation records. This architecture works well if app languages could change in the future and you don't want to add a column each time a new language is added, or if you have a large number of translations to manage.

EfficientTranslations is designed to reduce the number of queries done to get model+translations and the amount of data retrieved.

I don't think it's the perfect solution, indeed I think it's far to be the perfect solution, but it's a step forward.

Translate a Model

To explain the gem usage we'll use the following use case:

We need a Product model with two localizable fields:
- name (string)
- another_field (integer)


You can create the translation table using the helper create_translation_table. The following:

create_table :products do |t|
create_translation_table :products, :name => :string, :another_field => :integer

is equivalent to:

create_table :products do |t|
create_table :product_translations do |t|
  t.references :products, :null => false
  t.string     :locale  , :null => false
  t.string     :name
  t.integer    :another_field


Now we have to modify our Product model as the following:

class Prouct < ActiveRecord::Base
  translates :name, :another_field

Done! You have the EfficientTranslations power in your hands :-)


Manage Translations

    I18n.default_locale = :en
    I18n.locale = :en
    product =

    # Read translations:

    # use #name_translation to get the translation for requested locale:
    product.name_translation :en # => nil

    # #name is a wrapper to #name_translation for the current locale (I18n.locale): # => nil

    # Write translation

    # use #set_name_translation to set the translation for requested locale:
    product.set_name_translation :en, 'Efficient Translations'

    # #name= is a wrapper to  #set_name_translation for the current locale (I18n.locale): = 'Efficient Translations'

    # Cached translations

    # Whenever you access a freshly set or retrieved from database translation the
    # cached value is used: # => 'Efficient Translations'

    # Retrieve default locale translation, when the current locale translation is missing:
    I18n.locale = :it # => 'Efficient Translations'
    product.name_translation # => 'Efficient Translations'

    # Don't retrieve default  locale translation, when the current locale translation is missing:! # => nil
    product.name_translation! :it # => nil

    # List all available translations for requested attribute:
    product.name_translations # => { :en => 'Efficient Translations', :it => 'Traduzioni Efficienti' }

    # Save translations to database:!

    # Create a product using nested attributes
    Product.create! :translations_attributes => [{:locale => I18n.locale.to_s, :name => 'Another'}] # => 'Another'


The validator validates_presence_of_default_locale is provided to prevent a model to be saved without a translation for the default locale. Eg:

    class Product < ActiveRecord::Base
      translates :name, :another_field


Named Scopes and Performances Overview

Three named scopes are defined:


    # Fetch products with all translations

This will include all the translations record. So in the case you have a product with translations for :en and :it.

    p = Product.with_translations.first # No sql is executed
    p.name_translation :it # No sql is executed
    p.name_translation :fr # No sql is executed


    # Fetch products with translations for I18n.locale or I18n.default_locale

This scope will fetch only the translations you usually need when you fetch your models. It's not perfect. Observe the following code to understand why:

    Product.create! :translations_attributes => [
      { :locale => :en, :name => 'Product1'  },
      { :locale => :it, :name => 'Prodotto1' }

    Product.create! :translations_attributes => [
      { :locale => :it, :name => 'Prodotto2' }

    I18n.locale = :en

    # The second product is not included in the result because it doesn't have the I18n.locale
    # or I18n.default_locale translation
    # To prevent this you can use validates_presence_of_default_locale
    Product.with_current_translation.size # => 1

    p = Product.with_current_translation.first # => 'Product1'; No qury is executed because we used the named scope
    # translations collection doesn't contain the 'it' value, so all calls to 'it'
    # translations will return the I18n.default_locale value
    p.name_translation :it # => 'Product2'

    # To fetch the 'it' value you have to do the following:
    p.translations true # reload all translations
    p.name_translation :it # => 'Prodotto1'


This scope behaves like with_current_translation but it will use, in order, a locale of your choice or I18n.default_locale to fetch the translations