跳至主要内容

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 │
└──────────────────────────────────────────────────────────────┘

關鍵模組位置

模組路徑
AbstractionsGST.Core.Abstractions\Localization\
Core ServicesGST.Core.Localization\
WPF 整合GST.UI.Wpf\Localization\
Metalama AspectGST.Core.Aspects\Localization\
Source GeneratorGST.Tools.LocalizationGenerator\

2. 支援語系

代碼語言說明
en-USEnglish預設語系(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.json
  • messages.zh-TW.json
  • messages.zh-CN.json
  • messages.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嚴重性說明
GST1001ErrorKey 在基礎語言 JSON 中找不到
GST1002WarningKey 存在於 en-US 但其他語系缺少
GST1003WarningPlaceholder 名稱在不同語系間不一致
GST1004InfoKey 已定義但未被參考(預設關閉)
GST1005Warning應使用 MessageKeys 常數而非字串 literal
GST1010WarningJSON 檔案缺少 SourceId metadata
GST1011Warning空的 Localization 檔案
GST1012Warning非標準的 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");

內部流程:

  1. 驗證 culture code(透過 CultureInfo
  2. 從所有 Source 載入該語系的翻譯
  3. 更新 Thread.CurrentCultureThread.CurrentUICulture
  4. 觸發 CultureChanged event
  5. 所有 LocalizedString 收到通知
  6. PropertyChanged("Value") 觸發
  7. 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 新增翻譯流程

  1. messages.en-US.json 加入新 key
  2. Source Generator 自動產生 KMessageKeys(編譯時)
  3. 在 XAML 或 Code 中使用新 key
  4. 補齊其他語系的翻譯(zh-TW, zh-CN, ja-JP
  5. 如缺少翻譯,編譯器會產生 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-JP0
ProcessVision~206 keysen-US, zh-TW, zh-CN, ja-JP100
Jope-SMB使用中en-US, zh-TW, zh-CN, ja-JP100

文件版本:v1.0 建立日期:2026-03-16 最後更新:2026-03-16 對應 Linear Issue:GST-170