0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

プリザンターの多言語対応の実装を詳しくみてみる[第5回:クライアントサイド多言語対応編]

0
Posted at

はじめに

前回(第 4 回)では、サーバーサイドでの言語解決メカニズムを見てきました。最終回となる第 5 回では、クライアントサイド(TypeScript)での多言語対応の仕組みを詳しくみていきます。

回 テーマ
第 1 回 多言語アーキテクチャ概要編 — 全体設計と構成要素
第 2 回 Display 定義と DisplayAccessor 編 — JSON 定義ファイルの構造とアクセサクラス
第 3 回 CodeDefiner によるコード生成編 — テンプレートから C# / TypeScript を自動生成する仕組み
第 4 回 言語解決メカニズム編 — ユーザー言語の決定ロジックとフォールバック
第 5 回(本記事) クライアントサイド多言語対応編 — TypeScript での言語切り替えと $p.display()

バージョン 1.5.1.0 を対象にしています

クライアントサイド多言語対応の全体像

プリザンターのクライアントサイドでは、ページ遷移なしに言語に応じた表示テキストを取得できる仕組みが用意されています。

サーバーサイドとの大きな違いは、ClientScript: true が設定された Display のみがクライアントサイドに公開されるという点です。全 1,255 件のうち、クライアントサイドに必要なものだけが TypeScript コードに含まれます。

自動生成される display.ts

CodeDefiner は display.ts にクライアントサイド用の多言語辞書を自動生成します。

Implem.PleasanterFrontend/wwwroot/src/scripts/generals/display.ts(自動生成)
const displays = {
    AdditionalColumns: 'Additional Items',
    AdditionalColumns_zh: '附加项目',
    AdditionalColumns_ja: '追加項目',
    AdditionalColumns_de: 'Zusätzliche Elemente',
    AdditionalColumns_ko: '추가 항목',
    AdditionalColumns_es: 'Elementos adicionales',
    AdditionalColumns_vn: 'Mục bổ sung',
    Confirm: 'Confirm',
    Confirm_zh: '确认',
    Confirm_ja: '確認',
    Confirm_de: 'Bestätigen',
    Confirm_ko: '확인',
    Confirm_es: 'Confirmar',
    Confirm_vn: 'Xác nhận',
    Ym: 'Month and year',
    Ym_zh: '月份和年份',
    Ym_ja: '年月',
    Ym_de: 'Monat und Jahr',
    Ym_ko: '월 및 년',
    Ym_es: 'Mes y año',
    Ym_vn: 'Tháng năm',
    // ... 多数のエントリ
} as const;

辞書のキー形式はサーバーサイドと同じ {DisplayId} / {DisplayId}_{Language} です。値は HTML エンコードされたテキスト(#DisplayContentEncoded# プレースホルダで生成)になっています。

display 関数の実装

display.ts には辞書に加えて、言語対応の表示テキスト取得関数が定義されています。

Implem.PleasanterFrontend/wwwroot/src/scripts/generals/display.ts
function display(defaultId: DisplayId): string {
    const language = ($('#Language').val() ?? '').toString();
    const localId = (language
        ? `${defaultId}_${language}`
        : defaultId) as DisplayIdAll;
    return displays[localId] ?? displays[defaultId] ?? defaultId;
}

この関数は以下の 3 つのステップで動作します。

ステップ 1: 言語コードの取得

const language = ($('#Language').val() ?? '').toString();

#Language は HTML ページ内に埋め込まれた hidden input 要素です。サーバーサイドでレンダリング時に Context.Language の値がセットされます。

ステップ 2: 言語固有キーの構築

const localId = (language
    ? `${defaultId}_${language}`
    : defaultId) as DisplayIdAll;

言語コードがあれば {DisplayId}_{Language}(例: Confirm_ja)、なければ {DisplayId}(例: Confirm)をキーとして使います。

ステップ 3: フォールバック付き辞書検索

return displays[localId] ?? displays[defaultId] ?? defaultId;
段階 検索キー 例 説明
1 localId Confirm_ja 言語固有のテキスト
2 defaultId Confirm デフォルト(英語)テキスト
3 defaultId 文字列 "Confirm" キーそのものをフォールバック値として返す

このフォールバックチェーンはサーバーサイドの Displays.Get() と同じロジックです。

TypeScript の型定義

display.ts では TypeScript の型安全性を活用しています。

display.ts
type DisplayId = keyof typeof displays;
type DisplayIdAll = DisplayId;

displays オブジェクトのキーから型を自動導出しているため、存在しない DisplayId を指定するとコンパイル時にエラーになります。

$p.display() としての公開

display 関数はグローバルオブジェクト $p のメソッドとして公開されます。

$p.display = display;

これにより、プリザンターの JavaScript コードやサーバスクリプトから以下のように使えます。

// クライアントサイドでの使用例
var confirmText = $p.display('Confirm');
// context.Language が "ja" の場合 → "確認"
// context.Language が "en" の場合 → "Confirm"

サーバーサイドとクライアントサイドの対比

サーバーサイドとクライアントサイドの多言語対応を比較してみましょう。

項目 サーバーサイド クライアントサイド
言語 C# TypeScript
辞書の形式 Dictionary<string, string> const displays = {...} as const
言語の取得元 Context.Language $('#Language').val()
取得関数 Displays.Get(context, id) $p.display(id)
フォールバック id_lang → id → id 文字列 id_lang → id → id 文字列
対象 Display 全件(約 1,255) ClientScript: true のみ
コード生成 Displays.cs(約 28,000 行) display.ts

両者のフォールバックロジックは一致しており、サーバーとクライアントで同じ表示テキストが取得されます。

#Language hidden input の仕組み

display 関数が参照する #Language 要素は、サーバーサイドの HTML レンダリング時に出力されます。

<input type="hidden" id="Language" value="ja" />

この値は Context.Language(第 4 回で解説した言語解決の結果)が設定されます。ページ遷移なしで言語を切り替える場合は、この要素の値を変更してから $p.display() を呼び出せば、新しい言語のテキストが返ります。

ClientScript フラグによるフィルタリング

クライアントサイドに公開する Display は ClientScript: true でフラグ管理されています。

App_Data/Displays/Ym.json
{
    "Id": "Ym",
    "Type": 120,
    "ClientScript": true,
    "Languages": [...]
}

CodeDefiner の CheckExclude() メソッド(第 3 回で紹介)がこのフラグをチェックし、ClientScript: true でない Display はクライアントサイドのコード生成対象から除外します。

if (codeDefinition.ClientScript && display.ClientScript != true)
    return true;  // 除外

この仕組みにより、サーバーサイド専用のメッセージ(セキュリティ関連のエラーメッセージなど)がクライアントサイドに露出することを防いでいます。

多言語対応のデータフロー全体図

連載の総まとめとして、プリザンターの多言語対応のデータフロー全体を図示します。

まとめ

本記事では、クライアントサイドの多言語対応を見てきました。

  • クライアントサイドには ClientScript: true の Display のみが公開される
  • display.ts に言語辞書({DisplayId}_{Language} 形式のキー)が自動生成される
  • $p.display(id) は #Language 要素から言語を取得し、サーバーサイドと同じフォールバックロジックで表示テキストを返す
  • サーバーサイドとクライアントサイドで一貫した多言語対応が実現されている

連載のまとめ

全 5 回を通じて、プリザンターの多言語対応の実装を詳しくみてきました。

回 キーポイント
第 1 回 3 層アーキテクチャ(定義層・生成層・実行層)と 7 言語サポート
第 2 回 Display JSON(1,255 ファイル)と DisplayAccessor クラス群のデータ構造
第 3 回 CodeDefiner のプレースホルダ置換によるコード自動生成(約 28,000 行の Displays.cs)
第 4 回 Context.Language の 5 段階フォールバック(ユーザー設定 → クエリ文字列 → セッション → Accept-Language → デフォルト)
第 5 回 クライアントサイド TypeScript での $p.display() と ClientScript フラグによるフィルタリング

プリザンターの多言語対応は、定義ファイルの一元管理と CodeDefiner による自動生成を組み合わせることで、約 1,255 の表示文字列を 7 言語に対応させています。新しい言語を追加する場合は、Display JSON の Languages 配列に要素を追加し、カラム定義に LabelText_{言語コード} を追加した上で CodeDefiner を実行すれば、サーバーサイドとクライアントサイドの両方にコードが反映されます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?