Internationalization in Rails app with Globalize3

Publié le 27 juin 2012 par Alexandre Salaun | outils

Cet article est publié sous licence CC BY-NC-SA

During the development of a Rails app, you frequently need to use internationalization. There are some gems for this. In this case, we will use Globalize3. You can see the Github repository for other informations.

Globalize3 allows you to translate content dynamically. For a basic usage, you do not need anything in your views. This gem displays the content in the current locale (I18n.locale). In the database, translations tables contain one line by locale for each object (in example, for a product, one product_translations table line for ‘fr’ and one for ‘en’). You can also use different locale in the same page and, in this case, Globalize3 allows you to use blocks to translate some parts of your page.

Globalize3 requires I18n and ActiveRecord 3.0 or more. There is a version of Globalize for Rails 2 named Globalize2.

Installation

In order to install this gem in your app, you just need to add this line to your Gemfile :

gem 'globalize3', :git => 'git://github.com/svenfuchs/globalize3.git'

Next, you have to run bundle install.

Basic usage

To use Globalize3, you must specify the translated fields in your model, for example, if you need translation for a product :

class Product < ActiveRecord::Base
  translates :name, :description
end

Then, you can generate the migration to create the translation table :

class CreateProductTransalations < ActiveRecord::Migration
  def up
    Product.create_translation_table! :name => :string, :description => :text
  end

  def down
    Product.drop_translation_table!
  end
end

Be careful, with Rails 3.1, do not use the change method. Indeed, Rails fail to do the reverse migration so we have to create up and down methods.

In the views, if there is content from different locales on the same page, you have to use the blocks below :

<% # specify the locale
   # you can have several blocks like this in the same page with different locale %>

<% Globalize.with_locale(:en) do %>
  <% render :partial => "my_translated_partial" %>
<% end %>

Migrations

There is a method to create translation table but not to add translated field to an existing translations table. You must add columns like columns in a generic table :

class AddFieldToProductTranslations < ActiveRecord::Migration
  def up
    add_column :product_translations, :some_field, :string
  end

  def down
    remove_column :product_translations, :some_field
  end
end

But in some cases, this can cause some problems because fields are present in the model (in translates method) but not in the database and migrations failed… For example, if you add some fields in this method in your controller :

class Product < ActiveRecord::Base
  translates :name, :description, :benefits, :warranty_informations
end

And this fields are added to the database in several migrations this can cause issues. In order to avoid this issues, you can do this :

# in your model

class Product < ActiveRecord::Base
	TRANSLATED_FIELD = [
    :name, :description, :benefits, :warranty_informations
  ].freeze

  translates *TRANSLATED_FIELD
end
# in your migrations

class AddTranslationsForProducts < ActiveRecord::Migration
  include Globalize::ActiveRecord::Migration
  class Product < ActiveRecord::Base
    @translated_fields = {
      :name => :string,
      :description => :text
    }

    def self.translated_fields
      @translated_fields
    end

    translates *@translated_fields.keys
  end

  def up
    # create translation table
    Product.reset_column_information
    Product.create_translation_table!(Product.translated_fields, :migrate_data => true)
  end

  def self.down
    # drop translations table
    Product.drop_translation_table!
  end
end

And next, in the other migrations (to add fields) yo can do like that :

class AddFieldsToProductTranslation < ActiveRecord::Migration
  include Globalize::ActiveRecord::Migration
  class Product < ActiveRecord::Base
    @translated_fields = { :benefits => :string, :warranty_informations => :string }

    def self.translated_fields
      @translated_fields
    end

    translates *@translated_fields.keys
  end

  def self.up
    # Adding the columns
    # add_column :product_translations
    Product.reset_column_information
    Product.translated_fields.each_pair { |field, type| add_column :product_translations, field, type }
  end

  def self.down
    # Do stuff to put the data back using the migrator from Globalize
    Product.reset_column_information

    # And remove unused columns
    Product.translated_fields.keys.each { |field| remove_column :product_translations, field }
  end
end

In some cases you may also need to add integer fields in your translations tables but Globalize3 only accepts string and text fields. In this case, you can not add this field with the create_translation_table! method, you need to use the example above.

The where clause

If on your model you have translated fields like name or description, be careful, you can not write this :

Product.where(:name => 'something')

If you do, there is an issue because Rails does not find the name column into products table.

To use the where clause you must do this :

Product.with_translations('en').where('product_translations.name' => 'something')

If there is only one condition, I advise you to use the find_by method, for example with the name column :

Product.find_by_name('something')

In this case, there is no problem, Rails join translations table and find the column in the right table.

Search engine and internationalization

Ransack is a gem that allows you to create search forms easily. Indeed, with this gem you can search objects in a model with different conditions. With a multi-languages application, you need to do the search in the correct language.

In your controller, you need to add the with_translations method :

class ProductsController < ApplicationController
  def index
	  @search   = Product.with_translations(I18n.locale).search(params[:q])
	  @products = @search.result(distinct: true)
  end
end

In this case, your search is made with the correct language.

In the search form, you can made the search on translated fields :

<%= search_form_for @search do |f| %>
  <%# name and description are translated fields %>
  <%= f.text_field :translations_name_or_translations_description_cont %>
  <%= f.submit %>

We must specify translations in the form because this fields are in the translations table. The search is made with the locale defined in the controller.

Conclusion

Globalize3 is a good gem for internationalization, it is a good complement of I18n. If you have to translate fields in different languages this is very helpful.

The Synbioz Team.

Free to be together.