# 機能設計書 81-翻訳・多言語対応

## 概要

本ドキュメントは、serve-dのGUIメッセージ多言語翻訳機能について、その処理内容・データ構造・呼び出しフローを記述した機能設計書である。

### 本機能の処理概要

本機能は、serve-dがLSPクライアント（エディタ）に表示するユーザー向けメッセージ（エラーメッセージ、ダイアログテキスト、進捗表示テキスト等）を、ユーザーの言語設定に基づいて翻訳する仕組みを提供する。翻訳リソースはコンパイル時にstring importで組み込まれ、実行時にキーベースで参照される。

**業務上の目的・背景**：serve-dは国際的に利用されるD言語のLSPサーバーであり、英語圏以外の開発者にも分かりやすいメッセージを提供する必要がある。本機能により、エディタのロケール設定（`--lang`オプション）に応じて、ユーザーに母国語でメッセージを表示できる。これにより、非英語圏の開発者のDX（Developer Experience）を向上させる。

**機能の利用シーン**：
- serve-d起動時に`--lang`コマンドラインオプションで言語が指定された場合、その言語の翻訳が自動的に適用される
- DCD更新確認ダイアログ、DUB依存関係ダウンロード確認、サブプロジェクト読み込み確認等の各種ダイアログメッセージの表示時
- エラーメッセージ（設定不正、DCD起動失敗、DUBスタートアップ失敗等）の表示時
- リネーム操作後のモジュール名更新通知メッセージの表示時

**主要な処理内容**：
1. コンパイル時に翻訳リソースファイル（en.txt, de.txt, fr.txt, ja.txt, ru.txt）をstring importで読み込み、モジュール初期化時にパースして翻訳テーブルを構築する
2. 翻訳キー文字列に対して現在の言語設定の翻訳テーブルを検索し、該当する翻訳テキストを返却する
3. 翻訳テキスト内のプレースホルダ（`{0}`, `{1}`等）を実引数で置換する
4. 複数形（plural）構文をサポートし、Mozillaのplural formsルールに基づいて適切な語形を選択する
5. エスケープシーケンス（`\n`, `\t`, `\r`, `\\`, `\{`）を処理する

**関連システム・外部連携**：LSPクライアント（VS Code等のエディタ）に対して`window/showMessage`や`window/showMessageRequest`で翻訳済みメッセージを送信する。翻訳リソースファイルは`views/`ディレクトリに配置され、DUBのstring import機能によりコンパイル時に組み込まれる。

**権限による制御**：特になし。全てのユーザーが同一の翻訳機能を利用可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 4 | DCD更新確認ダイアログ | 参照画面 | ダイアログメッセージの翻訳にtranslate機能を使用する |
| 5 | DUB依存関係ダウンロード確認ダイアログ | 参照画面 | ダイアログメッセージ（Upgrade/Always/Never）の翻訳にtranslate機能を使用する |
| 6 | サブプロジェクト読み込み確認ダイアログ | 参照画面 | ダイアログメッセージ（Load/Skip）の翻訳にtranslate機能を使用する |
| 8 | エラーメッセージ通知 | 参照画面 | エラーメッセージの翻訳にtranslate機能を使用する |
| 9 | DCD起動失敗通知 | 参照画面 | エラーメッセージの翻訳にtranslate機能を使用する |
| 10 | dubスタートアップ失敗通知 | 主画面 | dubの初期化失敗時のエラーメッセージの翻訳を使用する |
| 70 | 翻訳リソース | 主画面 | エディタ上に表示されるメッセージの翻訳リソースをen, de, fr, ja, ruの各言語で提供する |

## 機能種別

データ変換処理 / テキストローカライゼーション

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| s (翻訳キー) | string (テンプレートパラメータ) | Yes | 翻訳対象のメッセージキー（例: `"d.served.outdatedDCD"`） | コンパイル時固定文字列。翻訳テーブルに存在しない場合はキー文字列自体を返却 |
| args | 可変長テンプレート引数 (Args...) | No | プレースホルダに挿入するフォーマット引数 | 整数型・文字列型のみ（plural構文使用時） |
| --lang | string | No | コマンドライン引数で指定する言語コード（例: `ja-JP`, `en-GB`） | 先頭2文字が言語コードとして使用される。未指定時はデフォルト `"en"` |

### 入力データソース

- コマンドライン引数 `--lang` による言語指定（`source/app.d` 148行目）
- コンパイル時にstring importされる翻訳リソースファイル（`views/en.txt`, `views/de.txt`, `views/fr.txt`, `views/ja.txt`, `views/ru.txt`）
- 各呼び出し元から渡されるテンプレートパラメータ（翻訳キー）とフォーマット引数

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| 翻訳済みテキスト | string | 翻訳キーに対応するローカライズ済みメッセージ文字列。プレースホルダ置換・plural処理・エスケープ処理済み |

### 出力先

呼び出し元に翻訳済み文字列として返却される。最終的にはLSPプロトコルを通じて以下のようにクライアントに送信される:
- `window/showMessage` / `window/showMessageRequest` 通知
- `window/logMessage` 通知
- その他LSPレスポンスのメッセージフィールド

## 処理フロー

### 処理シーケンス

```
1. モジュール初期化（shared static this）
   └─ 5つの翻訳リソースファイル（en.txt, de.txt, fr.txt, ja.txt, ru.txt）をstring importで読み込み
   └─ 各ファイルをparseTranslationでパースし、translations連想配列に格納
2. 言語設定（app.d起動時）
   └─ --langオプションの先頭2文字をcurrentLanguageに設定（デフォルト: "en"）
3. 翻訳呼び出し（translate!キー(引数...)）
   └─ currentLanguageの翻訳テーブルからキーを検索
   └─ 見つからない場合は英語（"en"）テーブルにフォールバック
   └─ どちらにもない場合はキー文字列自体を返却し、警告ログを出力
4. フォーマット処理（formatTranslation）
   └─ プレースホルダ {N} を対応する引数で置換
   └─ {plural #形式 {引数インデックス} {単数形} {複数形}} 構文を処理
   └─ エスケープシーケンス（\n, \t, \r, \\, \{）を処理
```

### フローチャート

```mermaid
flowchart TD
    A[translate!キー 呼び出し] --> B{currentLanguageの翻訳テーブルにキーが存在?}
    B -->|Yes| C[翻訳テキストを取得]
    B -->|No| D{英語テーブルにキーが存在?}
    D -->|Yes| C
    D -->|No| E[警告ログ出力 + キー文字列をそのまま返却]
    C --> F[formatTranslation呼び出し]
    F --> G{テキストに特殊文字 curly brace/backslash が存在?}
    G -->|No| H[テキストをそのまま返却]
    G -->|Yes| I{先頭文字の種別}
    I -->|curly brace + plural| J[plural構文をパース<br>resolvePluralで語形選択<br>再帰的にformatTranslation適用]
    I -->|curly brace + 数字| K[引数インデックスを取得<br>対応する引数を文字列に変換して置換]
    I -->|backslash| L[エスケープ文字を実文字に変換]
    J --> M[次の特殊文字を検索]
    K --> M
    L --> M
    M --> G
    H --> N[翻訳済みテキストを返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-81-01 | 言語フォールバック | 指定言語の翻訳テーブルにキーが存在しない場合、英語テーブルにフォールバックする | 翻訳キー検索時 |
| BR-81-02 | キーフォールバック | 英語テーブルにもキーが存在しない場合、キー文字列自体を返却する | 翻訳キー検索時 |
| BR-81-03 | 言語コード短縮 | `--lang`で指定された値の先頭2文字のみを言語コードとして使用する（例: `ja-JP` -> `ja`） | 起動時 |
| BR-81-04 | 複数形解決 | Mozillaのplural forms仕様に基づき、20種類の複数形ルール（form 0-19）をサポートする | plural構文使用時 |
| BR-81-05 | デフォルト言語 | 未指定時は英語（"en"）がデフォルト言語となる | 常時 |

### 計算ロジック

**複数形解決（resolvePlural）**

Mozillaの複数形ルールに基づく。主要なルール:
- form 0（アジア言語等）: 常に0を返却（複数形の区別なし）
- form 1（ゲルマン語等）: n==1 ? 0 : 1
- form 7（ロシア語等）: 3形式の複雑なルール（末尾の数字と例外に基づく）

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

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

該当なし。本機能はデータベースを使用しない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 警告ログ | 翻訳キーが英語テーブルにも選択言語テーブルにも存在しない | キー文字列自体をそのまま返却し、warningfでログ出力 |
| - | 例外 (Exception) | plural構文の文法エラー（`#`の欠落、`{}`の不一致等） | Exceptionをスローする |
| - | 例外 (Exception) | 不明なplural form番号が指定された | `"Unknown plural form"`のExceptionをスローする |

### リトライ仕様

リトライは不要。翻訳キーが見つからない場合はフォールバック値を返却する。

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

該当なし。本機能はステートレスな文字列変換処理である。

## パフォーマンス要件

- 翻訳テーブルはモジュール初期化時（`shared static this`）にコンパイル時文字列から構築されるため、実行時のI/Oは発生しない
- 翻訳検索は連想配列（ハッシュテーブル）による O(1) の高速検索

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

- 翻訳リソースはコンパイル時にバイナリに組み込まれるため、実行時の外部ファイル改ざんリスクはない
- 翻訳テキスト内のプレースホルダは引数のto!string変換のみであり、インジェクションリスクは低い

## 備考

- 対応言語: 英語(en)、ドイツ語(de)、フランス語(fr)、日本語(ja)、ロシア語(ru)の5言語
- 翻訳リソースファイルの形式は「キー:値」の1行1エントリ形式で、`#`で始まる行はコメントとして無視される
- plural構文は `{plural #<form> {<argIndex>} {<singular>} {<plural>}}` の形式で、再帰的なフォーマットをサポートする
- `currentLanguage`はグローバル変数として管理されており、モジュールレベルで共有される

---

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

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

### 推奨読解順序

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

翻訳システムのデータ構造は非常にシンプルで、キーと値の連想配列の入れ子構造である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | translate.d | `source/served/utils/translate.d` | Translation型の定義（10行目）と翻訳テーブルの構造（12行目） |
| 1-2 | en.txt | `views/en.txt` | 翻訳リソースの形式（キー:値の1行1エントリ形式）を確認 |

**読解のコツ**: `Translation`は`string[string]`のエイリアスで、D言語の連想配列である。`translations`は`Translation[string]`型で、言語コードをキーとして各言語の翻訳テーブルを保持する二重連想配列構造となっている。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | app.d | `source/app.d` | `--lang`オプションの解析と`currentLanguage`の設定（147-150行目） |
| 2-2 | translate.d | `source/served/utils/translate.d` | `shared static this()`によるモジュール初期化（14-23行目） |

**主要処理フロー**:
1. **14-23行目**: `shared static this()`でコンパイル時string importされた翻訳リソースを`parseTranslation`でパースし、`translations`連想配列に格納
2. **39行目**: `currentLanguage`のデフォルト値は`"en"`
3. **app.d 147-150行目**: `--lang`オプションの先頭2文字を`currentLanguage`に設定

#### Step 3: 翻訳関数の処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | translate.d | `source/served/utils/translate.d` | `translate`テンプレート関数（41-57行目）：キー検索とフォールバック |
| 3-2 | translate.d | `source/served/utils/translate.d` | `formatTranslation`関数（59-226行目）：プレースホルダ置換・plural・エスケープ処理 |
| 3-3 | translate.d | `source/served/utils/translate.d` | `resolvePlural`関数（248-426行目）：Mozillaのplural forms実装 |

**主要処理フロー**:
- **41-57行目**: `translate`関数 - テンプレートパラメータ`s`をキーとして現在の言語テーブルを検索し、なければ英語テーブルにフォールバック、それもなければ警告ログ出力
- **59-226行目**: `formatTranslation`関数 - テキスト内の`{`と`\`を検出し、プレースホルダ置換・plural処理・エスケープ処理を実行。plural構文は再帰的に`formatTranslation`を呼び出す
- **248-426行目**: `resolvePlural`関数 - form番号と数値からplural formインデックスを返却（form 0-19の20種類をサポート）

#### Step 4: 呼び出し元を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | extension.d | `source/served/extension.d` | translate呼び出し箇所（121行目, 129行目, 431行目, 445-448行目, 756-759行目, 814-827行目, 883行目, 897行目, 1019行目等） |
| 4-2 | dub.d | `source/served/commands/dub.d` | translate呼び出し箇所（252行目, 268行目, 278行目, 282行目, 291行目, 319行目, 527行目, 540行目） |
| 4-3 | rename_listener.d | `source/served/workers/rename_listener.d` | translate呼び出し箇所（147行目） |

**主要処理フロー**:
- **extension.d 431行目**: DCD更新ダイアログのメッセージ `translate!"d.served.outdatedDCD"(version, installed)`
- **extension.d 756-757行目**: サブプロジェクト読み込みダイアログのLoad/Skipボタンテキスト
- **extension.d 814-816行目**: DUB依存関係ダウンロードダイアログのUpgrade/Always/Neverボタンテキスト

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

```
app.d (main)
    |
    +-- currentLanguage の設定 (--lang オプション)
    |
    +-- [モジュール初期化] shared static this()
    |       |
    |       +-- parseTranslation!(import("en.txt"))
    |       +-- parseTranslation!(import("de.txt"))
    |       +-- parseTranslation!(import("fr.txt"))
    |       +-- parseTranslation!(import("ja.txt"))
    |       +-- parseTranslation!(import("ru.txt"))
    |
    +-- extension.d / dub.d / rename_listener.d
            |
            +-- translate!"キー"(args...)
                    |
                    +-- translations[currentLanguage][key] (検索)
                    +-- translations["en"][key] (フォールバック)
                    +-- formatTranslation(text, args...)
                            |
                            +-- {N} プレースホルダ置換
                            +-- {plural ...} 構文処理
                            |       +-- resolvePlural(form, n)
                            |       +-- formatTranslation(...) [再帰呼び出し]
                            +-- エスケープ処理 (\n, \t, \r, \\, \{)
```

### データフロー図

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

views/en.txt ─────────┐
views/de.txt ─────────┤
views/fr.txt ─────────┼─▶ [parseTranslation] ─▶ translations連想配列
views/ja.txt ─────────┤    (コンパイル時string import)
views/ru.txt ─────────┘

--lang オプション ────────▶ [currentLanguage設定] ─▶ currentLanguage変数

翻訳キー + 引数 ─────────▶ [translate関数]
                              │
                              ├─▶ [翻訳テーブル検索]
                              │    (currentLanguage -> "en" フォールバック)
                              │
                              └─▶ [formatTranslation]
                                   │
                                   ├─▶ プレースホルダ置換
                                   ├─▶ plural処理 ─▶ [resolvePlural]
                                   └─▶ エスケープ処理
                                                     │
                                                     ▼
                                              翻訳済みテキスト
                                                     │
                                                     ▼
                                         rpc.window.showMessage等
                                         (LSPクライアントへ送信)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| translate.d | `source/served/utils/translate.d` | ソース | 翻訳エンジン本体（翻訳キー検索・フォーマット・plural処理） |
| en.txt | `views/en.txt` | 翻訳リソース | 英語（デフォルト言語）の翻訳テキスト（181行） |
| de.txt | `views/de.txt` | 翻訳リソース | ドイツ語の翻訳テキスト |
| fr.txt | `views/fr.txt` | 翻訳リソース | フランス語の翻訳テキスト |
| ja.txt | `views/ja.txt` | 翻訳リソース | 日本語の翻訳テキスト（166行） |
| ru.txt | `views/ru.txt` | 翻訳リソース | ロシア語の翻訳テキスト |
| app.d | `source/app.d` | ソース | `--lang`オプション解析とcurrentLanguage設定 |
| extension.d | `source/served/extension.d` | ソース | 翻訳関数の主要呼び出し元（ダイアログ・エラーメッセージ） |
| dub.d | `source/served/commands/dub.d` | ソース | DUB関連エラーメッセージでの翻訳関数呼び出し |
| rename_listener.d | `source/served/workers/rename_listener.d` | ソース | リネーム通知メッセージでの翻訳関数呼び出し |
| dub.json | `dub.json` | 設定 | stringImportPathsに`views`を指定（19-21行目） |
