第13章 - 国際化とローカライゼーション

国際的なアプリケーションを開発したことがあるのであれば、テキストの翻訳、ローカルスタンダードとローカライズされた内容のあらゆる面を処理することが悪夢であることはご存じでしょう。幸いにして、symfony は国際化のすべての面をネイティブに自動化します。

国際化 (internationalization) は長い単語なので、開発者はしばしば i18n と表記します(単語の文字数を数えてみれば理由がわかります)。ローカライゼーション (localization - 現地化) は l10n と短縮されます。これらは多言語の Web アプリケーションの2つの異なる面をカバーします。

国際化されたアプリケーションはさまざまな言語もしくはフォーマットで同じ内容のいくつかのバージョンを含みます。たとえば、Web メールのインターフェイスはインターフェイスを変更するだけでいくつもの言語で同じサービスを提供できます; インターフェイスだけ変わります。

ローカライズされたアプリケーションはブラウザーから得られた国の情報にしたがって相異なる情報を含みます。ポータルニュースを考えてみましょう。アメリカからブラウザーで見たとき、アメリカについての最新のヘッドラインを表示します。しかし、フランスからブラウザーで見たとき、ヘッドラインはフランスのニュースに関するものです。ローカライズされたアプリケーションは翻訳内容を提供するだけでなく、1つのローカライズされたバージョンから別のバージョンまで異なった内容を提供します。

全体で、国際化とローカライゼーションを扱うことはアプリケーションがつぎの内容を担当することを意味します:

  • テキストの翻訳 (インターフェイス、アセットと内容)
  • 標準とフォーマット (日付、量、数字など)
  • ローカライズされた内容 (国に従う多くのバージョンの任意のオブジェクト)

この章では symfony はこれらの要素と国際化とローカライズされたアプリケーションを開発するために symfony を使う方法をカバーします。

ユーザーカルチャ

symfony におけるすべての組み込みの国際化機能はカルチャ (culture) と呼ばれるユーザーセッションのパラメーターに基づいています。カルチャはユーザーの国と言語の組み合わせで、テキストとカルチャに依存する情報を表示する方法を決定します。これはユーザーセッションのなかでシリアライズされるので、カルチャはページのあいだで持続します。

デフォルトのカルチャを設定する

デフォルトでは、新しいユーザーカルチャは default_culture です。リスト13-1で示されるように、この設定を settings.yml 設定ファイルのなかで変更できます。

リスト13-1 - デフォルトカルチャを設定する (frontend/config/settings.yml)

all:
  .settings:
    default_culture: fr_FR

NOTE 開発期間において、settings.yml ファイルのなかのカルチャを変更してもブラウザーの現在のカルチャが変更されないことに驚くかもしれません。セッションが前のページからすでにカルチャを持っているからです。新しいデフォルトのカルチャを持つアプリケーションを見たい場合、ドメイン Cookie をクリアする、もしくはブラウザーを再起動する必要があります。

言語と国の両方をカルチャに保存しておくことが必要です。なぜなら、フランス、ベルギー、もしくはカナダからの異なったフランス語の翻訳、スペイン、もしくはメキシコからの異なるスペイン語の内容が存在するかもしれないからです。言語は ISO の 639-1 標準規格にしたがって小文字の2文字で表記されます (たとえば en は英語)。ISO の 3166-1 標準規格にしたがって国は大文字の2文字で表記されます (たとえば GB はイギリス) 。

ユーザーのカルチャを変更する

ユーザーのカルチャはブラウジングセッションのあいだに変更できます。たとえば、ユーザーがアプリケーションを英語バージョンからフランス語バージョンに切り替えることを決めたとき、ユーザーがアプリケーションにログインしたとき、ユーザーのオプションに保存されている言語が使われます。sfUser クラスがユーザーのカルチャのためにゲッターメソッドとセッターメソッドを提供する理由はそういうわけです。リスト13-2はアクションでこれらのメソッドを使う方法を示しています。

リスト13-2 - アクションのなかでカルチャを設定して読みとる

[php]
// カルチャのセッター
$this->getUser()->setCulture('en_US');

// カルチャのゲッター
$culture = $this->getUser()->getCulture();
 => en_US

SIDEBAR URL のなかのカルチャ

symfony のローカライゼーションと国際化機能を使うとき、ページは単独の URL に対して異なるバージョンを持つ傾向にあります。これはすべてユーザーセッションしだいです。このことによってページが検索エンジンにキャッシュされる、もしくはインデックスに登録されることを防ぎます。

1つの解決方法はカルチャがすべての URL に表示されるようにすることで、翻訳されたページは外部の世界に対して異なる URL として見なされます。これを実現するには、:sf_culture トークンをアプリケーションの routing.yml のすべてのルールに追加します:

page:
  url: /:sf_culture/:page
  params: ...
  requirements: { sf_culture: (?:fr|en|de) }

article:
  url: /:sf_culture/:year/:month/:day/:slug
  params: ...
  requirements: { sf_culture: (?:fr|en|de) }

すべての link_to() のなかで sf_culture リクエストパラメーターを手動で設定することを避けるために、symfony はデフォルトのルーティングパラメーターにユーザーのカルチャを自動的に追加します。これは内部でも機能します。sf_culture パラメーターが URL のなかで見つかる場合、自動的にユーザーカルチャを変更するからです。

カルチャを自動的に決定する

多くのアプリケーションにおいて、ユーザーのカルチャはブラウザーのオプションに基づいて最初のリクエストで定義されます。ユーザーはブラウザーで受け入れられる言語リストを定義することが可能で、HTTP の Accept-Language ヘッダーにおいて、各リクエストによってこのデータはサーバーに送信されます。sfWebRequest オブジェクトを通してこれを symfony 内部で読みとることができます。たとえば、アクションのなかでユーザーの選択言語のリストを取得するには、つぎのコードを記入します:

[php]
$languages = $request->getLanguages();

HTTP ヘッダーは文字列ですが、symfony は自動的にこれを解析して配列に変換します。前の例ではユーザーの選択言語は $languages[0] でアクセスできます。

サイトのホームページもしくはすべてのページに対するフィルターのなかでこれはユーザーカルチャをユーザーが選択したブラウザーの言語に自動的に設定するさいに便利です。しかしおそらくあなたの Web サイトは限定された言語の一式しかサポートしないので、getPreferredCulture() メソッドを使うほうがベターです。これはユーザーが選択した言語とサポートされる言語を比較することでベストな言語を返します:

[php]
$language = $request->getPreferredCulture(array('en', 'fr')); // Web サイトは英語もしくはフランス語で利用できる

マッチする言語が存在する場合、メソッドは最初にサポートされる言語を返します (先の例では en)。

CAUTION HTTP の Accept-Language ヘッダーはあまり信用できる情報ではありません。ユーザーがそれをブラウザーで修正する方法をほとんど知らないからです。多くの場合、選択されたブラウザー言語はインターフェイスの言語で、ブラウザーはすべての言語で利用可能ではありません。ブラウザーが選択した言語にしたがってカルチャを自動的に設定することを決める場合、かならず代わりの言語を選択する方法をユーザーに提供してください。

標準規格とフォーマット

Web アプリケーションの内部では文化の特殊性が考慮されていません。たとえば、データベースはデータ、量などを保存する国際標準規格を使います。しかし、データがユーザーから送信されるもしくは読みとられる場合、変換を行う必要があります。ユーザーはタイムスタンプを理解しませんし、French の代わりに母国語の Français と表記することを望みます。ユーザーのカルチャに基づいて、自動的に変換を行うための助けが必要です。

ユーザーのカルチャでデータを出力する

いったんカルチャが定義されると、これに依存するヘルパーは自動的に適切な出力内容を持ちます。リスト13-3で示されるように、たとえば、format_number() ヘルパーは自動的にユーザーが慣れ親しんでいる書式で数字を表示します。

リスト13-3 - ユーザーのカルチャに合わせて数字を表示する

[php]
<?php use_helper('Number') ?>

<?php $sf_user->setCulture('en_US') ?>
<?php echo format_number(12000.10) ?>
 => '12,000.10'

<?php $sf_user->setCulture('fr_FR') ?>
<?php echo format_number(12000.10) ?>
 => '12 000,10'

明示的にカルチャをヘルパーに渡す必要はありません。ヘルパーは現在のセッションオブジェクトのなかでカルチャを自分たち自身で探します。リスト13-4は出力に対してユーザーのカルチャを考慮しているヘルパーの一覧です。

リスト13-4 - カルチャに依存するヘルパー

[php]
<?php use_helper('Date') ?>

<?php echo format_date(time()) ?>
 => '9/14/10'

<?php echo format_datetime(time()) ?>
 => 'September 14, 2010 6:11:07 PM CEST'

<?php use_helper('Number') ?>

<?php echo format_number(12000.10) ?>
 => '12,000.10'

<?php echo format_currency(1350, 'USD') ?>
 => '$1,350.00'

<?php use_helper('I18N') ?>

<?php echo format_country('US') ?>
 => 'United States'

<?php format_language('en') ?>
 => 'English'

<?php use_helper('Form') ?>

<?php echo input_date_tag('birth_date', mktime(0, 0, 0, 9, 14, 2010)) ?>
 => input type="text" name="birth_date" id="birth_date" value="9/14/10" size="11" />

<?php echo select_country_tag('country', 'US') ?>
 => <select name="country" id="country"><option value="AF">アフガニスタン</option>
      ...
      <option value="GB">イギリス</option>
      <option value="US" selected="selected">アメリカ合衆国</option>
      <option value="UM">合衆国領有小離島</option>
      <option value="UY">ウルグアイ</option>
      ...
    </select>

日付ヘルパーはカルチャから独立した表示を強制する追加のフォーマットパラメーターを受けとることができますが、アプリケーションが国際化されている場合は使うべきではありません。

ローカライズされた入力からデータを取得する

データをユーザーのカルチャに表示することが必要な場合、データを読みとることに関しては、可能なかぎり、すでに国際化されたデータを入力するようにアプリケーションのユーザーを後押しすべきです。このアプローチによって変化するフォーマットと不確定な地域によってデータを変換する方法を理解しなくてもすみます。たとえば、入力ボックスにコンマで区切られた通貨の値を入力しようとする人はいないでしょう。

実際のデータを隠す (select_country_tag()など)、もしくは複雑なデータの異なるコンポーネントをいくつかのシンプルな入力に分離することで、ユーザーの入力フォーマットをまとめることができます。

しかしながら、日付に関してこれを利用できないことがよくあります。ユーザーは自身の文化の書式で日付を入力することに慣れており、このようなデータは内部 (と国際化) 書式に変換できるようにする必要があります。これが sfI18N クラスを適用する事例です。リスト13-5はこのクラスの使いかたを示しています。

リスト13-5 - アクションでローカライズされた書式からデータを取得する

[php]
$date= $request->getParameter('birth_date');
$user_culture = $this->getUser()->getCulture();

// タイムスタンプを取得する
$timestamp = $this->getContext()->getI18N()->getTimestampForCulture($date, $user_culture);

// 構造化データを取得する
list($d, $m, $y) = $this->getContext()->getI18N()->getDateForCulture($date, $user_culture);

データベース内のテキスト情報

ローカライズされたアプリケーションはユーザーのカルチャにしたがって異なる内容を提供します。たとえば、オンラインショップは製品を同じ価格で世界中に提供できますが、国ごとにカスタマイズされた説明が付属します。このことは、データベースは異なるバージョンのデータの任意の部分を保存可能でなければならず、そのためには、特定の方法でスキーマを設計しローカライズされたモデルオブジェクトを操作するたびにカルチャを使う必要があります。

ローカライズされたスキーマを作成する

ローカライズされたいくつかのデータを含むそれぞれのテーブルに対して、テーブルを2つの部分に分割すべきです; 1つのテーブルは国際化カラムを持たず、一方のテーブルは国際化カラムだけを持ちます。2つのテーブルは一対多のリレーションによってリンクされます。モデルを変更しないことを求められたときに、このセットアップによってより多くの言語を追加できるようになります。たとえば Product テーブルを使うことを考えてみましょう。

リスト13-6で示されるように、最初は schema.yml ファイルのなかでテーブルを作ります。

リスト13-6 - 国際化データのためのスキーマのサンプル (config/schema.yml)

my_connection:
  my_product:
    _attributes: { phpName: Product, isI18N: true, i18nTable: my_product_i18n }
    id:          { type: integer, required: true, primaryKey: true, autoincrement: true }
    price:       { type: float }

  my_product_i18n:
    _attributes: { phpName: ProductI18n }
    id:          { type: integer, required: true, primaryKey: true, foreignTable: my_product, foreignReference: id }
    culture:     { isCulture: true, type: varchar, size: 7, required: true, primaryKey: true }
    name:        { type: varchar, size: 50 }

最初のテーブルの isI18N 属性と i18nTable 属性と、2番目のテーブルの特別な culture カラムに注目してください。これらすべては symfony 固有の Propel の強化機能です。

リスト 13-7 - Doctrine での国際化データのためのサンプルスキーマ(config/doctrine/schema.yml)

Product:
  actAs:
    I18n:
      fields: [name]
  columns:
    price: { type: float }
    name: { type: string(50) }

symfony の自動化によってこれを書く作業がずっと速くなります。国際化データを含むテーブルがサフィックスとして_i18n を持つメインテーブルと同じ名前を持ち、それらが両方のテーブルで id という名前のカラムに関連する場合、メインテーブルに対する i18n 属性と同様に、_i18n テーブルにおいて id カラムと culture カラムを省略できます; symfony はこれらを推察します。このことは symfony がリスト13-8のスキーマをリスト13-6のスキーマと同じものとみなすことを意味します。

リスト13-8 - 省略形式の、国際化データのためのスキーマのサンプル (config/schema.yml)

my_connection:
  my_product:
    _attributes: { phpName: Product }
    id:
    price:       float
  my_product_i18n:
    _attributes: { phpName: ProductI18n }
    name:        varchar(50)

生成された国際化オブジェクトを使う

いったん対応するオブジェクトモデルがビルドされると (schema.yml をそれぞれ修正した後に propel:build --model タスクを呼び出すことをお忘れなく)、リスト13-9で示されるように、あたかも1つのテーブルしか存在しないかのように、国際化をサポートする Product クラスを使うことができます。

リスト13-9 - 国際化オブジェクトを処理する

[php]
$product = ProductPeer::retrieveByPk(1);
$product->setName('Nom du produit'); // デフォルトでは、カルチャは現在のユーザーカルチャ
$product->save();

echo $product->getName();
 => 'Nom du produit'

$product->setName('Product name', 'en'); // カルチャの値を 'en' に変更する
$product->save();

echo $product->getName('en');
 => 'Product name'

ピアオブジェクトを持つクエリに関しては、リスト13-10で示されるように、通常の doSelect の代わりに、doSelectWithI18n メソッドを使うことで現在のカルチャのための翻訳内容を持つオブジェクトへの結果を制限できます。加えて、このメソッドは同時に通常のオブジェクトに関連する国際化オブジェクトを作成します。結果として全内容を取得するクエリの回数を減らすことになります (パフォーマンスにおけるこのメソッドのプラスの影響に関する詳細な情報は18章を参照)。

リスト13-10 - 国際化された Criteria でオブジェクトを取得する

[php]
$c = new Criteria();
$c->add(ProductPeer::PRICE, 100, Criteria::LESS_THAN);
$products = ProductPeer::doSelectWithI18n($c, $culture);
// $culture 引数はオプション
// カルチャが与えられていない場合現在のユーザーのカルチャが使われる

ですので、基本的には、国際化オブジェクトを直接処理する必要は決してありませんが、代わりに通常のオブジェクトを持つクエリを行うたびにモデルにカルチャを渡します (もしくは推測させます)。

インターフェイスの翻訳

ユーザーのインターフェイスは国際化アプリケーションに適用させる必要があります。テンプレートは、同じプレゼンテーションによってですが、いくつかの言語でラベル、メッセージ、とナビゲーションを表示できます。symfony は、デフォルトの言語でテンプレートを開発し、その上でテンプレート内部で使われる翻訳フレーズを辞書ファイルで提供することを推奨します。そういうわけで、翻訳内容を修正、追加、もしくは削除するたびにテンプレートを変更する必要はありません。

設定の翻訳

テンプレートはデフォルトでは翻訳されません。このことは、リスト13-11で示されるように、ほかのすべてに先駆けて settings.yml ファイルでテンプレート翻訳機能を有効にする必要があることを意味します。

リスト13-11 - インターフェイス翻訳を有効にする (frontend/config/settings.yml)

all:
  .settings:
    i18n: true

翻訳ヘルパーを使う

英語がデフォルトの言語で、英語とフランス語のサイトを作りたい場合を例に挙げてみましょう。サイトを翻訳することを考えるまえに、おそらくはリスト13-12の例で示されるようなテンプレートを書くでしょう。

リスト13-12 - 単独の言語テンプレート

[php]
Welcome to our website. Today's date is <?php echo format_date(date()) ?>

テンプレートの語句を翻訳する symfony のために、これらの語句は翻訳される文章として認識されなければなりません。これが国際化ヘルパーグループのメンバーである __() ヘルパー (2つのアンダースコア) の目的です。ですので、すべてのテンプレートは翻訳するためにこのような関数呼び出し内部で語句を閉じる必要があります。リスト13-11は、たとえば、リスト13-13のように修正できます (この章の後のほうの「複雑な翻訳ニーズを扱う」のセクションでご覧頂けます。そこではこの例の翻訳ヘルパーを呼び出すより優れた方法があります) 。

リスト13-13 - 多言語の準備ができているテンプレート

[php]
<?php use_helper('I18N') ?>

<?php echo __('Welcome to our website.') ?>
<?php echo __("Today's date is ") ?>
<?php echo format_date(date()) ?>

TIP アプリケーションがすべてのページに対して国際化ヘルパーグループを使う場合、それぞれのテンプレートに対して use_helper('I18N') の繰り返しを避けるために、ヘルパーを settings.yml ファイルの standard_helpers 設定に含めることはよいアイディアでしょう。

辞書ファイルを使う

__() 関数が呼び出されるたびに、symfony は現在のユーザーカルチャの辞書で引数の翻訳を探します。対応する語句が見つかったら、翻訳が送り戻され、レスポンスに表示されます。ですのでユーザーインターフェイスの翻訳は辞書ファイルに依存します。

辞書ファイルは messages.[language code].xml にしたがって名づけられた XLIFF (XML Localization Interchange File Format) で書かれており、アプリケーションの i18n/ ディレクトリに保存されます。

XLIFF は XML に基づく標準フォーマットです。よく知られているように、Web サイトにおいてテキストを参照し翻訳するためにサードパーティの翻訳ツールを利用できます。翻訳会社はこのようなファイルの扱いかたや、新しい XLIFF フォーマットの翻訳文を追加することでサイト全体を翻訳する方法を知っています。

TIP XLIFF 標準規格に加え、symfony は辞書のために翻訳用のバックエンドツールもいくつか提供します: gettext、MySQL、SQLite です。これらのバックエンドツールの設定に関する詳細な情報は API ドキュメントを参照してください。

リスト13-14はリスト13-13をフランス語に翻訳するために必要な messages.fr.xml ファイルのなかで XLIFF 構文の例を示しています。

リスト13-14 - フランス語の XLIFF 辞書 (frontend/i18n/messages.fr.xml)

[xml]
<?xml version="1.0" ?>
<!DOCTYPE xliff PUBLIC "-//XLIFF//DTD XLIFF//EN" "http://www.oasis-open.org/committees/xliff/documents/xliff.dtd" >
<xliff version="1.0">
  <file orginal="global" source-language="en_US" datatype="plaintext">
    <body>
      <trans-unit id="1">
        <source>Welcome to our website.</source>
        <target>Bienvenue sur notre site web.</target>
      </trans-unit>
      <trans-unit id="2">
        <source>Today's date is </source>
        <target>La date d'aujourd'hui est </target>
      </trans-unit>
    </body>
  </file>
</xliff>

source-language 属性はつねにあなたカルチャのすべての ISO コードを含まなければなりません。それぞれの翻訳は一意的な id 属性を持つ trans-unit タグで記述します。

デフォルトのユーザーカルチャによって (en_US に設定します)、語句は翻訳されず、__() の生の引数呼び出しが表示されます。リスト13-13の結果はリスト13-12に似ています。しかしながら、カルチャが fr_FR もしくは fr_BE に変更された場合、リスト13-15のような messages.fr.xml ファイルからの翻訳が代わりに表示されます。

リスト13-15 - 翻訳されたテンプレート

[php]
Bienvenue sur notre site web. La date d'aujourd'hui est
<?php echo format_date(date()) ?>

追加翻訳が必要な場合、同じディレクトリに新しい messages.XX.xml 翻訳ファイルを追加するだけです。

TIP 辞書ファイルを探し、それらを解析し、任意の文字列に対して正しい翻訳を見つけるにはある程度の時間がかかるので、symfony はプロセスを加速するために内部キャッシュを利用します。デフォルトでは、このキャッシュはファイルシステムを使います。(たとえば、複数のサーバー間のキャッシュを共有するために) factories.yml (19章を参照) で国際化キャッシュのふるまいを設定できます。

辞書を管理する

messages.XX.xml ファイルが長すぎて読むのが大変になったら、つねに翻訳をテーマによって名づけられたいくつかの辞書ファイルに分割できます。たとえば、アプリケーションの i18n/ ディレクトリのなかで messages.fr.xml ファイルをつぎの3つのファイルに分割できます:

  • navigation.fr.xml
  • terms_of_service.fr.xml
  • search.fr.xml

翻訳がデフォルトの messages.XX.xml ファイルですぐに見つからないのであれば、3番目の引数を使う __() ヘルパーを呼び出すたびにどの辞書を使うのか宣言しなければならないことに注意してください。たとえば、navigation.fr.xml の辞書で翻訳された文字列を出力するには、つぎのように書きます:

[php]
<?php echo __('Welcome to our website', null, 'navigation') ?>

翻訳辞書を編成する別の方法はモジュールによって分割することです。アプリケーション全体に対して単独の messages.XX.xml ファイルを書く代わりに、modules/[module_name]/i18n/ ディレクトリごとに1つのモジュールを書けます。このことによってモジュールはアプリケーションから独立したものになります。プラグイン(17章を参照)などを再利用したい場合に必要です。

国際化用の辞書を手動で更新するとよくエラーになりやすいので、symfony はこのプロセスを自動化するタスクを提供します。 i18n-extract タスクは翻訳されるすべての文字列を抽出するために symfony のアプリケーションを解析します。このタスクは引数としてアプリケーションとカルチャを受けとります:

$ php symfony i18n:extract frontend en

デフォルトでは、タスクは辞書を修正しないので、これは新旧の国際化された文字列の文字数を出力します。新しい文字列を辞書に追加するには、--auto-save オプションを渡します:

$ php symfony i18n:extract --auto-save frontend en

--auto-delete オプションを渡すことで自動的に古い文字列を削除することもできます:

$ php symfony i18n:extract --auto-save --auto-delete frontend en

NOTE 現在のタスクは既知の制限をいくつか持ちます。これはデフォルトの messages 辞書と、バックエンドに基づくファイル (XLIFFgettext) に対してのみ動作します。このタスクはメインの apps/frontend/i18n/messages.XX.xml ファイルの文字列の保存と削除のみ行います。

翻訳が必要なそのほかの要素を扱う

つぎの項目は翻訳が必要かもしれないそのほかの要素です:

  • 画像、テキストのドキュメント、もしくはほかのタイプのアセットもユーザーカルチャにしたがって変わることがあります。最良の例は実際は画像であるタイポグラフィを持つテキストの一部です。これらのために、ユーザーの culture の名前をとったサブディレクトリを作ることができます:

    [php]
    <?php echo image_tag($sf_user->getCulture().'/myText.gif') ?>
    
  • バリデーションファイルからのエラーメッセージは __() によって自動的に出力されるので、これらを翻訳する辞書に追加する必要があります。

  • symfony のデフォルトページ (ページが見つからない、サーバーの内部エラー、アクセスの制限など) は英語なので、国際化アプリケーションで書き直さなければなりません。おそらくはアプリケーションにあなた独自の default モジュールを作成し、__() をテンプレートのなかで使うべきです。これらのページをカスタマイズする方法は19章を参照してください。

複雑な翻訳ニーズを扱う

翻訳は __() の引数が完全な文である場合のみ意味をなします。しかしながら、単語と混ざったフォーマットもしくは変数を持つ場合、文をいくつかのチャンク(塊)に分割したくなることがありますが、ヘルパーを無意味な文に呼び出す結果になります。幸いにして、__() ヘルパーはトークン (字句) に基づく置き換え機能を提供します。この機能は翻訳者がより扱いやすい意味にある辞書を持つための助けになります。HTML整形と同じように、ヘルパー呼び出しで同じようにトークンをそのままにできます。リスト13-16は例を示しています。

リスト13-16 - コードを含むセンテンスを翻訳する

[php]
// 基本例
Welcome to all the <b>new</b> users.<br />
There are <?php echo count_logged() ?> persons logged.

// テキストの翻訳機能を有効にするためのわるい方法
<?php echo __('Welcome to all the') ?>
<b><?php echo __('new') ?></b>
<?php echo __('users') ?>.<br />
<?php echo __('There are') ?>
<?php echo count_logged() ?>
<?php echo __('persons logged') ?>

// テキストの翻訳機能を有効にするためのよい方法
<?php echo __('Welcome to all the <b>new</b> users') ?> <br />
<?php echo __('There are %1% persons logged', array('%1%' => count_logged())) ?>

この例では、トークンは %1% ですが、決して何でもよいわけではありません。 なぜなら翻訳ヘルパーが使う置き換え関数が strtr() だからです。

翻訳に関する共通問題の1つは複数形の使いかたです。結果の数に応じて、テキストは変わりますが、言語に従った同じ方法では変わりません。たとえば、リスト13-16の最後のセンテンスは count_logged()0もしくは1を返す場合には正しくはありません。この関数の戻り値をテストし、使うセンテンスがどれなのかを選択できますが、これはたくさんのコードが必要になることを意味します。加えて、異なる言語は異なる文法のルールを持ち、複数形の語形変化のルールはとても複雑になる場合があります。この問題は非常にありふれたものなので、この問題に対処するために symfony は format_number_choice() と呼ばれるヘルパーを提供します。リスト13-17はこのヘルパーを使う方法を示しています。

リスト13-17 - パラメーターの値に依存するセンテンスを翻訳する

[php]
<?php echo format_number_choice(
  '[0]Nobody is logged|[1]There is 1 person logged|(1,+Inf]There are %1% persons logged', array('%1%' => count_logged()), count_logged()) ?>

最初の引数はテキストの複数の選択肢です。2番目の引数は (__() ヘルパーに関する)置き換えパターンのオプションです。3番目の引数はどのテキストが取られるのかを決めるテスト上の数字です。

つぎのような構文を使うことで、メッセージ/文字列の選択は許可される値の配列の後に続くパイプ (|) 文字によって分離されます:

  • [1,2]: 1と2も含めて、1と2のあいだの値を許可する
  • (1,2): 1と2を除いて、1と2のあいだの値を許可する
  • {1,2,3,4}: 集合で定義された値のみ許可する
  • [-Inf,0): 負の無限大よりも大きいもしくは厳密に0未満の値を許可する
  • {n: n % 10 > 1 && n % 10 < 5} pliki: 2、3、4、 22、23、24のような数字をマッチする (ポーランド語やロシア語のような言語に対して便利です)

角かっこと丸かっこの区切り文字の空ではない組み合わせが許容されます。

翻訳機能を適切に機能させるにはメッセージを XLIFF ファイルに明示的に表示しなければなりません。リスト13-18は例を示しています。

リスト13-18 - format_number_choice() の引数のためのXLIFF辞書

...
<trans-unit id="3">
  <source>[0]Nobody is logged|[1]There is 1 person logged|(1,+Inf]There are %1% persons logged</source>
  <target>[0]Personne n'est connecté|[1]Une personne est connectée|(1,+Inf]Il y a %1% personnes en ligne</target>
</trans-unit>
...

SIDEBAR 文字集合のための少しの説明

テンプレートで国際化された内容を処理することはしばし文字集合の問題につながります。ローカライズされた文字集合を使う場合、ユーザーがカルチャを変更するたびに文字集合も変更することが必要になります。加えて、任意の文字集合で書かれたテンプレートはほかの文字集合の文字を正確に表示しません。

複数のカルチャを扱うと同時に、すべてのテンプレートが UTF-8 で保存され、レイアウトがこの文字集合で内容を宣言しなければならない訳はそういうわけです。つねに UTF-8 で扱えば、不愉快な不意打ちに合わなくてすみますし、やっかいな問題を悩まずにすみます。

symfony のアプリケーションは settings.yml ファイルのなかの字集合に関する1つのなか心的な設定に依存します。このパラメーターを変更することはすべてのレスポンスの Content-Type ヘッダーを変更することになります。

all:
  .settings:
    charset: utf-8

テンプレート外部から翻訳ヘルパーを呼び出す

ページに表示されるテキストのすべてがテンプレートによってもたらされるわけではありません。アプリケーションのほかの部分: アクション、フィルター、モデルクラスなどで、__()ヘルパーをしばし呼び出す必要があるのはそういうわけです。リスト13-19は Context Singleton を通して I18N オブジェクトの現在のインスタンスを読みとることでアクションでヘルパーを呼び出す方法を示しています。

リスト13-19 - アクションで __() を呼び出す

[php]
$this->getContext()->getI18N()->__($text, $args, 'messages');

まとめ

ユーザーカルチャの扱いかたを理解しているのであれば Web アプリケーションで国際化とローカライズする作業は苦痛をともなわずに行うことができます。ヘルパーはフォーマットされたデータを正しく出力する方法を自動的に考慮し、データベースから読みとられたローカライズされた内容はあたかもシンプルなテーブルの一部として見なされます。インターフェイスの翻訳に関しては、__()ヘルパーと XLIFF の辞書によって最小限の労力で最大限の結果が保証されます。