将Feature/XP.Common和Feature/XP.Hardware分支合并至Develop/XP.forHardwareAndCommon,完善XPapp注册和相关硬件类库通用类库功能。
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.Globalization;
|
||||
using XP.Common.Localization.Enums;
|
||||
using XP.Common.Localization.Interfaces;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
|
||||
namespace XP.Common.Localization.Configs
|
||||
{
|
||||
/// <summary>
|
||||
/// 本地化配置实现 | Localization configuration implementation
|
||||
/// 使用 App.config 存储语言设置 | Uses App.config to store language settings
|
||||
/// </summary>
|
||||
public class LocalizationConfig : ILocalizationConfig
|
||||
{
|
||||
private const string LanguageKey = "Language";
|
||||
private readonly ILoggerService _logger;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
/// <param name="logger">日志服务 | Logger service</param>
|
||||
public LocalizationConfig(ILoggerService logger)
|
||||
{
|
||||
_logger = (logger ?? throw new ArgumentNullException(nameof(logger))).ForModule<LocalizationConfig>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取保存的语言设置 | Get saved language setting
|
||||
/// </summary>
|
||||
/// <returns>语言设置,如果未设置则返回 null | Language setting, or null if not set</returns>
|
||||
public SupportedLanguage? GetSavedLanguage()
|
||||
{
|
||||
try
|
||||
{
|
||||
var languageString = ConfigurationManager.AppSettings[LanguageKey];
|
||||
|
||||
if (string.IsNullOrWhiteSpace(languageString))
|
||||
{
|
||||
_logger.Info("No saved language setting found in configuration");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Enum.TryParse<SupportedLanguage>(languageString, true, out var language))
|
||||
{
|
||||
_logger.Info($"Loaded saved language setting: {language}");
|
||||
return language;
|
||||
}
|
||||
|
||||
_logger.Warn($"Invalid language setting in configuration: '{languageString}'. Expected values: ZhCN, ZhTW, EnUS");
|
||||
return null;
|
||||
}
|
||||
catch (ConfigurationErrorsException ex)
|
||||
{
|
||||
_logger.Error(ex, "Configuration error while reading language setting");
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Unexpected error while reading saved language setting");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存语言设置 | Save language setting
|
||||
/// </summary>
|
||||
/// <param name="language">要保存的语言 | Language to save</param>
|
||||
public void SaveLanguage(SupportedLanguage language)
|
||||
{
|
||||
try
|
||||
{
|
||||
var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
|
||||
var appSettings = config.AppSettings.Settings;
|
||||
|
||||
var languageValue = language.ToString();
|
||||
|
||||
if (appSettings[LanguageKey] == null)
|
||||
{
|
||||
appSettings.Add(LanguageKey, languageValue);
|
||||
_logger.Info($"Added new language setting to configuration: {language}");
|
||||
}
|
||||
else
|
||||
{
|
||||
appSettings[LanguageKey].Value = languageValue;
|
||||
_logger.Info($"Updated language setting in configuration: {language}");
|
||||
}
|
||||
|
||||
config.Save(ConfigurationSaveMode.Modified);
|
||||
ConfigurationManager.RefreshSection("appSettings");
|
||||
|
||||
_logger.Info($"Successfully saved language setting: {language}");
|
||||
}
|
||||
catch (ConfigurationErrorsException ex)
|
||||
{
|
||||
_logger.Error(ex, $"Configuration error while saving language setting: {language}");
|
||||
throw new InvalidOperationException($"Failed to save language setting to configuration file: {ex.Message}", ex);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
_logger.Error(ex, $"Access denied while saving language setting: {language}");
|
||||
throw new InvalidOperationException("Access denied to configuration file. Please check file permissions.", ex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, $"Unexpected error while saving language setting: {language}");
|
||||
throw new InvalidOperationException($"Failed to save language setting: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取系统默认语言 | Get system default language
|
||||
/// </summary>
|
||||
/// <returns>系统默认语言 | System default language</returns>
|
||||
public SupportedLanguage GetSystemDefaultLanguage()
|
||||
{
|
||||
try
|
||||
{
|
||||
var systemCulture = CultureInfo.CurrentUICulture.Name;
|
||||
_logger.Info($"Detecting system default language from culture: {systemCulture}");
|
||||
|
||||
var defaultLanguage = systemCulture switch
|
||||
{
|
||||
"zh-CN" => SupportedLanguage.ZhCN,
|
||||
"zh-TW" or "zh-HK" or "zh-MO" => SupportedLanguage.ZhTW,
|
||||
"en-US" or "en" => SupportedLanguage.EnUS,
|
||||
_ when systemCulture.StartsWith("zh", StringComparison.OrdinalIgnoreCase) => SupportedLanguage.ZhCN,
|
||||
_ when systemCulture.StartsWith("en", StringComparison.OrdinalIgnoreCase) => SupportedLanguage.EnUS,
|
||||
_ => SupportedLanguage.ZhCN // 默认简体中文 | Default to Simplified Chinese
|
||||
};
|
||||
|
||||
_logger.Info($"System default language determined: {defaultLanguage} (from culture: {systemCulture})");
|
||||
return defaultLanguage;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Error while detecting system default language, falling back to ZhCN");
|
||||
return SupportedLanguage.ZhCN; // 默认简体中文 | Default to Simplified Chinese
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace XP.Common.Localization.Enums
|
||||
{
|
||||
/// <summary>
|
||||
/// 支持的语言枚举 | Supported language enumeration
|
||||
/// </summary>
|
||||
public enum SupportedLanguage
|
||||
{
|
||||
/// <summary>
|
||||
/// 简体中文 | Simplified Chinese
|
||||
/// </summary>
|
||||
[Description("zh-CN")]
|
||||
ZhCN,
|
||||
|
||||
/// <summary>
|
||||
/// 繁体中文 | Traditional Chinese
|
||||
/// </summary>
|
||||
[Description("zh-TW")]
|
||||
ZhTW,
|
||||
|
||||
/// <summary>
|
||||
/// 美式英语 | American English
|
||||
/// </summary>
|
||||
[Description("en-US")]
|
||||
EnUS
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Prism.Events;
|
||||
|
||||
namespace XP.Common.Localization.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Prism 语言切换事件 | Prism language changed event
|
||||
/// 用于跨模块通知语言切换 | Used for cross-module language change notification
|
||||
/// </summary>
|
||||
public class LanguageChangedEvent : PubSubEvent<LanguageChangedEventArgs>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using XP.Common.Localization.Enums;
|
||||
|
||||
namespace XP.Common.Localization.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 语言切换事件参数 | Language changed event arguments
|
||||
/// </summary>
|
||||
public class LanguageChangedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 旧语言 | Old language
|
||||
/// </summary>
|
||||
public SupportedLanguage OldLanguage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 新语言 | New language
|
||||
/// </summary>
|
||||
public SupportedLanguage NewLanguage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 切换时间 | Change timestamp
|
||||
/// </summary>
|
||||
public DateTime Timestamp { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
/// <param name="oldLanguage">旧语言 | Old language</param>
|
||||
/// <param name="newLanguage">新语言 | New language</param>
|
||||
public LanguageChangedEventArgs(SupportedLanguage oldLanguage, SupportedLanguage newLanguage)
|
||||
{
|
||||
OldLanguage = oldLanguage;
|
||||
NewLanguage = newLanguage;
|
||||
Timestamp = DateTime.Now;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
|
||||
namespace XP.Common.Localization.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 本地化异常基类 | Localization exception base class
|
||||
/// </summary>
|
||||
public class LocalizationException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
/// <param name="message">异常消息 | Exception message</param>
|
||||
public LocalizationException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
/// <param name="message">异常消息 | Exception message</param>
|
||||
/// <param name="innerException">内部异常 | Inner exception</param>
|
||||
public LocalizationException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
|
||||
namespace XP.Common.Localization.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 本地化初始化异常 | Localization initialization exception
|
||||
/// 当资源文件无法加载时抛出 | Thrown when resource files cannot be loaded
|
||||
/// </summary>
|
||||
public class LocalizationInitializationException : LocalizationException
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
/// <param name="message">异常消息 | Exception message</param>
|
||||
public LocalizationInitializationException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
/// <param name="message">异常消息 | Exception message</param>
|
||||
/// <param name="innerException">内部异常 | Inner exception</param>
|
||||
public LocalizationInitializationException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
|
||||
namespace XP.Common.Localization.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 资源键未找到异常 | Resource key not found exception
|
||||
/// </summary>
|
||||
public class ResourceKeyNotFoundException : LocalizationException
|
||||
{
|
||||
/// <summary>
|
||||
/// 资源键 | Resource key
|
||||
/// </summary>
|
||||
public string ResourceKey { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
/// <param name="resourceKey">资源键 | Resource key</param>
|
||||
public ResourceKeyNotFoundException(string resourceKey)
|
||||
: base($"Resource key not found: {resourceKey}")
|
||||
{
|
||||
ResourceKey = resourceKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Windows.Markup;
|
||||
using XP.Common.Localization.Interfaces;
|
||||
|
||||
namespace XP.Common.Localization.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 本地化标记扩展 | Localization markup extension
|
||||
/// 用法: {loc:Localization Key=ResourceKey} | Usage: {loc:Localization Key=ResourceKey}
|
||||
/// </summary>
|
||||
[MarkupExtensionReturnType(typeof(string))]
|
||||
public class LocalizationExtension : MarkupExtension
|
||||
{
|
||||
private static ILocalizationService? _localizationService;
|
||||
private string _key = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 资源键 | Resource key
|
||||
/// </summary>
|
||||
[ConstructorArgument("key")]
|
||||
public string Key
|
||||
{
|
||||
get => _key;
|
||||
set => _key = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 默认构造函数 | Default constructor
|
||||
/// </summary>
|
||||
public LocalizationExtension()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 带资源键的构造函数 | Constructor with resource key
|
||||
/// </summary>
|
||||
/// <param name="key">资源键 | Resource key</param>
|
||||
public LocalizationExtension(string key)
|
||||
{
|
||||
_key = key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化本地化服务(由 CommonModule 调用)| Initialize localization service (called by CommonModule)
|
||||
/// </summary>
|
||||
/// <param name="localizationService">本地化服务实例 | Localization service instance</param>
|
||||
public static void Initialize(ILocalizationService localizationService)
|
||||
{
|
||||
_localizationService = localizationService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 提供本地化字符串值 | Provide localized string value
|
||||
/// </summary>
|
||||
/// <param name="serviceProvider">服务提供者 | Service provider</param>
|
||||
/// <returns>本地化字符串 | Localized string</returns>
|
||||
public override object ProvideValue(IServiceProvider serviceProvider)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_key))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (_localizationService == null)
|
||||
{
|
||||
// 设计时回退 | Design-time fallback
|
||||
return $"[{_key}]";
|
||||
}
|
||||
|
||||
// 直接返回当前语言的翻译字符串 | Return translated text for current language
|
||||
return _localizationService.GetString(_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Globalization;
|
||||
using System.Resources;
|
||||
using XP.Common.Localization.Interfaces;
|
||||
using XP.Common.Resources;
|
||||
|
||||
namespace XP.Common.Localization
|
||||
{
|
||||
/// <summary>
|
||||
/// 静态本地化帮助类 | Static localization helper
|
||||
/// 用法:Loc.Get("ResourceKey") 或 Loc.Get("ResourceKey", param1, param2)
|
||||
/// Usage: Loc.Get("ResourceKey") or Loc.Get("ResourceKey", param1, param2)
|
||||
/// </summary>
|
||||
public static class LocalizationHelper
|
||||
{
|
||||
private static ILocalizationService? _localizationService;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化本地化帮助类(由 CommonModule 或 App 启动时调用)
|
||||
/// Initialize the localization helper (called by CommonModule or App at startup)
|
||||
/// </summary>
|
||||
/// <param name="localizationService">本地化服务实例 | Localization service instance</param>
|
||||
public static void Initialize(ILocalizationService localizationService)
|
||||
{
|
||||
_localizationService = localizationService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取本地化字符串(使用当前 UI 语言)| Get localized string (using current UI culture)
|
||||
/// </summary>
|
||||
/// <param name="key">资源键 | Resource key</param>
|
||||
/// <returns>本地化字符串,找不到时返回键本身 | Localized string, returns key itself if not found</returns>
|
||||
public static string Get(string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
return string.Empty;
|
||||
|
||||
if (_localizationService != null)
|
||||
return _localizationService.GetString(key);
|
||||
|
||||
// 兼容回退:未初始化时仍使用原始 ResourceManager
|
||||
// Fallback: use original ResourceManager when not initialized
|
||||
return Resources.Resources.ResourceManager.GetString(key, CultureInfo.CurrentUICulture) ?? key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取本地化字符串并格式化 | Get localized string with formatting
|
||||
/// </summary>
|
||||
/// <param name="key">资源键 | Resource key</param>
|
||||
/// <param name="args">格式化参数 | Format arguments</param>
|
||||
/// <returns>格式化后的本地化字符串 | Formatted localized string</returns>
|
||||
public static string Get(string key, params object[] args)
|
||||
{
|
||||
var template = Get(key);
|
||||
return args.Length > 0 ? string.Format(template, args) : template;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Resources;
|
||||
|
||||
namespace XP.Common.Localization.Implementations
|
||||
{
|
||||
/// <summary>
|
||||
/// 资源源条目,封装名称与 ResourceManager 的映射
|
||||
/// Resource source entry, encapsulating the mapping between name and ResourceManager
|
||||
/// </summary>
|
||||
internal class ResourceSource
|
||||
{
|
||||
/// <summary>
|
||||
/// 资源源唯一标识 | Resource source unique identifier
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// .NET 资源管理器实例 | .NET ResourceManager instance
|
||||
/// </summary>
|
||||
public ResourceManager ResourceManager { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
/// <param name="name">资源源名称(如 "XP.Scan")| Resource source name (e.g. "XP.Scan")</param>
|
||||
/// <param name="resourceManager">模块的 ResourceManager 实例 | Module's ResourceManager instance</param>
|
||||
/// <exception cref="ArgumentNullException">当 name 或 resourceManager 为 null 时抛出 | Thrown when name or resourceManager is null</exception>
|
||||
public ResourceSource(string name, ResourceManager resourceManager)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
ResourceManager = resourceManager ?? throw new ArgumentNullException(nameof(resourceManager));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using XP.Common.Localization.Enums;
|
||||
|
||||
namespace XP.Common.Localization.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// 本地化配置接口 | Localization configuration interface
|
||||
/// 负责语言设置的加载和保存 | Responsible for loading and saving language settings
|
||||
/// </summary>
|
||||
public interface ILocalizationConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取保存的语言设置 | Get saved language setting
|
||||
/// </summary>
|
||||
/// <returns>语言设置,如果未设置则返回 null | Language setting, or null if not set</returns>
|
||||
SupportedLanguage? GetSavedLanguage();
|
||||
|
||||
/// <summary>
|
||||
/// 保存语言设置 | Save language setting
|
||||
/// </summary>
|
||||
/// <param name="language">要保存的语言 | Language to save</param>
|
||||
void SaveLanguage(SupportedLanguage language);
|
||||
|
||||
/// <summary>
|
||||
/// 获取系统默认语言 | Get system default language
|
||||
/// </summary>
|
||||
/// <returns>系统默认语言 | System default language</returns>
|
||||
SupportedLanguage GetSystemDefaultLanguage();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Resources;
|
||||
using XP.Common.Localization.Enums;
|
||||
using XP.Common.Localization.Events;
|
||||
|
||||
namespace XP.Common.Localization.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// 本地化服务接口 | Localization service interface
|
||||
/// 提供多语言资源访问和语言切换功能 | Provides multilingual resource access and language switching
|
||||
/// </summary>
|
||||
public interface ILocalizationService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前语言 | Get current language
|
||||
/// </summary>
|
||||
SupportedLanguage CurrentLanguage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 语言切换事件 | Language changed event
|
||||
/// </summary>
|
||||
event EventHandler<LanguageChangedEventArgs> LanguageChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 获取本地化字符串(使用当前语言)| Get localized string (using current language)
|
||||
/// </summary>
|
||||
/// <param name="key">资源键 | Resource key</param>
|
||||
/// <returns>本地化字符串 | Localized string</returns>
|
||||
string GetString(string key);
|
||||
|
||||
/// <summary>
|
||||
/// 获取本地化字符串(指定语言)| Get localized string (specified language)
|
||||
/// </summary>
|
||||
/// <param name="key">资源键 | Resource key</param>
|
||||
/// <param name="culture">文化信息 | Culture info</param>
|
||||
/// <returns>本地化字符串 | Localized string</returns>
|
||||
string GetString(string key, CultureInfo culture);
|
||||
|
||||
/// <summary>
|
||||
/// 设置当前语言 | Set current language
|
||||
/// </summary>
|
||||
/// <param name="language">目标语言 | Target language</param>
|
||||
void SetLanguage(SupportedLanguage language);
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有支持的语言 | Get all supported languages
|
||||
/// </summary>
|
||||
/// <returns>支持的语言列表 | List of supported languages</returns>
|
||||
IEnumerable<SupportedLanguage> GetSupportedLanguages();
|
||||
|
||||
/// <summary>
|
||||
/// 注册模块资源源到 Fallback Chain 末尾(最高优先级)
|
||||
/// Register a module resource source to the end of the Fallback Chain (highest priority)
|
||||
/// </summary>
|
||||
/// <param name="name">资源源名称(如 "XP.Scan"),用于标识和注销 | Resource source name (e.g. "XP.Scan"), used for identification and unregistration</param>
|
||||
/// <param name="resourceManager">模块的 ResourceManager 实例 | The module's ResourceManager instance</param>
|
||||
void RegisterResourceSource(string name, ResourceManager resourceManager);
|
||||
|
||||
/// <summary>
|
||||
/// 从 Fallback Chain 中注销指定资源源
|
||||
/// Unregister the specified resource source from the Fallback Chain
|
||||
/// </summary>
|
||||
/// <param name="name">资源源名称 | Resource source name</param>
|
||||
void UnregisterResourceSource(string name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using XP.Common.Localization.Enums;
|
||||
|
||||
namespace XP.Common.Localization.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 语言选项数据模型 | Language option data model
|
||||
/// 用于在 UI 中显示可选语言 | Used to display available languages in UI
|
||||
/// </summary>
|
||||
public class LanguageOption
|
||||
{
|
||||
/// <summary>
|
||||
/// 语言枚举值 | Language enum value
|
||||
/// </summary>
|
||||
public SupportedLanguage Language { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 显示名称 | Display name
|
||||
/// </summary>
|
||||
public string DisplayName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 语言标志/图标 | Language flag/icon
|
||||
/// </summary>
|
||||
public string Flag { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
/// <param name="language">语言 | Language</param>
|
||||
/// <param name="displayName">显示名称 | Display name</param>
|
||||
/// <param name="flag">标志 | Flag</param>
|
||||
public LanguageOption(SupportedLanguage language, string displayName, string flag = "")
|
||||
{
|
||||
Language = language;
|
||||
DisplayName = displayName;
|
||||
Flag = flag;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using Prism.Commands;
|
||||
using Prism.Mvvm;
|
||||
using XP.Common.Localization.Enums;
|
||||
using XP.Common.Localization.Interfaces;
|
||||
using XP.Common.Logging.Interfaces;
|
||||
|
||||
|
||||
namespace XP.Common.Localization.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// 语言切换器 ViewModel | Language switcher ViewModel
|
||||
/// 提供语言选择和切换功能 | Provides language selection and switching functionality
|
||||
/// </summary>
|
||||
public class LanguageSwitcherViewModel : BindableBase
|
||||
{
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly ILocalizationConfig _config;
|
||||
private readonly ILoggerService _logger;
|
||||
private LanguageOption? _selectedLanguage;
|
||||
|
||||
/// <summary>
|
||||
/// 选中的语言选项 | Selected language option
|
||||
/// </summary>
|
||||
public LanguageOption? SelectedLanguage
|
||||
{
|
||||
get => _selectedLanguage;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _selectedLanguage, value))
|
||||
{
|
||||
ApplyCommand.RaiseCanExecuteChanged();
|
||||
UpdatePreviewTexts();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可用语言列表 | Available languages list
|
||||
/// </summary>
|
||||
public IEnumerable<LanguageOption> AvailableLanguages { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 应用按钮命令 | Apply button command
|
||||
/// </summary>
|
||||
public DelegateCommand ApplyCommand { get; }
|
||||
|
||||
private string _previewRestartNotice = string.Empty;
|
||||
/// <summary>
|
||||
/// 预览重启提示文本 | Preview restart notice text
|
||||
/// </summary>
|
||||
public string PreviewRestartNotice
|
||||
{
|
||||
get => _previewRestartNotice;
|
||||
set => SetProperty(ref _previewRestartNotice, value);
|
||||
}
|
||||
|
||||
private string _previewApplyButtonText = string.Empty;
|
||||
/// <summary>
|
||||
/// 预览应用按钮文本 | Preview apply button text
|
||||
/// </summary>
|
||||
public string PreviewApplyButtonText
|
||||
{
|
||||
get => _previewApplyButtonText;
|
||||
set => SetProperty(ref _previewApplyButtonText, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数 | Constructor
|
||||
/// </summary>
|
||||
/// <param name="localizationService">本地化服务 | Localization service</param>
|
||||
/// <param name="config">本地化配置 | Localization configuration</param>
|
||||
/// <param name="logger">日志服务 | Logger service</param>
|
||||
public LanguageSwitcherViewModel(
|
||||
ILocalizationService localizationService,
|
||||
ILocalizationConfig config,
|
||||
ILoggerService logger)
|
||||
{
|
||||
_localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService));
|
||||
_config = config ?? throw new ArgumentNullException(nameof(config));
|
||||
_logger = logger.ForModule<LanguageSwitcherViewModel>();
|
||||
|
||||
// 初始化可用语言列表 | Initialize available languages list
|
||||
AvailableLanguages = new[]
|
||||
{
|
||||
new LanguageOption(SupportedLanguage.ZhCN, "简体中文", "🇨🇳"),
|
||||
new LanguageOption(SupportedLanguage.ZhTW, "繁體中文", "🇹🇼"),
|
||||
new LanguageOption(SupportedLanguage.EnUS, "English", "🇺🇸")
|
||||
};
|
||||
|
||||
// 设置当前选中的语言 | Set currently selected language
|
||||
var currentLang = localizationService.CurrentLanguage;
|
||||
foreach (var lang in AvailableLanguages)
|
||||
{
|
||||
if (lang.Language == currentLang)
|
||||
{
|
||||
_selectedLanguage = lang;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化命令 | Initialize commands
|
||||
ApplyCommand = new DelegateCommand(OnApply, CanApply);
|
||||
|
||||
// 初始化预览文本 | Initialize preview texts
|
||||
UpdatePreviewTexts();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新预览文本 | Update preview texts
|
||||
/// </summary>
|
||||
private void UpdatePreviewTexts()
|
||||
{
|
||||
if (_selectedLanguage == null)
|
||||
return;
|
||||
|
||||
// 获取选中语言的 CultureInfo | Get CultureInfo for selected language
|
||||
var previewCulture = GetCultureInfo(_selectedLanguage.Language);
|
||||
|
||||
// 使用选中的语言获取本地化文本 | Get localized text using selected language
|
||||
PreviewRestartNotice = _localizationService.GetString("Settings_Language_RestartNotice", previewCulture);
|
||||
PreviewApplyButtonText = _localizationService.GetString("Button_Apply", previewCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否可以应用 | Determine if can apply
|
||||
/// 与配置文件中已保存的语言比较 | Compare with saved language in config
|
||||
/// </summary>
|
||||
private bool CanApply()
|
||||
{
|
||||
if (_selectedLanguage == null)
|
||||
return false;
|
||||
|
||||
// 获取配置文件中已保存的语言 | Get saved language from config
|
||||
var savedLanguage = _config.GetSavedLanguage() ?? _localizationService.CurrentLanguage;
|
||||
return _selectedLanguage.Language != savedLanguage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用语言切换 | Apply language change
|
||||
/// </summary>
|
||||
private void OnApply()
|
||||
{
|
||||
if (_selectedLanguage == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var newLanguage = _selectedLanguage.Language;
|
||||
_localizationService.SetLanguage(newLanguage);
|
||||
|
||||
_logger.Info($"Language switched to {newLanguage}");
|
||||
|
||||
// 显示重启提示对话框 | Show restart prompt dialog
|
||||
MessageBox.Show(
|
||||
_localizationService.GetString("Settings_Language_SavedRestartRequired"),
|
||||
_localizationService.GetString("Dialog_Notice"),
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Information);
|
||||
|
||||
// 更新命令状态 | Update command state
|
||||
ApplyCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, $"Failed to switch language to {_selectedLanguage.Language}");
|
||||
|
||||
// 显示错误对话框 | Show error dialog
|
||||
var errorMsg = string.Format(
|
||||
_localizationService.GetString("Settings_Language_SwitchFailed"),
|
||||
ex.Message);
|
||||
MessageBox.Show(
|
||||
errorMsg,
|
||||
_localizationService.GetString("Dialog_Error"),
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Error);
|
||||
|
||||
// 恢复到之前的语言 | Restore previous language
|
||||
var currentLang = _localizationService.CurrentLanguage;
|
||||
foreach (var lang in AvailableLanguages)
|
||||
{
|
||||
if (lang.Language == currentLang)
|
||||
{
|
||||
_selectedLanguage = lang;
|
||||
RaisePropertyChanged(nameof(SelectedLanguage));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取语言对应的 CultureInfo | Get CultureInfo for language
|
||||
/// </summary>
|
||||
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")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<Window x:Class="XP.Common.Localization.Views.LanguageSwitcherWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
|
||||
xmlns:loc="clr-namespace:XP.Common.Localization.Extensions"
|
||||
xmlns:vm="clr-namespace:XP.Common.Localization.ViewModels"
|
||||
mc:Ignorable="d"
|
||||
d:DataContext="{d:DesignInstance Type=vm:LanguageSwitcherViewModel}"
|
||||
Title="{loc:Localization Key=Settings_Language}"
|
||||
Height="315" Width="500"
|
||||
MinHeight="300" MinWidth="450"
|
||||
ResizeMode="CanResize"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Background="White">
|
||||
|
||||
<!-- 主容器,带边距 | Main container with margins -->
|
||||
<Grid Margin="24,16,24,16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 标题 | Title -->
|
||||
<TextBlock Grid.Row="0"
|
||||
Text="{loc:Localization Key=Settings_Language}"
|
||||
FontSize="18"
|
||||
FontWeight="SemiBold"
|
||||
Margin="0,0,0,8"/>
|
||||
|
||||
<!-- 副标题/描述 | Subtitle/Description -->
|
||||
<TextBlock Grid.Row="1"
|
||||
Text="{loc:Localization Key=Settings_Language_Description}"
|
||||
FontSize="12"
|
||||
Foreground="#757575"
|
||||
Margin="0,0,0,20"/>
|
||||
|
||||
<!-- 直接显示下拉选择框(无卡片)| Direct ComboBox display (no card) -->
|
||||
<telerik:RadComboBox Grid.Row="2"
|
||||
ItemsSource="{Binding AvailableLanguages}"
|
||||
SelectedItem="{Binding SelectedLanguage, Mode=TwoWay}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Height="36"
|
||||
Margin="0,0,0,16"
|
||||
telerik:StyleManager.Theme="Crystal">
|
||||
<telerik:RadComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal" Margin="4,2">
|
||||
<!-- 语言标志 | Language flag -->
|
||||
<TextBlock Text="{Binding Flag}"
|
||||
Margin="0,0,8,0"
|
||||
FontSize="16"
|
||||
VerticalAlignment="Center"/>
|
||||
<!-- 语言名称 | Language name -->
|
||||
<TextBlock Text="{Binding DisplayName}"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="13"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</telerik:RadComboBox.ItemTemplate>
|
||||
</telerik:RadComboBox>
|
||||
|
||||
<!-- 提示信息区域 | Notice information area -->
|
||||
<Border Grid.Row="3"
|
||||
Background="#E3F2FD"
|
||||
BorderBrush="#2196F3"
|
||||
BorderThickness="1"
|
||||
CornerRadius="4"
|
||||
Padding="12"
|
||||
Margin="0,0,0,16">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 信息图标 | Information icon -->
|
||||
<Path Grid.Column="0"
|
||||
Data="M12,2C6.48,2 2,6.48 2,12C2,17.52 6.48,22 12,22C17.52,22 22,17.52 22,12C22,6.48 17.52,2 12,2M13,17H11V11H13V17M13,9H11V7H13V9Z"
|
||||
Fill="#2196F3"
|
||||
Width="20"
|
||||
Height="20"
|
||||
Stretch="Uniform"
|
||||
VerticalAlignment="Top"
|
||||
Margin="0,2,12,0"/>
|
||||
|
||||
<!-- 提示文本 | Notice text -->
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="{Binding PreviewRestartNotice}"
|
||||
FontSize="12"
|
||||
FontWeight="Medium"
|
||||
Foreground="#1976D2"
|
||||
TextWrapping="Wrap"
|
||||
VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- 占位空间 | Spacer -->
|
||||
<Grid Grid.Row="4"/>
|
||||
|
||||
<!-- 底部操作区 | Bottom action area -->
|
||||
<Grid Grid.Row="5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 应用按钮(绿色)| Apply button (green) -->
|
||||
<telerik:RadButton Grid.Column="1"
|
||||
Content="{Binding PreviewApplyButtonText}"
|
||||
Command="{Binding ApplyCommand}"
|
||||
Width="100"
|
||||
Height="36"
|
||||
telerik:StyleManager.Theme="Crystal">
|
||||
<telerik:RadButton.Background>
|
||||
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
|
||||
<GradientStop Color="White"/>
|
||||
<GradientStop Color="#FF77E062" Offset="1"/>
|
||||
</LinearGradientBrush>
|
||||
</telerik:RadButton.Background>
|
||||
</telerik:RadButton>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.Windows;
|
||||
using XP.Common.Localization.ViewModels;
|
||||
|
||||
namespace XP.Common.Localization.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// LanguageSwitcherWindow.xaml 的交互逻辑 | Interaction logic for LanguageSwitcherWindow.xaml
|
||||
/// </summary>
|
||||
public partial class LanguageSwitcherWindow : Window
|
||||
{
|
||||
public LanguageSwitcherWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数(带 ViewModel)| Constructor (with ViewModel)
|
||||
/// </summary>
|
||||
/// <param name="viewModel">语言切换器 ViewModel | Language switcher ViewModel</param>
|
||||
public LanguageSwitcherWindow(LanguageSwitcherViewModel viewModel) : this()
|
||||
{
|
||||
DataContext = viewModel;
|
||||
|
||||
if (Application.Current?.MainWindow != null)
|
||||
Icon = Application.Current.MainWindow.Icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user