GST Localization 多語系整合指南
本文件說明 GST Framework 的多語系架構、使用方式與開發規範。 適用於所有基於 GST Framework 的 WPF 應用專案。
1. 架構總覽
┌──────────────────────────────────────────────────────────────┐
│ XAML 層(WPF UI) │
│ {l:L Button.Save} → LocalizeExtension → LocalizedString │
│ (自動監聽語系切換,UI 即時更新) │
├──────────────────────────────────────────────────────────────┤
│ Core 層(Services) │
│ ILocalizationService — 語系狀態管理、來源註冊、切換事件 │
│ ILocalizer — 字串查詢、Placeholder 格式化 │
├──────────────────────────────────────────────────────────────┤
│ Source 層(資料來源) │
│ JsonFileLocalizationSource — 磁碟 JSON 檔(Hot Reload) │
│ EmbeddedResourceLocalizationSource — Assembly 內嵌資源 │
├──────────────────────────────────────────────────────────────┤
│ Source Generator(編譯時) │
│ messages.en-US.json → LocalizationSourceGenerator │
│ → K.cs(Flat Keys,XAML IntelliSense) │
│ → MessageKeys.cs(Nested Classes,Code 用) │
├──────────────────────────────────────────────────────────────┤
│ Metalama Aspect │
│ [Localized] — 自動攔截 Property getter 回傳翻譯值 │
│ [NoLocalized] — 排除特定 Property │
└──────────────────────────────────────────────────────────────┘
關鍵模組位置
| 模組 | 路徑 |
|---|---|
| Abstractions | GST.Core.Abstractions\Localization\ |
| Core Services | GST.Core.Localization\ |
| WPF 整合 | GST.UI.Wpf\Localization\ |
| Metalama Aspect | GST.Core.Aspects\Localization\ |
| Source Generator | GST.Tools.LocalizationGenerator\ |
2. 支援語系
| 代碼 | 語言 | 說明 |
|---|---|---|
en-US | English | 預設語系(Default Culture) |
zh-TW | 正體中文(台灣) | — |
zh-CN | 简体中文(中國) | — |
ja-JP | 日本語 | — |
可透過 LocalizationOptions.SupportedCultures 擴充。
3. 核心介面
3.1 ILocalizationService — 語系狀態管理
public interface ILocalizationService
{
string CurrentCulture { get; }
string DefaultCulture { get; }
IReadOnlyList<string> SupportedCultures { get; }
ILocalizer Localizer { get; }
void SetCulture(string cultureCode);
Task SetCultureAsync(string cultureCode);
void RegisterSource(ILocalizationSource source);
void Reload();
event EventHandler<CultureChangedEventArgs> CultureChanged;
}
SetCulture()— 同步切換語系SetCultureAsync()— 非同步切換(UI Thread 安全)CultureChanged— 語系切換時通知所有訂閱者
3.2 ILocalizer — 字串查詢
public interface ILocalizer
{
string Get(string key);
string Get(string key, params object[] args); // 位置式:{0}
string Get(string key, IDictionary<string, object> namedArgs); // 命名式:{name}
bool TryGet(string key, out string? value);
IEnumerable<string> GetAllKeys();
string this[string key] { get; }
}
3.3 ILocalizationSource — 可插拔資料來源
public interface ILocalizationSource
{
string SourceId { get; }
int Priority { get; } // 數字越大優先權越高
IDictionary<string, string> LoadMessages(string cultureCode);
IEnumerable<string> GetSupportedCultures();
}
Priority 規則:高 Priority 的來源會覆蓋低 Priority 的翻譯。
| 來源 | 典型 Priority | 說明 |
|---|---|---|
| GST Framework 底層 | 0 | 底層預設翻譯 |
| App 應用層 | 100 | 應用可覆蓋底層翻譯 |
4. JSON 翻譯檔格式
4.1 檔案命名
messages.{culture}.json
範例:
messages.en-US.jsonmessages.zh-TW.jsonmessages.zh-CN.jsonmessages.ja-JP.json
4.2 檔案格式
{
"$schema": "https://gst.gathertech.com/schemas/localization.json",
"$culture": "en-US",
"Menu.File": "File",
"Menu.Exit": "Exit",
"Button.Save": "Save",
"Button.Cancel": "Cancel",
"License.Expired": "License expired on {date}.",
"Error.DeviceTimeout": "Device {0} timeout after {1}ms."
}
$開頭的 key 為 metadata,不會被載入- 支援 Placeholder:
{0}位置式、{name}命名式
4.3 Key 命名規範
{Category}.{SubCategory}.{Name}
| 範例 | 說明 |
|---|---|
Menu.File | 選單 → 檔案 |
Button.Save | 按鈕 → 儲存 |
Dialog.Confirm.Title | 對話框 → 確認 → 標題 |
Error.DeviceTimeout | 錯誤 → 裝置逾時 |
Validation.Required | 驗證 → 必填 |
5. Source Generator — 編譯時 Key 產生
5.1 設定方式(.csproj)
<ItemGroup>
<AdditionalFiles Include="Resources\Localization\ProcessVision\messages.en-US.json"
LocalizationSourceId="ProcessVision"
LocalizationGenerateKeys="true"
LocalizationNamespace="GST.App.ProcessVision.Localization" />
</ItemGroup>
| 屬性 | 說明 |
|---|---|
LocalizationSourceId | 來源識別名稱 |
LocalizationGenerateKeys | 設為 true 啟用 Key 產生 |
LocalizationNamespace | 產生的類別所屬 namespace |
5.2 產生的類別
K — Flat Keys(XAML IntelliSense 用)
// Auto-generated
public static class K
{
/// <summary>File</summary>
public const string Menu_File = "Menu.File";
/// <summary>Save</summary>
public const string Button_Save = "Button.Save";
}
用途:XAML 中搭配 x:Static 提供 IntelliSense。
MessageKeys — Nested Classes(Code 用)
// Auto-generated
public static class MessageKeys
{
public static class Menu
{
public const string File = "Menu.File";
public const string Exit = "Menu.Exit";
}
public static class Button
{
public const string Save = "Button.Save";
public const string Cancel = "Button.Cancel";
}
}
用途:C# 程式碼中使用,結構化存取。
5.3 Compiler Diagnostics
| ID | 嚴重性 | 說明 |
|---|---|---|
| GST1001 | Error | Key 在基礎語言 JSON 中找不到 |
| GST1002 | Warning | Key 存在於 en-US 但其他語系缺少 |
| GST1003 | Warning | Placeholder 名稱在不同語系間不一致 |
| GST1004 | Info | Key 已定義但未被參考(預設關閉) |
| GST1005 | Warning | 應使用 MessageKeys 常數而非字串 literal |
| GST1010 | Warning | JSON 檔案缺少 SourceId metadata |
| GST1011 | Warning | 空的 Localization 檔案 |
| GST1012 | Warning | 非標準的 Key 格式 |
6. WPF XAML 整合
6.1 Namespace 宣告
xmlns:l="clr-namespace:GST.UI.Wpf.Localization;assembly=GST.UI.Wpf"
xmlns:keys="clr-namespace:GST.App.ProcessVision.Localization"
6.2 使用方式
<!-- 簡短語法(推薦) -->
<Button Content="{l:L Button.Save}"/>
<!-- 完整語法 -->
<Button Content="{l:Localize Menu.File}"/>
<!-- 搭配 x:Static 取得 IntelliSense -->
<Button Content="{l:L {x:Static keys:K.Button_Save}}"/>
<!-- 帶 Fallback -->
<Button Content="{l:L NewFeature.Button, Fallback=Default Text}"/>
6.3 運作機制
{l:L Button.Save}
↓
LocalizeExtension.ProvideValue()
↓
建立 LocalizedString(INotifyPropertyChanged)
↓ 訂閱 CultureChanged 事件
WPF Binding 綁定到 LocalizedString.Value
↓ 語系切換時
CultureChanged → PropertyChanged("Value") → UI 自動更新
6.4 LocalizationServiceLocator 初始化
// App.xaml.cs OnStartup
protected override void OnStartup(StartupEventArgs e)
{
// ... build services ...
// 初始化 XAML Localization 支援(必須在 UI 顯示前)
LocalizationServiceLocator.Initialize(Services);
}
注意:
LocalizationServiceLocator是 Service Locator Pattern, 用來橋接 XAML MarkupExtension(無法使用 DI)和 DI Container。
7. Metalama Aspect 整合
7.1 [Localized] Attribute
public class DeviceViewModel
{
[Localized]
public string StatusText { get; } = MessageKeys.Device.Connected;
// getter 被攔截,自動回傳當前語系的翻譯值
[Localized(LogActivity = true)]
public string ErrorMessage { get; } = MessageKeys.Error.Timeout;
// 同上,額外記錄 log
}
7.2 [NoLocalized] Attribute
public class DeviceViewModel
{
[NoLocalized]
public string DeviceId { get; } = "TAIE-FC-001";
// 不經過 Localization,直接回傳原值
}
8. DI 註冊與初始化
8.1 完整範例(App.xaml.cs)
// 1. 註冊 Localization 服務
services.AddGstLocalization(options =>
{
options.DefaultCulture = "en-US";
options.SupportedCultures = ["en-US", "zh-TW", "zh-CN", "ja-JP"];
options.EnableHotReload = true;
options.ReturnKeyOnMissing = true;
options.LogMissingTranslations = true;
});
// 2. 註冊底層翻譯來源(Priority 0)
var gstCorePath = Path.Combine(
AppContext.BaseDirectory, "Resources", "Localization", "GST.Core");
services.AddJsonFileLocalization("GST.Core", gstCorePath, priority: 0);
// 3. 註冊應用層翻譯來源(Priority 100,可覆蓋底層)
var appPath = Path.Combine(
AppContext.BaseDirectory, "Resources", "Localization", "ProcessVision");
services.AddApplicationLocalization("ProcessVision", appPath);
// 4. 初始化 XAML 支援
LocalizationServiceLocator.Initialize(Services);
// 5. 啟動 Localization(載入資源)
Services.UseGstLocalization();
// 6. 套用使用者偏好語系
var localizationService = Services.GetRequiredService<ILocalizationService>();
localizationService.SetCulture(configService.Config.Language);
8.2 Extension Methods
| 方法 | 用途 |
|---|---|
AddGstLocalization() | 註冊核心 Localization 服務 |
AddJsonFileLocalization() | 註冊 JSON 檔案來源(含 Hot Reload) |
AddEmbeddedResourceLocalization() | 註冊 Assembly 內嵌資源來源 |
AddApplicationLocalization() | 便利方法,等同 Priority 100 的 JSON 來源 |
UseGstLocalization() | DI Container 建立後初始化 |
9. Runtime 語系切換
9.1 切換流程
var localizationService = Services.GetRequiredService<ILocalizationService>();
await localizationService.SetCultureAsync("zh-TW");
內部流程:
- 驗證 culture code(透過
CultureInfo) - 從所有 Source 載入該語系的翻譯
- 更新
Thread.CurrentCulture和Thread.CurrentUICulture - 觸發
CultureChangedevent - 所有
LocalizedString收到通知 PropertyChanged("Value")觸發- WPF Binding 自動刷新 → UI 即時更新
9.2 Thread Safety
SetCultureAsync()確保 UI Thread 安全- 內部使用
lock (_syncLock)防止競態條件 - 翻譯字典使用
ConcurrentDictionary<string, string>
10. 檔案結構範例
應用專案結構
GST.App.ProcessVision/
├── Resources/
│ └── Localization/
│ └── ProcessVision/
│ ├── messages.en-US.json ← 基礎語系(Source Generator 來源)
│ ├── messages.zh-TW.json
│ ├── messages.zh-CN.json
│ └── messages.ja-JP.json
└── GST.App.ProcessVision.csproj ← AdditionalFiles 設定
GST Framework 結構
GST.Core.Localization/
├── Resources/
│ └── Localization/
│ └── GST.Core/
│ ├── messages.en-US.json ← 底層共用翻譯
│ ├── messages.zh-TW.json
│ ├── messages.zh-CN.json
│ └── messages.ja-JP.json
11. 開發規範
11.1 禁止硬編碼字串
// ❌ 錯誤:硬編碼中文/英文
MessageBox.Show("儲存成功");
MessageBox.Show("Save successful");
// ✅ 正確:使用 Localization Key
MessageBox.Show(localizer[MessageKeys.Dialog.SaveSuccess]);
11.2 新增翻譯流程
- 在
messages.en-US.json加入新 key - Source Generator 自動產生
K和MessageKeys(編譯時) - 在 XAML 或 Code 中使用新 key
- 補齊其他語系的翻譯(
zh-TW,zh-CN,ja-JP) - 如缺少翻譯,編譯器會產生 GST1002 Warning
11.3 Placeholder 使用
// 位置式
"Error.Timeout": "Device {0} timeout after {1}ms."
// 命名式
"License.Expired": "License expired on {date}."
// 位置式
localizer.Get(MessageKeys.Error.Timeout, "TAIE-FC-001", 3000);
// 命名式
localizer.Get(MessageKeys.License.Expired, new Dictionary<string, object>
{
{ "date", "2026-12-31" }
});
11.4 XAML 最佳實踐
<!-- 推薦:使用 x:Static 搭配 K 類別(有 IntelliSense + 編譯檢查) -->
<Button Content="{l:L {x:Static keys:K.Button_Save}}"/>
<!-- 可接受:直接寫 Key 字串(簡潔但無編譯檢查) -->
<Button Content="{l:L Button.Save}"/>
<!-- 不推薦:硬編碼文字 -->
<Button Content="Save"/>
12. 已整合專案
| 專案 | Key 數量 | 語系 | Source Priority |
|---|---|---|---|
| GST Framework(底層) | — | en-US, zh-TW, zh-CN, ja-JP | 0 |
| ProcessVision | ~206 keys | en-US, zh-TW, zh-CN, ja-JP | 100 |
| Jope-SMB | 使用中 | en-US, zh-TW, zh-CN, ja-JP | 100 |
文件版本:v1.0 建立日期:2026-03-16 最後更新:2026-03-16 對應 Linear Issue:GST-170