はじめに
前回(第 1 回)では、プリザンターの多言語対応を構成する 3 つの層(定義層・生成層・実行層)の全体像を紹介しました。第 2 回では定義層の中核である Display JSON ファイルと、それを読み込む DisplayAccessor クラス群を詳しくみていきます。
| 回 | テーマ |
|---|---|
| 第 1 回 | 多言語アーキテクチャ概要編 — 全体設計と構成要素 |
| 第 2 回(本記事) | Display 定義と DisplayAccessor 編 — JSON 定義ファイルの構造とアクセサクラス |
| 第 3 回 | CodeDefiner によるコード生成編 — テンプレートから C# / TypeScript を自動生成する仕組み |
| 第 4 回 | 言語解決メカニズム編 — ユーザー言語の決定ロジックとフォールバック |
| 第 5 回 |
クライアントサイド多言語対応編 — TypeScript での言語切り替えと $p.display()
|
バージョン 1.5.1.0 を対象にしています
Display JSON ファイルの配置と数
Display JSON ファイルは App_Data/Displays/ ディレクトリに配置されています。1 ファイルにつき 1 つの表示文字列を定義しており、ファイル数は約 1,255 です。
Implem.Pleasanter/App_Data/Displays/
├── AccessControls.json
├── Add.json
├── Confirm.json
├── Delete.json
├── Normal.json
├── Ym.json
└── ... (約 1,255 ファイル)
ファイル名は {DisplayId}.json という規則で、DisplayId がそのまま多言語テキストの識別子になります。
Display JSON の構造
各 JSON ファイルは以下の 4 つのフィールドで構成されています。
| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
Id |
string |
Yes | 表示文字列の一意な識別子 |
Type |
int |
Yes | メッセージ種別(Displays.Types 列挙型に対応) |
ClientScript |
bool? |
No |
true の場合、クライアントサイドにも公開される |
Languages |
DisplayElement[] |
Yes | 各言語のテキスト定義 |
通常の Display 定義
ボタンやラベルなど、通常の UI テキストの例です。
{
"Id": "Normal",
"Type": 110,
"Languages": [
{ "Body": "Normal" },
{ "Language": "zh", "Body": "正常" },
{ "Language": "ja", "Body": "標準" },
{ "Language": "de", "Body": "Normal" },
{ "Language": "ko", "Body": "표준" },
{ "Language": "es", "Body": "Estándar" },
{ "Language": "vn", "Body": "Bình thường" }
]
}
クライアントサイドにも公開される Display 定義
ClientScript: true を設定すると、CodeDefiner がクライアントサイド用のコードにも含めます。
{
"Id": "Ym",
"Type": 120,
"ClientScript": true,
"Languages": [
{ "Body": "Month and year" },
{ "Language": "zh", "Body": "月份和年份" },
{ "Language": "ja", "Body": "年月" },
{ "Language": "de", "Body": "Monat und Jahr" },
{ "Language": "ko", "Body": "월 및 년" },
{ "Language": "es", "Body": "Mes y año" },
{ "Language": "vn", "Body": "Tháng năm" }
]
}
拡張フィールドを持つ Display 定義
DisplayElement は Body 以外にも LabelText、Description、InputGuide といった拡張フィールドを持てます。
{
"Id": "DisplayName",
"Type": 110,
"ClientScript": true,
"Languages": [
{
"Body": "Name displayed",
"Description": "The name displayed on the screen"
},
{
"Language": "ja",
"Body": "表示名",
"Description": "画面上に表示される名前"
}
]
}
Languages 配列のルール
Languages 配列にはいくつかの重要なルールがあります。
-
先頭要素にはデフォルト(英語)を配置する —
Languageプロパティを持たない要素がフォールバック値になります -
以降の要素には
Languageを指定する —"ja"、"zh"などの言語コードを設定します - すべての言語に対応する必要はない — 未定義の言語ではデフォルト値がフォールバックとして使われます
DisplayAccessor クラス群
Display JSON を読み込んでメモリ上に保持するのが Implem.DisplayAccessor プロジェクトのクラス群です。
DisplayElement クラス
1 つの言語バリアントを表すクラスです。
public class DisplayElement
{
public string Language; // 言語コード(null = デフォルト/英語)
public string Body; // 表示テキスト
public string LabelText; // ラベルテキスト(任意)
public string Description; // 説明テキスト(任意)
public string InputGuide; // 入力ガイド(任意)
}
Display クラス
1 つの表示文字列とその全言語バリアントを保持するコンテナクラスです。
public class Display
{
public string Id; // 表示文字列の識別子
public Displays.Types Type; // メッセージ種別
public bool? ClientScript; // クライアントサイド公開フラグ
public List<DisplayElement> Languages; // 言語バリアントのリスト
}
Displays 静的クラス(DisplayAccessor 版)
DisplayHash ディクショナリと Types 列挙型を定義する静的クラスです。
public static class Displays
{
public enum Types : int
{
Normal = 110,
Date = 120,
DateFormat = 130,
Success = 210,
Information = 220,
Warning = 230,
Error = 240,
Confirmation = 310,
Validation = 410
}
public static Dictionary<string, Display> DisplayHash;
public static string Get(string id)
{
return DisplayHash[id].Languages.FirstOrDefault().Body;
}
}
ここでの DisplayHash は キーが DisplayId、値が Display オブジェクト です。Get() メソッドは Languages の先頭要素(デフォルト/英語)を返すだけのシンプルな実装です。
この DisplayAccessor.Displays クラスは、後述する Implem.Pleasanter 側の Displays クラスとは別物です。DisplayAccessor 版はデータモデルとしての役割を持ち、Implem.Pleasanter 版は言語を考慮した実際の表示テキスト取得を担当します。
DisplayHash の初期化
アプリケーション起動時に Initializer.SetDefinitions() が呼ばれ、Display JSON ファイルがメモリにロードされます。
public static void SetDefinitions()
{
Displays.DisplayHash = DisplayHash(); // JSON ファイルをロード
// ... 他の定義の初期化 ...
SetDisplayAccessor(); // カラム定義を統合
}
private static Dictionary<string, Display> DisplayHash()
{
var hash = new Dictionary<string, Display>();
new DirectoryInfo(Directories.Displays())
.GetFiles("*.json")
.ForEach(file =>
{
var data = Files.Read(file.FullName)
.Deserialize<Display>();
hash.Add(data.Id, data);
});
return hash;
}
処理の流れは以下のとおりです。
-
App_Data/Displays/ディレクトリ内の全 JSON ファイルを列挙 - 各ファイルを
Displayオブジェクトにデシリアライズ -
IdをキーとしてDisplayHashディクショナリに追加
カラム定義の統合(SetDisplayAccessor)
カラム定義の多言語ラベルも DisplayHash に統合されます。
private static void SetDisplayAccessor()
{
Def.ColumnDefinitionCollection
.Where(o => !o.Base)
.Select(o => new
{
o.Id,
En = o.LabelText_en,
Zh = o.LabelText_zh,
Ja = o.LabelText, // 日本語が主キー
De = o.LabelText_de,
Ko = o.LabelText_ko,
Es = o.LabelText_es,
Vn = o.LabelText_vn
})
.Where(o => !Displays.DisplayHash.ContainsKey(o.Id))
.ForEach(o => Displays.DisplayHash.UpdateOrAdd(
o.Id, new Display
{
Id = o.Id,
Languages = new List<DisplayElement>
{
new DisplayElement { Body = o.En },
new DisplayElement { Language = "zh", Body = o.Zh },
new DisplayElement { Language = "ja", Body = o.Ja },
new DisplayElement { Language = "de", Body = o.De },
new DisplayElement { Language = "ko", Body = o.Ko },
new DisplayElement { Language = "es", Body = o.Es },
new DisplayElement { Language = "vn", Body = o.Vn }
}
}));
}
ここで重要なのは JSON 定義が優先される という点です。.Where(o => !Displays.DisplayHash.ContainsKey(o.Id)) によって、既に Display JSON で定義されているキーはカラム定義で上書きされません。
DisplayHash のデータ構造
初期化後の DisplayHash の構造を図示すると以下のようになります。
2 つの Displays クラスの関係
プリザンターには Displays という名前のクラスが 2 つ存在します。混同しやすいので、ここで整理しておきましょう。
| クラス | 名前空間 | 役割 | キーの形式 |
|---|---|---|---|
DisplayAccessor.Displays |
Implem.DisplayAccessor |
データモデル、DisplayHash を保持 |
DisplayId → Display オブジェクト |
Displays |
Implem.Pleasanter.Libraries.Responses |
言語対応の表示テキスト取得 |
DisplayId_language → テキスト文字列 |
Implem.Pleasanter 側の Displays クラスは、DisplayAccessor.Displays.DisplayHash をフラット化して独自の DisplayHash を構築します。フラット化では、各言語バリアントがサフィックス付きのキーとして展開されます。
| DisplayAccessor 側のキー | Implem.Pleasanter 側のキー | 値 |
|---|---|---|
Confirm → Languages[0] |
Confirm |
"Confirm" |
Confirm → Languages[1] |
Confirm_zh |
"确认" |
Confirm → Languages[2] |
Confirm_ja |
"確認" |
まとめ
本記事では、プリザンターの多言語対応における定義層の中核を見てきました。
- Display JSON ファイルは
App_Data/Displays/に約 1,255 ファイル存在し、1 ファイル 1 表示文字列を 7 言語分定義する -
DisplayAccessorクラス群(DisplayElement/Display/Displays)が JSON をメモリ上に保持する - 起動時に
Initializerが JSON とカラム定義を統合してDisplayHashを構築する - JSON 定義はカラム定義より優先される
-
Implem.Pleasanter側のDisplaysクラスはDisplayAccessorのデータをフラット化し、DisplayId_language形式のキーで高速検索できるようにしている
次回は、CodeDefiner がこれらの定義データからどのように C# や TypeScript のコードを自動生成するのかを見ていきます。