現在、Railsに対応した国際化の仕組みがいくつかあります。しかし、それぞれが 独自の方法で実現しているため、それらを組み合わせて使うと混沌 とした状態に陥ることも少なくありません。
ここでは、モデルから動的にきれいな画面とコントローラ部分を生 成するActiveScaffoldを用 いた場合の国際化(i18n)と地域化(l10n)の実現方法のひとつを 紹介します。この方法では、 ActiveScaffoldLocalize と Ruby-GetText-Package を組み合わせます。混沌とする部分はそれなりになじませます。
Railsで使用できる国際化の仕組みの比較はRails Wiki (英語)が詳しいです。
Ruby-GetText-Package には、以下のような地域化対象のメンテナン スのことを考慮した機能があるので、地域化対象メッセージが増加 したり更新される場合には有力な候補になるでしょう。
Railsやプラグインなどが提供しているメッセージだけを地域化した いなど、地域対象メッセージが変化しない場合はその他の仕組みも 有力な候補になるでしょう。例えば、ActiveScaffold用の ActiveScaffoldLocalizeがその場合です。
ActiveScaffoldは、国際化の仕組みとしてObject#as_を提供してい ます。その仕組みを利用して国際化・地域化を実現しているのが ActiveScaffoldLocalizeです。
ActiveScaffoldLocalizeには日本語用のメッセージも含まれている ので、以下のようにすればActiveScaffoldのメッセージを日本語に することができます。
% rails shelf % cd shelf % script/generate resource book title:string % rake db:migrate % script/plugin install git://github.com/activescaffold/active_scaffold.git % script/plugin install git://github.com/edwinmoss/active_scaffold_localize.git
config/routes.rb:
- map.resources :books + map.resources :books, :active_scaffold => true
app/controllers/books_controller.rb:
class BooksController < ApplicationController active_scaffold :book end
app/views/layouts/application.html.erb:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="content-type" content="text/html;charset=UTF-8" /> <title>ActiveScaffold l10n</title> <%= javascript_include_tag(:defaults) %> <%= active_scaffold_includes %> </head> <body> <h1>ActiveScaffold l10n</h1> <%= yield %> </body> </html>
app/controllers/applications.rb:
class ApplicationController < ActionController::Base # ... private before_filter :localize_active_scaffold def localize_active_scaffold ActiveScaffold::Localization.lang = "ja-jp" true end end
サーバを起動してhttp://localhost:3000/books/にアクセスします。
% script/server % firefox http://localhost:3000/books/
見ての通り、「検索」などのメニューは日本語になりますが、テー ブル名からきている「Books」やカラム名の「Title」などは日本語 になりません。
ActiveScaffoldLocalizeの方針では、これらを日本語にするために以下のような内容の config/initializers/lang/ja-jp.rb*1を作成します。
config/initializers/lang/ja-jp.rb:
# -*- coding: utf-8 -*- ActiveScaffold::Localization.define('ja-jp') do |lang| lang["Books"] = "本一覧" lang["Book"] = "本" lang["Title"] = "タイトル" end
config/initializers/以下を変更したので、サーバを再起動してか ら再度アクセスすると、日本語で表示されます。
(「本を作成」ではなく「本一覧を作成」になっているのはこ のパッチ で直ります。)
ActiveScaffoldLocalizeのこのやり方は手軽ですが、地域化対象の メッセージが変更になった場合(例: 「Title」から「Name」に変更) や、地域化対象のメッセージをtypoした場合(例: 「Title」ではな く「title」としていた)に気づきにくいという問題があります。 このような問題に対してはRuby-GetText-Packageが有効です。
ということで、ActiveScaffoldのメッセージは ActiveScaffoldLocalizeで地域化し、それ以外は Ruby-GetText-Packageで地域化するようにします。
ActiveScaffoldLocalizeとRuby-GetText-Packageのすみわけは上述 の通りですが、エラーメッセージの地域化はRuby-GetText-Package ではなく、ActiveScaffldLocalizeに任せます。これは、 ActiveScaffoldがエラーメッセージ部分を上書きしているため、 Ruby-GetText-Packageが提供するエラーメッセージ国際化処理とな じまないためです。
また、Ruby-GetText-Packageが取得したロケール情報を使って ActiveScaffoldLocalizeのlangを設定していることもコツのひとつ です。
config/environment.rb:
# ... Rails::Initializer.run do |config| # ... config.gem "gettext", :lib => "gettext/rails" # ... end
lib/active_scaffold_gettext.rb:
module ActiveScaffoldGetText include GetText::Rails bindtextdomain(GETTEXT_DOMAIN) end class Object def as__with_gettext(message, *args) return nil if message.nil? localized_message = ActiveScaffoldGetText.send(:sgettext, message) if localized_message == message as__without_gettext(message, *args) else localized_message % args end end alias_method_chain :as_, :gettext end module ActiveScaffold::DataStructures class Column def initialize_with_gettext(name, active_record_class) initialize_without_gettext(name, active_record_class) self.label = "#{active_record_class.name.demodulize}|#{@label.humanize}" end alias_method_chain :initialize, :gettext end end
config/initializers/gettext.rb:
GETTEXT_DOMAIN = "your-rails-application" require 'active_scaffold_gettext' class ActiveRecord::Errors # restore default error messages overridden by Ruby-GetText-Package. @@default_error_messages = { :inclusion => "is not included in the list", :exclusion => "is reserved", :invalid => "is invalid", :confirmation => "doesn't match confirmation", :accepted => "must be accepted", :empty => "can't be empty", :blank => "can't be blank", :too_long => "is too long (maximum is %d characters)", :too_short => "is too short (minimum is %d characters)", :wrong_length => "is the wrong length (should be %d characters)", :taken => "has already been taken", :not_a_number => "is not a number", :greater_than => "must be greater than %d", :greater_than_or_equal_to => "must be greater than or equal to %d", :equal_to => "must be equal to %d", :less_than => "must be less than %d", :less_than_or_equal_to => "must be less than or equal to %d", :odd => "must be odd", :even => "must be even" } alias_method :on, :on_without_gettext alias_method :[], :on end
lib/tasks/gettext.rb:
namespace :gettext do namespace :po do desc "Update pot/po files." task :update => :environment do require 'gettext/utils' module GetText::ActiveRecordParser class << self alias_method :add_target_original, :add_target def add_target(targets, file, msgid) if /\|/ !~ msgid add_target_original(targets, file, msgid.classify) add_target_original(targets, file, msgid.classify.pluralize) end add_target_original(targets, file, msgid) end end end targets = Dir.glob("{app,config,components,lib}/**/*.{rb,erb,rjs}") GetText.update_pofiles(GETTEXT_DOMAIN, targets, "#{GETTEXT_DOMAIN} 0.0.1") end end namespace :mo do desc "Create mo-files" task :create do require 'gettext/utils' GetText.create_mofiles(true, "po", "locale") end end end
app/controllers/application.rb:
class ApplicationController < ActionController::Base init_gettext GETTEXT_DOMAIN # ... private before_filter :localize_active_scaffold def localize_active_scaffold posix_locale = GetText.locale.to_posix posix_locale = "#{posix_locale}-#{posix_locale}" if /_/ !~ posix_locale lang = posix_locale.gsub(/_/, '-').downcase ActiveScaffold::Localization.lang = lang true end end
翻訳メッセージのファイルpoを作って翻訳します。
% rake gettext:po:update % mkdir po/ja % msginit -i po/your-rails-application.pot -o po/ja/your-rails-application.po -l ja_JP # 途中でメールアドレスを聞かれるので入力する
po/ja/your-rails-application.po:
# ... #: app/models/book.rb:- msgid "Book" msgstr "本" #: app/models/book.rb:- msgid "Books" msgstr "本一覧" # ... #: app/models/book.rb:- msgid "Book|Title" msgstr "タイトル" # ...
翻訳メッセージをmoにコンパイルしてアクセスするとテーブル名や カラム名などが日本語になります。
% rake gettext:mo:create
config/initializers/以下を変更したので、サーバを再起動してか ら再度アクセスすると、日本語で表示されます。
ActiveScaffoldLocalizeとRuby-GetText-Packageを使って、 ActiveScaffoldを用いたアプリケーションの国際化・地域化を実現する方法 のひとつを紹介しました。
基本的に複数の国際化のしくみを同時に使うと問題が起きますが、 今回は以下のようにそれぞれの長所を活かすようにすみわけて、問 題を回避しています。
*1 config/initializers/lang/以下にファイルを作るというのはActiveScaffoldLocalizeの方針ではありません。ファイルの場所は特に方針はないようです。