PHPでgettextを使って国際化しようとかいうとだいたいこういう記事がヒットする。
<?php
setlocale(LC_ALL, 'ja_JP');
bind_textdomain_codeset('message', 'UTF-8');
bindtextdomain('message', dirname(__FILE__));
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になれば成功。
debianでロケールを追加しないままだとこうなる。
$ 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は知らん。もっとややこしいらしい。
PHP: setlocale - Manual
注意:
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の設置場所を書くかしかない。
なんか、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ライブラリがある。
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() は、渡されたメッセージ文字列をそのまま返します。
ということで、ダメみたい。