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?

プリザンターの多言語対応の実装を詳しくみてみる[第2回:Display定義とDisplayAccessor編]

0
Posted at

はじめに

前回(第 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 テキストの例です。

App_Data/Displays/Normal.json
{
    "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 がクライアントサイド用のコードにも含めます。

App_Data/Displays/Ym.json
{
    "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 定義

DisplayElementBody 以外にも LabelTextDescriptionInputGuide といった拡張フィールドを持てます。

App_Data/Displays/DisplayName.json
{
    "Id": "DisplayName",
    "Type": 110,
    "ClientScript": true,
    "Languages": [
        {
            "Body": "Name displayed",
            "Description": "The name displayed on the screen"
        },
        {
            "Language": "ja",
            "Body": "表示名",
            "Description": "画面上に表示される名前"
        }
    ]
}

Languages 配列のルール

Languages 配列にはいくつかの重要なルールがあります。

  1. 先頭要素にはデフォルト(英語)を配置するLanguage プロパティを持たない要素がフォールバック値になります
  2. 以降の要素には Language を指定する"ja""zh" などの言語コードを設定します
  3. すべての言語に対応する必要はない — 未定義の言語ではデフォルト値がフォールバックとして使われます

DisplayAccessor クラス群

Display JSON を読み込んでメモリ上に保持するのが Implem.DisplayAccessor プロジェクトのクラス群です。

DisplayElement クラス

1 つの言語バリアントを表すクラスです。

Implem.DisplayAccessor/DisplayElement.cs
public class DisplayElement
{
    public string Language;       // 言語コード(null = デフォルト/英語)
    public string Body;           // 表示テキスト
    public string LabelText;      // ラベルテキスト(任意)
    public string Description;    // 説明テキスト(任意)
    public string InputGuide;     // 入力ガイド(任意)
}

Display クラス

1 つの表示文字列とその全言語バリアントを保持するコンテナクラスです。

Implem.DisplayAccessor/Display.cs
public class Display
{
    public string Id;                        // 表示文字列の識別子
    public Displays.Types Type;              // メッセージ種別
    public bool? ClientScript;               // クライアントサイド公開フラグ
    public List<DisplayElement> Languages;   // 言語バリアントのリスト
}

Displays 静的クラス(DisplayAccessor 版)

DisplayHash ディクショナリと Types 列挙型を定義する静的クラスです。

Implem.DisplayAccessor/Displays.cs
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 ファイルがメモリにロードされます。

Implem.DefinitionAccessor/Initializer.cs
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;
}

処理の流れは以下のとおりです。

  1. App_Data/Displays/ ディレクトリ内の全 JSON ファイルを列挙
  2. 各ファイルを Display オブジェクトにデシリアライズ
  3. Id をキーとして DisplayHash ディクショナリに追加

カラム定義の統合(SetDisplayAccessor)

カラム定義の多言語ラベルも DisplayHash に統合されます。

Implem.DefinitionAccessor/Initializer.cs
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 を保持 DisplayIdDisplay オブジェクト
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 のコードを自動生成するのかを見ていきます。

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?