RitoLabo

CakePHP3でメッセージやテキストのローカライズ(多言語化)を行う。英語から日本語へ

  • 公開:
  • カテゴリ: PHP CakePHP
  • タグ: PHP,Localization,CakePHP,3.5,3.6,i18n

WEBアプリケーション構築の際に日本語だけでなく、英語や中国語などにも対応させたい場合があります。ともするとCakePHPのメッセージはデフォルトで英語なので、それらを日本語化したい場合も多いと思います。

CakePHPでは、翻訳ファイルを用いてテキストや動的なメッセージを多言語化する事が可能です。

今回は、i18n シェルを用いて翻訳ファイルを作成し、テキストやメッセージの日本語化をまず行います。

アジェンダ
  1. 開発環境
  2. 翻訳ファイルの生成
  3. 翻訳ファイルの定義
  4. 翻訳ファイルのセット
    1. デフォルト設定を変更する
      1. 設定ファイルで切り替える
      2. コントローラで切り替える
  5. 動作確認
  6. メッセージの記法
    1. __()
    2. __d()
    3. __x()
  7. 動的な値の挿入

開発環境

今回の開発環境は以下の通りです。

  • Linux CentOS 7
  • Apache 2.4
  • PHP 7.1/7.2
  • CakePHP 3.5/3.6

CakePHPのルートディレクトリを「cakephp/」とします。

翻訳ファイルの生成

まずは翻訳ファイルを作成します。i18nシェルを用いる事で生成が可能です。CakePHPルートディレクトリに移動し、以下のコマンドを叩きます。

# CakePHPのルートディレクトリへ移動する
cd /path/to/cakephp

# 翻訳ファイルを生成
bin/cake i18n extract

# 実行結果
[demo@localhost cakephp]$ bin/cake i18n extract
Current paths: None
What is the path you would like to extract?
[Q]uit [D]one

[/path/to/cakephp/src/] >
[そのままEnter]

Current paths: /path/to/cakephp/src/
What is the path you would like to extract?
[Q]uit [D]one

[D] >
[そのままEnter]

Would you like to extract the messages from the CakePHP core? (y/n)
[n] > y
What is the path you would like to output?
[Q]uit

[/path/to/cakephp/src/Locale] >
[そのままEnter]

Would you like to merge all domain strings into the default.pot file? (y/n)
[n] > y


Extracting...
---------------------------------------------------------------
Paths:
/path/to/cakephp/src/
/path/to/cakephp/vendor/cakephp/cakephp/src/
Output Directory: /path/to/cakephp/src/Locale/
---------------------------------------------------------------
==========================================================================> 100%
Done.

ちなみに

bin/cake i18n

と叩くと、最初から対話形式で進めていけますが、最初に出てくる選択肢は以下になります。

[E]Extract POT file from sources
ソースからPOTファイルを抽出する
[I]Initialize a language from POT file
POTファイルから言語を初期化する
[H]Help
ヘルプを表示
[Q]Quit
終了

先ほど実行した bin/cake i18n extract は、bin/cake i18n からの [E]を選択した流れになります。

翻訳ファイルの生成が完了すると、cakephp/src 配下に Localeディレクトリが生成され、その配下に default.pot が生成されます。

cakephp
├─ src
│   ├─
Locale
│   │   └─
default.pot
cakephp/src/Locale/default.pot
# LANGUAGE translation of CakePHP Application
# Copyright YEAR NAME <EMAIL@ADDRESS>
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n"
"Last-Translator: NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <EMAIL@ADDRESS>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"

#: Template/Error/error400.ctp:36
#: Template/Error/error500.ctp:41
#: Template/Layout/error.ctp:35
msgid "Error"
msgstr ""

#: Template/Error/error400.ctp:37
msgid "The requested address {0} was not found on this server."
msgstr ""

#: Template/Error/error500.ctp:39
msgid "An Internal Error Has Occurred"
msgstr ""

#: Controller/Component/AuthComponent.php:499
msgid "You are not authorized to access that location."
msgstr ""

#: Controller/Component/CsrfComponent.php:162
#: Http/Middleware/CsrfProtectionMiddleware.php:190
msgid "Missing CSRF token cookie"
msgstr ""

#: Controller/Component/CsrfComponent.php:166
#: Http/Middleware/CsrfProtectionMiddleware.php:194
msgid "CSRF token mismatch."
msgstr ""

#: Error/ExceptionRenderer.php:262
msgid "Not Found"
msgstr ""

#: Error/ExceptionRenderer.php:264
msgid "An Internal Error Has Occurred."
msgstr ""

#: Http/Response.php:2588
msgid "The requested file contains `..` and will not be read."
msgstr ""

#: Http/Response.php:2609
msgid "The requested file was not found"
msgstr ""

#: I18n/Number.php:90
msgid "{0,number,#,###.##} KB"
msgstr ""

#: I18n/Number.php:92
msgid "{0,number,#,###.##} MB"
msgstr ""

#: I18n/Number.php:94
msgid "{0,number,#,###.##} GB"
msgstr ""

#: I18n/Number.php:96
msgid "{0,number,#,###.##} TB"
msgstr ""

#: I18n/Number.php:88
msgid "{0,number,integer} Byte"
msgid_plural "{0,number,integer} Bytes"
msgstr[0] ""
msgstr[1] ""

#: I18n/RelativeTimeFormatter.php:80
msgid "{0} from now"
msgstr ""

#: I18n/RelativeTimeFormatter.php:80
msgid "{0} ago"
msgstr ""

#: I18n/RelativeTimeFormatter.php:83
msgid "{0} after"
msgstr ""

#: I18n/RelativeTimeFormatter.php:83
msgid "{0} before"
msgstr ""

#: I18n/RelativeTimeFormatter.php:114
msgid "just now"
msgstr ""

#: I18n/RelativeTimeFormatter.php:151
msgid "about a second ago"
msgstr ""

#: I18n/RelativeTimeFormatter.php:152
msgid "about a minute ago"
msgstr ""

#: I18n/RelativeTimeFormatter.php:153
msgid "about an hour ago"
msgstr ""

#: I18n/RelativeTimeFormatter.php:154;332
msgid "about a day ago"
msgstr ""

#: I18n/RelativeTimeFormatter.php:155;333
msgid "about a week ago"
msgstr ""

#: I18n/RelativeTimeFormatter.php:156;334
msgid "about a month ago"
msgstr ""

#: I18n/RelativeTimeFormatter.php:157;335
msgid "about a year ago"
msgstr ""

#: I18n/RelativeTimeFormatter.php:168
msgid "in about a second"
msgstr ""

#: I18n/RelativeTimeFormatter.php:169
msgid "in about a minute"
msgstr ""

#: I18n/RelativeTimeFormatter.php:170
msgid "in about an hour"
msgstr ""

#: I18n/RelativeTimeFormatter.php:171;346
msgid "in about a day"
msgstr ""

#: I18n/RelativeTimeFormatter.php:172;347
msgid "in about a week"
msgstr ""

#: I18n/RelativeTimeFormatter.php:173;348
msgid "in about a month"
msgstr ""

#: I18n/RelativeTimeFormatter.php:174;349
msgid "in about a year"
msgstr ""

#: I18n/RelativeTimeFormatter.php:304
msgid "today"
msgstr ""

#: I18n/RelativeTimeFormatter.php:370
msgid "%s ago"
msgstr ""

#: I18n/RelativeTimeFormatter.php:371
msgid "on %s"
msgstr ""

#: I18n/RelativeTimeFormatter.php:47;126;316
msgid "{0} year"
msgid_plural "{0} years"
msgstr[0] ""
msgstr[1] ""

#: I18n/RelativeTimeFormatter.php:51;129;319
msgid "{0} month"
msgid_plural "{0} months"
msgstr[0] ""
msgstr[1] ""

#: I18n/RelativeTimeFormatter.php:57;132;322
msgid "{0} week"
msgid_plural "{0} weeks"
msgstr[0] ""
msgstr[1] ""

#: I18n/RelativeTimeFormatter.php:59;135;325
msgid "{0} day"
msgid_plural "{0} days"
msgstr[0] ""
msgstr[1] ""

#: I18n/RelativeTimeFormatter.php:64;138
msgid "{0} hour"
msgid_plural "{0} hours"
msgstr[0] ""
msgstr[1] ""

#: I18n/RelativeTimeFormatter.php:68;141
msgid "{0} minute"
msgid_plural "{0} minutes"
msgstr[0] ""
msgstr[1] ""

#: I18n/RelativeTimeFormatter.php:72;144
msgid "{0} second"
msgid_plural "{0} seconds"
msgstr[0] ""
msgstr[1] ""

#: ORM/RulesChecker.php:57
msgid "This value is already in use"
msgstr ""

#: ORM/RulesChecker.php:104
msgid "This value does not exist"
msgstr ""

#: ORM/RulesChecker.php:128
msgid "The count does not match {0}{1}"
msgstr ""

#: Utility/Text.php:916
msgid "and"
msgstr ""

#: Validation/Validator.php:111
msgid "This field is required"
msgstr ""

#: Validation/Validator.php:112
msgid "This field cannot be left empty"
msgstr ""

#: Validation/Validator.php:2053
msgid "The provided value is invalid"
msgstr ""

#: View/Helper/FormHelper.php:1097
msgid "Edit {0}"
msgstr ""

#: View/Helper/FormHelper.php:1099
msgid "New {0}"
msgstr ""

#: View/Helper/FormHelper.php:1986
msgid "Submit"
msgstr ""

#: View/Helper/HtmlHelper.php:815
msgid "Home"
msgstr ""

#: View/Widget/DateTimeWidget.php:540
msgid "January"
msgstr ""

#: View/Widget/DateTimeWidget.php:541
msgid "February"
msgstr ""

#: View/Widget/DateTimeWidget.php:542
msgid "March"
msgstr ""

#: View/Widget/DateTimeWidget.php:543
msgid "April"
msgstr ""

#: View/Widget/DateTimeWidget.php:544
msgid "May"
msgstr ""

#: View/Widget/DateTimeWidget.php:545
msgid "June"
msgstr ""

#: View/Widget/DateTimeWidget.php:546
msgid "July"
msgstr ""

#: View/Widget/DateTimeWidget.php:547
msgid "August"
msgstr ""

#: View/Widget/DateTimeWidget.php:548
msgid "September"
msgstr ""

#: View/Widget/DateTimeWidget.php:549
msgid "October"
msgstr ""

#: View/Widget/DateTimeWidget.php:550
msgid "November"
msgstr ""

#: View/Widget/DateTimeWidget.php:551
msgid "December"
msgstr ""

#: Template/Layout/error.ctp:43
msgid "Back"
msgstr ""

#: View/Helper/PaginatorHelper.php:1220
msgid "View"
msgstr ""

ちなみに、上記のメッセージたちは、実行したCakePHPのソースから集約されてこのファイルに集められています。従って、デフォルトのものと、既に何かの機能を実装していればそのメッセージも集約されてきますので、その場合は各々メッセージに差異が出てくるはずです。

例えば既に実装した機能がある場合、そこに日本語のメッセージを設定していたら以下のように日本語も混在したファイルが生成されます。

#: SampleController.php:45
msgid "例外が発生しました"
msgstr ""

#: Controller/SampleController.php:53;77
msgid "The sample has been saved."
msgstr ""

#: Controller/SubmissionController.php:74
msgid "課題を受け付けました"
msgstr ""

#: Controller/SubmissionController.php:77
msgid "ファイルの保存に失敗しました"
msgstr ""

#: Controller/SubmissionController.php:108
msgid "The submission has been saved."
msgstr ""

#: Controller/SubmissionController.php:112
msgid "The submission could not be saved. Please, try again."
msgstr ""

#: Controller/SubmissionController.php:129
msgid "The submission has been deleted."
msgstr ""

#: Controller/SubmissionController.php:131
msgid "The submission could not be deleted. Please, try again."
msgstr ""

#: Controller/UploadFilesController.php:114
msgid "The upload file has been deleted."
msgstr ""

#: Controller/UploadFilesController.php:116
msgid "The upload file could not be deleted. Please, try again."
msgstr ""

#: Controller/UsersController.php:39
msgid "ユーザ名もしくはパスワードが間違っています"
msgstr ""

#: Controller/UsersController.php:92;116
msgid "The user has been saved."
msgstr ""

#: Controller/UsersController.php:96;120
msgid "The user could not be saved. Please, try again."
msgstr ""


翻訳ファイルはこれを基にして作成していきますので、翻訳ファイルを作るなら途中ではなく最初に生成した方が日本語が混在しないのでおすすめです。

翻訳ファイルの定義

それでは、生成した default.pot を使って翻訳ファイルを作成します。今回は英語版と日本語版を作成します。

まず、cakephp/src/Locale 配下に en(英語) ディレクトリと ja(日本語) ディレクトリを作成し、その中にdefault.potをそれぞれ設置します。そしてその際に「default.pot」を「default.po」へリネームします。

cakephp
├─ src
│   ├─ Locale
│   │   ├─
en
│   │   │   └─
default.po
│   │   └─
ja
│   │   └─
default.po

enディレクトリに設置したファイルは英語版なので、デフォルトで出力されていれば全て英語なので、そのまま使用できます。

という事で、jaディレクトリに設置した翻訳ファイルを日本語にしていきます。

cakephp/src/Locale/ja/default.po
# LANGUAGE translation of CakePHP Application
# Copyright YEAR NAME <EMAIL@ADDRESS>
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n"
"Last-Translator: NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <EMAIL@ADDRESS>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"

#: Template/Error/error400.ctp:36
#: Template/Error/error500.ctp:41
#: Template/Layout/error.ctp:35
msgid "Error"
msgstr "エラー"

#: Template/Error/error400.ctp:37
msgid "The requested address {0} was not found on this server."
msgstr "要求されたアドレス {0} はこのサーバーで見つかりませんでした"

#: Template/Error/error500.ctp:39
msgid "An Internal Error Has Occurred"
msgstr "内部エラーが発生しました"

#: Controller/Component/AuthComponent.php:499
msgid "You are not authorized to access that location."
msgstr "あなたはその場所にアクセスする権限がありません"

#: Controller/Component/CsrfComponent.php:162
#: Http/Middleware/CsrfProtectionMiddleware.php:190
msgid "Missing CSRF token cookie"
msgstr "CSRFトークンクッキーがありません"

#: Controller/Component/CsrfComponent.php:166
#: Http/Middleware/CsrfProtectionMiddleware.php:194
msgid "CSRF token mismatch."
msgstr "CSRFトークンの不一致"

#: Error/ExceptionRenderer.php:262
msgid "Not Found"
msgstr "見つかりません"

#: Error/ExceptionRenderer.php:264
msgid "An Internal Error Has Occurred."
msgstr "内部エラーが発生しました"

#: Http/Response.php:2588
msgid "The requested file contains `..` and will not be read."
msgstr "要求されたファイルには `..`が含まれており、読み取れません。"

#: Http/Response.php:2609
msgid "The requested file was not found"
msgstr "要求されたファイルが見つかりませんでした"

#: I18n/Number.php:90
msgid "{0,number,#,###.##} KB"
msgstr ""

#: I18n/Number.php:92
msgid "{0,number,#,###.##} MB"
msgstr ""

#: I18n/Number.php:94
msgid "{0,number,#,###.##} GB"
msgstr ""

#: I18n/Number.php:96
msgid "{0,number,#,###.##} TB"
msgstr ""

#: I18n/Number.php:88
msgid "{0,number,integer} Byte"
msgid_plural "{0,number,integer} Bytes"
msgstr[0] ""
msgstr[1] ""

#: I18n/RelativeTimeFormatter.php:80
msgid "{0} from now"
msgstr ""

#: I18n/RelativeTimeFormatter.php:80
msgid "{0} ago"
msgstr ""

#: I18n/RelativeTimeFormatter.php:83
msgid "{0} after"
msgstr ""

#: I18n/RelativeTimeFormatter.php:83
msgid "{0} before"
msgstr ""

#: I18n/RelativeTimeFormatter.php:114
msgid "just now"
msgstr ""

#: I18n/RelativeTimeFormatter.php:151
msgid "about a second ago"
msgstr ""

#: I18n/RelativeTimeFormatter.php:152
msgid "about a minute ago"
msgstr ""

#: I18n/RelativeTimeFormatter.php:153
msgid "about an hour ago"
msgstr ""

#: I18n/RelativeTimeFormatter.php:154;332
msgid "about a day ago"
msgstr ""

#: I18n/RelativeTimeFormatter.php:155;333
msgid "about a week ago"
msgstr ""

#: I18n/RelativeTimeFormatter.php:156;334
msgid "about a month ago"
msgstr ""

#: I18n/RelativeTimeFormatter.php:157;335
msgid "about a year ago"
msgstr ""

#: I18n/RelativeTimeFormatter.php:168
msgid "in about a second"
msgstr ""

#: I18n/RelativeTimeFormatter.php:169
msgid "in about a minute"
msgstr ""

#: I18n/RelativeTimeFormatter.php:170
msgid "in about an hour"
msgstr ""

#: I18n/RelativeTimeFormatter.php:171;346
msgid "in about a day"
msgstr ""

#: I18n/RelativeTimeFormatter.php:172;347
msgid "in about a week"
msgstr ""

#: I18n/RelativeTimeFormatter.php:173;348
msgid "in about a month"
msgstr ""

#: I18n/RelativeTimeFormatter.php:174;349
msgid "in about a year"
msgstr ""

#: I18n/RelativeTimeFormatter.php:304
msgid "today"
msgstr ""

#: I18n/RelativeTimeFormatter.php:370
msgid "%s ago"
msgstr ""

#: I18n/RelativeTimeFormatter.php:371
msgid "on %s"
msgstr ""

#: I18n/RelativeTimeFormatter.php:47;126;316
msgid "{0} year"
msgid_plural "{0} years"
msgstr[0] ""
msgstr[1] ""

#: I18n/RelativeTimeFormatter.php:51;129;319
msgid "{0} month"
msgid_plural "{0} months"
msgstr[0] ""
msgstr[1] ""

#: I18n/RelativeTimeFormatter.php:57;132;322
msgid "{0} week"
msgid_plural "{0} weeks"
msgstr[0] ""
msgstr[1] ""

#: I18n/RelativeTimeFormatter.php:59;135;325
msgid "{0} day"
msgid_plural "{0} days"
msgstr[0] ""
msgstr[1] ""

#: I18n/RelativeTimeFormatter.php:64;138
msgid "{0} hour"
msgid_plural "{0} hours"
msgstr[0] ""
msgstr[1] ""

#: I18n/RelativeTimeFormatter.php:68;141
msgid "{0} minute"
msgid_plural "{0} minutes"
msgstr[0] ""
msgstr[1] ""

#: I18n/RelativeTimeFormatter.php:72;144
msgid "{0} second"
msgid_plural "{0} seconds"
msgstr[0] ""
msgstr[1] ""

#: ORM/RulesChecker.php:57
msgid "This value is already in use"
msgstr "この値はすでに使用されています"

#: ORM/RulesChecker.php:104
msgid "This value does not exist"
msgstr "この値は存在しません"

#: ORM/RulesChecker.php:128
msgid "The count does not match {0}{1}"
msgstr "カウントが一致しません"

#: Utility/Text.php:916
msgid "and"
msgstr ""

#: Validation/Validator.php:111
msgid "This field is required"
msgstr "この項目は必須です"

#: Validation/Validator.php:112
msgid "This field cannot be left empty"
msgstr "このフィールドを空白にすることはできません"

#: Validation/Validator.php:2053
msgid "The provided value is invalid"
msgstr "指定された値は無効です"

#: View/Helper/FormHelper.php:1097
msgid "Edit {0}"
msgstr ""

#: View/Helper/FormHelper.php:1099
msgid "New {0}"
msgstr ""

#: View/Helper/FormHelper.php:1986
msgid "Submit"
msgstr ""

#: View/Helper/HtmlHelper.php:815
msgid "Home"
msgstr ""

#: View/Widget/DateTimeWidget.php:540
msgid "January"
msgstr ""

#: View/Widget/DateTimeWidget.php:541
msgid "February"
msgstr ""

#: View/Widget/DateTimeWidget.php:542
msgid "March"
msgstr ""

#: View/Widget/DateTimeWidget.php:543
msgid "April"
msgstr ""

#: View/Widget/DateTimeWidget.php:544
msgid "May"
msgstr ""

#: View/Widget/DateTimeWidget.php:545
msgid "June"
msgstr ""

#: View/Widget/DateTimeWidget.php:546
msgid "July"
msgstr ""

#: View/Widget/DateTimeWidget.php:547
msgid "August"
msgstr ""

#: View/Widget/DateTimeWidget.php:548
msgid "September"
msgstr ""

#: View/Widget/DateTimeWidget.php:549
msgid "October"
msgstr ""

#: View/Widget/DateTimeWidget.php:550
msgid "November"
msgstr ""

#: View/Widget/DateTimeWidget.php:551
msgid "December"
msgstr ""

#: Template/Layout/error.ctp:43
msgid "Back"
msgstr ""

#: View/Helper/PaginatorHelper.php:1220
msgid "View"
msgstr ""

翻訳ファイルのセット

現時点で英語版と日本語版の翻訳ファイルが出来上がりましたが、「どの時点で言語を切り替えるのか?」によって設定する場所が変わってきます。

ちなみに、特に設定しない場合はシステム言語によって自動で切り替わるので、日本人で日本語の端末を使っていれば自動的に日本語ファイルが採用されます。(それ以外は、英語版が適用される事になります。)

デフォルト設定を変更する

前途の通り、翻訳ファイルは自動で切り替わりますが、デフォルトを設定する事もできます。

設定ファイルで切り替える

設定ファイルのAppプロパティにあるdefaultLocaleを変更します。

cakephp/config/app.php
'App' => [
//'defaultLocale' => env('APP_DEFAULT_LOCALE', 'en_US'),
'defaultLocale' => env('APP_DEFAULT_LOCALE', 'ja_JP'),
],

また、上記を見るとenvファイルで設定されているので、導入している場合はそちらを変更します。

cakephp/config/.env
export APP_DEFAULT_LOCALE=ja_JP

ENVファイルを導入しているなら、設定ファイルは変更なしでenvファイルのみの変更で良いと思います。

コントローラで切り替える

スポット的に切り替えるなら、コントローラからでも設定できます。以下のようにI18nを読み込み、設定を切り替える記述を行います。

<?php
namespace App\Controller;

use App\Controller\AppController;
use Cake\I18n\I18n; // ← 追加

class UsersController extends AppController
{
public function login()
{
I18n::setLocale('ja_JP'); // ← 追加

}

}

上記で紹介した例にて設定すると、システム言語を無視し強制的にその言語へと切り替えられます。

動作確認

ここまでで大体のローカライズの基本を解説しましたが、実際に一連の作業を行って翻訳ファイルの切り替わりを確認してみます。

まずは単純に以下のようなログイン画面を作ります。

cakephp/src/Template/Users/login.ctp
<div class="users form">
<?= $this->Flash->render() ?>
<?= $this->Form->create() ?>
<fieldset>
<legend><?= __('Please enter user name and password') ?></legend>
<?= $this->Form->control('username') ?>
<?= $this->Form->control('password') ?>
</fieldset>
<?= $this->Form->button(__('Login')); ?>
<?= $this->Form->end() ?>
</div>

「Please enter user name and password」というメッセージがあることが確認できると思います。ブラウザから表示させると、以下のようになります。

英語でのメッセージ表示

これを日本語に翻訳します。日本語の翻訳ファイルへ、以下を記述します。

cakephp/src/Locale/ja/default.po
msgid "Please enter user name and password"
msgstr "ユーザ名とパスワードを入力してください"

ブラウザからアクセスしてみます。

日本語に翻訳されたメッセージ表示

テキストが日本語に翻訳されている事が確認できました。

メッセージの記法

各処理でメッセージを代入、または表示する際に、翻訳に対応させる為の記法がいくつか存在します。

__()

<?= __('Cakephp localize message.') ?>

上記記法にて記述すると、そのフルテキストに対しての翻訳が試みられます。見つからなかった場合は、そのままのテキストが表示されます。

__d()

<?= __d('original_plugin', 'Cakephp localize message.') ?>

もし翻訳ファイルを機能ごとやプラグインで切り分けていた場合は、上記の記法を使用します。

上記の場合は、cakephp/src/Locale/言語ディレクトリ/original_plugin.po から翻訳を取得しようとします。

__x()

同じ文章でも意味合いが違う場合にはこのメソッドを使います。以下のようにして文章に対してその意味付けを行い、それによって翻訳を変更していきます。意味づけという事ですが、識別子をつけているような感じです。

// 驚きの「なんてこったい」
<?= __x('surprise', 'I can not believe that.') ?>

// 絶望の「なんてこったい」
<?= __x('despair', 'I can not believe that.') ?>

この2つのメッセージに対して、翻訳ファイルへは以下のように記述します。

msgctxt "surprise"
msgid "I can not believe that."
msgstr "すごい!なんてことだ!"

msgctxt "despair"
msgid "I can not believe that."
msgstr "なんてこったい…。"

こうする事で、同じテキストに対しても別の翻訳を充てる事ができます。

動的な値の挿入

翻訳し、表示するテキストやメッセージが常に固定のものだけとは限りません。以下のように記述する事で、動的な値を挿入する事も出来ます。

<?= __('Today is {0}. The weather is {1}.', ['Sunday', 'sunny']) ?>

第一引数にメッセージをプレースホルダーを、第二引数に配列の形でそれぞれ動的な値を設定します。

そして翻訳ファイルには以下のように記述します。

msgid "Today is {0}. The weather is {1}."
msgstr "今日は {0} です。天気は {1} です。"

msgid "Sunday"
msgstr "日曜日"

msgid "Saturday"
msgstr "土曜日"

msgid "sunny"
msgstr "晴れ"

msgid "cloudy"
msgstr "曇り"

msgid "rainy"
msgstr "雨"

こうする事で、メッセージに動的な値を挿入しつつ、翻訳も行えます。

まとめ

以上で作業は完了です。動作確認で行った感じで適宜開発を進めつつ、翻訳ファイルを更新していくか、コマンドを叩いて再度potファイルを吐き出してマージするなどしていくと、開発スタイルとしては一定の流れが作れると思います。

多言語化という部分にはなりますが、例えば必要なメッセージのみ(例えばエラーメッセージだけとか)を集約する事でそれらをまとめて管理を行う事もでき、状況によっては結構便利なので(とはいえ結局は翻訳ファイル的な動きですが)是非試してみてください。

[cookbook]国際化と地域化
https://book.cakephp.org/3.0/ja/core-libraries/internationalization-and-localization.html

[cookbook]I18N シェル
https://book.cakephp.org/3.0/ja/console-and-shells/i18n-shell.html