Files
XplorePlane/XP.Common/Documents/Localization.README.md

266 lines
8.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 多语言支持快速开始 | Localization Quick Start
## 概述 | Overview
XplorePlane 多语言支持系统基于 .NET 原生 Resx 资源文件实现,与 Prism MVVM 架构无缝集成。系统支持简体中文(zh-CN)、繁体中文(zh-TW)和美式英语(en-US)三种语言。
### 核心特性 | Key Features
- ✅ 基于 .NET Resx 资源文件,编译时类型安全
- ✅ 简洁的 XAML 标记扩展语法
- ✅ 完整的 ViewModel 集成支持
- ✅ 语言设置持久化到 App.config
- ✅ 跨模块事件通知机制
- ✅ 健壮的错误处理和回退机制
- ✅ 多资源源 Fallback Chain 机制,支持模块级资源注册
---
## 快速开始 | Quick Start
### 1. 在 XAML 中使用本地化资源 | Using Localization in XAML
#### 基础用法 | Basic Usage
```xml
<Window x:Class="XplorePlane.App.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:XplorePlane.Common.Localization.Extensions;assembly=XP.Common"
Title="{loc:Localization Key=App_Title}">
<Grid>
<!-- 按钮文本本地化 | Localized button text -->
<Button Content="{loc:Localization Key=Button_OK}" />
<!-- 标签文本本地化 | Localized label text -->
<Label Content="{loc:Localization Key=Settings_Language}" />
<!-- 菜单项本地化 | Localized menu item -->
<MenuItem Header="{loc:Localization Key=Menu_File}" />
</Grid>
</Window>
```
#### 命名空间声明 | Namespace Declaration
在 XAML 文件顶部添加命名空间引用:
```xml
xmlns:loc="clr-namespace:XplorePlane.Common.Localization.Extensions;assembly=XP.Common"
```
#### 语法说明 | Syntax Explanation
- `{loc:Localization Key=ResourceKey}` - 完整语法
- `{loc:Localization App_Title}` - 简化语法(Key 可省略)
- 资源键不存在时,显示键名本身(便于调试)
---
### 2. 在 C# 代码中使用静态帮助类 | Using Static Helper in C# Code
适用于不方便依赖注入的场景(静态方法、工具类等)。
```csharp
using XP.Common.Localization;
// 基本用法 | Basic usage
var title = LocalizationHelper.Get("App_Title");
// 带格式化参数 | With format arguments
var errorMsg = LocalizationHelper.Get("Settings_Language_SwitchFailed", ex.Message);
```
- 在 ViewModel / Service 中优先使用 `ILocalizationService`(可测试、可 Mock
- 在静态方法、工具类、或不方便注入的地方使用 `LocalizationHelper`
- 两者读取同一套 Resx 资源文件,结果一致
> **V1.4.1.1 变更:** `LocalizationHelper` 新增 `Initialize(ILocalizationService)` 方法。初始化后,`Get()` 会优先通过 `ILocalizationService` 获取字符串(支持 Fallback Chain);未初始化时仍兼容回退到原始 `ResourceManager`。建议在 `CommonModule` 或 App 启动时调用初始化。
```csharp
// 在 CommonModule 或 App 启动时调用 | Call at CommonModule or App startup
LocalizationHelper.Initialize(localizationService);
```
---
### 3. 在 ViewModel 中使用本地化服务 | Using Localization Service in ViewModel
#### 依赖注入 | Dependency Injection
```csharp
using XplorePlane.Common.Localization.Interfaces;
using XplorePlane.Common.Localization.Enums;
using Prism.Mvvm;
namespace XplorePlane.App.ViewModels
{
public class MyViewModel : BindableBase
{
private readonly ILocalizationService _localizationService;
private readonly ILoggerService _logger;
public MyViewModel(
ILocalizationService localizationService,
ILoggerService logger)
{
_localizationService = localizationService;
_logger = logger;
}
// 获取本地化字符串 | Get localized string
public string GetWelcomeMessage()
{
return _localizationService.GetString("Welcome_Message");
}
// 获取当前语言 | Get current language
public SupportedLanguage CurrentLanguage => _localizationService.CurrentLanguage;
}
}
```
#### 动态文本绑定 | Dynamic Text Binding
```csharp
public class StatusViewModel : BindableBase
{
private readonly ILocalizationService _localizationService;
private string _statusMessage;
public string StatusMessage
{
get => _statusMessage;
set => SetProperty(ref _statusMessage, value);
}
public StatusViewModel(ILocalizationService localizationService)
{
_localizationService = localizationService;
// 订阅语言切换事件 | Subscribe to language changed event
_localizationService.LanguageChanged += OnLanguageChanged;
// 初始化状态消息 | Initialize status message
UpdateStatusMessage();
}
private void OnLanguageChanged(object sender, LanguageChangedEventArgs e)
{
// 语言切换时更新文本 | Update text when language changes
UpdateStatusMessage();
}
private void UpdateStatusMessage()
{
StatusMessage = _localizationService.GetString("Status_Ready");
}
}
```
#### 数据验证消息本地化 | Localized Validation Messages
```csharp
public class FormViewModel : BindableBase, IDataErrorInfo
{
private readonly ILocalizationService _localizationService;
private string _username;
public string Username
{
get => _username;
set => SetProperty(ref _username, value);
}
public string this[string columnName]
{
get
{
if (columnName == nameof(Username))
{
if (string.IsNullOrWhiteSpace(Username))
{
return _localizationService.GetString("Validation_UsernameRequired");
}
if (Username.Length < 3)
{
return _localizationService.GetString("Validation_UsernameTooShort");
}
}
return null;
}
}
public string Error => null;
}
```
---
### 4. 多资源源 Fallback Chain | Multi-Source Fallback Chain
V1.1 版本引入了多资源源 Fallback Chain 机制,允许各模块注册自己的 Resx 资源文件。查找资源键时,从最后注册的资源源开始向前遍历,第一个返回非 null 值的即为结果。
#### 架构说明 | Architecture
```
Fallback Chain(查找顺序从右到左):
[XP.Common (默认)] → [XP.Scan (模块注册)] → [XP.Hardware (模块注册)]
↑ 最高优先级
```
- `XP.Common` 为默认资源源,始终位于 Chain[0],不可注销
- 后注册的模块优先级更高
- 单个资源源查找异常时自动跳过,继续遍历下一个
- 全部未找到时返回 key 本身并记录警告日志
#### 注册模块资源源 | Register Module Resource Source
在 Prism 模块的 `OnInitialized` 中注册:
```csharp
using System.Resources;
using XP.Common.Localization.Interfaces;
public class ScanModule : IModule
{
private readonly ILocalizationService _localizationService;
public ScanModule(ILocalizationService localizationService)
{
_localizationService = localizationService;
}
public void OnInitialized(IContainerProvider containerProvider)
{
// 注册模块资源源到 Fallback Chain
var resourceManager = new ResourceManager(
"XP.Scan.Resources.Resources",
typeof(ScanModule).Assembly);
_localizationService.RegisterResourceSource("XP.Scan", resourceManager);
}
public void RegisterTypes(IContainerRegistry containerRegistry) { }
}
```
#### 注销模块资源源 | Unregister Module Resource Source
```csharp
// 注销指定资源源(不可注销默认的 "XP.Common"
_localizationService.UnregisterResourceSource("XP.Scan");
```
#### 注意事项 | Notes
- 资源源名称不可重复,重复注册会抛出 `InvalidOperationException`
- 注销 `"XP.Common"` 会抛出 `InvalidOperationException`
- 注销不存在的名称会静默忽略并记录警告日志
- 线程安全:内部使用 `ReaderWriterLockSlim` 保护读写操作
---
**版本 | Version:** 1.1
**最后更新 | Last Updated:** 2026-04-01