Ruby-GetText-Package HOWTO for Ruby on Rails
ruby-gettext-howto-rails
[Back]
Note: This tutorial is for Rails-2.3.2 or later and Ruby-GetText-Package-2.0.0 or later
This tutorial explains how to use Ruby-GetText-Package on Ruby on Rails-2.3.2 with small blog application which include gettext_rails as the sample(See gettext_rails-x.x.x/sample/ in the gem or github.com.)
Table of Contents
- Table of Contents
- Let's start to develop L10n "blog" application
- Install gettext_rails gems
- Edit the files
- Run application
- Tips, FAQ
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 - 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.
Install gettext_rails gems
(su) $ gem install gettext_rails
Edit the files
config/environment.rb
Rails::Initializer.run do |config| : : config.gem "locale" config.gem "locale_rails", :version => '2.0.4' config.gem "gettext" config.gem "gettext_activerecord", :version => '2.0.4' config.gem "gettext_rails", :version => '2.0.4' end
locale_rails, gettext_activerecord and gettext_rails depend on Rails version(because of monkey patching), so it's better to set the version obviously. See 'Support Matrix' section of README.rdoc what rails version is supported.
ApplicationController
app/controller/application_controller.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/articles_controller.rb:
class ArticlesController < ApplicationController init_gettext "blog" : : end
Note Ruby-GetText-Package sets the HTTP header's content-type 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/articles_controller.rb:
class ArticlesController < 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 such as _(), n_(), N_(), Nn_(). This sample shows the code for a view.
app/views/articles/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
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/articles/list.html.erb /app/views/articles/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_("%{attribute} can't be empty!")
validates_length_of :description, :minimum => 10, :message => N_("%{attribute} is too short (min is %{count} characters)")
protected
# Your own validations (Use _() instead of N_()).
def validate
unless title =~ /\A[A-Z]+\z/
errors.add("title", _("%{attribute} is not correct: %{title}") % {:title => title})
end
end
# Your own validations with the lazy evaluation (Use :default => N_()).
# Use this if you need to show error messages in 2 or more languages.
# (gettext_activerecord-2.0.2 or later)
def validate
unless title =~ /\A[A-Z]+\z/
errors.add("title", :default => N_("%{attribute} is not correct: %{value}"), :value => title)
end
end
end
%{attribute} means "field name". You can use it freely. If you omit %{attribute}, 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._().
%{value} for validators
validates_(format|inclusion|exclusion)_of accepts %{value} as the value.
validates_inclusion_of :name, :in => %w(a, b), :message => N_("%{attribute} can't be %{value}")
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_("%{attribute} 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
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
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
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
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>
Localized Routing
Using ":lang" in config/routes.rb, you can use localized URLs.
# config/routes.rb ActionController::Routing::Routes.draw do |map| # Localized Routing. map.connect '/:lang/:controller/:action/:id' map.connect '/:lang/:controller/:action/:id.:format' end
This :lang is used as param[:lang] and it is treated as the highest priority locale.
The URL of this routing is below:
http://www.foobar.com/ja_JP/foos/show http://www.foobar.com/fr/foos/show
Then, url_for, link_to and foos_path are:
link_to "Click", :controller => :foos, :action => :show, :lang => lang url_for :controller => :foos, :action => :show, :lang => lang url = foos_path(:lang => lang)
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
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.
Rakefile
Create lib/tasks/gettext.rake like as:
desc "Update pot/po files."
task :updatepo do
require 'gettext_rails/tools' #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_rails/tools' #HERE!
GetText.create_mofiles
end
See HOWTO maintain po/mo files for more details.
Put "require 'gettext_rails/tools'" line into the task blocks. If you put the line on the top of Rakefile, some other tasks will be failure.
Note to use gettext_rails/tools with rake gems:unpack
Add Rails::GemDependency.add_frozen_gem_path on the top of Rakefile.
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/tools'
GetText::ActiveRecordParser.init(:use_classname => false, :db_mode => "development") # default db_mode is development.
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_classes: an Array of the module/class names. These modules/classes are ignored as the msgids. Default is ["ActiveRecord::Base", "ActiveRecord::SessionStore::Session"]].
- :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 only need to use it in your language then set GetText.locale= first.
class ApplicationController < ActionController::Base GetText.locale = "ja" init_gettext "myapp" end
Note that you need to call GetText.locale= before init_gettext.
Restrict the locales
Use locale_rails's I18n.supported_locales to restrict the locales.
I18n.supported_locales = Dir[File.join(RAILS_ROOT, "locale/*")].collect{|v| File.basename(v)}
To set the default locale except "en", use I18n.default_locale.
I18n.default_locale = "ja"
Even if the default_locale isn't in I18n.supported_locales, default_locale is used as the last fallback locale.
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 the language from
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.
Keyword(s):
References:[Ruby-GetText-Package HOWTO] [Ruby-GetText for Ruby on Rails] [Ruby-GetText-Package document for Developers] [HOWTO for Rails Engines] [Ruby-GetText-Package HOWTO for Ruby on Rails (until Rails-2.1.x)]