Caution: You are browsing the legacy symfony 1.x part of this website.
Cover of the book Symfony 5: The Fast Track

Symfony 5: The Fast Track is the best book to learn modern Symfony development, from zero to production. +300 pages showcasing Symfony with Docker, APIs, queues & async tasks, Webpack, SPAs, etc.

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

1.4
Symfony version Language

人気のある Web アプリケーションの多くはさまざまな言語で利用可能で、ときにはユーザーカルチャにもとづいてカスタマイズされます。 symfony はこれらの機能の管理を楽にする組み込みのフレームワークを搭載しています (「A Gentle Introduction to symfony」 の「国際化とローカライゼーション」の章をご参照ください)。

フォームフレームワークはユーザーインターフェイス用の組み込みサポートを備えており国際化オブジェクトの管理を楽にする方法を提供します。

フォームの国際化

symfony のフォームはデフォルトで国際化に対応しています。翻訳ファイルを編集してラベルヘルパーテキストエラーメッセージを翻訳できます。これらは XLIFFgettext もしくは symfony がサポートするその他のフォーマットです。

リスト8-1は以前の章で開発した問い合わせフォームを示します。

リスト8-1 - 問い合わせフォーム

class ContactForm extends BaseForm
{
  public function configure()
  {
    $this->setWidgets(array(
      'name'  => new sfWidgetFormInputText(),    // デフォルトのラベルは "Name"
      'email' => new sfWidgetFormInputText(),    // デフォルトのラベルは "Email"
      'body'  => new sfWidgetFormTextarea(), // デフォルトのラベルは "Body"
    ));
 
    // email ウィジェットのラベルを変更します
    $this->widgetSchema->setLabel('email', 'Email address');
  }
}

リスト8-2で示されるようにラベルのフランス語訳を XLIFF ファイルで定義します。

リスト8-2 - XLIFF 翻訳ファイル

// apps/frontend/i18n/messages.fr.xml
<?xml version="1.0" ?>
<xliff version="1.0">
  <file original="global" source-language="en" datatype="plaintext">
    <body>
      <trans-unit>
        <source>Name</source>
        <target>Nom</target>
      </trans-unit>
      <trans-unit>
        <source>Email address</source>
        <target>Adresse email</target>
      </trans-unit>
      <trans-unit>
        <source>Body</source>
        <target>Message</target>
      </trans-unit>
    </body>
  </file>
</xliff>

翻訳カタログを指定する

symfony の国際化フレームワークの機能を利用する場合、フォームを特定のカタログにバインドできます(カタログは「A Gentle Introduction to symfony」の「国際化とローカライゼーションの章」をご参照ください) 。 リスト8-3において、ContactForm フォームを contact_form カタログに関連づけます。ですので、フォーム要素の翻訳は contact_form.fr.xml ファイルで捜索されます。

リスト8-3 - 翻訳カタログのカスタマイズ

class ContactForm extends BaseForm
{
  public function configure()
  {
    // ...
 
    $this->widgetSchema->getFormFormatter()
      ->setTranslationCatalogue('contact_form');
  }
}

note

カタログを使うことで、たとえばフォームごとに1つのファイルを使った翻訳の組織化が改善されます。

エラーメッセージの国際化

ときどき、エラーメッセージにはユーザーによって投稿された値が埋め込まれます (たとえば、 「メールアドレスの [email protected] は有効ではありません。」)。 これをフォームクラスのなかでカスタマイズされたエラーメッセージを定義してユーザーが投稿した値をかんたんに利用できることを2章ですでに見ました。これらの参照は %parameter_name% パターンにしたがいます。

リスト8-4はこの原則を問い合わせフォームの name フィールドに適用する方法を示しています。

リスト8-4 - エラーメッセージの国際化

class ContactForm extends BaseForm
{
  public function configure()
  {
    // ...
 
    $this->validatorSchema['name'] = new sfValidatorEmail(
      array('min_length' => 2, 'max_length' => 45),
      array('min_length' => 'Name "%value%" must be at least %min_length% characters.',
            'max_length' => 'Name "%value%" must not exceed %max_length% characters.',
      ),
    );
  }
}

リスト8-5で示されるように XLIFF ファイルを編集することでこれらのエラーメッセージを翻訳できます。

リスト8-5 - エラーメッセージの XLIFF 翻訳ファイル

<trans-unit>
  <source>Name "%value%" must be at least %min_length% characters</source>
  <target>Le nom "%value%" doit comporter un minimum de %min_length% caractères</target>
</trans-unit>
<trans-unit>
  <source>Name "%value%" must not exceed %max_length% characters</source>
  <target>Le nom "%value%" ne peut comporter plus de %max_length% caractères</target>
</trans-unit>

翻訳オブジェクトのカスタマイズ

symfony の国際化フレームワークなしで symfony のフォームフレームワークを使いたい場合、翻訳オブジェクトを提供する必要があります。

翻訳オブジェクトは PHP の単なる callable です。これは次の3つのうちの1つです。

  • my_function のような関数の名前をあらわす文字列

  • array($anObject, 'oneOfItsMethodsName') のような、クラスのインスタンスとメソッドの1つの名前への参照をともなう配列

  • sfCallable インスタンス。このカプセルは一貫した方法で PHP の callable をカプセル化します。

note

PHP の callable は関数もしくはメソッドのインスタンスへの参照です。これは is_callable() 関数に渡されるときに true を返す PHP 変数でもあります。

リスト8-6で示されるクラスによって提供される独自の国際化メカニズムをすでにもっているプロジェクトをマイグレートしなければならない場合を考えてみましょう。

リスト8-6 - 国際化クラスのカスタマイズ

class myI18n
{
  static protected $default_culture = 'en';
  static protected $messages = array('fr' => array(
    'Name'    => 'Nom',
    'Email'   => 'Courrier électronique',
    'Subject' => 'Sujet',
    'Body'    => 'Message',
  )); 
 
  static public function translateText($text)
  {
    $culture = isset($_SESSION['culture']) ? $_SESSION['culture'] : self::$default_culture; 
    if (array_key_exists($culture, self::$messages)
        && array_key_exists($text, self::$messages[$culture]))
    {
      return self::$messages[$_SESSION['culture']][$text];
    }
    return $text;
  }
}
 
// クラスの使い方
$myI18n = new myI18n();
 
$_SESSION['culture'] = 'en';
echo $myI18n->translateText('Subject'); // => 「Subject」を表示します
 
$_SESSION['culture'] = 'fr';
echo $myI18n->translateText('Subject'); // => 「Suject」を表示します

リスト8-7で示されるようにそれぞれのフォームはフォームの要素の国際化を管理する独自の callable を定義できます。

リスト8-7 - フォームのための国際化メソッドをオーバーライドする

class ContactForm extends BaseForm
{
  public function configure()
  {
    // ...
    $this->widgetSchema->getFormFormatter()->setTranslationCallable(array(new myI18n(), 'translateText'));
  }
}

パラメータとして受け入れられる翻訳の callable

翻訳の callable は3つの引数をとります。

  • 翻訳するテキスト;

  • オリジナルのテキストの範囲で置き換える引数の連想配列。よくある目的はこの章で以前見たように動的な引数を置き換えるためです。

  • テキストを翻訳するときに使われるカタログの名前

下記のコードは翻訳の callable を呼び出すために sfFormWidgetSchemaFormatter::translate() メソッドによって使われる呼び出しです。

return call_user_func(self::$translationCallable, $subject, $parameters, $catalogue);

self::$translationCallable は翻訳の callable への参照です。ですので、以前のコードは下記のものと同等です。

$myI18n->translateText($subject, $parameters, $catalogue);

下記のコードはこれらの追加の引数をサポートする MyI18n クラスのアップデートされたバージョンです。

class myI18n
{
  static protected $default_culture = 'en';
  static protected $messages = array('fr' => array(
    'messages' => array(
      'Name'    => 'Nom',
      'Email'   => 'Courrier électronique',
      'Subject' => 'Sujet',
      'Body'    => 'Message',
    ),
  ));
 
  static public function translateText($text, $arguments = array(), $catalogue = 'messages')
  {
    $culture = isset($_SESSION['culture']) ? $_SESSION['culture'] : self::$default_culture; 
    if (array_key_exists($culture, self::$messages) &&
        array_key_exists($messages, self::$messages[$culture] &&
        array_key_exists($text, self::$messages[$culture][$messages]))
    {   
      $text = self::$messages[$_SESSION['culture']][$messages][$text];
      $text = strtr($text, $arguments);
    }   
    return $text;
  }
}

sidebar

翻訳処理をカスタマイズするのになぜ sfWidgetFormSchemaFormatter を使うのか?

2章で見てきたように、フォームフレームワークは MVC アーキテクチャにもとづき sfWidgetFormSchemaFormatter クラスはビューレイヤーに所属します。このクラスはすべてのテキストのレンダリングに関与するので、すべてのテキストの文字列をインターセプトしてこれらを即時に翻訳します。

Propel のオブジェクトの国際化

フォームフレームワークは国際化された Propel オブジェクト用の組み込みサポート機能をもちます。これが動作する方法を説明するために国際化されたモデルの例を見てみましょう。

propel:
  article:
    id:
    author:     varchar(255)
    created_at:
  article_i18n:
    title:      varchar(255)
    content:    longvarchar

次のコマンドで Propel のクラスと関連するフォームクラスを生成できます。

$ php symfony build:model
$ php symfony build:forms

これらのコマンドは symfony のプロジェクトディレクトリのなかで次のようなファイルを生成します。

lib/
  form/
    ArticleForm.class.php
    ArticleI18nForm.class.php
    BaseFormPropel.class.php
  model/
    Article.php
    ArticlePeer.php
    ArticleI18n.php
    ArticleI18nPeer.php

リスト8-8は同じフォームでフランス語と英語の記事を編集できるようにするために ArticleForm を設定する方法を示しています。

リスト8-8 - 国際化された Propel オブジェクト用の国際化フォーム

class ArticleForm extends BaseArticleForm
{
  public function configure()
  {
    $this->embedI18n(array('en', 'fr'));
  }
}

リスト8-9で示されるように次のコードを configure() メソッドに追加することでフォームの言語ラベルをカスタマイズすることもできます。

リスト8-9 - 言語ラベルのカスタマイズ

$this->widgetSchema->setLabel('en', 'English');
$this->widgetSchema->setLabel('fr', 'French');

図8-1 - 国際化対応の Propel フォーム

国際化対応の Propel フォーム

これでお終いです。フォームオブジェクトの save() メソッドを呼び出すと、関連する Propel オブジェクトと国際化オブジェクトは自動的に保存されます。

sidebar

ユーザーカルチャをフォームに渡す方法は?

フォームを現在のユーザーカルチャにバインドしたい場合、フォームを作るときに追加の culture オプションを渡すことができます。

class articleActions extends sfActions
{
  public function executeCreate(sfWebRequest $request)
  {
    $this->form = new ArticleForm(null, array('culture' => $this->getUser()->getCulture()));
 
    if ($request->isMethod('post') && $this->form->bindAndSave($request->getParameter('article')))
    {
      $this->redirect('article/created');
    }
  }
}

ArticleForm クラスにおいて、options の配列から値を取得できます。

class ArticleForm extends BaseArticleForm
{
  public function configure()
  {
    $this->embedI18n(array($this->getCurrentCulture()));
  }
 
  public function getCurrentCulture()
  {
    return isset($this->options['culture']) ? $this->options['culture'] : 'en';
  }
}

ローカライズされたウィジェット

symfony のフォームフレームワークは国際化を「認識する」(aware) いくつかのウィジェットを搭載しています。ユーザーカルチャにしたがってウィジェットをローカライズするためにこれらのウィジェットを使うことができます。

日付セレクタ

日付をローカライズするために利用できるウィジェットは次のとおりです。

  • sfWidgetFormI18nDate ウィジェットは日付用の入力を表示します (日にち、月、年)。

    $this->widgetSchema['published_on'] = new sfWidgetFormI18nDate(array('culture' => 'fr'));

    3つの異なる値を受け取る month_format オプションのおかげで、月の表示フォーマットも定義できます。

    • 月の名前を表示するための name(デフォルト)
    • 月の短縮名を表示するための short_name
    • 月の番号を表示するための number(1から12)
  • sfWidgetFormI18nTime ウィジェットは時刻の入力を表示します (時、分、秒)。

    $this->widgetSchema['published_on'] = new sfWidgetFormI18nTime(array('culture' => 'fr'));
  • sfWidgetFormI18nDateTime ウィジェットは日付と時刻の入力を表示します。

    $this->widgetSchema['published_on'] = new sfWidgetFormI18nDateTime(array('culture' => 'fr'));

カントリ (国コード) セレクタ

sfWidgetFormI18nChoiceCountry ウィジェットは国コードのリストで満たされたセレクトボックスを表示します。国コードの名前は指定された言語に翻訳されます。

$this->widgetSchema['country'] = new sfWidgetFormI18nChoiceCountry(array('culture' => 'fr'));

countries オプションのおかげで、セレクトボックス内の国コードを制限することもできます。

$countries = array('fr', 'en', 'es', 'de', 'nl');
$this->widgetSchema['country'] = new sfWidgetFormI18nChoiceCountry(array('culture'   => 'fr',
                                                                         'countries' => $countries));

カルチャセレクタ

sfWidgetFormI18nChoiceLanguage ウィジェットは言語コードの一覧で満たされたセレクトボックスを表示します。言語コードの名前は指定された言語に翻訳されます。

$this->widgetSchema['language'] = new sfWidgetFormI18nChoiceLanguage(array('culture' => 'fr'));

languages オプションのおかげで、セレクトボックスの言語を制限することもできます:

$languages = array('fr', 'en', 'es', 'de', 'nl');
$this->widgetSchema['language'] = new sfWidgetFormI18nChoiceLanguage(array('culture'   => 'fr',
                                                                           'languages' => $languages));
                                                                           ### タイムゾーンセレクタ

sfWidgetFormI18nChoiceTimezone ウィジェットはタイムゾーンで満たされたセレクトボックスを表示します。

$this->widgetSchema['timezone'] = new sfWidgetFormI18nChoiceTimzione();