PHPでgettextを使って国際化しようとかいうとだいたいこういう記事がヒットする。
<?php // 日本-日本語にロケールをセットして setlocale(LC_ALL, 'ja_JP'); // 翻訳カタログが入ったmessage.moファイルのエンコードをUTF-8と宣言して bind_textdomain_codeset('message', 'UTF-8'); // message.moのディレクトリを指定して bindtextdomain('message', dirname(__FILE__)); // gettext()がコールされた際に、message.moからカタログを検索するようにして textdomain('message'); // 翻訳言語を出す echo gettext('hoge');
罠1:message.moのディレクトリは指定したディレクトリ+ロケール名+LC_MESSAGESである。
bindtextdomain()で、/path/to/language/catalog/directoryを設定したら、実際に.moファイルを置くのは
/path/to/language/catalog/directory/ja_JP/LC_MESSAGES
である。
まぁ、これはちょっとよくハマリがちな罠。
罠2:Linux系のOSだとシステムに入ってないロケールには切り替えられない
これにハマった。setlocale効かない。
RHEL/CentOS系だとロケールは何でもガンガンと入れてくれるらしい。
$ locale -a C POSIX aa_DJ aa_DJ.iso88591 aa_DJ.utf8 ... en_SG.utf8 en_US en_US.iso88591 en_US.iso885915 en_US.utf8 ... ja_JP ja_JP.eucjp ja_JP.ujis ja_JP.utf8 japanese japanese.euc ... zu_ZA zu_ZA.iso88591 zu_ZA.utf8
と500近く出てくるが、debianだと
$ locale -a C C.UTF-8 en_US.utf8 POSIX
これだけになる。
setlocale()が成功したかどうかをチェックしなければならない。
返り値をチェックして、設定した言語とstrcasecmp()===0になれば成功。
$ php -r 'var_dump(setlocale(LC_ALL, "en_US.UTF-8"));' string(11) "en_US.UTF-8" $ php -r 'var_dump(setlocale(LC_ALL, "en_US"));' bool(false) $ php -r 'var_dump(setlocale(LC_ALL, "ja_JP.UTF-8"));' bool(false) $ php -r 'var_dump(setlocale(LC_ALL, "ja_JP"));' bool(false)
Windowsの罠
Windowsは知らん。もっとややこしいらしい。
注意: Windows では、setlocale(LC_ALL, '') を使用するとシステムの地域と言語の設定の値を使用します (コントロールパネルで確認できます)。
自体に色々と罠が潜んでいるとマニュアルに書いてあった。
たとえば
$ php -r 'var_dump(setlocale(LC_ALL, ""));' string(18) "Japanese_Japan.932"
などというものを返す。
$ php -r 'var_dump(setlocale(LC_ALL, "en_US"));' bool(false)
en_USすらダメ。ほとんどsetlocaleを使うなという気もする。あるいは、完全にWindowsのみでコードと.moの設置場所を書くかしかない。
罠3 Apacheを再起動せよ
なんか、setlocaleが成功したり失敗したりする。。。。
検索したらPHPドキュメントのコメントが・・・。 PHP: setlocale - Manual
Omer Sabic
On Linux/Apache, when you install and try to use a new locale, the setlocale() function with the new locale will fail sometimes, but not always. To furthermore complicate, setlocale() will always complete with any of the previously installed locales. This would seem a really weird behaviour, which you can fix by restarting Apache, as Kari Sderholm aka Haprog mentioned, but I felt it needed to be properly pointed out.
Apacheを再起動せよと・・・・。
確かに治った。
でまぁ、そんな理由は…
ロケールに関する設定は、OSのロケール機能を使っているからということになる。PHP側でも実装するのダルイもんね。
ただ、ロケールに影響を受けるPHP関数は多々あるようで、それはそれで問題だとは思うけどとりあえずgettextが使えればいいので今のところはスルーする。時間表記とか気になるのだけれども、それはその時に下記のエントリを読んで考える。 hnw.hatenablog.com
ポータビリティの高いgettextを使うための対応方法
setlocaleしないで自前でロケールの切り替えを行う。一番簡単なのは、ドメイン名にロケール名称を入れてしまうこと。
また、PHPのgettextモジュールが入ってない場合もあるかもしれないが、その場合も考慮したPHPのgettextライブラリがある。
名称・リンク | リリース(初版 - 最新) | ライセンス | 備考 |
---|---|---|---|
php-gettext | 2003/10/23 - 2015/11/11 | GPL | WordPressもこれを使っている |
PEARのFile_Gettext | 2004/03/30 - 2012/03/04 | BSD | 4年半の沈黙を破り、現時点のリリースはびびる |
oscarotero/Gettext | 2013/12/01 - 2017/03/05 | MIT | composerの名称がgettext/gettextという意欲作。Bladeからpotを作る機能もある。PHP5.4以上 |
oscarotero/Gettextを使いたいなーと思うのだけど、意外と依存性が高いのとPHP5.3は使えないみたいなので若干敷居が高い。
phpMyAdminも独自でgettextを持っていて、CVE - CVE-2015-8980なんてあげられてたりする。
PEAR File_Gettextを使ったサンプル
<?php require_once 'File/Gettext.php'; function __($str) { static $mo; if (is_null($mo)){ $mo = File_Gettext::factory('MO'); $mo->load('locale/ja_JP/LC_MESSAGES/message.mo'); } $catalog = $mo->toArray(); if (isset($catalog['strings'][$str])){ return $catalog['strings'][$str]; } return $str; } echo __('email');
こんな感じかな。gettextの機能を全く使わないでいるので、複数形とか来た時にイマイチな感じがする。
ただし、PHPのgettextモジュールが組み込まれてなくても使えるのでポータビリティとしてはかなり高い。
PHPのgettextは使うんだけど、OSにロケールが無い場合にドメインでなんとかしたい。
今回の自分のケース。
gettextは現在のロケールとドメインによってカタログを見に行くので、どのディレクトリでも対応するなら、Cロケールを使ってその中に各言語ごとのファイルを用意しておけばいいんじゃん?っておもったのだけど、
gettext(3C) (SunOS リファレンスマニュアル (3) : 基本ライブラリ関数)
現在のロケールが C ロケールの場合、gettext()、gettext()、dcgettext() は、渡されたメッセージ文字列をそのまま返します。
ということで、ダメみたい。