Ruby-GetText-Package HOWTO for Ruby on Rails
ruby-gettext-howto-rails
Note: This tutorial requires Ruby-GetText-Package-1.2.0 or later
Ruby-GetText-Package supports Ruby on Rails powerfully.
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 refered.
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 recommanded.
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.
$KCODE = 'u' # Needless since 2.0. require 'jcode' : : require 'gettext/rails'
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 to edit 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 to edit 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 overrided 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.
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", _("%{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._().
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", _("%{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 obivously.
# 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 localized your Rails Plugins easily even if it's 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'
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'
GetText.create_mofiles(true, "po", "locale")
end
See HOWTO maintain po/mo files for more details.
the parser options for Models
Usually you don't need to care about this section, but you can also change the behavior 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.
- config: a Hash of the config. It can takes some values below:
"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 need to use it only 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 tha you can't GetText.locale= here. Because your application include GetText::Rails module and it's functions instead of GetText.
Make Ruby-GetText as an option of your app
Sometimes you may want to work 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 a good idea now because both of 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-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.
Keyword(s):
References:[Ruby-GetText-Package HOWTO] [Ruby-GetText-Package document for Developers] [HOWTO for Rails Engines]