Rails3 + ActiveRecord で正しくtimezoneを設定する

アプリケーションのI18N対応をする中で出てくるタイムゾーン問題。今回はRails3環境で
ActiveRecordのTimezoneの設定をどのようにすべきかまとめてみました。


photo credit: Myxi via photo pin cc

Timezoneの問題はOS、DB、Frameworkといろいろ絡みうるので、一つ一つ行きたいと思います。

OSのTimezone

Amazon EC2などだったらそれぞれの地域のタイムゾーンで運用しているケースも
あるかと思いますが、JST(日本標準時)で運用しているところが多いのではないでしょうか。

$ date
Tue Jul  3 12:54:53 JST 2012
$ ls -l /etc/localtime
lrwxrwxrwx 1 root root 30 Jul  3 10:51 /etc/localtime -> /usr/share/zoneinfo/Asia/Tokyo

DB(MySQL)のTimezone

MySQLの場合、システムのタイムゾーンはローカル時間(JST)に、MySQLタイムゾーン
SYSTEM(つまりJST)になっています

$ mysql
> show variables like '%time_zone%';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | JST    |
| time_zone        | SYSTEM |
+------------------+--------+

OSのタイムゾーンを変更してDBを再起動するとタイムゾーンがOSにあわせて変更されている様子が分かります

# rm /etc/localtime; ln -s /usr/share/zoneinfo/UTC /etc/localtime
# date
Tue Jul  3 04:03:05 UTC 2012
# service mysqld restart
# mysql
> show variables like '%time_zone%';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | UTC    |
| time_zone        | SYSTEM |
+------------------+--------+

Rails3 + ActiveRecordのTimezone

結論からいうとDB自体のタイムゾーンは関係ありません。MySQLのtime_zoneがUTCであれ、
JSTであれActiveRecordで指定したタイムゾーンに変換された時刻が保存されます。
DBのNOW()を呼ぶのではなく、アプリーケーション側でタイムゾーン変換をしてるのでしょうね。

データ保存に利用される設定
  1. MyApp::Application.config.active_record.default_timezone
  2. ActiveRecord::Base.default_timezone
$ rails console
> MyApp::Application.config.active_record.default_timezone
=> nil
> ActiveRecord::Base.default_timezone
=> :utc

config/application.rbにて、何もTimezoneの設定をしていない場合、
MyApp::Application.config.active_record.default_timezoneがnilとなり、
ActiveRecord::Base.default_timezoneが:utcとなります。

最終的にはActiveRecord::Base.default_timezoneの値でDBに保存されるため、
この場合は:utcで保存されます。保存に利用するタイムゾーンを変更するには
config/application.rbにて、config.active_record.default_timezoneに
値を設定するとよいでしょう

# config/application.rb
config.active_record.default_timezone = :local

config.active_record.default_timezoneは:local もしくは :utcのいずれかを
設定することができます:localを指定するとOSのローカル時間で保存されます

$ rails console
> ActiveRecord::Base.default_timezone
=> :local

config/application.rbを修正したところです。 :localが設定されていることが分かります。
この状態でdatetimeを入力するとDBにはJSTで保存されていることが確認できます。
データの保存時のタイムゾーンはアプリケーションで統一せねばならず、かつその基準はUTCもしくは
OSのローカル時間であろうというとても合理的な設計になっています。

# config/application.rb
config.active_record.default_timezone = :utc
データ取得時に利用される設定
  1. MyApp::Application.config.time_zone
  2. Time.zone

データ取得時にはTime.zoneの値にしたがってデータが変換されます。
変換されるタイミングは、そのアトリビュートにアクセスしたタイミングになるため、注意してください
(変換時の基準となるDBのタイムゾーンには
MyApp::Application.config.active_record.default_timezoneが利用される)。
Time.zoneのデフォルト値を変更したい時は、config/application.rbのconfig.time_zoneを変更します

# config/application.rb
config.time_zone = 'Tokyo'

アクセスするユーザによってタイムゾーンを変更したい場合などは、before_filterなどで
セッション等からユーザ固有の設定タイムゾーンを取り出した上でTime.zoneを上書きするとよいと思います

結論:推奨設定

I18N対応のアプリケーション

以下の設定をした上で、ユーザごとにTime.zoneを設定する

# config/application.rb
config.time_zone = 'UTC'
config.active_record.default_timezone = :utc
日本のみで利用されるアプリケーション
# config/application.rb
config.time_zone = 'Tokyo'
config.active_record.default_timezone = :local

clang, llvm-gccの関係

clangとllvm-gccがごっちゃになってはまったので整理してみた。
Mac上でXcodeを使わずにObjective-Cのプログラムを書いた時のお話。
前提はMac OS X 10.7.3, Xcode 4.3.2です。

以下のようなmain関数からいくつかのクラスを呼び出すコードを書いて、
gccを使ってコンパイルした所エラーが発生。

#import "Song.h"
#import "Singer.h"

int main(void) {
  id song;
  id singer;

  song = [[Song alloc] init];
  [song setLyrics:@"HaHaHa"];

  singer = [[Singer alloc] init];
  [singer setSong:song];
  [singer sing];

  return 0;
}

$ gcc -o app main.m Song.m Singer.m -framework Foundation
objc[14770]: Object 0x100414a50 of class __NSCFData autoreleased with no pool in place - just leaking - break on objc_autoreleaseNoPool() to debug

LLVM-compiler3.0からARCが導入されているので、@autoreleasepool {}を使えばOKではないかと思い、

#import "Song.h"
#import "Singer.h"
#import 

int main(void) {
  @autoreleasepool {
    id song;
    id singer;

    song = [[Song alloc] init];
    [song setLyrics:@"Lala"];

    singer = [[Singer alloc] init];
    [singer setSong:song];
    [singer sing];

   }
  return 0;
}            

うまくいくかと思いきや、コンパイル時にエラー。
もしやgccがLLVM-compilerを使っていないかなと思いみてみると、

$ ls -l /usr/bin/gcc
lrwxr-xr-x  1 root  wheel  12  4 21 06:32 /usr/bin/gcc -> llvm-gcc-4.2
$ ls -l /usr/bin/llvm-gcc-4.2
lrwxr-xr-x  1 root  wheel  32  4 21 06:32 /usr/bin/llvm-gcc-4.2 -> ../llvm-gcc-4.2/bin/llvm-gcc-4.2
$ ls -l /usr/llvm-gcc-4.2/bin
rwxr-xr-x  1 root  wheel  127840  4 21 06:32 gcov-4.2
rwxr-xr-x  1 root  wheel  544448  4 21 06:32 i686-apple-darwin11-llvm-g++-4.2
rwxr-xr-x  1 root  wheel  544448  4 21 06:32 i686-apple-darwin11-llvm-gcc-4.2
rwxr-xr-x  1 root  wheel  117152  4 21 06:32 llvm-c++-4.2
rwxr-xr-x  1 root  wheel  257600  4 21 06:32 llvm-cpp-4.2
rwxr-xr-x  1 root  wheel  117152  4 21 06:32 llvm-g++-4.2
rwxr-xr-x  1 root  wheel  117152  4 21 06:32 llvm-gcc-4.2

うーん。ちゃんとllvmのgccを見ているもよう。ただ、i686-apple-darwin11-llvm-gcc-4.2がバイナリサイズも違うし怪しいと
思ったので念のためバージョンを確認。

$ /usr/llvm-gcc-4.2/bin/i686-apple-darwin11-llvm-gcc-4.2 -v
Using built-in specs.
Target: i686-apple-darwin11
Configured with: /private/var/tmp/llvmgcc42/llvmgcc42-2336.9~22/src/configure --disable-checking --enable-werror --prefix=/Applications/Xcode.app/Contents/Developer/usr/llvm-gcc-4.2 --mandir=/share/man --enable-languages=c,objc,c++,obj-c++ --program-prefix=llvm- --program-transform-name=/^[cg][^.-]*$/s/$/-4.2/ --with-slibdir=/usr/lib --build=i686-apple-darwin11 --enable-llvm=/private/var/tmp/llvmgcc42/llvmgcc42-2336.9~22/dst-llvmCore/Developer/usr/local --program-prefix=i686-apple-darwin11- --host=x86_64-apple-darwin11 --target=i686-apple-darwin11 --with-gxx-include-dir=/usr/include/c++/4.2.1
Thread model: posix
gcc version 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.9.00)

$ /usr/bin/llvm-gcc -v
Using built-in specs.
Target: i686-apple-darwin11
Configured with: /private/var/tmp/llvmgcc42/llvmgcc42-2336.9~22/src/configure --disable-checking --enable-werror --prefix=/Applications/Xcode.app/Contents/Developer/usr/llvm-gcc-4.2 --mandir=/share/man --enable-languages=c,objc,c++,obj-c++ --program-prefix=llvm- --program-transform-name=/^[cg][^.-]*$/s/$/-4.2/ --with-slibdir=/usr/lib --build=i686-apple-darwin11 --enable-llvm=/private/var/tmp/llvmgcc42/llvmgcc42-2336.9~22/dst-llvmCore/Developer/usr/local --program-prefix=i686-apple-darwin11- --host=x86_64-apple-darwin11 --target=i686-apple-darwin11 --with-gxx-include-dir=/usr/include/c++/4.2.1
Thread model: posix
gcc version 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.9.00)

中は同じっぽい??
遅ればせながらここでclangの存在を思い出したので、clangでコンパイル&実行したところエラーが解消。
$ clang -o app main.m Song.m Singer.m -framework Foundation

追加で調査したところ、以下のエントリーがよくまとまっていた。
http://blog.fenrir-inc.com/jp/2011/07/llvm.html

プロジェクトのビルド設定の Compiler Version に「LLVM Compiler 2.0」を指定すれば Clang をフロントエンドとして高速コンパイルや親切なエラーメッセージの恩恵を受けられます。

なぁにー、紛らわしい!結論としては、

  • Apple LLVM-Compilerというのはllvm-gccを指すのではなく、clangを指す。(clangもコンパイル時にはllvm-gccを利用しているのだろうけど、そのあたりの関係は引き続き調査が必要。)
  • @autoreleasepool {}などのARC関連の機能を利用するためには、clangでコンパイルする必要がある
  • 紛らわしいが、LLVM GCCApple LLVM-Compilerとは別ものでllvm-gccを指す。
  • MacOSXのデフォルトのコンパイラはllvm-gccとなっている。

ということですね。

慣れていればなんてことないのでしょうが、@autoreleasepool周りのエラーを調べてもおそらくXCode4.3以降では
Apple LLVM-Compilerを使っている人が多いせいか、海外の文献もあまり適切なものがなくハマってしまったので共有です。

ノマドの終焉と、これからの仕事環境について

昨今ノマド的な働き方が流行っているようですが、
個人的にはあまり生産性の高い仕事の仕方とは言えないと思います。
ということで唐突ですが、ノマド反対!w


photo credit: hyku via photopin cc

理由は簡単です。
ノマドワーカーになれる条件として以下の3つが挙げられています。

  • 集中力のコントロールができるということ
  • 情報をコントロールできるということと
  • 仲間とのコラボレーション(協調)をコントロールできること

けど、それだけ自律的に仕事ができる人ってそもそも付加価値が高くないですか?
そういった人材がこの3つをコントロールすることに労力を掛ける方が
仕事の効率のロスが大きいので、ノマドをできるぐらいの人は
ノマドをやらない方がお得というパラドックスになる
というわけです。
ノマド自体が目的化しているのであれば話は別ですが。

エンジニア以外の職種の方は分かりませんが、
少なくともものづくりの環境として重要なものは連続した思考の時間であり、
割り込みが少ない環境で仕事をすることだと思います。
ノマドというワークスタイルではそういった環境を常時は確保できませんよね。
カフェやコワーキングスペースで仕事をしていると、
ついついfacebookとかtwitterとか気になったり、
隣に座ったかわいい女の子に目が行ってしまいませんか?
ええ、すみませんが、少なくとも私は行ってしまいます(^^;

だからといって今までのオフィス環境の方がよいというわけでもない
というのも事実です。
ノマドというワークスタイルが出てきたのも、既存のオフィス環境が
仕事の内容の変化についていけなかったのがきっかけですし。
オフィス自体が、そもそも集中しにくく、情報をコントロールしにくく、
仲間とのコラボレーションもしにくいのであれば、
オフィスの外で仕事をすることで通勤時間を短くしたりしたくなる気持も分かります。

おそらく次の時代のワークスタイルや経営の方向性というのは、
働き手がそこで仕事をすることを自発的に最も効率的だと思える場のデザインを行うこと
ではないかと思います。そのためには、「集中」と「つながり」の両立がキーになると思っています。

今回、設立した10x Labはソフトウェアプロダクトを作るだけでなく、
プロダクトが生み出される環境を作ることも一つのミッションだと考えています。
例えば、
お互い視線が交錯しないようにデザインした机の配置だったり、
人が移動しても視界に入ってこないようにディスプレイを配置したり、
カフェ等では確保できない広いデスクトップ環境を整備したり、
長時間座っても疲れない椅子を準備したり、
割り込みが発生しないように電話を置かなかったり、
食事やちょっとした機会に集まれる場をつくったり、
他にもいろいろと環境に関する仮設のポイントをおいて設計していたります。
またこれらの取り組みの結果が出てきたらみなさんとも共有したいと思います。

設計にあたって、この分野に造詣が深いアドアルファさんに
協力をお願いしました。
私たちはソフトウェア工学的な観点から、
アドアルファさんはオフィスデザインの観点から、
双方意見を出し合いつつ今の形ができてきました。
業界を超えたコラボレーションは発見があって面白いですね。
この場を借りて御礼をさせて頂きます。

余談になりますが、実験であるというからには
定性的な評価が求められます。
ソフトウェアの生産性の計測が難しいというのは、以前のエントリーでもご紹介しましたが、
ソフトウェアメトリクスを取得する環境の整備できたら、
ワークスペースが生産性に与える検証も行って共有していきたいと思っています。
サンプルが多い方がいいので、ご興味ある方はぜひご一報ください!

雑談エントリー

社内で企画のCさんとつらつらと話をした内容をエントリーにまとめてみた。たまには雑談的な内容もいいかなと思いまして。

親の世代が定年後マレーシアに移住する計画をしてたりする。年金だけでも生活できる場所に移動しても生活できてしまう世の中になっている。ノマドが当たり前になっている。

けど、ノマドもそろそろ終わりかなと。ふわふわしたつながりより、密度の高い関係や集中が求められる時代になってくる。

映画の手法として最初の10分でオーディエンスのメンタルモデルを作る方法がある。Webサービスにもいかせるのでは?ユーザにすべて答えを与えてしまってはいけない。ユーザが知りたい、探ろうとする能動にヒントや答えをおいておくおもてなしが重要。

大学時代、6面投影ディスプレイで行った実験。落下する映像を360度投影すると人間は、本当に落下しているように錯覚する。宮崎駿は早く走る映像だとスピードを感じさせられないことに限界を感じて、上下に落下する映像をよく使っている。そっちの方が観客に魅せられるれるので。人間はだまされやすい。

ディスプレイの話ではないが、人間は錯覚の中で生きている。そもそも貨幣という仕組みもすごい錯覚。貨幣食べられないし。生きることが簡単になってきたので、死ぬまでの暇つぶしをしてる。そういうゲームになっているのでは。ソーシャルゲームを非難することは簡単だけど、一歩引いてみると人生自体がソーシャルゲーム化している。

ものは有限なので、仮想的なものに価値を見いだすように努力しないと経済が成長しない。感情の起伏にお金を払っている。エンターテイメント産業。幻想を共有することで、そこに価値があると言い切ってしまうことでしか、経済の成長を実感できなくなる傾向になっていく。今後の方向性としては、幻想を膨らませていく方向が規定路線、ダークホースとしては経済の成長という幻想から降りるという方向性もある。

交換ができる動物は人間だけ。交換の媒体が社会をデザインしている。評価交換経済とか言っている人がいるらしいがよくわからない。交換媒体としてユニバーサルな感じしないけど。まぁ、けど極論をいうのも大切か。

Facebookは開発者からすると魅力の少ないプラットフォーム。Microsoft, Apple, Googleに共通するのは、開発プラットフォームでどこまで開発者を囲い込めるかに心血を注いでいるのがわかるが、Facebookからはそれを感じない。ユーザ8億人いても、今後の展開意外と厳しいかもしれない。

Twitterの赤字について。もう取り返すことはできないのでは。一時メッセージのエントリーポイントとしての役割としてメールアドレス的になっていけるかもなと思ったが、だいぶスパムの嵐になっている。エントロピー増大の法則で、情報の精度が落ちて行く方向にあがらえない。

一度大きな図体になったシステムは、それを超える品質の高い小さなシステムにより代替されていく。アメリカが小さな政府を本気で目指すのであれば、まずはシンガポールを買収するところから始めるのがいいのでは。日本だったら四国?いや、大きすぎる。淡路島ぐらいの規模で、国の中に小さな実験国家を作りそこでいろいろ実験したいところ。

島国、一民族、一言語の国家って珍しい。日本はボリュームを追って外部から移民を受け入れるより、もっとこのアイデンティティにこだわって戦略をたてた方がいい。

ソニーのリストラについて、自分たちがソニーのCEOだったらどうしていたか?Appleでやれたことは本来ソニーでもできたはずなのに。要素技術の時代は終わった。全体の仕組みをデザインできないとこれからはつらい。エジソンがすごかったのは、エンジンや発電機を作ったからではなく、送電網までデザインしたのがすごい。仕組みをデザインする必要がある。iTunesって送電網だねぇ。

Chef-solo + Capistranoで簡単サーバ構成管理

サーバに対して何台も同じような設定をしていると、そんな刺身にたんぽぽのせるような仕事やってられるかー!となりますよね?特に最近だとクラウドや仮想化技術が身近になってきたので、環境をイメージコピーで構築する手法も増えているのではないかと思いますが、一方で、ハードやOSレベルでも技術が進化していくので、OSより上のレイヤー(ミドルウェアやアプリケーション)とOS以下のレイヤー(ハードウェアやOS)を粗結合にしておくことが重要だと思います。

OSより上のレイヤーのシステムの構成管理を自動化ツールとしてPuppetが有名でしたが、最近だとChefがRubyでスクリプトが書けて便利です。

ChefはChef-server, Chef-client, Chef-solo という3つの構成に分かれています。しかしChef-serverとChef-clientを利用した構成は構成がやや複雑になるので、中央で各サーバに対して指示を出す部分をCapistranoで行い、各サーバにはChef-soloを導入して各サーバ内でChef-soloを実行するという極力シンプルな構成にすることにしました。Chefが実行するスクリプトをRecipeと呼びますが今回はそれらのコードもGit上で管理する前提としています。構築の手順は以下の通り。

中央サーバにCapistranoをインストール
$ gem install capistrano
構成管理の対象サーバの設定

環境構築の自動化を目指しているので、これらのサーバはOSがインストールばかりのまっさらな環境であることを想定しています

  • ネットワーク / sshの設定

中央サーバからsshの公開鍵認証でアクセスできるようにします。/etc/sysconfig/network-scriptsや~/.ssh/authorized_keysなど適宜設定します。

  • Rubyとgitのインストール
$ wget http://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.3-p125.tar.gz
$ ./configure
$ make 
$ make install
$ yum install git

ここまでは、タンポポのせ作業ですが、我慢しましょう(^^;

Capistranoの設定
  • deploy.rbの準備

deploy.rbの設定例です。

role :web, "web001-desk001"                          # Your HTTP server, Apache/etc
role :app, "web001-desk001"                          # This may be the same as your `Web` server
role :db,  "db001-desk001", :primary => true # This is where Rails migrations will run
role :db,  "db001-desk001"

# Install Application and Middleware using Chef. (This task has to be execute by root user.)
set :chef_repo_path, '/usr/local/src/chef-repo'

namespace :chef do
  task :init do
    sync_chef_repo
    install_chef_solo
  end 

  task :sync_chef_repo do
    run "git clone git@github.com:twodollarz/chef-repo.git #{chef_repo_path}"
  end
  task :install_chef_solo do
    run "gem install chef"
  end

  task :default do
    chef_solo_base
    chef_solo_web
    chef_solo_db
  end

  task :chef_solo_base do
    run "chef-solo -j #{chef_repo_path}/config/base.json -c #{chef_repo_path}/solo.rb" 
  end

  task :chef_solo_web, :roles => [ :web ] do
    run "chef-solo -j #{chef_repo_path}/config/web.json -c #{chef_repo_path}/solo.rb" 
  end

  task :chef_solo_db, :roles => [ :db ] do
    run "chef-solo -j #{chef_repo_path}/config/db.json -c #{chef_repo_path}/solo.rb" 
  end
end
  • ssh_configの活用
role :web, "web001-desk001"

のように簡単にsshの接続先を指定していますがsshの細かい設定(接続ユーザや利用する鍵など)はdeploy.rbの方に細かく指定するのではなくssh_configで設定すると管理が楽になります。

  • role

webサーバやDBサーバなど、インストール内容が異なるサーバ群をroleとして管理します。このあたりは普通のデプロイと同じです。

  • 初期化処理(chef:init)

サーバの初期設定時に1回だけ実行することを想定しています。

$ cap chef:init

初期化処理では、chefのrecipeをgit cloneしています(ここではrecipeの詳細について説明しません)。その後chef-soloをgemを使ってインストールします。このようにchefのインストール自体も自動化しています。

  • 通常処理(chef)

アプリケーションやミドルウェアのインストールを行います。

$ cap chef

実行するのはこれだけ。
deploy.rbの中で、以下のように全サーバに共通のtask(chef_solo_base)とroleごとのtask(chef_solo_web, chef_solo_db)を実行しています。

  task :default do
    chef_solo_base
    chef_solo_web
    chef_solo_db
  end

例えばchef_solo_baseを実行するときに利用されるbase.jsonは以下のような内容になっています。このように各roleごとにインストールパッケージを選ぶことが出来るようになっています。

{
  "run_list": [ "recipe[build-essential]", "recipe[emacs]", "recipe[git]", "recipe[screen]", "recipe[tmux]", "recipe[vim]"  ]
}

ここまで枠組みを準備してしまえば、あとはRoleにサーバを追加していくだけで、Chefがタンポポをのせてくれるようになります。結果、サーバの追加と設定まで今まで半日かかっていた作業が実作業は3分ほどでできるようになりました。

Githubの脆弱性に対する対応(SSH公開鍵の監査依頼)について

先日Githubの脆弱性が話題になっていましたが、その後の対応として今回公開鍵が不正に追加されていないか確認依頼がメールで来ていましたね。その内容まとめました。

要点
  • 攻撃者が任意のアカウントに公開鍵を登録できるようになっていた
  • 3/4 5:53 PM UTC の段階でこの脆弱性は存在しない
  • 攻撃者による公開鍵の改ざんの問題は報告はされていないが、すべての公開鍵に関して確認を実施する
  • 確認がされていない公開鍵では、clone/pull/push ができなくなる
  • 今後の対策として以下を実施予定
    • 公開鍵の変更時にパスワードを要求する
    • 公開鍵が追加された時に登録アカウントにメールを送信する
    • アカウント設定のページの変更ログを閲覧できる
確認手順

https://github.com/settings/ssh/audit にいってApproveをする。公開鍵の確認はFingerprintによる確認になっているので、ssh-keygen -l -f {public_key_file} の結果と画面の値が一致するか確認する。

所感

Mass assignment問題は、公開鍵の不正な追加以外にも利用しうると思うのだけど、その辺も対応が完了しているのかよく分からなかった。

Webサービスの国際化対応(I18N)について

Webサービスもリリース時から国際化対応する時代になってきてます。国際化対応について設計時に考えたことをまとめました。前提としているWeb Application FrameworkはRuby on Railsです。大きく、言語、フォーマット、デザインの3つに分けてます。

言語

I18Nという時に一番最初に思いつくのが、言語の切替です。Rails3ではI18nというモジュールが使えるのでそれを利用します。RailsI18nに関してはRails Internationalization (I18n) API が一番まとまっているので参照して下さい。

Locale設定

Localeの設定は、ユーザ設定を優先して、設定が無い場合はリクエストヘッダーのHTTP_ACCEPT_LANGUAGE、それも無ければRailsのデフォルト設定言語を使うようにしました。HTTP_ACCEPT_LANGUAGEの取得のためhttp_accept_languageモジュールを利用します。

 # Controller File
before_filter :set_locale
 
def set_locale
  # サービスが対応している言語を指定
  available = %w{en ja}
  I18n.locale = user[:language] || request.preferred_language_from(available) || I18n.default_locale
end
言語ファイルパス/デフォルト言語の設定

config/initializers/locale.rbで設定します。

 # tell the I18n library where to find your translations
I18n.load_path += Dir[Rails.root.join('lib', 'locale', '*.{rb,yml}')]
 # set default locale to something other than :en
I18n.default_locale = :en
翻訳メソッド

翻訳メソッド(I18n.t)はシンボル、文字列どちらも引数にできます。特に名前空間を指定する時は、表記方法にバリエーションがあります。以下の表現はすべて同じです。

I18n.t 'activerecord.errors.messages.record_invalid'
I18n.t 'errors.messages.record_invalid', :scope => :active_record
I18n.t :record_invalid, :scope => 'activerecord.errors.messages'
I18n.t :record_invalid, :scope => [:activerecord, :errors, :messages]

名前空間の指定のしやすさを考えると引数は文字列でドット(.)つなぎで統一した方が読みやすいです。

言語ファイル

YAMLruby形式どちらも利用できます。どちらを使うべきでしょうか。

You may use YAML (.yml) or plain Ruby (.rb) files for storing your translations in SimpleStore. YAML is the preferred option among Rails developers. However, it has one big disadvantage. YAML is very sensitive to whitespace and special characters, so the application may not load your dictionary properly. Ruby files will crash your application on first request, so you may easily find what’s wrong. (If you encounter any “weird issues” with YAML dictionaries, try putting the relevant portion of your dictionary into a Ruby file.)

要は、YAMLの方が書きやすいためよく使われているが、半角スペース、特殊文字の扱いで正しく辞書を読み込めなかった場合に問題になる。ruby形式だと実行時にエラーが発生するため問題に気づきやすいという利点がある、とのこと。言語ファイルにはruby形式を使った方がよさそうです。

フォーマット

日時

言語ファイルでフォーマットを作成しておくと、テンプレートファイルから呼び出すことができます。

# config/locales/ja.yml
ja:
  time:
    formats:
      short: "%Y年%m月%d日"
      long: "%Y年%m月%d日 %H時%M分%S秒"  
# template file
<%= l Time.now, :format => :short %>
姓名

グローバル利用を想定すると、ユーザ登録時に英語表記を必須入力にしておいた方が後々楽です。HTTP_ACCEPT_LANGUAGEが英語以外の場合は、その言語の姓名もオプションで併せて入力できるようにします。言語により姓名の順序が異なります。これはRailsでは考慮されていないので自前で設計が必要です。姓が先か、名が先かを言語ごとに設定しておき、Localeにより順序を切替えます。

フォーマットでいうと住所や通貨も検討しないといけないのでしょうが、今回のサービスの要件に無かったためあまり調査していません。

デザイン

デザインについては調査中です。ピクトグラムの意味の違い、Right to Left言語圏対応、言語による文字幅の違いあたりをもう少し詰めていかないといけないなと思っています。よい文献等ありましたら教えてください(^^;

まとめ

書いてみて思ったのは国際化も作るサービスによって設計方針が変わるという点です。

  • コンテンツがユーザにより生成され、全世界で共通になっていて、UIのみ言語ごとに切り替えられるパターン(例、SNS
  • コンテンツがユーザにより生成され、言語ごとに分けられているパターン(例、オンラインゲームのワールド)
  • サービス側から言語ごとにコンテンツを提供するパターン(例、コーポレートサイト)

今回は一番目のパターンでした。UIのみの切替が前提だったためLocale設定はユーザ設定を最優先にするようにしました。しかし、例えば3番目のコーポレートサイトのパターンの場合であれば、LocaleはURLに埋め込まれている言語を取得して設定する方が普通の設計だと思います。このように作るサービスによって国際化の方針も変わるため、サービスにあわせてベストな戦略をとれることが重要だと思いました。