現在、Railsに対応した国際化の仕組みがいくつかあります。しかし、それぞれが 独自の方法で実現しているため、それらを組み合わせて使うと混沌 とした状態に陥ることも少なくありません。
ここでは、モデルから動的にきれいな画面とコントローラ部分を生 成するActiveScaffoldを用 いた場合の国際化(i18n)と地域化(l10n)の実現方法のひとつを 紹介します。この方法では、 ActiveScaffoldLocalize と Ruby-GetText-Package を組み合わせます。混沌とする部分はそれなりになじませます。
国際化の仕組み
Railsで使用できる国際化の仕組みの比較はRails Wiki (英語)が詳しいです。
Ruby-GetText-Package には、以下のような地域化対象のメンテナン スのことを考慮した機能があるので、地域化対象メッセージが増加 したり更新される場合には有力な候補になるでしょう。
- 地域化対象のメッセージを抽出する機能
- テーブルにカラムを追加した場合、画面に表示するメッセージを追加・更新した場合などに利用
- 抽出したメッセージを既存の翻訳済みメッセージにマージする機能
- 翻訳者が新しい地域化対象のメッセージを翻訳する場合に利用
- gettext用の翻訳支援ツールを利用可能(.po
のフォーマットがGNU gettextと互換性があるため)
- Emacs用のpo-modeや.po専用のエディタ
Railsやプラグインなどが提供しているメッセージだけを地域化した いなど、地域対象メッセージが変化しない場合はその他の仕組みも 有力な候補になるでしょう。例えば、ActiveScaffold用の ActiveScaffoldLocalizeがその場合です。
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.rb1を作成します。
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で地域化するようにします。
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を用いたアプリケーションの国際化・地域化を実現する方法 のひとつを紹介しました。
基本的に複数の国際化のしくみを同時に使うと問題が起きますが、 今回は以下のようにそれぞれの長所を活かすようにすみわけて、問 題を回避しています。
- ActiveScaffoldが利用する固定のメッセージは ActiveScaffoldLocalizeで地域化
- モデル関連や追加・更新が行われるメッセージについては Ruby-GetText-Packageで地域化
-
config/initializers/lang/以下にファイルを作るというのはActiveScaffoldLocalizeの方針ではありません。ファイルの場所は特に方針はないようです。 ↩