# 帳票設計書 9-ICU ResourceBundle翻訳ファイル

## 概要

Symfonyの翻訳コンポーネントにおけるICU ResourceBundleバイナリ形式の翻訳ファイル出力機能の設計書である。メッセージカタログをICU ResourceBundleのバイナリ(.res)ファイルとして出力する。

### 本帳票の処理概要

本帳票は、Symfonyアプリケーション内部のメッセージカタログ（MessageCatalogue）に格納された翻訳メッセージを、ICU（International Components for Unicode）のResourceBundleバイナリ形式ファイルとして出力する機能を提供する。

**業務上の目的・背景**：ICU ResourceBundleは、Unicode Consortium/ICUプロジェクトが定める国際化データフォーマットであり、ICUライブラリとのネイティブ連携が可能である。PHPのIntl拡張（ResourceBundle クラス）で直接読み込めるバイナリ形式であり、高速なデータアクセスを実現する。ICUのロケール管理機能との統合が必要な場合や、ICUベースの国際化フレームワークとのデータ交換に使用される。

**帳票の利用シーン**：ICUライブラリとの直接連携が必要な場合、PHPのIntl拡張のResourceBundleクラスで読み込むバイナリデータの生成時、ICUベースの国際化システムとの翻訳データ交換時に利用される。

**主要な出力内容**：
1. ICU ResourceBundleバイナリヘッダー（32バイト、マジックナンバー・バージョン情報含む）
2. ルートリソーステーブル（リソースオフセット・インデックス情報）
3. キー文字列データ（NULL終端、2バイトアラインメント）
4. 値文字列データ（UTF-16LEエンコーディング、長さプレフィックス付き）
5. インデックステーブルとリソースオフセットテーブル

**帳票の出力タイミング**：プログラム内から`IcuResFileDumper::dump()`メソッドを呼び出した時に出力される。

**帳票の利用者**：アプリケーション開発者、国際化エンジニア

## 帳票種別

翻訳データファイル（ICU ResourceBundleバイナリ）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| N/A | プログラムAPI | `IcuResFileDumper::dump()` | メソッド呼び出し |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | ICU ResourceBundle バイナリ (.res) |
| 用紙サイズ | N/A（バイナリデータファイル） |
| 向き | N/A |
| ファイル名 | `%domain%/%locale%.res`（ドメイン名ディレクトリ配下にロケール名ファイル） |
| 出力方法 | ファイルシステムへの書き込み（file_put_contents） |
| 文字コード | キー: UTF-8、値: UTF-16LE |

### ICU ResourceBundle固有設定

| 項目 | 内容 |
|-----|------|
| ヘッダーサイズ | 32バイト |
| マジックナンバー | 0xDA, 0x27 |
| データフォーマット識別子 | "ResB" (0x52, 0x65, 0x73, 0x42) |
| データバージョン | 1.2.0.0 |
| Unicodeバージョン | 1.4.0.0 |
| charサイズ | 2バイト |
| アラインメント | 4バイト境界（パディング: 0xAA） |
| リソースタイプ | テーブル型（type 2、`2 << 28`） |

## 帳票レイアウト

### レイアウト概要

ICU ResourceBundleバイナリは以下のセクションで構成される。

```
┌─────────────────────────────────────┐
│ ヘッダー（32バイト）                    │
│   headerSize, magic, version,        │
│   dataFormat, dataVersion,           │
│   unicodeVersion                     │
├─────────────────────────────────────┤
│ ルートリソース（28バイト）               │
│   resOffset, indexLength,            │
│   keysTop, resourcesTop,            │
│   bundleTop, maxTableLength,         │
│   attributes                         │
├─────────────────────────────────────┤
│ キー文字列データ                       │
│   key1\0key2\0... + パディング        │
├─────────────────────────────────────┤
│ 値文字列データ                         │
│   [length][UTF-16LE string\0]×N     │
│   + パディング                        │
├─────────────────────────────────────┤
│ テーブルリソース                        │
│   count + インデックス + パディング     │
│   + リソースオフセット配列               │
└─────────────────────────────────────┘
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | headerSize | ヘッダーサイズ | 固定値 32 | unsigned short (2バイト) |
| 2 | magic1 | マジックナンバー1 | 固定値 0xDA | unsigned char |
| 3 | magic2 | マジックナンバー2 | 固定値 0x27 | unsigned char |
| 4 | sizeofUChar | char型のサイズ | 固定値 2 | unsigned short |
| 5 | dataFormat | データフォーマット識別子 | 固定値 "ResB" | 4バイト文字列 |
| 6 | dataVersion | データバージョン | 固定値 1.2.0.0 | 4バイト |
| 7 | unicodeVersion | Unicodeバージョン | 固定値 1.4.0.0 | 4バイト |

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | キー文字列 | NULL終端のキー文字列群 | MessageCatalogue::all($domain)のキー | UTF-8 NULL終端 | 可変 |
| 2 | 値文字列 | 長さプレフィックス+UTF-16LE文字列群 | MessageCatalogue::all($domain)の値 | pack('V', len) + UTF-16LE + NULL | 可変 |
| 3 | インデックス | キー位置のインデックステーブル | 計算値 | pack('v', offset) | 2バイト/エントリ |
| 4 | リソースオフセット | 値位置のオフセットテーブル | 計算値 | pack('V', position) | 4バイト/エントリ |

### フッター部

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

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| path | 出力先ディレクトリパス | Yes |

### ソート順

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

### 改ページ条件

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

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

### 参照テーブル一覧

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| N/A | 本帳票はデータベースを直接参照しない | - |

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

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

| 参照項目（プロパティ） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| messages[$domain] | キー/値データ | all($domain) | ドメイン別メッセージ配列 |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| パディング | `$padding = strlen($data) % 4; $padding ? str_repeat("\xAA", 4 - $padding) : null` | N/A | 4バイト境界アラインメント |
| ポジション | `(strlen($data) + 28) / 4` | N/A | ヘッダーサイズ(28)を加算し4で除算 |
| リソースオフセット | `$resOffset + (2 << 28)` | N/A | テーブルリソースタイプ(2)を上位4ビットに設定 |
| インデックス値 | `pack('v', strlen($data) + 28)` | N/A | データ先頭からの位置（ヘッダーサイズ込み） |
| UTF-16LE変換 | `mb_convert_encoding($target . "\0", 'UTF-16LE', 'UTF-8')` | N/A | 値文字列のUTF-16LE変換 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[dump呼び出し] --> B{pathオプション存在?}
    B -->|No| C[InvalidArgumentException]
    B -->|Yes| D[ドメインループ開始]
    D --> E[formatCatalogue呼び出し]
    E --> F[第1ループ: キー文字列データ構築+インデックス生成]
    F --> G[パディング挿入]
    G --> H[keyTop計算]
    H --> I[第2ループ: 値文字列データ構築+リソースオフセット生成]
    I --> J[resOffset計算]
    J --> K[テーブルリソース構築: count+indexes+padding+resources]
    K --> L[bundleTop計算]
    L --> M[ルートリソース構築: 7個のunsigned long]
    M --> N[バイナリヘッダー構築: 32バイト]
    N --> O[header+root+data結合]
    O --> P[file_put_contentsで書き込み]
    P --> D
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| InvalidArgumentException | pathオプションが未指定 | "The file dumper needs a path option." | pathオプションを指定する |
| RuntimeException | 出力先ディレクトリの作成失敗 | 'Unable to create directory "%s".' | ディレクトリの書き込み権限を確認する |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 数百〜数千メッセージ/ドメイン |
| 目標出力時間 | 特に規定なし |
| 同時出力数上限 | 特に規定なし |

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

- バイナリ形式のため直接的なインジェクションリスクは低い
- mb_convert_encoding()によるUTF-16LE変換が行われる
- 出力先ディレクトリのパーミッションは0o777で作成される（umaskにより制限される）

## 備考

- relativePathTemplateが他のDumperと異なり`%domain%/%locale%.%extension%`となっている。ドメイン名のディレクトリ配下にロケール名のファイルが作成される
- ICU ResourceBundleのバイナリ形式はICUライブラリの内部フォーマットに準拠しており、PHPのIntl拡張のResourceBundleクラスで読み込み可能
- メッセージカタログを2回走査する（第1ループでキーデータ、第2ループで値データを構築）

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | MessageCatalogue.php | `src/Symfony/Component/Translation/MessageCatalogue.php` | all($domain)の戻り値を確認 |

**読解のコツ**: IcuResFileDumperはICU ResourceBundleバイナリの内部構造を直接構築するため、pack()関数によるバイナリデータのパッキングに慣れている必要がある。'V'はリトルエンディアンunsigned long（4バイト）、'v'はリトルエンディアンunsigned short（2バイト）、'C'はunsigned char（1バイト）。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | FileDumper.php | `src/Symfony/Component/Translation/Dumper/FileDumper.php` | dump()メソッドの共通処理フロー（41-77行目）。relativePathTemplateが異なる点に注意 |

**主要処理フロー**:
1. **41-45行目**: pathオプション必須チェック
2. **48-77行目**: ドメインループ。IcuResFileDumperのrelativePathTemplateは`%domain%/%locale%.%extension%`

#### Step 3: ICU ResourceBundle固有のバイナリ構築処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | IcuResFileDumper.php | `src/Symfony/Component/Translation/Dumper/IcuResFileDumper.php` | formatCatalogue()の3段階構築処理を理解する |

**主要処理フロー**:
- **23行目**: relativePathTemplateの変更（`%domain%/%locale%.%extension%`）
- **27行目**: 変数初期化
- **29-32行目**: 第1ループ - キー文字列データとインデックステーブルの構築
- **34行目**: パディング挿入（writePadding()）
- **36行目**: keyTop計算（getPosition()）
- **38-45行目**: 第2ループ - 値文字列データとリソースオフセットテーブルの構築
- **41-43行目**: UTF-16LEエンコーディング変換
- **47行目**: resOffset計算
- **49-53行目**: テーブルリソース構築
- **55行目**: bundleTop計算
- **57-65行目**: ルートリソース構築（7個のunsigned long）
- **67-74行目**: バイナリヘッダー構築（32バイト）
- **76行目**: header + root + data の結合
- **79-83行目**: writePadding() - 4バイト境界パディング
- **86-88行目**: getPosition() - バイト位置計算

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

```
DumperInterface::dump()
    │
    └─ FileDumper::dump()                              [FileDumper.php:41]
           │
           ├─ MessageCatalogue::getDomains()            [MessageCatalogue.php:42]
           ├─ FileDumper::getRelativePath()              [FileDumper.php:93]
           │      └─ IcuResFileDumper::getExtension()    [IcuResFileDumper.php:91]
           └─ IcuResFileDumper::formatCatalogue()        [IcuResFileDumper.php:25]
                  │
                  ├─ MessageCatalogue::all($domain)       [MessageCatalogue.php:56]（2回呼出）
                  ├─ writePadding()                        [IcuResFileDumper.php:79]
                  ├─ getPosition()                         [IcuResFileDumper.php:86]
                  └─ pack()                                [PHP組み込み関数]
```

### データフロー図

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

MessageCatalogue ───────▶ FileDumper::dump()
  └─ messages[domain]           │
                                ├─ ドメインループ
                options ────────▶     │
                  └─ path              ├─ formatCatalogue()
                                      │     │
                                      │     ├─ キー文字列構築(UTF-8)
                                      │     ├─ 値文字列構築(UTF-16LE) ▶ {domain}/{locale}.res
                                      │     ├─ インデックス・オフセット    （ICU ResBundle）
                                      │     ├─ ルートリソース構築
                                      │     └─ ヘッダー構築
                                      │
                                      └─ file_put_contents()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| DumperInterface.php | `src/Symfony/Component/Translation/Dumper/DumperInterface.php` | ソース | Dumperインターフェース定義 |
| FileDumper.php | `src/Symfony/Component/Translation/Dumper/FileDumper.php` | ソース | ファイル出力共通基底クラス |
| IcuResFileDumper.php | `src/Symfony/Component/Translation/Dumper/IcuResFileDumper.php` | ソース | ICU ResourceBundleバイナリ固有のフォーマット処理 |
| MessageCatalogue.php | `src/Symfony/Component/Translation/MessageCatalogue.php` | ソース | メッセージカタログデータ構造 |
| MessageCatalogueInterface.php | `src/Symfony/Component/Translation/MessageCatalogueInterface.php` | ソース | メッセージカタログインターフェース |
