# 機能設計書 120-多言語対応

## 概要

本ドキュメントは、QuickerSite CMSにおける「多言語対応」機能の設計仕様を定義する。

### 本機能の処理概要

管理画面およびフロントエンドの表示ラベルを複数言語で切り替える機能である。データベース（tblLabel、tblLabelValue、tblLanguage）に格納された言語リソースを、l()関数を通じて動的に取得・表示する。

**業務上の目的・背景**：QuickerSite CMSはグローバルに利用されるWebコンテンツ管理システムであり、様々な言語環境での使用が想定される。多言語対応により、管理者の母国語でCMSを操作できるため、操作効率と使いやすさが向上する。また、フロントエンドの共通要素（ボタン、リンク、メッセージ等）も多言語化することで、多言語サイトの構築が容易になる。

**機能の利用シーン**：
- 管理画面のメニュー・ボタン・メッセージ表示
- エラーメッセージの表示
- フォームのラベル表示
- フロントエンド共通要素の表示（戻るボタン、検索など）
- メール本文のテンプレート

**主要な処理内容**：
1. 現在の言語設定取得（getCurrentLanguage）
2. ラベルコードからラベル値の取得（l関数）
3. Application変数へのキャッシュ
4. データベースからのラベル値読み込み（initLanguage2）
5. ブランド名の置換（rebrand）

**関連システム・外部連携**：
- データベース（tblLabel、tblLabelValue、tblLanguage）
- セッション管理（session("QS_LANG")）
- Application変数（キャッシュ）

**権限による制御**：言語設定は管理者設定またはセッションで制御。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 145 | ラベル一覧 | 主機能 | 多言語ラベルの一覧表示 |
| 146 | ラベル編集 | 主機能 | 多言語ラベルの編集 |
| - | 全管理画面 | 使用 | l()関数によるラベル表示 |
| - | フロントエンド | 使用 | l()関数による共通要素表示 |

## 機能種別

多言語対応 / ローカライゼーション

## 入力仕様

### 入力パラメータ（l関数）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| code | String | Yes | ラベルコード（例: "save"、"back"） | 小文字変換して使用 |

### 入力データソース

- データベース（tblLabel、tblLabelValue、tblLanguage）
- セッション変数（session("QS_LANG")）
- customer.language（サイト設定の言語）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| ラベル値 | String | 現在の言語に対応したラベルテキスト |

### 出力先

- HTML出力（ボタン、リンク、メッセージ等）

## 処理フロー

### 処理シーケンス

```
1. l(code)関数呼び出し
   └─ 引数: ラベルコード（例: "save"）
2. 現在の言語取得
   └─ getCurrentLanguage()呼び出し
       ├─ session("QS_LANG")が設定済み → その値を使用
       ├─ customer.languageが設定済み → その値を使用
       └─ どちらもなし → デフォルト言語ID=1
3. キャッシュ確認
   ├─ Application(QS_CMS_lb_ & useLanguage & code)確認
   ├─ キャッシュあり → キャッシュ値を返却
   └─ キャッシュなし → initLanguage2()呼び出し
4. initLanguage2()処理
   ├─ p_initLanguage2がnull → DB読み込み
   │   ├─ tblLabel、tblLabelValueをJOIN
   │   ├─ 指定言語のラベル値を全取得
   │   ├─ Dictionaryオブジェクトに格納
   │   └─ Application変数にキャッシュ
   └─ p_initLanguage2(code)を返却
5. rebrand()処理
   └─ ブランド名置換（QuickerSite → カスタム名）
```

### フローチャート

```mermaid
flowchart TD
    A[l&#40;code&#41;呼び出し] --> B[getCurrentLanguage&#40;&#41;]
    B --> C{session&#40;QS_LANG&#41;<br>あり?}
    C -->|Yes| D[セッション言語使用]
    C -->|No| E{customer.language<br>あり?}
    E -->|Yes| F[サイト設定言語使用]
    E -->|No| G[デフォルト言語=1]
    D --> H{Application<br>キャッシュあり?}
    F --> H
    G --> H
    H -->|Yes| I[キャッシュ値返却]
    H -->|No| J[initLanguage2&#40;&#41;]
    J --> K{p_initLanguage2<br>初期化済み?}
    K -->|Yes| L[Dictionary検索]
    K -->|No| M[DB読み込み]
    M --> N[tblLabel/tblLabelValue<br>JOIN取得]
    N --> O[Dictionary作成]
    O --> P[Applicationキャッシュ]
    P --> L
    L --> Q[rebrand&#40;&#41;]
    Q --> R[ラベル値返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-120-01 | 言語優先順位 | session > customer.language > デフォルト(1) | 言語判定時 |
| BR-120-02 | ラベルコード小文字化 | ラベルコードは小文字で管理 | initLanguage2呼び出し時 |
| BR-120-03 | キャッシュ利用 | 一度取得したラベルはApplication変数にキャッシュ | 全l()呼び出し |
| BR-120-04 | ブランド置換 | "QuickerSite"をカスタムブランド名に置換 | rebrand()処理時 |
| BR-120-05 | デフォルト言語 | 言語設定がない場合はID=1（通常英語）を使用 | 言語判定時 |

### 計算ロジック

#### l関数

```vb
function l(code)
    dim uselanguage
    uselanguage = getCurrentLanguage

    if isLeeg(Application(QS_CMS_lb_ & useLanguage & code)) then
        l = initLanguage2(lcase(code))
    else
        l = Application(QS_CMS_lb_ & useLanguage & code)
    end if
end function
```

#### getCurrentLanguage関数

```vb
function getCurrentLanguage()
    if not isleeg(session("QS_LANG")) then
        getCurrentLanguage = session("QS_LANG")
    elseif convertGetal(customer.language) <> 0 then
        getCurrentLanguage = customer.language
    else
        getCurrentLanguage = 1
    end if
end function
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ラベル取得 | tblLabel, tblLabelValue | SELECT | 言語別ラベル値の取得 |

### テーブル別操作詳細

#### tblLabel

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | iId, sCode | tblLabelValueとJOIN | ラベルコードマスタ |

#### tblLabelValue

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | sValue, iLabelId, iLanguageID | iLanguageID=現在の言語 | 言語別ラベル値 |

#### tblLanguage

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | iId, sName | 言語マスタ参照 | 言語一覧表示時に使用 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ラベル未定義 | 指定コードのラベルが存在しない | 空文字または初期化されていない値を返却 |
| - | 言語未定義 | 指定言語IDが存在しない | デフォルト言語にフォールバック |

### リトライ仕様

特になし

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

データベース参照のみ（SELECT）のため、トランザクション管理は不要。

## パフォーマンス要件

- ラベル取得: キャッシュヒット時1ms以内、キャッシュミス時10ms以内
- キャッシュはApplication変数で全リクエスト共有
- 初回アクセス時に全ラベルを一括読み込みしてキャッシュ

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

1. **SQLインジェクション対策**: 言語IDは数値変換して使用
2. **XSS対策**: ラベル値はコンテンツとして信頼（管理者が入力）
3. **キャッシュ汚染防止**: Application変数のキーは言語ID+ラベルコードで一意

## 備考

- 言語リソースはQuickerLabels.mdbに別途格納されることもある（getDynamicRSLabels）
- rebrand関数でQuickerSiteブランドをカスタムブランドに置換可能
- ラベルコードは慣例的に小文字で使用（例: "save"、"back"、"err_mandatory"）
- Application変数のキー形式: QS_CMS_lb_ + 言語ID + ラベルコード

---

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

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

### 推奨読解順序

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

多言語対応に使用するデータ構造を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | languageInit.asp | `asp/includes/languageInit.asp` 2-8行目 | p_initLanguage2（Dictionary）の初期化 |
| 1-2 | データベース設計書 | `docs/code-to-docs/データベース設計書` | tblLabel、tblLabelValue、tblLanguageの構造 |

**読解のコツ**: p_initLanguage2はDictionaryオブジェクトで、sCode → sValueのマッピングを保持。

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

多言語取得の起点となるl()関数を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | languageInit.asp | `asp/includes/languageInit.asp` 36-47行目 | l()関数 |

**主要処理フロー**:
1. **39行目**: `getCurrentLanguage` - 現在の言語ID取得
2. **41行目**: `Application(QS_CMS_lb_ & useLanguage & code)` - キャッシュ確認
3. **42行目**: `initLanguage2(lcase(code))` - キャッシュミス時にDB読み込み
4. **44行目**: キャッシュヒット時は直接返却

#### Step 3: 言語判定ロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | languageInit.asp | `asp/includes/languageInit.asp` 59-69行目 | getCurrentLanguage関数 |

**ポイント**:
- session("QS_LANG") > customer.language > デフォルト(1)の優先順位

#### Step 4: データベース読み込みを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | languageInit.asp | `asp/includes/languageInit.asp` 4-34行目 | initLanguage2関数 |

**主要処理**:
- **10-11行目**: tblLabel、tblLabelValueのJOINクエリ
- **19-29行目**: 結果をDictionaryとApplication変数にキャッシュ

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

```
l(code)
    │
    ├─ getCurrentLanguage()
    │      ├─ session("QS_LANG") ... セッション言語
    │      ├─ customer.language ... サイト設定言語
    │      └─ デフォルト=1 ... フォールバック
    │
    ├─ Application(QS_CMS_lb_ & useLanguage & code) ... キャッシュ確認
    │
    └─ initLanguage2(code) ... キャッシュミス時
           │
           ├─ db.getDynamicRSLabels ... DBコネクション取得
           │
           ├─ SELECT tblLabel.sCode, tblLabelValue.sValue
           │      FROM tblLabelValue
           │      INNER JOIN tblLabel on tblLabel.iId=tblLabelValue.iLabelId
           │      WHERE iLanguageID=useLanguage
           │
           ├─ p_initLanguage2.Add(sCode, sValue) ... Dictionary追加
           │
           ├─ Application(QS_CMS_lb_ & useLanguage & sCode)=rebrand(sValue)
           │      │
           │      └─ rebrand() ... ブランド名置換
           │
           └─ p_initLanguage2(code) ... Dictionary検索
```

### データフロー図

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

l("save")呼び出し ─────────> getCurrentLanguage() ──────────> 言語ID
                                    │                         (例: 1)
                                    v
                            キャッシュ確認 ────────────────> Application変数
                            (QS_CMS_lb_1save)
                                    │
                                    ├─ [ヒット] ──────────────> ラベル値
                                    │                           ("Save")
                                    v
                            [ミス] initLanguage2()
                                    │
tblLabel ──────────────────────────> JOIN
tblLabelValue ─────────────────────>  │
                                    v
                            全ラベル取得 ─────────────────> Dictionary
                                    │                       (p_initLanguage2)
                                    v
                            Application保存 ──────────────> キャッシュ
                                    │
                                    v
                            rebrand() ────────────────────> ブランド置換
                                    │
                                    v
                            ラベル値返却 ─────────────────> "Save"
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| languageInit.asp | `asp/includes/languageInit.asp` | ソース | l()関数、initLanguage2関数定義 |
| language.asp | `asp/includes/language.asp` | ソース | 言語クラス定義 |
| label.asp | `asp/includes/label.asp` | ソース | ラベルクラス定義 |
| ad_labels.asp | `asp/ad_labels.asp` | ソース | ラベル一覧画面 |
| ad_label.asp | `asp/ad_label.asp` | ソース | ラベル編集画面 |
| QuickerLabels.mdb | データベース | データ | 多言語ラベルデータベース |

### 利用例

```asp
' ボタンラベルの表示
<input type="submit" value="<%=l("save")%>" />

' リンクテキストの表示
<a href="list.asp"><%=l("back")%></a>

' エラーメッセージの表示
<%=l("err_mandatory")%>

' 確認ダイアログ
onclick="return confirm('<%=l("areyousure")%>')"

' メールテンプレートの表示
<%=l("messagessent")%>
```

### 注意事項

1. **キャッシュの更新**: ラベルを編集した場合、Application変数のリセットが必要な場合がある
2. **未定義ラベル**: 存在しないラベルコードを指定すると空文字が返される
3. **言語追加**: 新しい言語を追加する場合は、tblLanguageとtblLabelValueへのデータ追加が必要
4. **パフォーマンス**: 初回アクセス時に全ラベルを一括読み込みするため、ラベル数が多いと初回読み込み時間が増加する可能性がある
