feat: Introduce localization features.

This commit is contained in:
real-zony
2026-01-09 23:37:20 +08:00
parent a39302dfb7
commit 00e2118645
9 changed files with 200 additions and 33 deletions

View File

@@ -48,7 +48,7 @@ namespace ZonyLrcTools.Cli
#region > <
private static void ConfigureErrorMessage() => ErrorCodeHelper.LoadErrorMessage();
private static void ConfigureErrorMessage() => ErrorCodeHelperStatic.LoadErrorMessage();
private static void ConfigureLogger()
{
@@ -90,6 +90,7 @@ namespace ZonyLrcTools.Cli
services.BeginAutoDependencyInject<Program>();
services.BeginAutoDependencyInject<IWarpHttpClient>();
services.ConfigureConfiguration();
services.ConfigureLocalization();
services.ConfigureToolService();
services.AddHostedService<UpdaterHostedService>();
})
@@ -102,7 +103,7 @@ namespace ZonyLrcTools.Cli
{
case ErrorCodeException exception:
Log.Logger.Error(
$"出现了未处理的异常。\n错误代码: {exception.ErrorCode}\n错误信息: {ErrorCodeHelper.GetMessage(exception.ErrorCode)}\n原始信息:{exception.Message}\n调用栈:{exception.StackTrace}");
$"出现了未处理的异常。\n错误代码: {exception.ErrorCode}\n错误信息: {ErrorCodeHelperStatic.GetMessage(exception.ErrorCode)}\n原始信息:{exception.Message}\n调用栈:{exception.StackTrace}");
Environment.Exit(exception.ErrorCode);
return exception.ErrorCode;
case { } unknownException:

View File

@@ -9,6 +9,7 @@
<PackageReference Include="McMaster.Extensions.CommandLineUtils"/>
<PackageReference Include="McMaster.Extensions.Hosting.CommandLine"/>
<PackageReference Include="Microsoft.Extensions.Hosting"/>
<PackageReference Include="Microsoft.Extensions.Localization"/>
<PackageReference Include="Serilog.Extensions.Hosting"/>
<PackageReference Include="Serilog.Sinks.Async"/>
<PackageReference Include="Serilog.Sinks.Console"/>

View File

@@ -1,3 +1,4 @@
using System.Globalization;
using System.Net;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@@ -51,5 +52,23 @@ namespace ZonyLrcTools.Common.Infrastructure.DependencyInject
return services;
}
/// <summary>
/// 配置本地化服务。
/// </summary>
/// <param name="services">服务集合。</param>
/// <param name="defaultCulture">默认语言,默认为 zh-CN。</param>
public static IServiceCollection ConfigureLocalization(this IServiceCollection services, string defaultCulture = "zh-CN")
{
// Note: Don't set ResourcesPath because the embedded resource names are based on
// the marker class namespace (e.g., ZonyLrcTools.Common.Messages), not folder path
services.AddLocalization();
var culture = new CultureInfo(defaultCulture);
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
return services;
}
}
}

View File

@@ -1,41 +1,132 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using ZonyLrcTools.Common.Infrastructure.DependencyInject;
using ZonyLrcTools.Common.Infrastructure.Localization;
namespace ZonyLrcTools.Common.Infrastructure.Exceptions
namespace ZonyLrcTools.Common.Infrastructure.Exceptions;
/// <summary>
/// 错误码相关的帮助类,支持国际化。
/// </summary>
public class ErrorCodeHelper : IErrorCodeHelper, ISingletonDependency
{
/// <summary>
/// 错误码相关的帮助类。
/// </summary>
public static class ErrorCodeHelper
{
public static Dictionary<int, string> ErrorMessages { get; }
private readonly ILocalizationService _localizationService;
private readonly Dictionary<int, string> _fallbackMessages;
static ErrorCodeHelper()
public ErrorCodeHelper(ILocalizationService localizationService)
{
_localizationService = localizationService;
_fallbackMessages = new Dictionary<int, string>();
LoadFallbackMessages();
}
public string GetMessage(int errorCode)
{
var localizedMessage = _localizationService.GetErrorMessage(errorCode);
// 如果本地化消息不是默认的 "Unknown error" 格式,则返回本地化消息
if (!localizedMessage.StartsWith("Unknown error:"))
{
ErrorMessages = new Dictionary<int, string>();
return localizedMessage;
}
/// <summary>
/// 从 err_msg.json 文件加载错误信息。
/// </summary>
public static void LoadErrorMessage()
// 回退到 JSON 文件的消息
return _fallbackMessages.TryGetValue(errorCode, out var message)
? message
: $"未知错误: {errorCode}";
}
public string GetWarningMessage(int warningCode)
{
var localizedMessage = _localizationService.GetWarningMessage(warningCode);
// 如果本地化消息不是默认的 "Unknown warning" 格式,则返回本地化消息
if (!localizedMessage.StartsWith("Unknown warning:"))
{
// 防止重复加载。
if (ErrorMessages.Count != 0)
return localizedMessage;
}
// 回退到 JSON 文件的消息
return _fallbackMessages.TryGetValue(warningCode, out var message)
? message
: $"未知警告: {warningCode}";
}
/// <summary>
/// 从 error_msg.json 加载回退消息(用于兼容旧系统)。
/// </summary>
private void LoadFallbackMessages()
{
try
{
var jsonPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "error_msg.json");
if (!File.Exists(jsonPath))
{
return;
}
var jsonPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "error_msg.json");
using var jsonReader = new JsonTextReader(File.OpenText(jsonPath));
var jsonObj = JObject.Load(jsonReader);
var errors = jsonObj.SelectTokens("$.Error.*");
var warnings = jsonObj.SelectTokens("$.Warning.*");
errors.Union(warnings).Select(m => m.Parent).OfType<JProperty>().ToList()
.ForEach(m => ErrorMessages.Add(int.Parse(m.Name), m.Value.Value<string>() ?? string.Empty));
.ForEach(m => _fallbackMessages[int.Parse(m.Name)] = m.Value.Value<string>() ?? string.Empty);
}
catch
{
// 忽略加载失败,使用本地化消息
}
}
}
/// <summary>
/// 静态错误码帮助类(用于不支持 DI 的场景)。
/// </summary>
public static class ErrorCodeHelperStatic
{
private static readonly Dictionary<int, string> ErrorMessages = new();
private static bool _isLoaded;
/// <summary>
/// 从 error_msg.json 文件加载错误信息。
/// </summary>
public static void LoadErrorMessage()
{
if (_isLoaded)
{
return;
}
public static string GetMessage(int errorCode) => ErrorMessages[errorCode];
try
{
var jsonPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "error_msg.json");
if (!File.Exists(jsonPath))
{
_isLoaded = true;
return;
}
using var jsonReader = new JsonTextReader(File.OpenText(jsonPath));
var jsonObj = JObject.Load(jsonReader);
var errors = jsonObj.SelectTokens("$.Error.*");
var warnings = jsonObj.SelectTokens("$.Warning.*");
errors.Union(warnings).Select(m => m.Parent).OfType<JProperty>().ToList()
.ForEach(m => ErrorMessages[int.Parse(m.Name)] = m.Value.Value<string>() ?? string.Empty);
_isLoaded = true;
}
catch
{
_isLoaded = true;
}
}
}
public static string GetMessage(int errorCode)
{
return ErrorMessages.TryGetValue(errorCode, out var message)
? message
: $"未知错误: {errorCode}";
}
}

View File

@@ -35,7 +35,7 @@ namespace ZonyLrcTools.Common.Infrastructure.Extensions
}
var sb = new StringBuilder();
sb.Append($"错误代码: {exception.ErrorCode},信息: {ErrorCodeHelper.GetMessage(exception.ErrorCode)}");
sb.Append($"错误代码: {exception.ErrorCode},信息: {ErrorCodeHelperStatic.GetMessage(exception.ErrorCode)}");
sb.Append($"\n附加信息:\n {JsonConvert.SerializeObject(exception.AttachObject)}");
logger.WarnAsync(sb.ToString()).GetAwaiter().GetResult();
}

View File

@@ -8,6 +8,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http"/>
<PackageReference Include="Microsoft.Extensions.Localization"/>
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions"/>
<PackageReference Include="MusicDecrypto.Library"/>
<PackageReference Include="Newtonsoft.Json"/>