CakePHP3でメッセージやテキストのローカライズ(多言語化)を行う。英語から日本語へ
- 公開日
- 更新日
- カテゴリ:CakePHP
- タグ:PHP,Localization,CakePHP,i18n

WEB アプリケーション構築の際に日本語だけでなく、英語や中国語などにも対応させたい場合があります。ともすると CakePHP のメッセージはデフォルトで英語なので、それらを日本語化したい場合も多いと思います。
CakePHP では、翻訳ファイルを用いてテキストや動的なメッセージを多言語化する事が可能です。
今回は、i18n シェルを用いて翻訳ファイルを作成し、テキストやメッセージの日本語化をまず行います。
Contents
開発環境
今回の開発環境は以下の通りです。
- PHP 7.2
- CakePHP 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.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 "" #: 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