Ruby-GetText-Package HOWTO for Ruby on Rails (until Rails-2.1.x)

ruby-gettext-howto-rails-old

Note: This tutorial is for Ruby-GetText-Package-1.93.0 and Rails-2.1.x

[Ruby-GetText-Package HOWTO for Ruby on Rails(since 2.3.2)]

The features for Rails are:

  • Translates Controllers/Views.
  • Translates Models(table names, column names)
  • Singular/plural messages in each locales.
  • Auto finding the client locale
  • Rake task to extract strings automatically.
  • multi-textdomain supported. You can bind plural textdomains to a class/module.
  • Action/Fragment Caching in each locales.
  • Translates Rails error messages.
  • Some helper functions are localized.

This tutorial explains how to use Ruby-GetText-Package on Ruby on Rails-2.0.x with small blog application which include Ruby-GetText-Package as a sample(See ruby-gettext-x.x.x/samples/rails/ in the tar-ball.)

Using Gettext To Translate Your Rails Application is the first and great tutorial for Ruby-GetText-Package and Ruby on Rails by Sascha Ebach which I referred.

Please check the tutorial because it mentions some useful things which aren't mentioned here.

Let's start to develop L10n "blog" application

The application's information on this tutorial

  • Appliction name: blog
  • Textdomain name: blog
  • Version: 1.0.0
  • charset: UTF-8 - Almost of all cases, UTF-8 is recommended.

It has a table the name is "articles":

# db/schema.rb:
ActiveRecord::Schema.define(:version => 1) do
 create_table "articles", :force => true do |t|
   t.string   "title",       :default => "", :null => false
   t.text     "description", :default => "", :null => false
   t.date     "lastupdate"
   t.datetime "created_at"
   t.datetime "updated_at"
 end
end

Let's create the database and generate "blog" application using script/generate.

Edit the files

Edit rb/rhtml files to use Ruby-GetText-Package.

config/environment.rb

Set charset first. And require 'gettext/rails' at bottom of the line.

(2.0 or earlier)

$KCODE = 'u'   # Needless since 2.0. 
require 'jcode'
 :
 :
require 'gettext/rails'

(2.1 or later)

Rails::Initializer.run do |config|
 :
 :
 config.gem "gettext", :lib => "gettext/rails"
end

ApplicationController

All of applications need to call init_gettext.

app/controller/application.rb:

class ApplicationController < ActionController::Base
  init_gettext "blog"
end

That's all! Now you can find the validation messages are localized.

init_gettext options

You can also set the charset and content_type here.

ActionController::Base.init_gettext(textdomain, options = {})
Bind 'textdomain' to all of the controllers/views/models.
  • textdomain: the textdomain
  • options:
    • :charset - the output charset. IANA character sets are recommanded. This value is used not only outputting the messages in the charset, but also the charset of "Content-Type" in the HTTP Header. Default is "UTF-8".
    • :content_type - the content type. Default is "text/html"
    • :locale_path - the path to locale directory. Default is RAILS_ROOT or plugin root directory.

If you want to separate the textdomain each controllers, you need to call init_gettext in the each controllers.

app/controller/blog_controller.rb:

class BlogController < ApplicationController
  init_gettext "blog"
  :
  :
end

Note Ruby-GetText sets the charset correctly. So you shouldn't set the charset by yourself like as follows:

#Needless!
before_filter :set_charset
def set_charset
  headers['Content-Type'] = "text/html;charset=utf-8"
end

Controllers

Since this section, you can use these functions as you need.

Edit controllers/views using GetText methods. This sample shows the code for a controller.

app/controller/blog_controller.rb:

class BlogController < ApplicationController
   :
   :
  def create
    @article = Article.new(params[:article])
    if @article.save
      flash[:notice] = _('Article was successfully created.')  #Here!
      redirect_to :action => 'list'
    else
      render :action => 'new'
    end
  end
   :
   :
end

Views/ActionMailer

Views/ActionMailer are also can use GetText methods. This sample shows the code for a view.

(Note ActionMailer has been supported since 1.3.0).

app/views/blog/edit.html.erb:

<h1><%= _('Editing article') %></h1>

<%= error_messages_for :article %>

<% form_for(@article) do |f| %>
  <p>
    <p><%= f.date_select :lastupdate, :use_month_numbers => true %></p>
    <p><%= f.text_field :title %></p>
    <p><%= f.text_area :description %></p>
    <p><%= f.submit _("Edit") %></p>
  </p>
<% end %>
<p>
 <%= link_to _('Show'), @article %> |
 <%= link_to _('Destroy'), @article, :confirm => _('Are you sure?'), :method => :delete %> |
 <%= link_to _('Back'), articles_path %>
</p>

error_messages_for, error_message_on are also localized (you don't need to do anything here).

Localized templates for Views/ActionMailer

Since 1.3.0 ActionController::Base.render_text is overridden to find localized templates such as foo_ja.hml.erb, foo_ja_JP.html.erb.

(e.g.)

/app/views/blog/list.html.erb
/app/views/blog/list_ja.html.erb

In this case, list_ja.html.erb is used when language is "ja" only. Otherwise list.rhml is used. You can use this way for render_partial. Use _foo.html.erb, _foo_ja.html.

Models

All of the table names(class name) and field names are extracted as msgids to the po-file(the format is "ClassName|FieldName", (e.g.) "Article|Title"). So you don't need to do anything. Default validation/error messages are also localized automatically.

If you want to add your own validation messages, you can also use GetText way:

# app/models/article.rb:
class Article < ActiveRecord::Base
  # Simple 
  validates_presence_of :title
  validates_length_of :description, :minimum => 10

  # With messages (Use N_() here)
  validates_presence_of :title, :message => N_("%{fn} can't be empty!"
  validates_length_of :description, :minimum => 10, :message => N_("%{fn} is too short (min is %d characters)")

  # Your own validations (Use _() instead of N_()).
  protected
  def validate
    unless title =~ /\A[A-Z]+\z/
      errors.add("title", N_("%{fn} is not correct: %{title}") % {:title => title}) 
    end   
  end   
end

%{fn} means "field name". You can use it freely. If you omit %{fn}, the field name is precede the message. %d is the number of the message which is used with validates_length_of.

Custom messages in validates_* or set_error_message(title|explanation) should be used GetText.N_(), not GetText._().

%{val} for validators

Since 1.92.0/Rails-2.1.0 validates_(format|inclusion|exclusion)_of accepts %{val} as the value.

validates_inclusion_of :name, :in => %w(a, b), :message => N_("%{fn} can't be %{val}")

Translate the attributes which doesn't exist in your Database

Use N_() like as N_("ClassName|Attribute").

(e.g.1) the User table which doesn't have both of the password and password_confirmation fields.

class User < ActiveRecord::Base
  attr_accessor :password, :password_confirmation

  N_("User|Password")
  N_("User|Password confirmation")

  validates_presence_of :password

  def validate
    errors.add("password", N_("%{fn} is wrong!")) unless password == password_confirmation
  end
end

(e.g.2) has_one relationships.

class Person < ActiveRecord::Base
  has_one :profile
  validates_associated :profile
  N_('Person|Profile')
end

Untranslation option

Since 1.7.0 You may want to prevent to extract some table names(class names) and field names to the po-file. Because those table names/field names won't appear on HTML.

In this case, you can use untranslate, untranslate_all methods.

class Article < ActiveRecord::Base
  untranslate :title, :description
end

class Article < ActiveRecord::Base
  untranslate_all
end

untranslate(*fields) is for each fields in the Model. untranslate_all is for whole the Model.

STI models

The table/field names of STI models(e.g. Manage < User < ActiveRecord::Base) won't appear the po-file. But to put the word "ActiveRecord::Base" in your model file, the file is handled as ActiveRecord::Base class.

# app/models/manager.rb
class Manager < User   # ActiveRecord::Base   # <- Important!
end

Translate the title/explanation on the error message dialog

Since 1.7.0 You can override your own title/explanation on the top of the error messages.

class ApplicationController < ActionController::Base
  init_gettext ....
  :
  :
  ActionView::Helpers::ActiveRecordHelper::L10n.set_error_message_title(
    N_("An error was found in %{record}"), N_("%{num} errors were found in %{record}"))
  ActionView::Helpers::ActiveRecordHelper::L10n.set_error_message_explanation(
    N_("The error is:"), N_("The errors are:"))
end

Since 1.90.0 . You also be able to set message_title/message_explanation as the parameter of error_messages_for. If you want to set these messages in each erb files.

# app/views/users/edit.html.erb
<%= error_messages_for 'user', {
   :message_title => Nn_("Singular Custom Error message %{record}: %{num}", "Plural Custom Error message %{record}: %{num}"),
   :message_explanation => Nn_("Singular Custom Error explanation %{num}", "Plural Custom Error explanation %{num}")
} %>

ActionView::Helpers::FormBuilder#label localization

Since 1.91.0 It's simple but useful ;).

# app/views/article/edit.html.erb
<% form_for(@article) do |f| %>
  <p><%= f.label :title %></p>
<% end %> 
=> <p><label for="article_title">(Translated)Title</label></p>

In this case, "Article|Title"(which is extracted as the msgid of "Article" model) is used as the msgid and the translated string is shown as the label.

Of course, you can use your own localized message with label method.

# app/views/article/edit.html.erb
<% form_for(@article) do |f| %>
  <p><%= f.label :title, _("Foo Bar Title") %></p>
<% end %> 
=> <p><label for="article_title">(Translated)Foo Bar Title</label></p>

ActionWebService

Since 1.4.0 . You can use Ruby-GetText-Package with ActionWebService. Notice that clients need to send "lang" value obviously.

# app/apis/product_api.rb
class ProductApi < ActionWebService::API::Base
  api_method :find_product_by_id, :expects => [:lang => :string], :returns => [{:string => String}]
end

# app/controller/backend_controller.rb
class BackendController < ApplicationController
  wsdl_service_name 'Backend'
  web_service_api ProductApi
  def find_product_by_id(id)
   _("foo") + ...
  end
end

Routing

Sets the :lang in config/routes.rb.

ActionController::Routing::Routes.draw do |map|
 # http://yourhost/main_ja/ calls the blog controller with list action, and localized in Japanese
 map.connect 'main_ja', :lang=>'ja', :controller=>'blog', :action=>'list'

 # http://yourahost/blog/ja/list/ calls the blog controller with list action, and localized in Japanese.
 map.connect ':controller/:lang/:action/:id'
end

Thus, Ruby-GetText-Package uses @params["lang"] value first.

Rails Engines

See Ruby-GetText-Package HOWTO for Ruby Engines

Rails Plugins

You can localize your Rails Plugins easily even if they do not depend on Rails Engines.

require 'gettext/rails'

module FooPlugin
  include GetText::Rails

  bindtextdomain("foo_plugin", :path => File.join(RAILS_ROOT, "vendor/plugins/foo/locale"))
  :
  :
end

In this example, the FooPlugin module binds "foo_plugin" textdomain. And the mo files are in vendor/plugins/foo/locale/#{lang}/LC_MESSGES/foo_plugin.mo.

If you want to use the same textdomain with the application, you can use ActionController::Base.textdomainname.

Rakefile

Create lib/tasks/gettext.rake like as:

desc "Update pot/po files."
task :updatepo do
  require 'gettext/utils'  #HERE!
  GetText.update_pofiles("blog", Dir.glob("{app,lib,bin}/**/*.{rb,erb,rjs}"), "blog 1.0.0")
end

desc "Create mo-files"
task :makemo do
  require 'gettext/utils'  #HERE!
  GetText.create_mofiles(true, "po", "locale")
end

See HOWTO maintain po/mo files for more details.

Put "require 'gettext/utils'" line into the task blocks. If you put the line on the top of Rakefile, some other tasks will be failure.

the parser options for Models

Usually you don't need to care about this section, but you can also change the behaviour of the rgettext parser for Models.

Add(Change) the "updatepo" task below to Rakefile:

desc "Update pot/po files."
task :updatepo do
  require 'gettext/utils'
  GetText::ActiveRecordParser.init(:use_classname => false, :db_mode => "production")
  GetText.update_pofiles("blog", Dir.glob("{app,lib,bin}/**/*.{rb,erb,rjs}"), "blog 1.0.0")
end
GetText::ActiveRecordParser.init(config)
Set some preferences to parse Model files.
  • config: a Hash of the config. It can takes some values below:
    • :db_yml: the path of database.yml. Default is "config/database.yml".
    • :db_mode: the mode of the database. Default is "development"
    • :activerecord_classes: an Array of the superclass of the models. The classes should be String value. Default is ["ActiveRecord::Base"]
    • :untranslate_columns: an Array of the field names. These fields are ignored as the msgids. Default is ["id"].
    • :use_classname: If true, the msgids of ActiveRecord become "ClassName|FieldName" (e.g. "Article|Title"). Otherwise the ClassName is not used (e.g. "Title"). Default is true.

"ClassName|FieldName" uses GetText.sgettext. So you don't need to translate the left-side of "|". See Documents for Translators for more details.

Run "updatepo" and "makemo" tasks

To create po/blog.pot, run "updatepo" task.

$ rake updatepo

To create mo-files, run "makemo" task.

$ rake makemo

To update po/{lang}/blog.po, run "updatepo" task again.

$ rake updatepo

See HOWTO maintain po/mo files for more details.

Run application

Did you run "rake makemo"? Then, your application has been localized.

Let's try your application!

Run blog application:

$ script/server

Then, access http://localhost:3000/blog/ via WWW browser. The application shows localized message what WWW browser requested.

If you want to try other languages, use "lang" parameter as follows:

http://localhost:3000/blog/?lang=ja_JP

See How to get the locale information from WWW borwser for more detail.

Tips, FAQ

Run your application in a language only

If an application supports L10n but you only need to use it in your language then set GetText.locale= first.

class ApplicationController < ActionController::Base
  GetText.locale = "ja"
  init_gettext "myapp"
end

Notice that you need to call GetText.locale= before init_gettext.

before/after_init_gettext

Since1.8 These methods are same with before/after_filter. The functions are called before/after initializing gettext on each request(get the user locale, select textdomain of the user locale).

(e.g.1) Set default language except English

If you want to run your application in a language, see previous section, but you want to keep the ability to change to any other languages, too. Try this sample:

class ApplicationController < ActionController::Base
  before_init_gettext :default_locale
  def default_locale
    if (cookies["lang"].nil? or cookies["lang"].empty?)
      set_locale "zh_CN"
    else
      set_locale cookies["lang"]
    end
  end 
  init_gettext "myapp"
end

This sample uses cookie. So you need to set your locale to the cookie in other places like as user-preference page. And also you may want to use session or database not cookie.

(e.g.2) Initialize other localized library

class ApplicationController < ActionController::Base
 after_init_gettext :ya_l10n
 def ya_l10n
   YetAnotherL10n.set_locale(locale)
 end
 init_gettext "myapp"

Set the locale in a View(or multi-lingual content in same view)

If you want to set the locale in a View, use set_locale method.

<% set_locale "fr" %>
<%=_("Hello world") %>
<% set_locale "en" %>
<%=_("Hello world") %>

Note that you can't GetText.locale= here. Because your application includes GetText::Rails module and it's functions instead of GetText.

Make Ruby-GetText as an option of your app

Sometimes you may want to use your application without Ruby-GetText if the environment doesn't have Ruby-GetText.

I wrote a sample "pseudo_gettext.rb". Include this in your application, and require this instead of "gettext/rails".

Caching

You can use Action/Fragment caches. The functions are extended by Ruby-GetText to manage data in each locales.

Page caching is not supported. I don't have any idea how do this now because both the http server and the rails app need to help each other to return the cache/non cache data. Tell me if you have good idea about Page caching.

ChangeLog

  • 2008-09-01 Changed some typos. - mtkd
  • 2008-08-02 Added 1.92.0(and Rails-2.1.0) features.
  • 2008-05-12 Added a sample for ActionView::Helpers::FormBuilder#label.
  • 2008-03-12 Added has_one relationships by Eric Northam.
  • 2008-02-03 Revised to apply 1.90.0 features.
  • 2007-07-08 Added 1.10.0 features.
  • 2006-09-14 Added 1.8.0 features.
  • 2006-07-24 Added "Make Ruby-GetText as an option of your app"
  • 2006-07-22 Added "Translate the attributes which doesn't exist in your Database"
  • 2006-07-17 Added 1.7.0 features.
  • 2006-05-14 Added multi-lingual content in same view.
  • 2006-05-10 Added 2 Tips.
  • 2006-03-25 Added ActionWebService, Routing.
  • 2006-03-10 Custom messages in validates_* or set_error_message(title|explanation) should be used GetText.N_(), not GetText._().
  • 2006-03-08 ActionMailer, localized templates explanations were added for 1.3.0. - Masao.
  • 2006-03-02 Revised(Add an example of ActiveRecord::Base.set_error_message_(title|explanation). - Masao.
  • 2005-01-31 Added "Run application" section. - Masao.
  • 2005-12-26 Added. - Masao.
Last modified:2009/03/22 23:27:51
Keyword(s):
References:[Ruby-GetText-Package HOWTO for Ruby on Rails]