# 機能設計書 51-Translation

## 概要

本ドキュメントは、Symfony Translationコンポーネントの機能設計を記述する。Translationコンポーネントはアプリケーションの国際化(i18n)ツールを提供し、メッセージカタログ、翻訳ローダー、プルーラライゼーション(複数形処理)を統合的に管理する。

### 本機能の処理概要

Translationコンポーネントは、アプリケーション内のユーザー向けテキストを複数言語に翻訳するための基盤機能を提供する。翻訳メッセージの管理、ロケールの切り替え、フォールバック機構、メッセージフォーマッティングを統合的に扱う。

**業務上の目的・背景**：グローバルに展開するWebアプリケーションでは、ユーザーの言語設定に応じたUIテキストの表示が必須である。Translationコンポーネントは、翻訳リソースの管理、ロケール間のフォールバック、ICU MessageFormatによる複雑なメッセージ書式を統一的に提供することで、多言語対応の実装コストを大幅に削減する。

**機能の利用シーン**：コントローラーやTwigテンプレートでの翻訳メッセージ表示、バリデーションエラーメッセージの翻訳、メール通知文面の多言語化、コンソールコマンドの出力メッセージ翻訳など、アプリケーション全体の多言語対応に利用される。

**主要な処理内容**：
1. 翻訳リソースファイル(XLIFF, YAML, PHP, PO, MO, JSON, CSV, INI等)の読み込みとメッセージカタログの構築
2. メッセージIDとロケールに基づく翻訳メッセージの検索と返却
3. ロケールフォールバックチェーン(例: fr_FR -> fr -> en)による未翻訳メッセージの解決
4. ICU MessageFormat対応のメッセージフォーマッティング(複数形、性別、数値フォーマット等)
5. 翻訳カタログのキャッシュ生成と管理
6. LocaleSwitcherによるランタイムロケール切り替え
7. TranslatableMessageによる遅延翻訳オブジェクトの提供
8. 翻訳リソースのダンプ(エクスポート)機能

**関連システム・外部連携**：ICU(International Components for Unicode)ライブラリとの連携によるMessageFormat処理、Symfony Configコンポーネントとの連携によるキャッシュ管理、各種翻訳プロバイダー(Crowdin, Loco, Lokalise等)との連携によるリモート翻訳管理。

**権限による制御**：翻訳機能自体には権限制御はないが、LocaleSwitcherを利用したロケール切り替えはアプリケーション側で制御可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 26 | 翻訳パネル | 主画面 | Translationコンポーネントの翻訳メッセージ使用状況の表示 |

## 機能種別

データ変換 / メッセージ管理 / 国際化処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | string | Yes | 翻訳メッセージID | null・空文字の場合は空文字を返却 |
| parameters | array | No | メッセージ内のプレースホルダー置換パラメータ | TranslatableInterface実装値は自動翻訳 |
| domain | string | No | メッセージドメイン(デフォルト: 'messages') | 文字列型であること |
| locale | string | No | 翻訳先ロケール(デフォルト: 現在のロケール) | `/^[a-z0-9@_\\.\\-]*$/i`パターンに一致すること |

### 入力データソース

翻訳リソースファイル(XLIFF, YAML, PHP, PO, MO, JSON, CSV, INI, ICU Resource Bundle)、外部翻訳プロバイダーAPI。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| translatedMessage | string | 翻訳済みメッセージ文字列 |
| catalogue | MessageCatalogueInterface | ロケールに対応するメッセージカタログオブジェクト |

### 出力先

アプリケーション内のビューレイヤー(Twigテンプレート、コンソール出力等)、キャッシュファイル(PHP形式でシリアライズされたカタログ)。

## 処理フロー

### 処理シーケンス

```
1. trans()メソッド呼び出し
   └─ メッセージIDが空の場合は空文字を即座に返却
2. メッセージカタログの取得(getCatalogue)
   └─ キャッシュが有効な場合はキャッシュからロード、無い場合は初期化
3. カタログ初期化(initializeCatalogue)
   └─ 登録済みリソースをローダーで読み込みカタログを構築
4. フォールバックカタログの構築(loadFallbackCatalogues)
   └─ ロケール階層(例: fr_FR -> fr)とフォールバックロケールを連鎖
5. メッセージ検索
   └─ カタログのdefines()で現在カタログを確認、無ければフォールバックを辿る
6. パラメータ内のTranslatableInterface処理
   └─ TranslatableInterfaceを実装するパラメータを再帰的に翻訳
7. グローバルパラメータのマージ
   └─ addGlobalParameter()で登録された共通パラメータを適用
8. メッセージフォーマッティング
   └─ ICU MessageFormat対応の場合はformatIntl()、それ以外はformat()を使用
9. 翻訳済み文字列を返却
```

### フローチャート

```mermaid
flowchart TD
    A[trans呼び出し] --> B{IDが空?}
    B -->|Yes| C[空文字を返却]
    B -->|No| D[カタログ取得]
    D --> E{キャッシュあり?}
    E -->|Yes| F[キャッシュからロード]
    E -->|No| G[リソースからカタログ初期化]
    G --> H[フォールバックカタログ構築]
    F --> I[メッセージ検索]
    H --> I
    I --> J{カタログに定義あり?}
    J -->|Yes| K[メッセージ取得]
    J -->|No| L{フォールバックあり?}
    L -->|Yes| M[フォールバックカタログで検索]
    M --> J
    L -->|No| K
    K --> N[パラメータ置換・フォーマット]
    N --> O{ICU対応?}
    O -->|Yes| P[formatIntl]
    O -->|No| Q[format]
    P --> R[翻訳済み文字列返却]
    Q --> R
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-51-01 | フォールバック解決 | ロケール階層(例: fr_FR -> fr -> en)に沿ってメッセージを検索する | 現在カタログにメッセージが定義されていない場合 |
| BR-51-02 | INTL ドメインサフィックス | '+intl-icu'サフィックス付きドメインのメッセージはICU MessageFormatで処理する | ドメインが'+intl-icu'で終わる場合 |
| BR-51-03 | メッセージID返却 | 翻訳が見つからない場合はメッセージIDをそのまま返却する | カタログとフォールバック全てで未定義の場合 |
| BR-51-04 | ロケールバリデーション | ロケール文字列は英数字、@、_、.、-のみ許容する | setLocale、addResource等のロケール指定時 |

### 計算ロジック

キャッシュファイルパスは `$cacheDir/catalogue.{locale}.{hash}.php` の形式で生成される。ハッシュは `cacheVary` 配列の `xxh128` ハッシュの Base64 エンコード先頭7文字から算出される(Translator.php 365行目)。

## データベース操作仕様

### 操作別データベース影響一覧

本コンポーネントは直接的なデータベース操作を行わない。翻訳リソースはファイルシステムまたは外部APIから取得される。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | InvalidArgumentException | ロケール文字列が不正な形式の場合 | 正しいロケール形式(例: en, en_US)を指定 |
| - | RuntimeException | 指定フォーマットのローダーが未登録の場合 | addLoader()で適切なローダーを登録 |
| - | NotFoundResourceException | 翻訳リソースファイルが存在しない場合 | ファイルパスの確認、フォールバックロケールの設定 |
| - | LogicException | カタログ追加時にロケール不一致の場合 | 同一ロケールのカタログを追加すること |
| - | LogicException | フォールバック設定で循環参照が検出された場合 | フォールバックチェーンの見直し |

### リトライ仕様

リトライ機構は実装されていない。翻訳リソースの読み込み失敗時は例外がスローされる。

## トランザクション仕様

トランザクション管理は対象外。翻訳カタログのキャッシュ書き込みはConfigCacheFactoryを通じたアトミック書き込みで行われる。

## パフォーマンス要件

- カタログキャッシュにより、2回目以降のロケール解決は高速(PHPファイルincludeのみ)
- フォールバックロケールが変更されない場合、カタログの再生成をスキップ(Translator.php 148-151行目)
- opcache.preloadによるMessageCatalogueクラスの事前ロード対応(Translator.php 29行目)

## セキュリティ考慮事項

- ロケール文字列のバリデーションによるインジェクション防止(assertValidLocale)
- 翻訳メッセージ内のユーザー入力はエスケープされないため、出力時のエスケープはビューレイヤーの責務

## 備考

Translationコンポーネントは `symfony/contracts` パッケージの `TranslatorInterface` を実装しており、他のPSR互換の翻訳ライブラリとの置換が可能。

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

まず、翻訳メッセージの格納構造と関連インターフェースを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | MessageCatalogueInterface.php | `src/Symfony/Component/Translation/MessageCatalogueInterface.php` | カタログのインターフェース定義、INTL_DOMAIN_SUFFIX定数 |
| 1-2 | MessageCatalogue.php | `src/Symfony/Component/Translation/MessageCatalogue.php` | メッセージの保持構造(ドメイン別配列)、フォールバックカタログの連鎖 |
| 1-3 | TranslatableMessage.php | `src/Symfony/Component/Translation/TranslatableMessage.php` | 遅延翻訳オブジェクトの構造(message, parameters, domain) |

**読解のコツ**: MessageCatalogueではドメイン名に `+intl-icu` サフィックスが付いたものとそうでないものを区別して管理している。`all()` メソッド(56-79行目)でのマージ処理に注目。

#### Step 2: エントリーポイントを理解する

翻訳処理の起点となるTranslatorクラスを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | Translator.php | `src/Symfony/Component/Translation/Translator.php` | 翻訳のメインロジック、カタログ管理、キャッシュ処理 |

**主要処理フロー**:
1. **76-87行目**: コンストラクタでロケール設定、MessageFormatterの初期化
2. **112-126行目**: addResource()でリソース登録、キャッシュのクリア制御
3. **185-232行目**: trans()メソッド本体 - カタログ取得、フォールバック検索、パラメータ処理、フォーマット適用
4. **264-271行目**: loadCatalogue() - キャッシュ有無に応じたカタログ初期化の分岐
5. **389-405行目**: loadFallbackCatalogues() - フォールバックチェーンの構築
6. **407-446行目**: computeFallbackLocales() - 親ロケールの算出ロジック(parents.jsonとlocale_parse利用)

#### Step 3: ローダーレイヤーを理解する

各種翻訳リソース形式の読み込み処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | LoaderInterface.php | `src/Symfony/Component/Translation/Loader/LoaderInterface.php` | ローダーのインターフェース定義(load メソッド) |
| 3-2 | XliffFileLoader.php | `src/Symfony/Component/Translation/Loader/XliffFileLoader.php` | XLIFF形式(最も標準的な翻訳ファイル形式)の読み込み処理 |
| 3-3 | YamlFileLoader.php | `src/Symfony/Component/Translation/Loader/YamlFileLoader.php` | YAML形式の翻訳ファイル読み込み |

#### Step 4: フォーマッターレイヤーを理解する

メッセージのフォーマット処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | MessageFormatterInterface.php | `src/Symfony/Component/Translation/Formatter/MessageFormatterInterface.php` | フォーマッターのインターフェース |
| 4-2 | MessageFormatter.php | `src/Symfony/Component/Translation/Formatter/MessageFormatter.php` | 標準フォーマッター実装 |
| 4-3 | IntlFormatterInterface.php | `src/Symfony/Component/Translation/Formatter/IntlFormatterInterface.php` | ICU MessageFormat対応インターフェース |

#### Step 5: ロケール管理を理解する

ランタイムでのロケール切り替え機構を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | LocaleSwitcher.php | `src/Symfony/Component/Translation/LocaleSwitcher.php` | ロケール切り替え、runWithLocale()による一時切り替え |

**主要処理フロー**:
- **35-51行目**: setLocale() - Locale::setDefault()、RequestContextのパラメータ更新、localeAwareServicesへの通知
- **67-77行目**: runWithLocale() - コールバック実行中のみ一時的にロケールを変更し、finallyで元に戻す

### プログラム呼び出し階層図

```
Translator::trans()
    |
    +-- getCatalogue($locale)
    |       |
    |       +-- loadCatalogue($locale)
    |               |
    |               +-- [キャッシュあり] initializeCacheCatalogue()
    |               |       +-- ConfigCacheFactory::cache()
    |               |       +-- dumpCatalogue()
    |               |
    |               +-- [キャッシュなし] initializeCatalogue()
    |                       |
    |                       +-- doLoadCatalogue()
    |                       |       +-- LoaderInterface::load()
    |                       |       +-- MessageCatalogue::addCatalogue()
    |                       |
    |                       +-- loadFallbackCatalogues()
    |                               +-- computeFallbackLocales()
    |                               +-- initializeCatalogue($fallback) [再帰]
    |
    +-- MessageCatalogue::defines($id, $domain)
    +-- MessageCatalogue::getFallbackCatalogue()
    +-- MessageCatalogue::get($id, $domain)
    |
    +-- MessageFormatterInterface::format() / formatIntl()
```

### データフロー図

```
[入力]                       [処理]                           [出力]

翻訳リソースファイル ──────> LoaderInterface::load() ──────> MessageCatalogue
(XLIFF/YAML/PHP/PO等)                                         |
                                                               v
trans($id, $params,    ──> Translator::trans()           翻訳済み文字列
  $domain, $locale)         |
                            +-- カタログ検索
                            +-- フォールバック解決
                            +-- MessageFormatter::format()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| Translator.php | `src/Symfony/Component/Translation/Translator.php` | ソース | 翻訳のメインクラス |
| MessageCatalogue.php | `src/Symfony/Component/Translation/MessageCatalogue.php` | ソース | メッセージカタログのデータ構造 |
| MessageCatalogueInterface.php | `src/Symfony/Component/Translation/MessageCatalogueInterface.php` | ソース | カタログインターフェース定義 |
| TranslatableMessage.php | `src/Symfony/Component/Translation/TranslatableMessage.php` | ソース | 遅延翻訳メッセージオブジェクト |
| LocaleSwitcher.php | `src/Symfony/Component/Translation/LocaleSwitcher.php` | ソース | ランタイムロケール切り替え |
| DataCollectorTranslator.php | `src/Symfony/Component/Translation/DataCollectorTranslator.php` | ソース | プロファイラー用データ収集ラッパー |
| LoggingTranslator.php | `src/Symfony/Component/Translation/LoggingTranslator.php` | ソース | ログ出力用ラッパー |
| IdentityTranslator.php | `src/Symfony/Component/Translation/IdentityTranslator.php` | ソース | 翻訳なし(IDをそのまま返す)実装 |
| PseudoLocalizationTranslator.php | `src/Symfony/Component/Translation/PseudoLocalizationTranslator.php` | ソース | 疑似ローカリゼーション用ラッパー |
| XliffFileLoader.php | `src/Symfony/Component/Translation/Loader/XliffFileLoader.php` | ソース | XLIFF形式ローダー |
| YamlFileLoader.php | `src/Symfony/Component/Translation/Loader/YamlFileLoader.php` | ソース | YAML形式ローダー |
| PhpFileLoader.php | `src/Symfony/Component/Translation/Loader/PhpFileLoader.php` | ソース | PHP形式ローダー |
| PoFileLoader.php | `src/Symfony/Component/Translation/Loader/PoFileLoader.php` | ソース | PO形式ローダー |
| JsonFileLoader.php | `src/Symfony/Component/Translation/Loader/JsonFileLoader.php` | ソース | JSON形式ローダー |
| MessageFormatter.php | `src/Symfony/Component/Translation/Formatter/MessageFormatter.php` | ソース | メッセージフォーマッター |
| parents.json | `src/Symfony/Component/Translation/Resources/data/parents.json` | データ | 親ロケール定義 |
