i18nしてますか?(gettext+PHPその2)
どうも、鹿島和郎(かしまかずお)です。3カ月程前に重度の五月病を患い、ずっと闘病生活を送ってきたのですが、最近少しずつ快復してきました。皆さまは夏バテなどされてないでしょうか。
さて、今回は引き続きPHP+gettextで、多言語対応のプログラムを作っていきます。
■引き続き簡単な作業
○翻訳したファイル(.po)からリソースファイル(.mo)を作成
前回で翻訳が完了し、各ロケール(といっても今回はen_USだけですが)に対応したメッセージファイル(.po)が完成しました。これらのファイルはそのままプログラムで使うわけではなく、プログラムに優しい形(.mo=バイナリ)に変換する必要があります。
変換方法は簡単で、コマンド1つ、あるいはGUI上でちょちょいと操作すれば完了です。以下、コマンドラインの例だけ載せます。
$ cd <ベースディレクトリ>/en_US/LC_MESSAGES/
$ msgfmt -o hello.mo hello.po
この変換作業は、antのタスクなどにして、モジュールのビルド(PHPでは基本的に発生しませんが)とか、パッケージングなどに組み込んでしまうと楽かもしれません。
○リソースを使用するようにソースを修正
ここまでで、gettextで使用するための各言語用のリソースファイル(*.mo)を準備しました。最後に、作成したmoファイルをプログラムから使う方法について説明します。
*.moファイルは、以下のようなディレクトリに配置されています。
<ベースディレクトリ>/<ロケール名>/LC_MESSAGES/
ここでは、<ベースディレクトリ> はプログラムが配置されているディレクトリと同じとします。従って、ディレクトリ構成としては以下のようになります。
./
hello.php
en_US/
LC_MESSAGES/
hello.po
hello.mo
まずはソースコードを以下に示し、その後説明していきます。
#!/usr/bin/php
<?
// 現在の環境変数の値をロケールに設定
setlocale(LC_ALL, "");
// "hello" という「ドメイン」のリソース用のベースディレクトリを指定
bindtextdomain("hello", ".");
// "hello" 「ドメイン」のリソースを使用する
textdomain("hello");
$name = "John";
printf(_("%sさん、こんにちわ!\n"), $name);
?>
ここで「ドメイン」という用語が出てきますが、moファイルのファイル名と覚えておけばとりあえずOKです。hello.moというファイルを作ったので、ここでのドメイン名は"hello"です。
まず、bindtextdomain関数でhelloドメインのリソースのベースディレクトリを指定しています。ここではプログラム(hello.php)と同じディレクトリに置くので、"."を指定します。
次の行のtextdomain関数で、どのドメインを使用するかを指定します。今回はhello.mo1つしか使いませんが、複数のmoファイルを切り替えて使用することも可能です。
○実行!
以上で準備完了です。さっそく実行! といきたいところですが、その前に環境変数を確認します。LANGにja_JP.UTF-8が設定されているようです。
$ env | grep LC_ALL
(設定されていない)
$ env | grep LANG
LANG=ja_JP.UTF-8
では、まずプログラムをそのまま実行してみます。以下のように日本語での心温まるメッセージが表示されるはずです。
$ ./hello.php
Johnさん、こんにちわ!
次に、環境変数を変えて実行してみましょう。LC_ALLかLANGにen_USを設定します。
$ LC_ALL=en_US ./hello.php
Hello John!
$ LANG=en_US ./hello.php
Hello John!
英語で表示されましたね。これでハリウッド進出は間近と言えるでしょう。
○対応していないロケール名で実行すると日本語に
対応していないロケール名を使用すると、デフォルトである日本語メッセージが表示されます。
$ LANG=fr_FR ./hello.php
Johnさん、こんにちわ!
$ LANG=en ./hello.php
Johnさん、こんにちわ!
では、以下のようにシンボリックリンクを作ったらどうなるでしょう。
$ ln -s en_US en
$ ls -l
合計 12
lrwxrwxrwx 1 kashima kashima 5 8月 9 01:51 en -> en_US
drwxrwxr-x 3 kashima kashima 4096 8月 8 10:18 en_US
-rwxrwxr-x 1 kashima kashima 261 8月 8 22:34 hello.php
-rw-rw-r-- 1 kashima kashima 769 8月 8 22:34 hello.pot
結果は以下のとおり、NGです。
$ LANG=en ./hello.php
Johnさん、こんにちわ!
前回、システムで利用可能なロケール名の一覧をlocale -aで調べられると書きましたが、わたしのテスト環境(CentOS 5.5)では「en」というのは有効なロケール名ではないため、enを指定しても英語では表示されませんでした(もちろん他の環境ではenというロケールが使用可能な場合もあります)。
$ locale -a | grep ^en$ # 有効でない
$ locale -a | grep ^en_US$ # 有効
en_US
■テキストの修正、追加
さて、ここまでで多言語対応の流れを一通り説明しました。ただ、実際のプロジェクトではテキストの修正を行ったり、翻訳対象のテキストが増えたり、というのが随時発生します。それらに関しても簡単な例を元に説明していきます。
○日本語テキストを修正するケース
・まずはソースのテキスト修正
今回、以下のメッセージの多言語対応をしてきました。
printf(_("%sさん、こんにちわ!\n"), $name);
ここでどこかから
「『こんにちわ!』ってなんか頭悪そう……」
とツッコミが入ったので、元のテキストを以下のように修正することにしました。
printf(_("%sさん、こんにちは。\n"), $name);
・新しいpoファイルの作成
ソースの修正が完了したら、新しいpotファイルを作成し、既存のpoファイルとマージして新しいpoファイルを作成します。
$ xgettext -k"_" --from-code=UTF-8 -o hello.pot hello.php
$ msgmerge en_US/LC_MESSAGES/hello.po hello.pot -o en_US/LC_MESSAGES/new-hello.po
.. 完了.
新しいpoファイルはどこが変わっているのか、確認してみましょう。
$ diff -u hello.po new-hello.po
--- hello.po 2010-08-08 15:54:48.000000000 +0900
+++ new-hello.po 2010-08-08 22:34:48.000000000 +0900
@@ -8,7 +8,7 @@
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-08-08 15:37+0900\n"
+"POT-Creation-Date: 2010-08-08 22:34+0900\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,6 +17,6 @@
"Content-Transfer-Encoding: 8bit\n"
#: hello.php:12
-#, php-format
-msgid "%sさん、こんにちわ!\n"
+#, fuzzy, php-format
+msgid "%sさん、こんにちは。\n"
msgstr "Hello %s!\n"
違いが分かりましたか?
・poの修正(fuzzyを削除)
新旧のpoファイルを比較した所、相違点は以下の3点でした。
- POT-Creation-Dateが変わっている(無視して良い)
- msgidが正しい日本語になっている
- fuzzyってタグみたいなのが追加された
3番のfuzzyに関して説明します。今回、翻訳元の日本語が、
- 変更前:「%sさん、こんにちわ!\n」
- 変更後:「%sさん、こんにちは。\n」
と変更になっているので、gettextが"Hello %s!\n"ってのが"%sさん、こんにちは。\n"に対する正しい翻訳なのかどうなのか判断できないため、「よく分かんねー!」って文句を言っている状態です。
このようにfuzzyというのがついていると、gettextの処理対象にはなりませんので、中身を見て問題なければviなどでfuzzyを消しましょう。修正後は以下のようになります。
#: hello.php:12
#, php-format
msgid "%sさん、こんにちは。\n"
msgstr "Hello %s!\n"
古いpoファイルは要らないので、新しいファイルで上書きしてしまいましょう。
$ mv new-hello.po hello.po
・moを再作成→動作確認
$ msgfmt hello.po -o hello.mo
$ ./hello.php
Johnさん、こんにちは。
$ LANG=en_US ./hello.php
Hello John!
これで日本語でも英語でも正しいメッセージが表示されるようになりました。
○日本語のテキストを追加するケース
先ほどは「日本語のテキストを修正するケース」でしたが、新たなテキストが追加になった時はどうでしょうか。結論から言うと、先ほどとほぼ同じ手順でOKですが、作り直したpoファイルにはfuzzyというのは付かないので、その分だけ楽です。
○英語のテキストを修正するケース
po内の英語(msgstr)を直接修正し、moを再作成してください。
■まとめ
gettextの説明は今回で以上です。日本人プログラマ・日本製プログラムがハリウッドデビューできる日ももうすぐではないかと思います。
最後の方は疲れてしまい、説明を端折ってしまいましたがご容赦ください。ご不明点などはコメントいただければ分かる範囲でお答えします。
さて、前回、今回と非常に簡単な例を取り上げてgettextの説明をしてきました。poファイルの編集には、何の説明もなしにテキストエディタを使う前提で進めてきましたが、ここまで読んだ方はgettextの基本的な仕組みを理解したと思いますので、Poeditなどのツールを使うことも検討してみてください。
次回は……i18nからいったん離れて軽めの話題にするか、i18nを続ける場合はJavaか.NETでの多言語対応にするか、あるいはgettextの補足でもしようと思います(要するに何も決まってません)。
それではまた。