CakePHPのl10nとphpのsetlocale()がかみ合わない件

setlocale()ロケールを設定する必要が出たので、CakePHPl10nと連動させようとしてます。ただ、残念な事にl10nsetlocale()が上手くかみ合わず行き詰まりました。

とりあえずの結論

l10nからは'ja'か'jpn'が取得できます。setlocale()ロケールを日本語にするには、'ja_jp'か'ja_jp.utf-8'を指定する必要があります。つまり、CakePHPが自動判別したCakePHPの設定をそのまま使って、setlocale()phpロケールを設定する事は出来ないようです。

日本語は基本的に日本でしか使われて無いので、ja_jpは冗長で、CakePHPでは略されてる様です。逆に例えば英語では、CakePHPでも'en_au'や'en_bz'など10個ほど設定が用意されています。なので、日本語と同じように国名コードの方が省略される言語の場合に、setlocale()に使えないという事になります。

CakePHPの言語設定

CakePHPアプリでの言語は、基本的にConfigure::write('Config.language', 'ja');の様に設定します。'ja'はもちろん日本語ですが、設定に使える文字列はl10n::__l10nCatalogというメンバ変数に定義されています。

l10n::__l10nCatalogに定義があって、英語など複数の国で使われる場合。

'en-gb' => array('language' => 'English (British)', 'locale' => 'en_gb', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'),

https://github.com/cakephp/cakephp/blob/1.3.6/cake/libs/l10n.php#L224

Configure::write('Config.language', 'en-gb');で設定し、この設定が反映されます。'en-gb'は英語ですが、特にイギリス英語を指してます。CakePHPで言語を設定するのは、大抵poファイルでの翻訳だと思いますが、それには'locale'か'localeFallback'が使われます。

$l10n->languagePath = array('en_gb', 'eng');

この様に'locale', 'localeFallback'の順番でlanguagePathに設定されます。この場合i18nで翻訳する際、APP/locale/en_gb/にpoファイルがあるかチェックし、無ければAPP/locale/eng/を見に行く事になります。具体的にはhttps://github.com/cakephp/cakephp/blob/1.3.6/cake/libs/i18n.php#L284このあたりのコードです。

l10n::__l10nCatalogに定義があって、日本語など国別コードが省略される場合。

'ja' => array('language' => 'Japanese', 'locale' => 'jpn', 'localeFallback' => 'jpn', 'charset' => 'utf-8', 'direction' => 'ltr'),

https://github.com/cakephp/cakephp/blob/1.3.6/cake/libs/l10n.php#L276

Configure::write('Config.language', 'ja');の場合は、この設定がl10n内で使われるようになります。前述の通り日本語の場合、基本的に日本でのみ使われる言語なので、'locale'と'localeFallback'のどちらも'jpn'が指定されています。

$l10n->languagePath = array('jpn', 'jpn');

当然、languagePathには'jpn'だけが入っています。翻訳に関してはAPP/locale/jpn/で、多分CakePHPでの標準的*1な位置だと思います。、ちょうどここがsetlocale()と合わない部分です。

l10n::__l10nCatalogに定義が無い場合。
$l10n->languagePath = array('ja_jp');

例えばConfigure::write('Config.language', 'ja_jp');を指定したら、指定した文字がそのままlanguagePathにも設定されます。APP/locale/ja_jp/をチェックする事になります。この例の場合、setlocale()には使える設定ですが、l10n::__l10nCatalogに設定が無いので'language'や'locale'や'charset'などが設定されません。

l10n::__l10nCatalogのカタログとは
  • 言語に対する様々な情報を保持しています。
  • リクエストヘッダのHTTP_ACCEPT_LANGUAGEをキーになっています。(振り分けが容易)
<?php
// 日本語の例
'ja' => array(
	'language' => 'Japanese', // 言語のフルネーム
	'locale' => 'jpn', // setlocale()などに適応される地域名
	'localeFallback' => 'jpn', // 下記を参照
	'charset' => 'utf-8', // この言語を使用する際に推奨される文字エンコーディング(自信なし)
	'direction' => 'ltr' // 文字の方向。ltr(left to right)とrtl(right to left)のどちらか
)
?>

localeFallbackですが、fallbackとは予備のもの、といった意味合いを持ちます。

例えば英語の場合、様々な地域・国で使われ、方言も多種多様にわたりますが、

  • locale => en_ca // カナダ英語
  • localeFallback => eng // カナダ英語も英語!

という風に、英語ならどこの地域・国でも「eng」を予備として利用できる、といった風になります。

つまりapp/locale/engに記述をおけば、多種多様な英語圏の国・地域に適応できるといったことが可能になります。

多言語化するときのConfig.languageの値 - 24時間CakePHP

この件を調べてる際の糸口になったエントリですが、カタログについて詳しく書いてあります。今でも気になってる事として「'locale' => 'jpn', // setlocale()などに適応される地域名」てあるけど、やっぱり'jpn'はsetlocale()に使えるはずなのかな?

setlocale()でロケール設定する際の引数

<?php
$locale = setlocale(LC_ALL, '0');var_dump($locale);           //string(1) "C"
$locale = setlocale(LC_ALL, 'jpn');var_dump($locale);         //bool(false)
$locale = setlocale(LC_ALL, 'jp');var_dump($locale);          //bool(false)
$locale = setlocale(LC_ALL, 'ja');var_dump($locale);          //bool(false)
$locale = setlocale(LC_ALL, 'ja_jp');var_dump($locale);       //string(5) "ja_jp"
$locale = setlocale(LC_ALL, 'ja_jp.utf-8');var_dump($locale); //string(11) "ja_jp.utf-8"
$locale = setlocale(LC_ALL, 'jpn_jp');var_dump($locale);      //bool(false)
$locale = setlocale(LC_ALL, 'ja-jp');var_dump($locale);       //bool(false)

CakePHPでは'locale'=>'jpn'としているし、最初はもしかしたら'jpn'でもsetlocale()ロケールの設定が出来るのかもと思い、いろんなパターンを試してみました。結果は上記の通り、よく見る国別コード付きのパターンじゃないと設定できませんでした。

カテゴリやロケール名は、 » RFC 1766 や » ISO 639 にあります。 ロケール命名方式は、システムによって異なります。

http://jp2.php.net/manual/ja/function.setlocale.php

setlocale()のマニュアルを見てみると、RFC 1766ISO 639を見るとあります。ISO 639は言語の略称の規格で、3文字か2文字の略称が定義されてます。ja_jpの最初のjaの部分は、RFC 1766で一次言語タグと呼ばれていて、ISO 639で規定される2文字の言語コードになっているようです。そのまま解釈するとsetlocale()に'jpn'は使えないという事になります。
なお、システムによって異なるとも書いてあるので、最後の環境の通りMacLinuxで試してみましたが、結果は同じでした。

あと、setlocale(LC_ALL, '0')の様に引数'0'だと、ロケール設定は適用されず、単に現在の設定が返されます。

C言語だと

よく知りませんが、C言語の方だとsetlocale(jpn) - Google 検索setlocale(japanese) - Google 検索の様な指定が出来るみたいです。phpC言語っぽい関数が多いので、同じ動きをしてくれれば良いんですけど。

CakePHPの問題?setlocale()の問題?

翻訳ファイルの置き場所を、en_gbやen_usとは別にengで統一したい場合というのは確かにありそうなので、jpnと言った3文字の略称も必要なんだとは思います。l10n::__l10nCatalogにRFC 1766準拠な値も必ず持たせるとかしてくれれば良いのに。もしくは逆にphpsetlocale()が'jpn'の様な形で指定できないのが変なのかな?個人的にはCakePHPRFC 1766に準拠したロケールを扱うようになって欲しいです。

環境

Mac
Mac Mac OS X 10.5.8(Leopard
MAMP 1.7.2
CakePHP 1.3.6
php 5.2.6
Linux
サーバー coreserver
CakePHP 1.3.6
php 5.2.5

*1:pluginに含まれる翻訳ファイルもjpnを想定してると思う。