# 帳票設計書 1-XLIFF翻訳ファイル

## 概要

Symfonyの翻訳コンポーネントにおけるXLIFF（XML Localization Interchange File Format）形式の翻訳ファイル出力機能の設計書である。メッセージカタログをXLIFF 1.2およびXLIFF 2.0形式のXMLファイルとして出力する。

### 本帳票の処理概要

本帳票は、Symfonyアプリケーション内部のメッセージカタログ（MessageCatalogue）に格納された翻訳メッセージを、国際標準であるXLIFF形式のXMLファイルとして出力する機能を提供する。

**業務上の目的・背景**：多言語対応するWebアプリケーションでは、翻訳リソースの管理・共有が不可欠である。XLIFF（XML Localization Interchange File Format）はOASIS標準の翻訳データ交換フォーマットであり、翻訳メモリツール（SDL Trados、memoQ等）との連携を可能にする。本帳票により、開発者が管理する翻訳データを翻訳者やローカライゼーションチームと標準フォーマットで共有できるようになる。

**帳票の利用シーン**：翻訳リソースのエクスポート時、CIパイプラインにおける翻訳ファイルのビルド時、翻訳管理サービス（Crowdin、Lokalise等）への翻訳データ送信時、開発環境からステージング・本番環境への翻訳データデプロイ時に利用される。

**主要な出力内容**：
1. XLIFF 1.2形式のXMLドキュメント（trans-unit要素にsource/targetペアを含む）
2. XLIFF 2.0形式のXMLドキュメント（unit/segment要素にsource/targetペアを含む）
3. メタデータ（notes、target-attributes、catalogue metadata）の出力
4. ドメイン別およびINTL-ICUドメインサフィックス対応の分割出力

**帳票の出力タイミング**：`translation:update`コマンド実行時、またはプログラム内から`XliffFileDumper::dump()`メソッドを呼び出した時に出力される。

**帳票の利用者**：アプリケーション開発者、翻訳者、ローカライゼーションマネージャー、CI/CDパイプライン

## 帳票種別

翻訳データファイル（XMLベース）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| N/A | CLIコマンド | `php bin/console translation:update` | コマンド実行 |
| N/A | プログラムAPI | `XliffFileDumper::dump()` | メソッド呼び出し |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | XML（XLIFF 1.2 / XLIFF 2.0） |
| 用紙サイズ | N/A（データファイル） |
| 向き | N/A |
| ファイル名 | `%domain%.%locale%.xlf`（デフォルト、relativePathTemplateで変更可能） |
| 出力方法 | ファイルシステムへの書き込み（file_put_contents） |
| 文字コード | UTF-8 |

### XLIFF 1.2固有設定

| 項目 | 内容 |
|-----|------|
| XML宣言 | `<?xml version="1.0" encoding="utf-8"?>` |
| 名前空間 | `urn:oasis:names:tc:xliff:document:1.2` |
| source-language | options['default_locale']またはLocale::getDefault()から取得（`_`を`-`に変換） |
| target-language | MessageCatalogue::getLocale()から取得（`_`を`-`に変換） |
| datatype | `plaintext` |
| tool情報 | tool-id: symfony, tool-name: Symfony（options['tool_info']でカスタマイズ可能） |
| trans-unit ID | xxh128ハッシュのBase64エンコード先頭7文字 |

### XLIFF 2.0固有設定

| 項目 | 内容 |
|-----|------|
| 名前空間 | `urn:oasis:names:tc:xliff:document:2.0` |
| メタデータ名前空間 | `urn:oasis:names:tc:xliff:metadata:2.0`（カタログメタデータ存在時） |
| file要素のID | `{domain}.{locale}` |
| unit要素のname属性 | sourceが80文字以下の場合のみ設定 |
| segment-attributes | メタデータから取得して設定 |

## 帳票レイアウト

### レイアウト概要

XLIFF 1.2の場合、以下のXML構造で出力される。

```
┌─────────────────────────────────────┐
│ <?xml version="1.0" encoding="utf-8"?>│
│ <xliff version="1.2">               │
│   <file>                            │
│     ┌─ <header>（ツール情報・メタデータ）│
│     ├─ <body>                       │
│     │    <trans-unit>（翻訳単位）×N  │
│     │      <source>原文</source>     │
│     │      <target>訳文</target>     │
│     │      <note>注釈</note>         │
│     │    </trans-unit>               │
│     └─ </body>                      │
│   </file>                           │
│ </xliff>                            │
└─────────────────────────────────────┘
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | source-language | ソース言語コード | options['default_locale']またはLocale::getDefault() | BCP 47形式（例: en-US） |
| 2 | target-language | ターゲット言語コード | MessageCatalogue::getLocale() | BCP 47形式（例: ja-JP） |
| 3 | tool-id | ツール識別子 | 固定値 "symfony" またはoptions['tool_info'] | 文字列 |
| 4 | tool-name | ツール名 | 固定値 "Symfony" またはoptions['tool_info'] | 文字列 |
| 5 | catalogue metadata | カタログメタデータ | MessageCatalogue::getCatalogueMetadata() | prop-group/prop要素 |

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | trans-unit id | 翻訳単位識別子 | sourceのxxh128ハッシュBase64先頭7文字 | 英数字 | N/A |
| 2 | resname | リソース名 | メッセージキー（source） | 文字列 | N/A |
| 3 | source | 原文 | MessageCatalogue::all($domain)のキー | テキストノード | N/A |
| 4 | target | 訳文 | MessageCatalogue::all($domain)の値 | テキストノードまたはCDATAセクション | N/A |
| 5 | target-attributes | ターゲット属性 | metadata['target-attributes'] | XML属性 | N/A |
| 6 | note | 注釈 | metadata['notes'] | テキストノード（priority, from属性付き） | N/A |

### フッター部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| N/A | なし | XLIFF形式にフッター部は存在しない | - | - |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| path | 出力先ディレクトリパス | Yes |
| xliff_version | XLIFFバージョン（"1.2"または"2.0"、デフォルト: "1.2"） | No |
| default_locale | ソース言語ロケール（デフォルト: Locale::getDefault()） | No |
| tool_info | ツール情報のカスタマイズ配列 | No |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | メッセージカタログの格納順 | 格納順（挿入順） |

### 改ページ条件

N/A（データファイルのため改ページなし）

## データベース参照仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| N/A | 本帳票はデータベースを直接参照しない。MessageCatalogueオブジェクトを入力とする | - |

### テーブル別参照項目詳細

本帳票はデータベースを直接参照せず、MessageCatalogueオブジェクトからデータを取得する。

#### MessageCatalogue（入力データ構造）

| 参照項目（プロパティ） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| locale | target-language / trgLang | getLocale() | ロケール文字列 |
| messages[$domain] | trans-unit / unit | all($domain) | ドメイン別メッセージ配列 |
| metadata[$domain][$key] | notes, target-attributes | getMetadata($source, $domain) | メッセージ単位のメタデータ |
| catalogueMetadata[$domain] | prop-group / m:metadata | getCatalogueMetadata('', $domain) | カタログ単位のメタデータ |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| trans-unit ID | `strtr(substr(base64_encode(hash('xxh128', $source, true)), 0, 7), '/+', '._')` | N/A | xxh128ハッシュのBase64先頭7文字。`/`は`.`に、`+`は`_`に変換 |
| CDATA判定 | `1 === preg_match('/[&<>]/', $target)` | N/A | `&`, `<`, `>`を含む場合はCDATAセクション使用 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[dump呼び出し] --> B{pathオプション存在?}
    B -->|No| C[InvalidArgumentException]
    B -->|Yes| D[ドメインループ開始]
    D --> E[出力先パス生成]
    E --> F{ディレクトリ存在?}
    F -->|No| G[ディレクトリ作成]
    F -->|Yes| H{INTL-ICUメッセージ存在?}
    G --> H
    H -->|Yes| I[INTLドメインファイル出力]
    I --> J{通常ドメインメッセージ残存?}
    J -->|Yes| K[通常ドメインファイル出力]
    J -->|No| D
    H -->|No| L[通常ドメインファイル出力]
    K --> D
    L --> D
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| InvalidArgumentException | pathオプションが未指定 | "The file dumper needs a path option." | pathオプションを指定する |
| InvalidArgumentException | サポートされていないXLIFFバージョン指定 | 'No support implemented for dumping XLIFF version "%s".' | "1.2"または"2.0"を指定する |
| RuntimeException | 出力先ディレクトリの作成失敗 | 'Unable to create directory "%s".' | ディレクトリの書き込み権限を確認する |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 数百〜数千メッセージ/ドメイン |
| 目標出力時間 | 特に規定なし（DOMDocument使用のため大量データ時はメモリに注意） |
| 同時出力数上限 | 特に規定なし |

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

- XLIFF出力にはDOMDocumentを使用しており、出力データのXMLエスケープは自動的に行われる
- target値に`&`, `<`, `>`が含まれる場合はCDATAセクションで囲む処理が実装されている
- 出力先ディレクトリのパーミッションは0o777で作成される（umaskにより制限される）

## 備考

- XliffFileDumperのコンストラクタでファイル拡張子をカスタマイズ可能（デフォルト: "xlf"）
- INTL-ICUドメインサフィックス（`+intl-icu`）付きメッセージは別ファイルとして出力される
- relativePathTemplateプロパティにより出力パスのテンプレートを変更可能（デフォルト: `%domain%.%locale%.%extension%`）

---

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

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

### 推奨読解順序

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

メッセージカタログがどのようなデータ構造でメッセージを保持しているかを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | MessageCatalogueInterface.php | `src/Symfony/Component/Translation/MessageCatalogueInterface.php` | INTL_DOMAIN_SUFFIX定数、all()、getDomains()、getLocale()の各メソッドインターフェースを理解する |
| 1-2 | MessageCatalogue.php | `src/Symfony/Component/Translation/MessageCatalogue.php` | messages配列の構造（ドメイン名をキーとする連想配列）、メタデータの管理方法を理解する |

**読解のコツ**: MessageCatalogueの`all($domain)`メソッド（56-79行目）はINTL-ICUサフィックス付きメッセージと通常メッセージをマージして返す。この挙動がFileDumperのINTLドメイン分離処理に影響する。

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

翻訳ファイル出力の起点となるFileDumper抽象クラスのdump()メソッドを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | DumperInterface.php | `src/Symfony/Component/Translation/Dumper/DumperInterface.php` | dump()メソッドのインターフェース定義を確認する |
| 2-2 | FileDumper.php | `src/Symfony/Component/Translation/Dumper/FileDumper.php` | dump()メソッドの共通処理フロー（ドメインループ、INTL分離、ファイル書き込み）を理解する |

**主要処理フロー**:
1. **41-45行目**: pathオプションの必須チェック
2. **48行目**: ドメインループ開始（getDomains()でドメイン一覧取得）
3. **49-55行目**: 出力先パス生成とディレクトリ作成
4. **57-58行目**: INTLドメインメッセージの取得
5. **60-74行目**: INTLメッセージが存在する場合の分離出力処理
6. **76行目**: 通常ドメインメッセージの出力

#### Step 3: XLIFF固有のフォーマット処理を理解する

XliffFileDumperクラスのformatCatalogue()メソッドとそのプライベートメソッドを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | XliffFileDumper.php | `src/Symfony/Component/Translation/Dumper/XliffFileDumper.php` | formatCatalogue()でバージョン分岐、dumpXliff1()とdumpXliff2()の各XML構築処理を理解する |

**主要処理フロー**:
- **29-50行目**: formatCatalogue() - xliff_versionオプションに基づくバージョン分岐
- **57-138行目**: dumpXliff1() - XLIFF 1.2形式のDOMDocument構築
- **64-69行目**: XML宣言とxliff要素の生成、version/xmlns属性設定
- **71-81行目**: file要素（source-language/target-language）とheader要素（tool情報）の生成
- **83-90行目**: カタログメタデータのprop-group出力
- **93-135行目**: trans-unit要素のループ生成（source/target/noteの構築）
- **96行目**: trans-unit IDの算出（xxh128ハッシュBase64先頭7文字）
- **103行目**: CDATA判定（`&`, `<`, `>`を含む場合）
- **140-221行目**: dumpXliff2() - XLIFF 2.0形式の構築（unit/segment構造）
- **223-226行目**: hasMetadataArrayInfo() - メタデータの配列判定ヘルパー

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

```
DumperInterface::dump()
    │
    └─ FileDumper::dump()                          [FileDumper.php:41]
           │
           ├─ MessageCatalogue::getDomains()        [MessageCatalogue.php:42]
           ├─ FileDumper::getRelativePath()          [FileDumper.php:93]
           │      └─ XliffFileDumper::getExtension() [XliffFileDumper.php:52]
           ├─ MessageCatalogue::all($intlDomain)     [MessageCatalogue.php:56]
           └─ XliffFileDumper::formatCatalogue()     [XliffFileDumper.php:29]
                  │
                  ├─ dumpXliff1()                    [XliffFileDumper.php:57]
                  │      ├─ MessageCatalogue::getCatalogueMetadata() [MessageCatalogue.php:259]
                  │      ├─ MessageCatalogue::all($domain)           [MessageCatalogue.php:56]
                  │      ├─ MessageCatalogue::getMetadata()          [MessageCatalogue.php:214]
                  │      └─ hasMetadataArrayInfo()                   [XliffFileDumper.php:223]
                  │
                  └─ dumpXliff2()                    [XliffFileDumper.php:140]
                         ├─ MessageCatalogue::getCatalogueMetadata()
                         ├─ MessageCatalogue::all($domain)
                         ├─ MessageCatalogue::getMetadata()
                         └─ hasMetadataArrayInfo()
```

### データフロー図

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

MessageCatalogue ───────▶ FileDumper::dump()
  ├─ locale                     │
  ├─ messages[domain]           ├─ ドメインループ
  ├─ metadata                   │     │
  └─ catalogueMetadata          │     ├─ INTL分離判定
                                │     │
                options ────────▶     ├─ formatCatalogue()
                  ├─ path              │     │
                  ├─ xliff_version     │     ├─ DOMDocument構築 ───▶ {domain}.{locale}.xlf
                  ├─ default_locale    │     ├─ XML要素生成          （XLIFF XML文書）
                  └─ tool_info         │     └─ saveXML()
                                      │
                                      └─ file_put_contents()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| DumperInterface.php | `src/Symfony/Component/Translation/Dumper/DumperInterface.php` | ソース | Dumperインターフェース定義 |
| FileDumper.php | `src/Symfony/Component/Translation/Dumper/FileDumper.php` | ソース | ファイル出力共通基底クラス（dump()メソッド、パス生成、INTL分離） |
| XliffFileDumper.php | `src/Symfony/Component/Translation/Dumper/XliffFileDumper.php` | ソース | XLIFF形式固有のフォーマット処理（formatCatalogue()） |
| MessageCatalogue.php | `src/Symfony/Component/Translation/MessageCatalogue.php` | ソース | メッセージカタログデータ構造 |
| MessageCatalogueInterface.php | `src/Symfony/Component/Translation/MessageCatalogueInterface.php` | ソース | メッセージカタログインターフェース（INTL_DOMAIN_SUFFIX定数含む） |
