# 導入

Zend_Locale は、 "ひとつのアプリケーションを世界中で使用するにはどうしたらいいでしょう?" という質問に対するフレームワーク側からの回答です。たいていの人は "かんたんじゃん。出力内容をいくつかの言語に翻訳すればいいんだよ。" と言うでしょう。 しかし、ただ単にフレーズを他の言語に置き換えるだけといった単純な翻訳テーブルでは不十分です。 たとえば姓と名の順番や敬称、そして数値や日付、時刻、通貨などの書式は、 地域によって異なります。

地域化だけでなく国際化 も必要となります。これらは、それぞれ L10n および I18n と略されることもあります。国際化とは、そのシステムを特定のユーザ集団の (言語、地域、数値書式、財務規約、日付時刻書式などの) ニーズにかかわらず使用できるようにすることを言います。 地域化とは、特定の集団のニーズに対応するために、 システムに明示的なサポートを追加することを言います。 たとえば言語の翻訳や、各地域の規約 (複数形の扱い、日付、時刻、通貨、名前、 記号、並び順など) が該当します。 L10n と I18n は、お互い補完しあうものです。 Zend Framework では、いくつかのコンポーネントを組み合わせることで これらのサポートを提供しています。たとえば Zend_Locale、Zend_Date、 Zend_Measure、Zend_Translate、Zend_Currency そして Zend_TimeSync といったコンポーネントがあります。

Zend_Locale と setLocale()

PHP のドキュメント には、setlocale() はプロセス単位で動作するのでスレッドセーフではないと書かれています。 つまり、マルチスレッド環境では「スクリプト内で一切ロケールを変更していないのに、 勝手にロケールが変わってしまう」といった問題が発生する可能性があるということです。 スクリプトで setlocale() を使用すると、予期せぬ挙動を引き起こすことがあります。

Zend_Locale にはこのような制限はありません。なぜなら Zend_Locale は PHP の setlocale() を使用しているわけではないからです。

## 地域化とは

地域化とは、あるアプリケーション (あるいはホームページ) が、さまざまな言語を話すユーザによって使用することができるということです。 しかし、ご存知のとおり、地域化とは単に文字列を翻訳するだけのことではありません。 以下のような内容が含まれます。

- Zend_Locale - 他の Zend Framework コンポーネントにおける地域化サポートで対応しているロケールのバックエンドとなります。

- Zend_Translate - 文字列を翻訳します。

- Zend_Date - 日付や時刻を地域化します。

- Zend_Calendar - カレンダーを地域化します (グレゴリオ暦以外の暦もサポートしています)。

- Zend_Currency - 通貨を地域化します。

- Zend_Locale_Format - 地域化された数値のパースおよび生成を行います。

- Zend_Locale_Data - 国名や言語名、そして CLDR にあるさまざまな内容 について、各地域の標準文字列を取得します。

## ロケールとは?

コンピュータのユーザは皆、(気づいていないかもしれませんが) ロケールを使用しています。 地域化をサポートしていないアプリケーションの場合は、 通常は暗黙的に特定のロケール (そのアプリケーションの作者のロケール) を使用しています。 クラスや関数が地域化されていることを、ここでは ロケールに対応しているということにします。 そのユーザがどの地域にいるのかを、どうやってコードで知るのでしょうか?

ロケール文字列あるいはロケールを表すオブジェクトを使用して、 Zend_Locale およびそのサブクラスはユーザが希望する言語および地域を知ります。 この情報にもとづいて、正しい書式化や正規化、規約を適用します。

## ロケールの表現方法は?

ロケール識別子に含まれる情報は、ユーザの言語と 地理上の地域 (たとえば自宅あるいは勤務先の属する州など) です。 Zend Framework が使用するロケール識別文字列は、 国際的に定義されている言語と地域の略称で、 language_REGION という形式です。 言語および地域は、どちらも ASCII 文字列となります。

大半の人の予想に反して、実はロケールは 2 文字とは限りません。 言語についても地域についても、短縮形が 2 文字にならないものがあります。 したがって、地域や言語の切り出しは自前では行わないようにしましょう。 ロケール文字列から言語や地域を切り出したい場合は Zend_Locale を使用しましょう。 自前で行ってしまうと、予期せぬ結果となってしまうことがあります。

アメリカのユーザの言語は英語で、地域は `USA` です。 そこで、ロケール識別子は "en_US" となります。 ドイツのユーザの言語はドイツ語で、地域はドイツです。そこで、ロケール識別子は "de_DE" となります。 ロケールおよび地域の組み合わせの定義済みの一覧 を参考に、Zend Framework で使用するロケールを選択しましょう。

特定のロケールの選択

    $locale = new Zend_Locale('de_DE'); // ドイツ語 _ ドイツ

アメリカに住むドイツ人は、言語はドイツ語で地域は `USA` としたいかも知れません。しかし、このような非標準の組み合わせは、 "ロケール" としては直接サポートしていません。 そのかわりに、もし無効な組み合わせが使用されると、 自動的に地域コードが切り捨てられます。 たとえば "de_IS" は "de" に切り捨てられ、"xh_RU" は "xh" に切り捨てられます。これらの組み合わせは無効だからです。 さらに、言語コードがサポートされていない場合 (例 "zz_US") や存在しない場合は、デフォルトの "root" ロケールを使用します。 "root" ロケールではデフォルトとして、 国際的に認知されている日付、時刻、数値、通過等を定義しています。 この切捨て処理は、要求された情報の内容に依存します。 言語と地域の組み合わせの中には、 特定の型のデータ (たとえば日付) では有効だけれども 別の型 (たとえば通貨) では無効だというものがあるからです。

過去の歴史には注意しましょう。Zend Framework コンポーネントは、 これまでさまざまな場所で変更されてきた過去のタイムゾーンについては対応しません。 たとえば この一覧表 をご覧ください。特定の地域が夏時間を採用するかどうかや、 どのタイムゾーンに属するかなどは、時の政府によって何度も変更されています。 したがって、日付の計算をおこなう際には、 Zend Framework コンポーネントはこれらの変更には対応しません。 その代わりに、現時点の夏時間対応、現時点のタイムゾーンに対応した正しい時刻を使用します。

## 正しいロケールの選択

たいていの場合は、`new Zend_Locale()` とすると自動的に正しいロケールを選択します。 これはユーザのウェブブラウザから送られてきた情報をもとに判断します。 しかし、`new Zend_Locale(Zend_Locale::ENVIRONMENT)` を使用すると、以下に示すようにホストサーバの環境設定から情報を取得するようになります。

ロケールの自動選択

    $locale  = new Zend_Locale();

    // デフォルトの挙動で、上と同じです
    $locale1 = new Zend_Locale(Zend_Locale::BROWSER);

    // ホストサーバ上の設定を使用します
    $locale2 = new Zend_Locale(Zend_Locale::ENVIRONMENT);

    // フレームワークアプリケーションのデフォルト設定を使用します
    $locale3 = new Zend_Locale(Zend_Locale::FRAMEWORK);

Zend_Locale がロケールの自動選択に使用する検索アルゴリズムは、 三種類の情報をもとにしています。

1. const `Zend_Locale::BROWSER` - ユーザのウェブブラウザは、リクエストの際に情報を送信します。これは、 PHP のグローバル変数 `$_SERVER['HTTP_ACCEPT_LANGUAGE']` で取得できます。 対応するロケールが見つからない場合は `ENVIRONMENT` による検索を行い、それでもだめなら最後は `FRAMEWORK` を使用します。

2. const `Zend_Locale::ENVIRONMENT` - PHP は、ホストサーバのロケールを setlocale() 関数で取得します。 対応するロケールが見つからない場合は `FRAMEWORK` による検索を行い、 それでもだめなら最後は `BROWSER` を使用します。

3. const `Zend_Locale::FRAMEWORK` - Zend Framework がコンポーネントのデフォルトを指定できる仕組みが定まったら (予定されていますが、現在はまだありません)、 この定数を使用することでデフォルト設定を選択できるようになります。 対応するロケールが見つからない場合は `ENVIRONMENT` による検索を行い、それでもだめなら最後は `BROWSER` を使用します。

## 自動ロケールの使用法

Zend_Locale では、さらに 3 つのロケールを用意しています。 これらのロケールは、どこか特定の言語や地域を表すものではありません。 これらは "自動" ロケール、つまり getDefault() メソッドと同じような働きをするものです。 しかし、インスタンスを作成したりといった余計な作業が不要になります。 これらの "自動" ロケールは、標準のロケールを使用できるところなら どこででも使用可能で、文字列で指定することになります。 これを使用することで、ブラウザが提供するロケール情報などをうまく利用できるようになります。

以下の 3 つのロケールは、それぞれ微妙に異なる働きをします。

1. 'browser' - Zend_Locale は、ユーザが使用するウェブブラウザが提供する情報を使用します。 これは、PHP のグローバル変数 `$_SERVER['HTTP_ACCEPT_LANGUAGE']` で取得します。

   ブラウザが複数のロケールを指定している場合は、 Zend_Locale は最初に見つけたロケールを使用します。 ブラウザがロケールを指定していなかったり、 あるいはコマンドラインからスクリプトを実行したりした場合は、 代わりに自動ロケール 'environment' を使用してその結果を返します。

2. 'environment' - Zend_Locale は、 サーバが提供する情報を使用します。これは、PHP の内部関数 setlocale() で取得します。

   複数のロケールを指定している場合は、 Zend_Locale は最初に見つけたロケールを使用します。 サーバがロケールを指定していない場合は、 代わりに自動ロケール 'browser' を使用してその結果を返します。

3. 'auto' - Zend_Locale は、 可能な限りの方法でロケールを検出しようとします。 まず最初にユーザが指定するロケールを探し、 それに失敗するとホストのロケールを探します。

   ロケールの検出に失敗した場合は例外をスローし、 自動検出に失敗したことを通知します。

自動ロケールの使用法

    // 自動検出なし
    //$locale = new Zend_Locale(Zend_Locale::BROWSER);
    //$date = new Zend_Date($locale);

    // 自動検出つき
    $date = new Zend_Date('auto');

## デフォルトのロケールの使用

環境によっては、ロケールの自動検出ができないこともあります。 コマンドラインからのリクエストを受け取った場合や リクエスト元のブラウザに言語が設定されていない場合、 そしてサーバ上のロケールが 'C' あるいはその他のプロプライエタリなロケールだったりする場合などです。

このような場合は、Zend_Locale は通常は例外をスローして自動検出に失敗したことを示します。 このような状況への対応方法は二通りあります。 新たなロケールを手動で設定するか、あるいはデフォルトのロケールを定義するかです。

ロケールの例外処理

    // 起動ファイル内で
    try {
        $locale = new Zend_Locale('auto');
    } catch (Zend_Locale_Exception $e) {
        $locale = new Zend_Locale('de');
    }

    // モデル/コントローラ内で
    $date = new Zend_Date($locale);

この方法には大きな問題点があります。Zend_Locale を使用するすべてのクラス内で、ロケールオブジェクトを設定することになるのです。 複数のクラスを使用している場合など、これはとても面倒です。

Zend Framework リリース 1.5 以降では、もっとうまいやりかたが用意されています。 デフォルトのロケールを、静的メソッド setDefault() で設定できるのです。 もちろん、未知のロケールや不完全なロケールが指定された場合も例外をスローします。 setDefault() は、Zend_Locale を使用するクラスのインスタンスを作成する前に最初にコールする必要があります。 詳細は、次の例を参照ください。

デフォルトのロケールの設定

    // 起動ファイル内で
    Zend_Locale::setDefault('de');

    // モデル/コントローラ内で
    $date = new Zend_Date();

ロケールが検出できなかった場合は、自動的にロケール de を使用します。 見つかった場合は、そのロケールを使用します。

## ZF のロケール対応のクラス

Zend Framework では、ロケール対応のクラスは Zend_Locale を使用しています。そして、上で説明したように自動的にロケールを選択します。 たとえば、Zend Framework のウェブアプリケーションで Zend_Date を使用して日付を作成すると、何もロケールを指定しなくても ユーザのウェブブラウザの設定からロケール情報を取得してそれを使用します。

日付のデフォルトが、ウェブのユーザのロケールになる例

    $date = new Zend_Date('2006',Zend_Date::YEAR);

このデフォルトを上書きし、ロケール対応の Zend Framework コンポーネントでウェブサイトの訪問者の設定にかかわらず特定のロケールを指定するには、 コンストラクタの三番目の引数でロケールを明示的に指定します。

デフォルトのロケール選択のオーバーライド

    $usLocale = new Zend_Locale('en_US');
    $date = new Zend_Date('2006',Zend_Date::YEAR, $usLocale);
    $temp = new Zend_Measure_Temperature('100,10',
                                         Zend_Measure::TEMPERATURE,
                                         $usLocale);

多くのオブジェクトですべて同一のデフォルトロケールを使用することがわかっている場合は、 それを明示的に指定すると、毎回デフォルトロケールを検索することによる オーバーヘッドを回避できます。

デフォルトのロケールを使用する際のパフォーマンスの最適化

    $locale = new Zend_Locale();
    $date = new Zend_Date('2006', Zend_Date::YEAR, $locale);
    $temp = new Zend_Measure_Temperature('100,10',
                                         Zend_Measure::TEMPERATURE,
                                         $locale);

## アプリケーション単位のロケール

Zend Framework ではアプリケーション単位でのロケールの使用も可能です。 そのためには、Zend_Locale のインスタンスをレジストリに 'Zend_Locale' というキーで登録します。すると、Zend Framework のロケール対応のクラスすべてがこのインスタンスを使うようになります。 このようにして一度レジストリにロケールを設定してしまえば、 あとでまた設定しなおす手間が省けます。 他のすべてのクラスでも自動的にこれが用いられるようになります。 具体的な使用法は、以下の例を参照ください。

アプリケーション単位のロケールの使用例

    // 起動ファイルで
    $locale = new Zend_Locale('de_AT');
    Zend_Registry::set('Zend_Locale', $locale);

    // モデルあるいはコントローラ内で
    $date = new Zend_Date();
    // print $date->getLocale();
    echo $date->getDate();

## Zend_Locale_Format::setOptions(array $options)

オプション 'precision' の値を使用して、桁数の切り詰めあるいは拡張を行います。 '-1' を指定すると、値の小数部分の桁数を変更しないようにします。 オプション 'locale' は、数値や日付をパースする際の区切り文字や月名を判断するために使用します。 オプション 'format_type' では、CLDR/ISO 日付書式指定トークンおよび PHP の date() で使用するトークンのどちらを使用するかを選択します。 オプション 'fix_date' は、無効な形式の日付に対する自動修正処理を有効あるいは無効にします。 オプション 'number_format' は、toNumber() で使用するデフォルトの数値書式を指定します (詳細は このセクションを参照してください)。

'date_format' オプションでデフォルトの日付書式文字列を指定できます。 しかし、setOptions() で 'date_format' を指定した後で getDate() や checkdateFormat() そして getTime() を使用する際には十分注意しましょう。 これらのメソッドをそのロケールのデフォルトの日付書式で使用するには array('date_format' => null, 'locale' => $locale) をメソッドのオプションで指定します。

日付のデフォルトを、ウェブユーザの正しいロケールに設定する

    Zend_Locale_Format::setOptions(array('locale' => 'en_US',
                                         'fix_date' => true,
                                         'format_type' => 'php'));

ロケールの標準の定義を使用する場合は、オプション `Zend_Locale_Format::STANDARD` を指定します。date_format に `Zend_Locale_Format::STANDARD` を設定すると、 実際に設定されているロケールの標準定義を使用します。 これを number_format に設定すると、このロケールの標準数値書式を使用します。 また、locale に設定すると、この環境あるいはブラウザの標準のロケールを使用します。

setOptions() での STANDARD の使用

    Zend_Locale_Format::setOptions(array('locale' => 'en_US',
                                         'date_format' => 'dd.MMMM.YYYY'));
    // グローバル設定の日付書式をオーバーライドします
    $date = Zend_Locale_Format::getDate('2007-04-20',
                                        array('date_format' =>
                                                  Zend_Locale_Format::STANDARD);

    // 標準ロケールのグローバル設定
    Zend_Locale_Format::setOptions(array('locale' => Zend_Locale_Format::STANDARD,
                                         'date_format' => 'dd.MMMM.YYYY'));

## Zend_Locale とそのサブクラスの高速化

Zend_Locale およびそのサブクラスを高速化するには、 Zend_Cache を使用します。Zend_Locale を使用している場合は、静的メソッド Zend_Locale::setCache($cache) を使用します。Zend_Locale_Format を高速化するには、 オプション cache を Zend_Locale_Format::setOptions(array('cache' => $adapter)); のように指定します。両方のクラスを使用している場合は Zend_Locale にのみキャッシュを設定します。そうしないと、 後から設定したキャッシュがそれまでの内容を上書きしてしまうことになります。 利便性を考慮して、静的メソッド getCache()、hasCache()、 clearCache() および removeCache() も用意されています。

キャッシュを設定しなかった場合は、Zend_Locale が自動的にキャッシュを設定します。 時には、パフォーマンスが低下するのを承知のうえで 敢えてキャッシュを設定しないようにしたいこともあるでしょう。 そんな場合は、静的メソッド disableCache(true) を使用します。 このメソッドは、実際に設定されているキャッシュを無効にする (削除はしない) だけでなく、 キャッシュの設定を省略した際に自動的にキャッシュが設定されてしまうことも防ぎます。