GST Localization Multi-Language Integration Guide
This document describes the multi-language architecture, usage, and development standards of the GST Framework. Applicable to all WPF application projects based on the GST Framework.
1. Architecture Overview
┌──────────────────────────────────────────────────────────────┐
│ XAML Layer (WPF UI) │
│ {l:L Button.Save} → LocalizeExtension → LocalizedString │
│ (Automatically listens for locale changes, UI updates live) │
├──────────────────────────────────────────────────────────────┤
│ Core Layer (Services) │
│ ILocalizationService — Locale state management, source │
│ registration, change events │
│ ILocalizer — String lookup, placeholder formatting │
├──────────────────────────────────────────────────────────────┤
│ Source Layer (Data Sources) │
│ JsonFileLocalizationSource — Disk JSON files (Hot Reload) │
│ EmbeddedResourceLocalizationSource — Assembly embedded │
│ resources │
├──────────────────────────────────────────────────────────────┤
│ Source Generator (Compile-time) │
│ messages.en-US.json → LocalizationSourceGenerator │
│ → K.cs (Flat Keys, XAML IntelliSense) │
│ → MessageKeys.cs (Nested Classes, for code usage) │
├──────────────────────────────────────────────────────────────┤
│ Metalama Aspect │
│ [Localized] — Automatically intercepts Property getter to │
│ return translated value │
│ [NoLocalized] — Excludes specific Properties │
└──────────────────────────────────────────────────────────────┘
Key Module Locations
| Module | Path |
|---|---|
| Abstractions | GST.Core.Abstractions\Localization\ |
| Core Services | GST.Core.Localization\ |
| WPF Integration | GST.UI.Wpf\Localization\ |
| Metalama Aspect | GST.Core.Aspects\Localization\ |
| Source Generator | GST.Tools.LocalizationGenerator\ |
2. Supported Locales
| Code | Language | Notes |
|---|---|---|
en-US | English | Default Culture |
zh-TW | Traditional Chinese (Taiwan) | — |
zh-CN | Simplified Chinese (China) | — |
ja-JP | Japanese | — |
Can be extended via LocalizationOptions.SupportedCultures.
3. Core Interfaces
3.1 ILocalizationService — Locale State Management
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()— Synchronously switch localeSetCultureAsync()— Asynchronously switch (UI thread safe)CultureChanged— Notifies all subscribers when locale changes
3.2 ILocalizer — String Lookup
public interface ILocalizer
{
string Get(string key);
string Get(string key, params object[] args); // Positional: {0}
string Get(string key, IDictionary<string, object> namedArgs); // Named: {name}
bool TryGet(string key, out string? value);
IEnumerable<string> GetAllKeys();
string this[string key] { get; }
}
3.3 ILocalizationSource — Pluggable Data Source
public interface ILocalizationSource
{
string SourceId { get; }
int Priority { get; } // Higher number = higher priority
IDictionary<string, string> LoadMessages(string cultureCode);
IEnumerable<string> GetSupportedCultures();
}
Priority Rules: Sources with higher Priority override translations from lower Priority sources.
| Source | Typical Priority | Description |
|---|---|---|
| GST Framework base | 0 | Base default translations |
| App application layer | 100 | App can override base translations |
4. JSON Translation File Format
4.1 File Naming
messages.{culture}.json
Examples:
messages.en-US.jsonmessages.zh-TW.jsonmessages.zh-CN.jsonmessages.ja-JP.json
4.2 File Format
{
"$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."
}
- Keys starting with
$are metadata and will not be loaded - Supports placeholders:
{0}positional,{name}named
4.3 Key Naming Conventions
{Category}.{SubCategory}.{Name}
| Example | Description |
|---|---|
Menu.File | Menu → File |
Button.Save | Button → Save |
Dialog.Confirm.Title | Dialog → Confirm → Title |
Error.DeviceTimeout | Error → Device Timeout |
Validation.Required | Validation → Required |
5. Source Generator — Compile-Time Key Generation
5.1 Configuration (.csproj)
<ItemGroup>
<AdditionalFiles Include="Resources\Localization\ProcessVision\messages.en-US.json"
LocalizationSourceId="ProcessVision"
LocalizationGenerateKeys="true"
LocalizationNamespace="GST.App.ProcessVision.Localization" />
</ItemGroup>
| Attribute | Description |
|---|---|
LocalizationSourceId | Source identifier name |
LocalizationGenerateKeys | Set to true to enable key generation |
LocalizationNamespace | Namespace for generated classes |
5.2 Generated Classes
K — Flat Keys (for 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";
}
Usage: In XAML with x:Static to provide IntelliSense.
MessageKeys — Nested Classes (for code usage)
// 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";
}
}
Usage: In C# code for structured access.
5.3 Compiler Diagnostics
| ID | Severity | Description |
|---|---|---|
| GST1001 | Error | Key not found in base language JSON |
| GST1002 | Warning | Key exists in en-US but missing in other locales |
| GST1003 | Warning | Placeholder names inconsistent across locales |
| GST1004 | Info | Key defined but not referenced (disabled by default) |
| GST1005 | Warning | Should use MessageKeys constant instead of string literal |
| GST1010 | Warning | JSON file missing SourceId metadata |
| GST1011 | Warning | Empty localization file |
| GST1012 | Warning | Non-standard key format |
6. WPF XAML Integration
6.1 Namespace Declaration
xmlns:l="clr-namespace:GST.UI.Wpf.Localization;assembly=GST.UI.Wpf"
xmlns:keys="clr-namespace:GST.App.ProcessVision.Localization"
6.2 Usage
<!-- Short syntax (recommended) -->
<Button Content="{l:L Button.Save}"/>
<!-- Full syntax -->
<Button Content="{l:Localize Menu.File}"/>
<!-- With x:Static for IntelliSense -->
<Button Content="{l:L {x:Static keys:K.Button_Save}}"/>
<!-- With Fallback -->
<Button Content="{l:L NewFeature.Button, Fallback=Default Text}"/>
6.3 How It Works
{l:L Button.Save}
↓
LocalizeExtension.ProvideValue()
↓
Creates LocalizedString (INotifyPropertyChanged)
↓ Subscribes to CultureChanged event
WPF Binding binds to LocalizedString.Value
↓ When locale switches
CultureChanged → PropertyChanged("Value") → UI auto-updates
6.4 LocalizationServiceLocator Initialization
// App.xaml.cs OnStartup
protected override void OnStartup(StartupEventArgs e)
{
// ... build services ...
// Initialize XAML Localization support (must be done before UI is shown)
LocalizationServiceLocator.Initialize(Services);
}
Note:
LocalizationServiceLocatoruses the Service Locator Pattern to bridge XAML MarkupExtension (which cannot use DI) with the DI Container.
7. Metalama Aspect Integration
7.1 [Localized] Attribute
public class DeviceViewModel
{
[Localized]
public string StatusText { get; } = MessageKeys.Device.Connected;
// Getter is intercepted, automatically returns the translated value for the current locale
[Localized(LogActivity = true)]
public string ErrorMessage { get; } = MessageKeys.Error.Timeout;
// Same as above, additionally logs activity
}
7.2 [NoLocalized] Attribute
public class DeviceViewModel
{
[NoLocalized]
public string DeviceId { get; } = "TAIE-FC-001";
// Bypasses localization, returns the original value directly
}
8. DI Registration & Initialization
8.1 Complete Example (App.xaml.cs)
// 1. Register Localization services
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. Register base translation source (Priority 0)
var gstCorePath = Path.Combine(
AppContext.BaseDirectory, "Resources", "Localization", "GST.Core");
services.AddJsonFileLocalization("GST.Core", gstCorePath, priority: 0);
// 3. Register application translation source (Priority 100, can override base)
var appPath = Path.Combine(
AppContext.BaseDirectory, "Resources", "Localization", "ProcessVision");
services.AddApplicationLocalization("ProcessVision", appPath);
// 4. Initialize XAML support
LocalizationServiceLocator.Initialize(Services);
// 5. Start Localization (load resources)
Services.UseGstLocalization();
// 6. Apply user-preferred locale
var localizationService = Services.GetRequiredService<ILocalizationService>();
localizationService.SetCulture(configService.Config.Language);
8.2 Extension Methods
| Method | Purpose |
|---|---|
AddGstLocalization() | Register core Localization services |
AddJsonFileLocalization() | Register JSON file source (with Hot Reload) |
AddEmbeddedResourceLocalization() | Register Assembly embedded resource source |
AddApplicationLocalization() | Convenience method, equivalent to Priority 100 JSON source |
UseGstLocalization() | Initialize after DI Container is built |
9. Runtime Locale Switching
9.1 Switching Flow
var localizationService = Services.GetRequiredService<ILocalizationService>();
await localizationService.SetCultureAsync("zh-TW");
Internal flow:
- Validate culture code (via
CultureInfo) - Load translations for the target locale from all Sources
- Update
Thread.CurrentCultureandThread.CurrentUICulture - Fire
CultureChangedevent - All
LocalizedStringinstances receive notification PropertyChanged("Value")fires- WPF Binding auto-refreshes → UI updates instantly
9.2 Thread Safety
SetCultureAsync()ensures UI thread safety- Internally uses
lock (_syncLock)to prevent race conditions - Translation dictionary uses
ConcurrentDictionary<string, string>
10. File Structure Example
Application Project Structure
GST.App.ProcessVision/
├── Resources/
│ └── Localization/
│ └── ProcessVision/
│ ├── messages.en-US.json ← Base locale (Source Generator source)
│ ├── messages.zh-TW.json
│ ├── messages.zh-CN.json
│ └── messages.ja-JP.json
└── GST.App.ProcessVision.csproj ← AdditionalFiles configuration
GST Framework Structure
GST.Core.Localization/
├── Resources/
│ └── Localization/
│ └── GST.Core/
│ ├── messages.en-US.json ← Base shared translations
│ ├── messages.zh-TW.json
│ ├── messages.zh-CN.json
│ └── messages.ja-JP.json
11. Development Standards
11.1 No Hard-Coded Strings
// ❌ Wrong: hard-coded Chinese/English
MessageBox.Show("儲存成功");
MessageBox.Show("Save successful");
// ✅ Correct: use Localization Key
MessageBox.Show(localizer[MessageKeys.Dialog.SaveSuccess]);
11.2 Adding Translation Workflow
- Add the new key in
messages.en-US.json - Source Generator automatically generates
KandMessageKeys(at compile time) - Use the new key in XAML or code
- Complete translations for other locales (
zh-TW,zh-CN,ja-JP) - If translations are missing, the compiler will produce GST1002 Warning
11.3 Placeholder Usage
// Positional
"Error.Timeout": "Device {0} timeout after {1}ms."
// Named
"License.Expired": "License expired on {date}."
// Positional
localizer.Get(MessageKeys.Error.Timeout, "TAIE-FC-001", 3000);
// Named
localizer.Get(MessageKeys.License.Expired, new Dictionary<string, object>
{
{ "date", "2026-12-31" }
});
11.4 XAML Best Practices
<!-- Recommended: use x:Static with K class (IntelliSense + compile-time checking) -->
<Button Content="{l:L {x:Static keys:K.Button_Save}}"/>
<!-- Acceptable: write Key string directly (concise but no compile-time checking) -->
<Button Content="{l:L Button.Save}"/>
<!-- Not recommended: hard-coded text -->
<Button Content="Save"/>
12. Integrated Projects
| Project | Key Count | Locales | Source Priority |
|---|---|---|---|
| GST Framework (base) | — | en-US, zh-TW, zh-CN, ja-JP | 0 |
| ProcessVision | ~206 keys | en-US, zh-TW, zh-CN, ja-JP | 100 |
| Jope-SMB | In use | en-US, zh-TW, zh-CN, ja-JP | 100 |
Document Version: v1.0 Created: 2026-03-16 Last Updated: 2026-03-16 Related Linear Issue: GST-170