Skip to main content

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

ModulePath
AbstractionsGST.Core.Abstractions\Localization\
Core ServicesGST.Core.Localization\
WPF IntegrationGST.UI.Wpf\Localization\
Metalama AspectGST.Core.Aspects\Localization\
Source GeneratorGST.Tools.LocalizationGenerator\

2. Supported Locales

CodeLanguageNotes
en-USEnglishDefault Culture
zh-TWTraditional Chinese (Taiwan)
zh-CNSimplified Chinese (China)
ja-JPJapanese

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 locale
  • SetCultureAsync() — 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.

SourceTypical PriorityDescription
GST Framework base0Base default translations
App application layer100App can override base translations

4. JSON Translation File Format

4.1 File Naming

messages.{culture}.json

Examples:

  • messages.en-US.json
  • messages.zh-TW.json
  • messages.zh-CN.json
  • messages.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}
ExampleDescription
Menu.FileMenu → File
Button.SaveButton → Save
Dialog.Confirm.TitleDialog → Confirm → Title
Error.DeviceTimeoutError → Device Timeout
Validation.RequiredValidation → 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>
AttributeDescription
LocalizationSourceIdSource identifier name
LocalizationGenerateKeysSet to true to enable key generation
LocalizationNamespaceNamespace 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

IDSeverityDescription
GST1001ErrorKey not found in base language JSON
GST1002WarningKey exists in en-US but missing in other locales
GST1003WarningPlaceholder names inconsistent across locales
GST1004InfoKey defined but not referenced (disabled by default)
GST1005WarningShould use MessageKeys constant instead of string literal
GST1010WarningJSON file missing SourceId metadata
GST1011WarningEmpty localization file
GST1012WarningNon-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: LocalizationServiceLocator uses 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

MethodPurpose
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:

  1. Validate culture code (via CultureInfo)
  2. Load translations for the target locale from all Sources
  3. Update Thread.CurrentCulture and Thread.CurrentUICulture
  4. Fire CultureChanged event
  5. All LocalizedString instances receive notification
  6. PropertyChanged("Value") fires
  7. 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

  1. Add the new key in messages.en-US.json
  2. Source Generator automatically generates K and MessageKeys (at compile time)
  3. Use the new key in XAML or code
  4. Complete translations for other locales (zh-TW, zh-CN, ja-JP)
  5. 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

ProjectKey CountLocalesSource Priority
GST Framework (base)en-US, zh-TW, zh-CN, ja-JP0
ProcessVision~206 keysen-US, zh-TW, zh-CN, ja-JP100
Jope-SMBIn useen-US, zh-TW, zh-CN, ja-JP100

Document Version: v1.0 Created: 2026-03-16 Last Updated: 2026-03-16 Related Linear Issue: GST-170