Yiiで気に入らない標準の翻訳文言を書き換える

Yiiでは、エラー等に使うメッセージには翻訳ファイルを使います。標準のバリデーターやウィジェットを使うと、Yiiに同梱されたメッセージリソースが使われます。この標準の文言が気に入らないという場合、次の3つの方法があります。

## 特定のバリデーションエラーをカスタマイズしたい

フォームのバリデーション定義で、個々のルールに `message` プロパティを指定します。それを、`yii` カテゴリのものではなく `app` カテゴリ、つまりアプリケーションの翻訳リソースのものにしてしまいます。

```php
    public function rules()
    {
        return [
            [['name'], 'required', 'message' => Yii::t('app', '{attribute} cannot be blank.')],
        ];
    }
```

そのまま文字列を書いてもかまいませんが、`Yii::t()` を使ってアプリケーション独自の翻訳リソースを使うようにすれば、サイトの多言語化に対応できます。

```php:messages/ja/app.php
<?php
return [
    '{attribute} cannot be blank.' => '{attribute}は必須です。',
];
```

ユーザーが使うフォームが比較的少ないサイトであれば大丈夫ですが、フォームが多いと、すべての箇所にこのカスタマイズを仕込むのは大変です。

## 翻訳ファイルをすべて管理する

`vendor/yiisoft/yii2/messages` の全ファイルをアプリケーションの `messages` にコピーし、アプリケーションの `i18n` コンポーネントをカスタマイズして `yii` カテゴリでも `@app/messages` を参照するようにさせます。

```php:config/web.php
$config = [
    'language' => 'ja',
    'components' => [
        'i18n' => [
            'translations' => [
                'yii' => [
                    'class' => 'yii\i18n\PhpMessageSource',
                    'sourceLanguage' => 'en-US',
                    'basePath' => '@app/messages',
                ],
            ],
        ],
    ],
];
```

もっとも簡単に文言変更が可能ですが、Yiiのマイナーバージョンアップ時に翻訳がメンテされる可能性を考えると、少々管理が大変です。

## Yiiならなんでもカスタマイズできる

Yiiの翻訳リソースローダーは、翻訳システムとは独立したオブジェクトで、差し替え可能です。カテゴリごとに、独自形式のファイルやデータベースから読み込むローダーとさえ差し替えられるようになっています。

標準の `PhpMessageSource` は特定のカテゴリにおけるすべての言語の翻訳ファイルが一箇所におさまっている想定で実装されているところだけが不都合なので、少しオーバーライドして、ファイルの配置が柔軟に設定できるバージョンの `PhpMessageSource` を作ってみます。

```php:utils/i18n/PhpMessageSource.php
<?php
namespace app\utils\i18n;

use Yii;

class PhpMessageSource extends \yii\i18n\PhpMessageSource
{
    /**
     * @var array
     *
     * 特定の言語のときのみ標準のパス以外からメッセージをロードする
     *
     * ~~~
     * [
     *     'ja' => '@app/messages/japanese',
     *     'en' => '@app/messages/english',
     * ]
     * ~~~
     */
    public $languagePathMap = [];

    /**
     * @inheritdoc
     */
    protected function getMessageFilePath($category, $language)
    {
        if (isset($this->languagePathMap[$language])) {
            $messageFile = rtrim(Yii::getAlias($this->languagePathMap[$language]), '/') . "/";
            if (isset($this->fileMap[$category])) {
                $messageFile .= $this->fileMap[$category];
            } else {
                $messageFile .= str_replace('\\', '/', $category) . '.php';
            }
            return $messageFile;
        } else {
            return parent::getMessageFilePath($category, $language);
        }
    }
}
```

この実装を使って...


```php:config/web.php
$config = [
    'language' => 'ja',
    'components' => [
        'i18n' => [
            'translations' => [
                'yii' => [
                    'class' => 'app\utils\i18n\PhpMessageSource',
                    'sourceLanguage' => 'en-US',
                    'basePath' => '@yii/messages',
                    'languagePathMap' => [
                        'ja' => '@app/messages/ja',
                    ],
                ],
            ],
        ],
    ],
];
```

これで、`yii` カテゴリの翻訳は、日本語の場合だけ `@yii/messages/*` (vendorの中) の代わりに `@app/messages/ja` から読むという設定になりました。

あとは vendor の下から `messages/ja/yii.php` にコピーして... よりも、こんなふうに、部分書き換えをしてやるといかがでしょうか。

```php:messages/ja/yii.php
<?php
return array_merge(include(\Yii::getAlias('@yii/messages/ja/yii.php')), [

    // 気に入らない: '{attribute} cannot be blank.' => '{attribute}は空白ではいけません。',

    '{attribute} cannot be blank.' => '{attribute}は必須です。',
]);
```

こんなのは超絶コンフィグ可能なライブラリなら想定済みなのかもしれませんが、Yiiの考え方では、素朴な実装で高速な動作を尊重します。そこで、ごく普通にユーザーとして使っている人でも、フレームワークのソースをちょっと調べるだけで、標準機能が仕事に合わない箇所をカスタマイズできる。この点が Yii の素敵なところですね。