PHPアプリケーションを国際化するとは、ボタンやメッセージを翻訳するだけではありません。複数の言語や市場に対応するアプリケーションでは、言語、地域形式、タイムゾーン、通貨、コンテンツ、ビジネスポリシーを分離する必要があります。これらの層が混在すると、拡張のたびにテストも保守も困難な機能分岐になり得ます。
複数の言語と市場で運用すると変わること

言語はテキストの表現方法を決定します。地域設定、すなわちlocaleは、小数点区切り、日付の順序、桁区切りといった表示規則を定義します。localeにはes-ESやfr-CAのように地域を含められますが、その地域を市場、法人、居住地、商業ポリシー、事業運営国の代替にしてはなりません。
また、同時に扱われがちでも同じ意味ではないコンテキストを区別することが重要です。
- タイムゾーン:時刻、期限、予定、運用上の締めを解釈します。
- 通貨:取引または価格表の金額を識別します。言語から推測してはなりません。
- 組織または法人:税金、権限、請求、データ保持を決定する場合があります。
- 市場:カタログ、物流、決済方法、利用可能なチャネルに影響する場合があります。
- ユーザー設定:選択された言語、locale、タイムゾーンであり、企業設定と異なる場合があります。
ある人が英語のインターフェースを使用し、欧州のタイムゾーンで業務を行い、別の通貨で請求する組織を管理することがあります。この実態を単一のlocale変数に還元すると、暗黙の判断が生まれます。
高コストな誤り:言語をビジネスルールにする
コードがインターフェース言語に基づいてドメイン上の判断を行う場合、if ($locale === 'es')は悪い兆候です。この条件分岐は異なるラベルの表示から始まり、税の適用、決済方法の非表示、承認の変更へと発展しかねません。
正しい問いは、「どのデータまたはポリシーがこの差異を説明するのか」です。法人に依存するなら、その法人を参照すべきです。商業ポリシーに応じるなら、識別可能かつバージョン管理可能なポリシーが存在すべきです。表現にのみ影響するなら、入力または出力の境界に属します。
言語は体験を翻訳するものであり、ドメインの振る舞いをそれ自体で認可、計算、定義するものではありません。
ドメインに残すべきもの
ドメインは安定した概念と正規値で扱うべきです。注文には数量、金額、明細、状態、計算ルールが必要であり、金額を1,234.50と表示するか1.234,50と表示するかを知る必要はありません。適格性ポリシーは、ユーザーの表示形式を読み取るのではなく、明示的な属性を受け取るべきです。
- ドメイン:不変条件、状態、計算、ビジネス上の認可、ポリシー、イベント。
- アプリケーション:ユースケース、コンテキストの読み込み、調整、ポリシーの選択。
- 入力アダプター:フォーム、ヘッダー、API、ファイル。検証と正規化。
- 出力アダプター:翻訳、シリアライズ、日付・金額・単位の書式設定。
PHPでは、エンティティや中核サービスがセッション、HTTPヘッダー、環境変数、プロセス全体のlocaleを直接参照しないようにしてください。こうした依存関係により、同じユースケースでもチャネルや実行時点に応じて異なる振る舞いになります。
明示的で限定されたコンテキストを設計する
ExecutionContextには、組織識別子、アクター、表示用locale、優先タイムゾーンを含められます。各フィールドには明確な意味論が必要です。一方、操作の通貨は、可変なグローバル設定ではなく、金額または適用された価格ポリシーの一部であるべきです。
各チャネルの境界でコンテキストを解決してください。Webリクエストでは保存済みの設定または制御されたネゴシエーションを使用でき、APIでは明示的かつ文書化されたフィールドを受け取るべきであり、キュー処理では作成時に必要な識別子を永続化する必要があります。非同期ジョブは、セッション、ユーザー、タイムゾーンを継承すると仮定してはなりません。
翻訳可能なコンテンツと運用データ
インターフェース文言、コミュニケーションテンプレート、編集コンテンツは、運用データとは異なるライフサイクルを持ちます。原文ではなく、たとえばbilling.invoice.overdueのような安定した意味的キーを使用してください。これにより、コード、テスト、統合を壊さずに文言を変更できます。
管理可能なコンテンツには公開が必要です。翻訳は下書き、承認済み、公開済みのいずれかになり得ます。フォールバックを定義してください。要求された言語、組織の基本言語、そして必要に応じて可視かつ制御された欠落です。代替翻訳は社内メモには許容されるかもしれませんが、契約上のコミュニケーションには必ずしも適しません。
データがローカライズされるものでない限り、言語ごとに完全な運用レコードを複製しないでください。製品では名称と説明を翻訳可能にできる一方、識別子、重量、状態、提供可否ルールは共通のままです。実際の商業上の差異があるなら、それを翻訳ではなくバリアントまたはポリシーとしてモデル化してください。
日付、金額、単位、丸め
時点は曖昧さのない形式で保存し、その意味がローカルな場合はタイムゾーンを保持してください。「会議は09:00に始まる」には定義された場所のタイムゾーンを知る必要があり、「イベントはこの時刻に発生した」には絶対的な時点が必要です。季節による時刻変更では存在しない時刻や重複する時刻が生じるため、入力を検証し、該当する場合は採用した解決方法を記録する必要があります。
金額については、通貨コードとともに最小単位の整数額を保存しますが、小数点以下2桁と仮定してはなりません。スケールまたは指数は、その操作に適用される通貨メタデータに由来します。一部の通貨は2とは異なるスケールを使用し、履歴上の要件や決済ネットワークの要件により、有効なスケールまたは使用したルールのバージョンを保持する必要がある場合があります。
final class Money {
public function __construct(
public readonly int $minorUnits,
public readonly string $currency,
public readonly int $scale
) {}
}
スケールにより最小単位を正しく解釈できますが、丸めポリシーの代替にはなりません。現金丸めが会計上の丸めと異なる場合があるため、丸めの時点、適用する方式、存在する場合は現金ルールを定義してください。float、計算中のローカライズ形式、暗黙の変換は避けてください。
同じ指針は測定値にも適用されます。運用上の意味がある場合は元の単位を保存し、計算で必要な場合は正規化し、変換は入力時または表示時にのみ行います。フォームには単位と許可される形式を示すべきであり、1,500が1.5を意味するのか1500を意味するのかを推測してはなりません。
入力・出力フローのアーキテクチャ
層の分離は、完全で再現可能なフローに現れるべきです。
- コンテキストと入力データを取得する:組織、アクター、チャネル、locale、タイムゾーン、受信した値を取得します。
- 許可される形式を検証する:必須フィールド、構文、単位、通貨、タイムゾーン、チャネルの制約を確認します。
- 正規値へ正規化する:ローカライズされたテキストを、曖昧さのない金額、日付、単位、識別子に変換します。
- ドメインユースケースを実行する:正規値に対して明示的なルールとポリシーを適用します。
- 出力を翻訳・書式設定する:公開済みメッセージを選択し、受信者またはAPI契約向けに値を表現します。
APIは、明示的なゾーン付きの日付や構造化された金額など、正規形式のみを受け入れると決定できます。人向けフォームは、パーサーが明示的である限り、ローカライズ形式を受け入れられます。どちらの場合も、ドメインは同じ安定した表現を受け取ります。
設定可能なポリシーか、別のビジネスルールか
プロセスを共有し、上限、祝日リスト、設定で定義された計算方式のような宣言可能なパラメーターだけが変わる場合、その差異は通常設定可能です。その設定にはスキーマ、バージョン、責任者、テストが必要です。
不変条件、状態、責務、データソース、法的帰結が変わる場合、その差異は別のルールを示します。その場合、オプションに隠すと不透明な設定が生まれます。明示的なインターフェースによってポリシーをモデル化するか、プロセスが実際に異なるなら別のフローにしてください。目的は単一の抽象化を強いることではなく、局所的な差異のためにアプリケーション全体を複製しないことです。
テストとチェックリスト

テストでは計算と表現を検証する必要があります。明示的な要件で定められている場合を除き、言語またはlocaleを変更しても、合計、認可、商業ポリシーは変わるべきではありません。
- 時刻変更時、曖昧な時刻、存在しない時刻の日付をテストします。
- ゼロおよび負の金額、異なるスケール、会計上および現金の丸めを網羅します。
- 許可されるローカライズ入力と、曖昧な形式の拒否を確認します。
- フォールバック、公開済みコンテンツの欠落、補間変数を検証します。
- 永続化済みコンテキストだけを使用し、セッションなしで非同期ジョブを実行します。
- 正規値と、必要な場合の形式メタデータを用いてAPI契約をテストします。
言語または市場を開放する前に、実際に何が変わるかを特定し、その信頼できる情報源を分離し、通貨と時間に関するルールを見直し、運用上制御された検証が必要なら可用性を段階的に有効化してください。この規律により、各市場を製品の並行バージョンに変えることなくアプリケーションを拡張できます。



