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 { /// /// Resx 本地化服务实现 | Resx localization service implementation /// 使用 .NET ResourceManager 加载 Resx 资源文件 | Uses .NET ResourceManager to load Resx resource files /// 支持多资源源 Fallback Chain 机制 | Supports multi-source Fallback Chain mechanism /// public class ResxLocalizationService : ILocalizationService { private readonly List _resourceSources = new List(); private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(); private readonly IEventAggregator _eventAggregator; private readonly ILocalizationConfig _config; private readonly ILoggerService _logger; private SupportedLanguage _currentLanguage; /// /// 获取当前语言 | Get current language /// public SupportedLanguage CurrentLanguage => _currentLanguage; /// /// 语言切换事件 | Language changed event /// public event EventHandler? LanguageChanged; /// /// 构造函数 | Constructor /// /// 事件聚合器 | Event aggregator /// 本地化配置 | Localization configuration /// 日志服务 | Logger service 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(); // 初始化默认资源源(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(); } /// /// 获取本地化字符串(使用当前语言)| Get localized string (using current language) /// /// 资源键 | Resource key /// 本地化字符串 | Localized string public string GetString(string key) { return GetString(key, CultureInfo.CurrentUICulture); } /// /// 获取本地化字符串(指定语言)| Get localized string (specified language) /// 从 Fallback Chain 末尾向前遍历,第一个返回非 null 值的 ResourceManager 即为结果 /// Traverses the Fallback Chain from end to beginning, first ResourceManager returning non-null wins /// /// 资源键 | Resource key /// 文化信息 | Culture info /// 本地化字符串 | Localized string 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; } /// /// 注册模块资源源到 Fallback Chain 末尾(最高优先级) /// Register a module resource source to the end of the Fallback Chain (highest priority) /// /// 资源源名称 | Resource source name /// 模块的 ResourceManager 实例 | Module's ResourceManager instance 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(); } } /// /// 从 Fallback Chain 中注销指定资源源 /// Unregister the specified resource source from the Fallback Chain /// /// 资源源名称 | Resource source name 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(); } } /// /// 设置当前语言(仅保存配置,重启后生效)| Set current language (save config only, takes effect after restart) /// /// 目标语言 | Target language 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(); } } /// /// 获取所有支持的语言 | Get all supported languages /// /// 支持的语言列表 | List of supported languages public IEnumerable GetSupportedLanguages() { return Enum.GetValues(typeof(SupportedLanguage)) .Cast(); } /// /// 初始化语言设置 | Initialize language settings /// 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; } } /// /// 获取语言对应的 CultureInfo | Get CultureInfo for language /// /// 支持的语言 | Supported language /// 文化信息 | Culture info 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 }; } /// /// 验证资源文件是否可加载 | Validate resource file can be loaded /// 使用默认资源源(XP.Common)进行验证 | Uses default resource source (XP.Common) for validation /// /// 文化信息 | Culture info 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}"); } } } }