# 帳票設計書 4-MO翻訳ファイル

## 概要

Symfonyの翻訳コンポーネントにおけるGettext MO（Machine Object）形式の翻訳ファイル出力機能の設計書である。メッセージカタログをGettextのMOバイナリ形式ファイルとして出力する。

### 本帳票の処理概要

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

**業務上の目的・背景**：MO（Machine Object）ファイルは、POファイルをコンパイルしたバイナリ形式であり、実行時の高速な翻訳メッセージ検索を可能にする。通常はPOファイルからmsgfmtコマンドで生成されるが、本Dumperを使用することでSymfonyのMessageCatalogueから直接MOファイルを生成できる。これにより、Gettextネイティブの翻訳読み込みシステムとの互換性を確保しつつ、Symfonyの翻訳管理ワークフローに統合できる。

**帳票の利用シーン**：Gettextネイティブランタイムとの連携時、PHPのgettext拡張を使用するレガシーアプリケーションへの翻訳データ提供時、POファイルなしにMOファイルを直接生成する必要がある時に利用される。

**主要な出力内容**：
1. MOファイルヘッダー（マジックナンバー、バージョン、メッセージ数、オフセット情報）
2. ソース文字列テーブル（原文のNULL終端文字列群）
3. ターゲット文字列テーブル（訳文のNULL終端文字列群）
4. オフセットテーブル（各文字列の長さと位置情報）

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

**帳票の利用者**：アプリケーション開発者、システムインテグレーター

## 帳票種別

翻訳データファイル（Gettextバイナリ形式）

## 利用画面

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

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | Gettext MO（バイナリ） |
| 用紙サイズ | N/A（データファイル） |
| 向き | N/A |
| ファイル名 | `%domain%.%locale%.mo`（デフォルト） |
| 出力方法 | ファイルシステムへの書き込み（file_put_contents） |
| 文字コード | バイナリ（リトルエンディアン） |

### MOバイナリ固有設定

| 項目 | 内容 |
|-----|------|
| マジックナンバー | 0x950412DE（リトルエンディアン） |
| フォーマットリビジョン | 0 |
| ヘッダーサイズ | 28バイト（MO_HEADER_SIZE） |
| バイトオーダー | リトルエンディアン（pack 'V'フォーマット） |
| ハッシュテーブル | なし（sizeHashes = 0） |

## 帳票レイアウト

### レイアウト概要

MOファイルはバイナリ構造を持ち、以下の領域で構成される。

```
┌─────────────────────────────────────┐
│ ヘッダー（28バイト）                    │
│   magic, revision, count,            │
│   offsetId, offsetTranslated,        │
│   sizeHashes, offsetHashes           │
├─────────────────────────────────────┤
│ ソースオフセットテーブル（8×N バイト）    │
│   [length, offset] × N              │
├─────────────────────────────────────┤
│ ターゲットオフセットテーブル（8×N バイト） │
│   [length, offset] × N              │
├─────────────────────────────────────┤
│ ソース文字列データ                      │
│   \0source1\0source2\0...            │
├─────────────────────────────────────┤
│ ターゲット文字列データ                   │
│   \0target1\0target2\0...            │
└─────────────────────────────────────┘
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | magicNumber | マジックナンバー | MoFileLoader::MO_LITTLE_ENDIAN_MAGIC (0x950412DE) | 4バイトunsigned long（リトルエンディアン） |
| 2 | formatRevision | フォーマットリビジョン | 固定値 0 | 4バイトunsigned long |
| 3 | count | メッセージ数 | メッセージカタログのエントリ数 | 4バイトunsigned long |
| 4 | offsetId | ソースオフセットテーブルの開始位置 | MO_HEADER_SIZE (28) | 4バイトunsigned long |
| 5 | offsetTranslated | ターゲットオフセットテーブルの開始位置 | MO_HEADER_SIZE + (8 * count) | 4バイトunsigned long |
| 6 | sizeHashes | ハッシュテーブルサイズ | 固定値 0 | 4バイトunsigned long |
| 7 | offsetHashes | ハッシュテーブル開始位置 | MO_HEADER_SIZE + (16 * count) | 4バイトunsigned long |

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | ソースオフセット | 各ソース文字列の長さと位置 | strlen($source)とオフセット計算 | pack('V', length).pack('V', offset) | 8バイト/エントリ |
| 2 | ターゲットオフセット | 各ターゲット文字列の長さと位置 | strlen($target)とオフセット計算 | pack('V', length).pack('V', offset) | 8バイト/エントリ |
| 3 | ソース文字列 | NULL区切りのソース文字列群 | MessageCatalogue::all($domain)のキー | NULL終端文字列 | 可変 |
| 4 | ターゲット文字列 | NULL区切りのターゲット文字列群 | MessageCatalogue::all($domain)の値 | NULL終端文字列 | 可変 |

### フッター部

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

## 出力条件

### 抽出条件

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

### ソート順

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

### 改ページ条件

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

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

### 参照テーブル一覧

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

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

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

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

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| offsetId | MO_HEADER_SIZE (28) | N/A | ヘッダー直後 |
| offsetTranslated | MO_HEADER_SIZE + (8 * count) | N/A | ソースオフセットテーブル直後 |
| offsetHashes | MO_HEADER_SIZE + (16 * count) | N/A | ターゲットオフセットテーブル直後 |
| sourcesStart | offsetHashes + 1 | N/A | ハッシュテーブル領域直後（サイズ0だが+1） |
| ソースオフセット値 | offset[0] + sourcesStart | N/A | ソースデータ先頭からの相対位置 + sourcesStart |
| ターゲットオフセット値 | offset[2] + sourcesStart + sourcesSize | N/A | ターゲットデータ先頭からの相対位置 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[dump呼び出し] --> B{pathオプション存在?}
    B -->|No| C[InvalidArgumentException]
    B -->|Yes| D[ドメインループ開始]
    D --> E[formatCatalogue呼び出し]
    E --> F[メッセージループ: オフセット計算・文字列連結]
    F --> G[ヘッダー構築: pack 7個のunsigned long]
    G --> H[ソースオフセットテーブル構築]
    H --> I[ターゲットオフセットテーブル構築]
    I --> J[バイナリデータ結合: header+offsets+strings]
    J --> K[file_put_contentsで書き込み]
    K --> D
```

## エラー処理

### エラーケース一覧

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

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 数百〜数千メッセージ/ドメイン |
| 目標出力時間 | 特に規定なし（バイナリ構築のためメモリ効率は良好） |
| 同時出力数上限 | 特に規定なし |

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

- バイナリ形式のため直接的なインジェクションリスクは低い
- 出力先ディレクトリのパーミッションは0o777で作成される（umaskにより制限される）
- MoFileLoader::MO_LITTLE_ENDIAN_MAGIC定数を使用してフォーマット互換性を確保している

## 備考

- 本DumperはMoFileLoaderの定数（MO_LITTLE_ENDIAN_MAGIC、MO_HEADER_SIZE）を使用してバイナリ構造の互換性を確保している
- ハッシュテーブルは生成されない（sizeHashes = 0）。Gettextランタイムはハッシュテーブルなしでも線形探索にフォールバックする
- pack('V*', ...)によりリトルエンディアンのunsigned longとしてパッキングされる

---

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

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

### 推奨読解順序

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

MOバイナリ形式の構造定数を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | MoFileLoader.php | `src/Symfony/Component/Translation/Loader/MoFileLoader.php` | MO_LITTLE_ENDIAN_MAGIC (0x950412DE)、MO_HEADER_SIZE (28)定数の定義を確認する |
| 1-2 | MessageCatalogue.php | `src/Symfony/Component/Translation/MessageCatalogue.php` | all($domain)メソッドの戻り値構造を理解する |

**読解のコツ**: MoFileLoaderのloadResource()メソッド（42-124行目）を読むと、MOバイナリの読み込み側の処理がわかり、書き込み側（MoFileDumper）との対称性を理解しやすくなる。

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

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

**主要処理フロー**:
1. **41-45行目**: pathオプション必須チェック
2. **48-77行目**: ドメインループとINTL分離処理

#### Step 3: MOバイナリ固有のフォーマット処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | MoFileDumper.php | `src/Symfony/Component/Translation/Dumper/MoFileDumper.php` | バイナリ構造の構築処理全体を理解する |

**主要処理フロー**:
- **26-28行目**: 変数初期化（sources, targets, sourceOffsets, targetOffsets, offsets, size）
- **30-35行目**: 第1ループ - メッセージ走査でオフセット情報を収集し、NULL区切り文字列を構築
- **37-45行目**: ヘッダー構築（7個のunsigned long値）
- **47-48行目**: sourcesSize、sourcesStart計算
- **50-55行目**: 第2ループ - ソース/ターゲットオフセットテーブル構築
- **57-61行目**: 全バイナリデータの結合（header + sourceOffsets + targetOffsets + sources + targets）
- **69-72行目**: writeLong() - pack('V*')によるリトルエンディアンパッキング

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

```
DumperInterface::dump()
    │
    └─ FileDumper::dump()                          [FileDumper.php:41]
           │
           ├─ MessageCatalogue::getDomains()        [MessageCatalogue.php:42]
           ├─ FileDumper::getRelativePath()          [FileDumper.php:93]
           │      └─ MoFileDumper::getExtension()    [MoFileDumper.php:64]
           └─ MoFileDumper::formatCatalogue()        [MoFileDumper.php:24]
                  │
                  ├─ MessageCatalogue::all($domain)   [MessageCatalogue.php:56]
                  └─ writeLong()                      [MoFileDumper.php:69]
```

### データフロー図

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

MessageCatalogue ───────▶ FileDumper::dump()
  └─ messages[domain]           │
                                ├─ ドメインループ
                options ────────▶     │
                  └─ path              ├─ formatCatalogue()
                                      │     │
                                      │     ├─ オフセット計算
                                      │     ├─ ヘッダーpack
                                      │     ├─ オフセットテーブルpack ──▶ {domain}.{locale}.mo
                                      │     └─ 文字列データ結合          （MOバイナリ）
                                      │
                                      └─ file_put_contents()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| DumperInterface.php | `src/Symfony/Component/Translation/Dumper/DumperInterface.php` | ソース | Dumperインターフェース定義 |
| FileDumper.php | `src/Symfony/Component/Translation/Dumper/FileDumper.php` | ソース | ファイル出力共通基底クラス |
| MoFileDumper.php | `src/Symfony/Component/Translation/Dumper/MoFileDumper.php` | ソース | MOバイナリ形式固有のフォーマット処理 |
| MoFileLoader.php | `src/Symfony/Component/Translation/Loader/MoFileLoader.php` | ソース | MOファイル読み込み（定数定義の参照元、構造の対称性理解用） |
| MessageCatalogue.php | `src/Symfony/Component/Translation/MessageCatalogue.php` | ソース | メッセージカタログデータ構造 |
| MessageCatalogueInterface.php | `src/Symfony/Component/Translation/MessageCatalogueInterface.php` | ソース | メッセージカタログインターフェース |
