はじめに
前回(第 3 回)では、CodeDefiner による多言語コードの自動生成の仕組みを見てきました。第 4 回では、ユーザーの言語がどのように決定され、表示テキストの取得にどうつながるのかを詳しくみていきます。
| 回 | テーマ |
|---|---|
| 第 1 回 | 多言語アーキテクチャ概要編 — 全体設計と構成要素 |
| 第 2 回 | Display 定義と DisplayAccessor 編 — JSON 定義ファイルの構造とアクセサクラス |
| 第 3 回 | CodeDefiner によるコード生成編 — テンプレートから C# / TypeScript を自動生成する仕組み |
| 第 4 回(本記事) | 言語解決メカニズム編 — ユーザー言語の決定ロジックとフォールバック |
| 第 5 回 |
クライアントサイド多言語対応編 — TypeScript での言語切り替えと $p.display()
|
バージョン 1.5.1.0 を対象にしています
Context.Language プロパティ
プリザンターのリクエスト処理では、すべてのコンテキスト情報が Context クラスに保持されます。ユーザーの言語もその一部で、Language プロパティとして定義されています。
public string Language { get; set; } = Parameters.Service.DefaultLanguage;
デフォルト値は Parameters.Service.DefaultLanguage(Service.json で設定)です。この値はリクエスト処理の過程で、ユーザーの言語設定に基づいて上書きされます。
3 つの言語決定パターン
Context の初期化時、ユーザーの認証状態に応じて 3 つのパターンで言語が決定されます。
パターン 1: 認証済みユーザー(セッション認証 / API キー認証)
ログイン済みのユーザーや API キーで認証されたユーザーの場合、SetUser() メソッドでユーザーモデルから言語を取得します。
private void SetUser(UserModel userModel, bool noHttpContext = false)
{
if (userModel.AccessStatus == Databases.AccessStatuses.Selected)
{
// ... 他のユーザープロパティの設定 ...
Language = userModel.Language; // DB の Users.Language カラム
// ...
}
}
Users テーブルの Language カラムに保存されたユーザーの言語設定がそのまま Context.Language に設定されます。
パターン 2: 未認証ユーザー
未認証のユーザー(ログイン画面など)の場合は SessionLanguage() メソッドで言語を決定します。
private void SetUserProperties(bool sessionStatus, bool setData)
{
if (HasRoute)
{
if (setData) SetData();
var jsonDeserializedRequestApi = RequestDataString
.Deserialize<Api>();
if (jsonDeserializedRequestApi?.ApiKey.IsNullOrEmpty() == false)
{
ApiKey = jsonDeserializedRequestApi.ApiKey;
SetUser(userModel: GetUser(...));
}
else if (!LoginId.IsNullOrEmpty())
{
SetUser(userModel: GetUser(...));
}
else
{
if (sessionStatus)
Language = SessionLanguage(); // 未認証時のフォールバック
}
}
}
SessionLanguage() の多段フォールバック
未認証ユーザーの言語決定は SessionLanguage() メソッドで行われます。このメソッドは 4 段階のフォールバックを持っています。
private string SessionLanguage()
{
var types = Def.ColumnTable.Users_Language.ChoicesText
.SplitReturn()
.Select(o => o.Split_1st())
.ToList();
var language = string.Empty;
if (HasRoute)
{
// 1. クエリ文字列のチェック
language = QueryStrings.Data("Language");
if (!language.IsNullOrEmpty())
{
SessionUtilities.Set(
context: this,
key: "Language",
value: language);
}
else
{
// 2. Accept-Language ヘッダーの解析
var lang = HttpAcceptLanguage()?.Split_1st('-');
switch (lang)
{
case "en":
case "zh":
case "ja":
case "de":
case "ko":
case "es":
language = lang;
break;
case "vi":
language = "vn"; // vi → vn の変換
break;
default:
// 3. システムデフォルト
language = Parameters.Service?.DefaultLanguage;
break;
}
}
// 4. セッションに保存された言語
language = SessionData.Get("Language") ?? language;
}
return types.Contains(language)
? language
: Parameters.Service?.DefaultLanguage;
}
この処理の流れを整理すると以下のようになります。
フォールバックの優先順位
| 優先度 | 取得元 | 説明 |
|---|---|---|
| 1(最高) | クエリ文字列 ?Language=ja
|
URL パラメータで明示的に指定。セッションにも保存される |
| 2 | セッション保存値 | 過去のリクエストでセッションに保存された言語 |
| 3 |
Accept-Language ヘッダー |
ブラウザの言語設定から取得 |
| 4(最低) | DefaultLanguage |
Service.json のシステムデフォルト |
優先度 2 のセッション保存値は、コード上では Accept-Language ヘッダーの後に評価されていますが、SessionData.Get("Language") ?? language によって、セッションに値があればそちらが優先されます。一度クエリ文字列で言語を設定すると、以降のリクエストではセッション経由でその言語が維持されます。
Accept-Language ヘッダーの解析
ブラウザの Accept-Language ヘッダーは ja-JP,ja;q=0.9,en-US;q=0.8 のような形式です。プリザンターは先頭の言語コードを抽出し、ハイフン前の部分(ja)を使用します。
var lang = HttpAcceptLanguage()?.Split_1st('-');
// "ja-JP,ja;q=0.9" → "ja"
ベトナム語については特別な変換が行われます。HTTP 標準の言語コードは vi ですが、プリザンター内部では vn として扱われるため、case "vi": language = "vn"; という変換が入っています。
バリデーション
最後に、取得した言語コードが Users_Language の ChoicesText に含まれているかチェックし、無効な値の場合は DefaultLanguage にフォールバックします。
return types.Contains(language)
? language
: Parameters.Service?.DefaultLanguage;
Service.json のデフォルト言語設定
フォールバックの最終段として使われる DefaultLanguage は Service.json で設定されます。
{
"Name": "Implem.Pleasanter",
"DefaultLanguage": "en",
"TimeZoneDefault": "UTC"
}
DefaultLanguage はサーバーのシステムデフォルト言語です。ユーザーの初期言語(新規ユーザー作成時のデフォルト値)は Users_Language カラムの Default フィールド("ja")で別途設定されています。
ユーザーの言語設定の保存先
ユーザーが言語を変更すると、Users テーブルの Language カラムに保存されます。
{
"Id": "Users_Language",
"ColumnName": "Language",
"TypeName": "nvarchar",
"MaxLength": "32",
"Default": "ja",
"Required": "1",
"ChoicesText": "en,English\nzh,Chinese\nja,Japanese\nde,German\nko,Korean\nes,Spanish\nvn,Vietnamese"
}
| プロパティ | 値 | 説明 |
|---|---|---|
TypeName |
nvarchar |
Unicode 文字列型 |
MaxLength |
32 |
最大 32 文字 |
Default |
ja |
新規ユーザーの初期値は日本語 |
Required |
1 |
必須フィールド |
Displays.Get() による表示テキスト取得
Context.Language が決定されると、Displays.Get() メソッドで表示テキストを取得します。
public static string Get(
Context context, string id, params string[] data)
{
return Get(
id: id,
language: context.Language,
data: data);
}
public static string Get(
string id, string language, params string[] data)
{
var screen = id;
var key = id + "_" + language;
if (DisplayHash.ContainsKey(key))
screen = DisplayHash[key]; // 言語固有キーを優先
else if (DisplayHash.ContainsKey(id))
screen = DisplayHash[id]; // なければデフォルト
return data?.Any() == true
? screen.Params(data)
: screen;
}
検索フローの図解
Displays.Get(context, "Confirm") を context.Language = "ja" で呼び出した場合の流れです。
パラメータ付きメッセージ
Displays.Get() は data パラメータを受け取り、{0} / {1} のようなプレースホルダを置換できます。
// Display 定義: "'{0}' has been saved."
// 日本語: "'{0}' を保存しました。"
var message = Displays.Get(
context,
"Updated",
data: new[] { recordTitle });
// → "'顧客A' を保存しました。"
Display 拡張メソッド
List<DisplayElement> に対する拡張メソッドも用意されています。
public static string Display(
this List<DisplayElement> languages, Context context)
{
return languages?
.FirstOrDefault(o => o.Language == context.Language)?.Body
?? languages?
.FirstOrDefault(o => o.Language.IsNullOrEmpty())?.Body
?? languages?.FirstOrDefault()?.Body;
}
この拡張メソッドは以下の 3 段階でフォールバックします。
| 段階 | 検索条件 | 例(context.Language = "ko") |
|---|---|---|
| 1 | Language == context.Language |
Language: "ko" の要素 |
| 2 |
Language が null/空 |
デフォルト(英語)の要素 |
| 3 | 先頭要素 | Languages[0] |
言語決定から表示までの全体フロー
最後に、HTTP リクエストから表示テキスト取得までの全体フローを図示します。
まとめ
本記事では、プリザンターの言語解決メカニズムを見てきました。
-
Context.Languageのデフォルト値はParameters.Service.DefaultLanguage - 認証済みユーザーは
Users.Languageカラムの値で上書きされる - 未認証ユーザーは
SessionLanguage()の多段フォールバック(クエリ文字列 → セッション → Accept-Language → デフォルト)で決定される - ベトナム語は HTTP 標準の
viからプリザンター内部のvnに変換される -
Displays.Get()はDisplayId_language→DisplayIdの 2 段階検索でテキストを取得する
次回は、クライアントサイド(TypeScript)での多言語対応の仕組みを見ていきます。