# 帳票設計書 3-PO翻訳ファイル

## 概要

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

### 本帳票の処理概要

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

**業務上の目的・背景**：GNU GettextのPO形式は、オープンソースソフトウェアの国際化において最も広く使用されている翻訳フォーマットの一つである。Poedit、Lokalize、Gtranslator等のGettext専用翻訳エディタとの連携が可能であり、複数形（plural forms）にも対応している。POファイルを使用することで、Gettextエコシステムの豊富なツール群を活用した翻訳ワークフローを構築できる。

**帳票の利用シーン**：Gettextツールチェーンとの連携時、POエディタ（Poedit等）での翻訳作業用データ生成時、既存のGettext翻訳ワークフローへの統合時、複数形対応が必要な翻訳データのエクスポート時に利用される。

**主要な出力内容**：
1. POファイルヘッダー（Content-Type、Content-Transfer-Encoding、Language）
2. 各メッセージのmsgid/msgstr対（単数形）
3. 複数形対応のmsgid_plural/msgstr[N]形式
4. コメント（#）、フラグ（#,）、ソース位置（#:）のメタデータ

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

**帳票の利用者**：アプリケーション開発者、翻訳者（Gettext系ツール利用者）、ローカライゼーションマネージャー

## 帳票種別

翻訳データファイル（Gettext PO形式テキスト）

## 利用画面

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

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | Gettext PO（テキスト） |
| 用紙サイズ | N/A（データファイル） |
| 向き | N/A |
| ファイル名 | `%domain%.%locale%.po`（デフォルト） |
| 出力方法 | ファイルシステムへの書き込み（file_put_contents） |
| 文字コード | UTF-8（ヘッダーで明示） |

### PO固有設定

| 項目 | 内容 |
|-----|------|
| ヘッダーContent-Type | text/plain; charset=UTF-8 |
| ヘッダーContent-Transfer-Encoding | 8bit |
| ヘッダーLanguage | MessageCatalogue::getLocale()の値 |
| エスケープ | addcslashes()で制御文字（0x00-0x1F）、ダブルクォート、バックスラッシュをエスケープ |

## 帳票レイアウト

### レイアウト概要

POファイルは、空のmsgidを持つヘッダーエントリと、各翻訳メッセージのmsgid/msgstrペアで構成される。

```
┌─────────────────────────────────────┐
│ msgid ""                             │
│ msgstr ""                            │
│ "Content-Type: ..."                  │
│ "Content-Transfer-Encoding: 8bit\n" │
│ "Language: ja\n"                     │
│                                      │
│ # コメント（任意）                     │
│ #, フラグ（任意）                      │
│ #: ソース位置（任意）                   │
│ msgid "source text"                  │
│ msgstr "target text"                 │
│ ...                                  │
└─────────────────────────────────────┘
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | Content-Type | コンテンツタイプとエンコーディング | 固定値 | `text/plain; charset=UTF-8` |
| 2 | Content-Transfer-Encoding | 転送エンコーディング | 固定値 | `8bit` |
| 3 | Language | 言語コード | MessageCatalogue::getLocale() | ロケール文字列 |

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | comments | コメント | metadata['comments'] | `# コメント` | N/A |
| 2 | flags | フラグ | metadata['flags'] | `#, flag1,flag2` | N/A |
| 3 | sources | ソース位置 | metadata['sources'] | `#: file:line` | N/A |
| 4 | msgid | 原文（単数形） | MessageCatalogue::all($domain)のキー | `msgid "text"` | N/A |
| 5 | msgid_plural | 原文（複数形） | パイプ区切りの2番目の要素 | `msgid_plural "text"` | N/A |
| 6 | msgstr | 訳文（単数形） | MessageCatalogue::all($domain)の値 | `msgstr "text"` | N/A |
| 7 | msgstr[N] | 訳文（複数形） | パイプ区切りの各要素 | `msgstr[0] "text"` | N/A |

### フッター部

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

## 出力条件

### 抽出条件

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

### ソート順

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

### 改ページ条件

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

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

### 参照テーブル一覧

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

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

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

| 参照項目（プロパティ） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| locale | Languageヘッダー | getLocale() | ロケール文字列 |
| messages[$domain] | msgid/msgstr | all($domain) | ドメイン別メッセージ配列 |
| metadata[$domain][$source]['comments'] | # コメント | getMetadata($source, $domain) | コメント配列 |
| metadata[$domain][$source]['flags'] | #, フラグ | getMetadata($source, $domain) | フラグ配列 |
| metadata[$domain][$source]['sources'] | #: ソース位置 | getMetadata($source, $domain) | ソース位置配列 |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| 複数形判定 | getStandardRules()で`\|`区切りを解析し、要素数が2かつ明示的ルール（`{N}`や`[a,b]`）でない場合に複数形 | N/A | Symfonyの複数形記法（パイプ区切り）をGettext形式に変換 |
| エスケープ | `addcslashes($str, "\0..\37\42\134")` | N/A | 制御文字（0x00-0x1F）、`"`（0x22）、`\`（0x5C）をCスタイルエスケープ |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[dump呼び出し] --> B{pathオプション存在?}
    B -->|No| C[InvalidArgumentException]
    B -->|Yes| D[ドメインループ開始]
    D --> E[formatCatalogue呼び出し]
    E --> F[POヘッダー生成]
    F --> G[メッセージループ]
    G --> H{メタデータ存在?}
    H -->|Yes| I[コメント/フラグ/ソース出力]
    H -->|No| J{複数形?}
    I --> J
    J -->|Yes| K[msgid/msgid_plural/msgstr_N出力]
    J -->|No| L[msgid/msgstr出力]
    K --> G
    L --> G
    G -->|ループ完了| M[file_put_contentsで書き込み]
    M --> D
```

## エラー処理

### エラーケース一覧

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

## パフォーマンス要件

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

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

- escape()メソッドでaddcslashes()を使用し、制御文字、ダブルクォート、バックスラッシュを適切にエスケープしている
- 出力先ディレクトリのパーミッションは0o777で作成される（umaskにより制限される）

## 備考

- 複数形対応: Symfonyの複数形記法（パイプ`|`区切り）をGettextのmsgid_plural/msgstr[N]形式に変換する。ただし、明示的ルール（`{N}`や`[a,b]`形式）が含まれる場合は通常のmsgid/msgstrとして出力される
- `||`はリテラルの`|`として扱われる（escape処理で`||`を`|`に変換）
- メタデータのcomments, flags, sourcesが存在する場合のみ、対応するコメント行が出力される

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | MessageCatalogue.php | `src/Symfony/Component/Translation/MessageCatalogue.php` | all($domain)とgetMetadata($source, $domain)を理解する |

**読解のコツ**: PoFileDumperはメタデータ（comments, flags, sources）も活用するため、MessageCatalogueのメタデータ管理（214-241行目）も重要。

#### 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: PO固有のフォーマット処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | PoFileDumper.php | `src/Symfony/Component/Translation/Dumper/PoFileDumper.php` | formatCatalogue()でのPOファイル構築、getStandardRules()での複数形判定、escape()でのエスケープ処理を理解する |

**主要処理フロー**:
- **25-29行目**: POヘッダー生成（空msgid + メタ情報）
- **33-63行目**: メッセージループ（メタデータ出力、複数形判定、msgid/msgstr出力）
- **41-49行目**: メタデータ（comments, flags, sources）の出力
- **51-52行目**: getStandardRules()による複数形解析
- **53-58行目**: 複数形の場合のmsgid_plural/msgstr[N]出力
- **68-109行目**: getStandardRules() - パイプ区切り解析と明示的ルール判定
- **116-119行目**: escape() - Cスタイルエスケープ処理
- **121-130行目**: formatComments() - コメント行フォーマット

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

```
DumperInterface::dump()
    │
    └─ FileDumper::dump()                          [FileDumper.php:41]
           │
           ├─ MessageCatalogue::getDomains()        [MessageCatalogue.php:42]
           ├─ FileDumper::getRelativePath()          [FileDumper.php:93]
           │      └─ PoFileDumper::getExtension()    [PoFileDumper.php:111]
           └─ PoFileDumper::formatCatalogue()        [PoFileDumper.php:23]
                  │
                  ├─ MessageCatalogue::all($domain)   [MessageCatalogue.php:56]
                  ├─ MessageCatalogue::getMetadata()   [MessageCatalogue.php:214]
                  ├─ formatComments()                  [PoFileDumper.php:121]
                  ├─ getStandardRules()                [PoFileDumper.php:68]
                  └─ escape()                          [PoFileDumper.php:116]
```

### データフロー図

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

MessageCatalogue ───────▶ FileDumper::dump()
  ├─ locale                     │
  ├─ messages[domain]           ├─ ドメインループ
  └─ metadata[domain]           │     │
                                │     ├─ formatCatalogue()
                options ────────▶     │     │
                  └─ path              │     ├─ POヘッダー生成
                                      │     ├─ 複数形判定 ────────▶ {domain}.{locale}.po
                                      │     ├─ メタデータ出力       （POテキスト）
                                      │     └─ msgid/msgstr出力
                                      │
                                      └─ file_put_contents()
```

### 関連ファイル一覧

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