Files
XplorePlane/XP.Common/Localization/Implementations/ResxLocalizationService.cs
T

312 lines
14 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Resources;
using System.Threading;
using Prism.Events;
using XP.Common.Localization.Enums;
using XP.Common.Localization.Events;
using XP.Common.Localization.Interfaces;
using XP.Common.Logging.Interfaces;
namespace XP.Common.Localization.Implementations
{
/// <summary>
/// Resx 本地化服务实现 | Resx localization service implementation
/// 使用 .NET ResourceManager 加载 Resx 资源文件 | Uses .NET ResourceManager to load Resx resource files
/// 支持多资源源 Fallback Chain 机制 | Supports multi-source Fallback Chain mechanism
/// </summary>
public class ResxLocalizationService : ILocalizationService
{
private readonly List<ResourceSource> _resourceSources = new List<ResourceSource>();
private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();
private readonly IEventAggregator _eventAggregator;
private readonly ILocalizationConfig _config;
private readonly ILoggerService _logger;
private SupportedLanguage _currentLanguage;
/// <summary>
/// 获取当前语言 | Get current language
/// </summary>
public SupportedLanguage CurrentLanguage => _currentLanguage;
/// <summary>
/// 语言切换事件 | Language changed event
/// </summary>
public event EventHandler<LanguageChangedEventArgs>? LanguageChanged;
/// <summary>
/// 构造函数 | Constructor
/// </summary>
/// <param name="eventAggregator">事件聚合器 | Event aggregator</param>
/// <param name="config">本地化配置 | Localization configuration</param>
/// <param name="logger">日志服务 | Logger service</param>
public ResxLocalizationService(
IEventAggregator eventAggregator,
ILocalizationConfig config,
ILoggerService logger)
{
_eventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
_config = config ?? throw new ArgumentNullException(nameof(config));
_logger = (logger ?? throw new ArgumentNullException(nameof(logger))).ForModule<ResxLocalizationService>();
// 初始化默认资源源(XP.Common),注册到 Fallback Chain[0]
// Initialize default resource source (XP.Common), registered at Fallback Chain[0]
var defaultResourceManager = new ResourceManager(
"XP.Common.Resources.Resources",
Assembly.GetExecutingAssembly());
_resourceSources.Add(new ResourceSource("XP.Common", defaultResourceManager));
// 加载保存的语言或使用默认语言 | Load saved language or use default language
InitializeLanguage();
}
/// <summary>
/// 获取本地化字符串(使用当前语言)| Get localized string (using current language)
/// </summary>
/// <param name="key">资源键 | Resource key</param>
/// <returns>本地化字符串 | Localized string</returns>
public string GetString(string key)
{
return GetString(key, CultureInfo.CurrentUICulture);
}
/// <summary>
/// 获取本地化字符串(指定语言)| Get localized string (specified language)
/// 从 Fallback Chain 末尾向前遍历,第一个返回非 null 值的 ResourceManager 即为结果
/// Traverses the Fallback Chain from end to beginning, first ResourceManager returning non-null wins
/// </summary>
/// <param name="key">资源键 | Resource key</param>
/// <param name="culture">文化信息 | Culture info</param>
/// <returns>本地化字符串 | Localized string</returns>
public string GetString(string key, CultureInfo culture)
{
if (string.IsNullOrEmpty(key))
{
return string.Empty;
}
_rwLock.EnterReadLock();
try
{
// 从末尾向前遍历(后注册 = 高优先级)
// Traverse from end to beginning (last registered = highest priority)
for (int i = _resourceSources.Count - 1; i >= 0; i--)
{
try
{
var value = _resourceSources[i].ResourceManager.GetString(key, culture);
if (value != null)
{
return value;
}
}
catch (Exception ex)
{
// 单个 ResourceManager 抛出异常时捕获并继续遍历下一个
// Catch exception from individual ResourceManager and continue to next
_logger.Error(ex, $"资源源 '{_resourceSources[i].Name}' 查找键 '{key}' 时出错 | Error looking up key '{key}' in resource source '{_resourceSources[i].Name}'");
}
}
}
finally
{
_rwLock.ExitReadLock();
}
// 全部未找到则返回 key 本身并记录警告日志
// If all sources fail to find the key, return the key itself and log warning
_logger.Warn($"资源键未找到 | Resource key not found: '{key}' for culture '{culture?.Name ?? "null"}'");
return key;
}
/// <summary>
/// 注册模块资源源到 Fallback Chain 末尾(最高优先级)
/// Register a module resource source to the end of the Fallback Chain (highest priority)
/// </summary>
/// <param name="name">资源源名称 | Resource source name</param>
/// <param name="resourceManager">模块的 ResourceManager 实例 | Module's ResourceManager instance</param>
public void RegisterResourceSource(string name, ResourceManager resourceManager)
{
if (name == null) throw new ArgumentNullException(nameof(name));
if (resourceManager == null) throw new ArgumentNullException(nameof(resourceManager));
_rwLock.EnterWriteLock();
try
{
// 检查重复名称 | Check for duplicate name
if (_resourceSources.Any(s => s.Name == name))
{
_logger.Warn($"资源源 '{name}' 已存在,注册被拒绝 | Resource source '{name}' already exists, registration rejected");
throw new InvalidOperationException($"Resource source '{name}' is already registered.");
}
_resourceSources.Add(new ResourceSource(name, resourceManager));
_logger.Info($"资源源 '{name}' 已注册到 Fallback Chain | Resource source '{name}' registered to Fallback Chain");
}
finally
{
_rwLock.ExitWriteLock();
}
}
/// <summary>
/// 从 Fallback Chain 中注销指定资源源
/// Unregister the specified resource source from the Fallback Chain
/// </summary>
/// <param name="name">资源源名称 | Resource source name</param>
public void UnregisterResourceSource(string name)
{
if (name == null) throw new ArgumentNullException(nameof(name));
// 禁止注销默认资源源 | Prevent unregistering default resource source
if (name == "XP.Common")
{
throw new InvalidOperationException("Cannot unregister the default resource source 'XP.Common'.");
}
_rwLock.EnterWriteLock();
try
{
var source = _resourceSources.FirstOrDefault(s => s.Name == name);
if (source != null)
{
_resourceSources.Remove(source);
_logger.Info($"资源源 '{name}' 已从 Fallback Chain 注销 | Resource source '{name}' unregistered from Fallback Chain");
}
else
{
// 注销不存在的名称时静默忽略,记录警告日志
// Silently ignore non-existent name, log warning
_logger.Warn($"资源源 '{name}' 不存在,注销操作被忽略 | Resource source '{name}' does not exist, unregister ignored");
}
}
finally
{
_rwLock.ExitWriteLock();
}
}
/// <summary>
/// 设置当前语言(仅保存配置,重启后生效)| Set current language (save config only, takes effect after restart)
/// </summary>
/// <param name="language">目标语言 | Target language</param>
public void SetLanguage(SupportedLanguage language)
{
_rwLock.EnterWriteLock();
try
{
// 如果语言相同,无需保存 | If language is the same, no need to save
if (_currentLanguage == language)
{
_logger.Info($"语言已经是 {language},无需切换 | Language is already {language}, no need to switch");
return;
}
try
{
_logger.Info($"保存语言设置,重启后生效 | Saving language setting, takes effect after restart: {_currentLanguage} -> {language}");
// 仅保存到配置,不更新运行时状态 | Only save to config, do not update runtime state
_config.SaveLanguage(language);
_logger.Info($"语言设置已保存 | Language setting saved: {language}");
}
catch (Exception ex)
{
_logger.Error(ex, $"语言设置保存失败 | Failed to save language setting: {language}");
throw;
}
}
finally
{
_rwLock.ExitWriteLock();
}
}
/// <summary>
/// 获取所有支持的语言 | Get all supported languages
/// </summary>
/// <returns>支持的语言列表 | List of supported languages</returns>
public IEnumerable<SupportedLanguage> GetSupportedLanguages()
{
return Enum.GetValues(typeof(SupportedLanguage))
.Cast<SupportedLanguage>();
}
/// <summary>
/// 初始化语言设置 | Initialize language settings
/// </summary>
private void InitializeLanguage()
{
try
{
// 尝试获取保存的语言 | Try to get saved language
var savedLanguage = _config.GetSavedLanguage();
var language = savedLanguage ?? _config.GetSystemDefaultLanguage();
_currentLanguage = language;
var culture = GetCultureInfo(language);
CultureInfo.CurrentUICulture = culture;
CultureInfo.CurrentCulture = culture;
_logger.Info($"语言初始化完成 | Language initialized: {language} (Culture: {culture.Name})");
// 验证资源文件是否可加载 | Verify resource file can be loaded
ValidateResourceFile(culture);
}
catch (Exception ex)
{
_logger.Error(ex, "语言初始化失败 | Language initialization failed");
throw;
}
}
/// <summary>
/// 获取语言对应的 CultureInfo | Get CultureInfo for language
/// </summary>
/// <param name="language">支持的语言 | Supported language</param>
/// <returns>文化信息 | Culture info</returns>
private CultureInfo GetCultureInfo(SupportedLanguage language)
{
return language switch
{
SupportedLanguage.ZhCN => new CultureInfo("zh-CN"),
SupportedLanguage.ZhTW => new CultureInfo("zh-TW"),
SupportedLanguage.EnUS => new CultureInfo("en-US"),
_ => new CultureInfo("zh-CN") // 默认简体中文 | Default to Simplified Chinese
};
}
/// <summary>
/// 验证资源文件是否可加载 | Validate resource file can be loaded
/// 使用默认资源源(XP.Common)进行验证 | Uses default resource source (XP.Common) for validation
/// </summary>
/// <param name="culture">文化信息 | Culture info</param>
private void ValidateResourceFile(CultureInfo culture)
{
try
{
// 使用第一个资源源(XP.Common)进行验证
// Use the first resource source (XP.Common) for validation
var testValue = _resourceSources[0].ResourceManager.GetString("App_Title", culture);
if (testValue != null)
{
_logger.Info($"资源文件验证成功 | Resource file validated for culture: {culture.Name}");
}
else
{
_logger.Warn($"资源文件可能不完整 | Resource file may be incomplete for culture: {culture.Name}");
}
}
catch (Exception)
{
_logger.Warn($"资源文件验证失败,将使用默认资源 | Resource file validation failed, will use default resources for culture: {culture.Name}");
}
}
}
}