Skip to content

Commit 9f67726

Browse files
authored
I18n v2 (#2709)
* feat(i18n): 添加界面与日志的国际化支持 - 新增 ITranslationService 接口及 JsonTranslationService 实现,提供基于 JSON 的翻译服务 - 添加 TrConverter 转换器,支持通过绑定动态翻译界面文本 - 引入 AutoTranslateInterceptor 行为,自动扫描并翻译界面中的静态文本 - 集成 TranslatingSerilogLoggerProvider,实现日志输出的实时翻译 - 在 App.xaml 中注册全局样式,为 Window、UserControl 和 Page 启用自动翻译 * refactor(AutoTranslateInterceptor): 优化自动翻译拦截器的加载与应用机制 - 移除 HomePage 中冗余的 EnableAutoTranslate 属性设置,改为继承属性 - 通过类构造函数注册全局 Loaded 事件处理器,替代在每个元素上单独添加 - 引入请求队列机制,批量处理待应用翻译的元素,避免重复调度 - 扩展属性类型检查,支持 object 类型以处理更多动态内容场景 * fix: 移除全局自动翻译拦截器以避免冲突 移除在 App.xaml 中为 Window、UserControl 和 Page 全局设置的 AutoTranslateInterceptor, 改为仅在 PickerWindow 中显式启用。这解决了全局样式可能导致的意外行为或冲突。 * feat(ui): 为多个窗口启用自动翻译拦截器 为 MapLabelSearchWindow、ArtifactOcrDialog、PromptDialog 等 14 个窗口添加了 AutoTranslateInterceptor.EnableAutoTranslate 属性,以启用自动翻译拦截功能。 * feat(i18n): 添加国际化目录支持并优化异常处理 * feat(ui): 添加软件UI语言设置并改进翻译服务 - 在通用设置页面新增UI语言选择控件,支持动态切换界面语言 - 修改游戏语言标签为“原神游戏语言”以明确区分 - 改进JsonTranslationService,支持UI语言切换时的实时翻译更新 - 优化AutoTranslateInterceptor,缓存原始文本值并在语言切换时恢复 - 添加属性变更监听机制,确保UI元素在语言切换后正确刷新 * feat(自动翻译): 添加排除自动翻译的依赖属性 在 AutoTranslateInterceptor 中新增 ExcludeAutoTranslate 附加属性,允许对特定依赖对象禁用自动翻译功能。当遍历元素进行翻译时,会检查此属性并跳过已标记排除的元素。 * feat(translation): 为缺失文本翻译添加详细上下文信息 扩展翻译服务以收集缺失文本的详细上下文,包括视图路径、元素类型、属性名等。 重构 `ITranslationService` 接口,引入 `TranslationSourceInfo` 类封装上下文信息。 修改 `AutoTranslateInterceptor` 自动收集 UI 元素信息,`JsonTranslationService` 合并多来源上下文。 * Revert "feat(自动翻译): 添加排除自动翻译的依赖属性" This reverts commit a1c2334. * fix: 跳过 GridViewRowPresenter 中的文本翻译 添加 IsInGridViewRowPresenter 检查,避免在 GridViewRowPresenter 控件内进行自动翻译,防止潜在的界面显示问题。 * fix: 修复自动翻译拦截器在组合框上下文中的误触发 在自动翻译拦截器中添加了 IsInComboBoxContext 方法,用于检测依赖对象是否处于 ComboBox 或其相关弹出菜单的上下文中。当检测到对象位于组合框上下文时,跳过自动翻译逻辑,避免对下拉选项等界面元素进行不必要的翻译操作,从而解决潜在的界面干扰问题。 * feat(translation): 添加缺失翻译上报至 Supabase 的功能 - 新增 IMissingTranslationReporter 接口及 SupabaseMissingTranslationReporter 实现 - 在 JsonTranslationService 中集成缺失翻译上报逻辑 - 添加缺失翻译收集的配置设置(MissingTranslationCollectionSettings) - 优化缺失翻译文件的序列化格式,将 Source 字段改为紧凑的数字表示 - 移除 ScriptRepoUpdater 中未使用的 using 语句 - 在 App.xaml.cs 中注册 SupabaseMissingTranslationReporter 服务 * fix: 修复自动翻译功能中原始值恢复和重复报告问题 - 移除未使用的法语翻译支持以简化语言选项 - 修复 Supabase 报告序列化时移除冗余字段 - 添加已缺失翻译键的缓存以避免重复报告 - 重构自动翻译拦截器,将原始值存储移至依赖属性 - 修复原始值恢复逻辑,确保正确遍历所有子元素 * feat(ui): 添加更新UI语言文件功能 - 在 ITranslationService 接口中添加 Reload 方法 - 在 JsonTranslationService 中实现 Reload 方法,支持重新加载语言文件并发送变更通知 - 在通用设置页面添加“更新”按钮,点击后从远程仓库下载最新语言文件 - 实现 OnUpdateUiLanguageAsync 命令,支持从 GitHub 和镜像源下载语言文件 - 下载后自动替换本地文件并重新加载翻译服务
1 parent a2bd48a commit 9f67726

33 files changed

+3111
-23
lines changed

BetterGenshinImpact/App.xaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
<bgivc:EnumToKVPConverter x:Key="EnumToKVPConverter" />
2626
<bgivc:CultureInfoNameToKVPConverter x:Key="CultureInfoNameToKVPConverter" />
2727
<bgivc:StringToColorConverter x:Key="StringToColorConverter" />
28+
<bgivc:TrConverter x:Key="TrConverter" />
2829
<Style BasedOn="{StaticResource DefaultTextBoxStyle}" TargetType="{x:Type TextBox}">
2930
<Setter Property="behavior:ClipboardInterceptor.EnableSafeClipboard" Value="True" />
3031
</Style>

BetterGenshinImpact/App.xaml.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,13 @@ public partial class App : Application
8686
}
8787

8888
Log.Logger = loggerConfiguration.CreateLogger();
89-
services.AddLogging(c => c.AddSerilog());
89+
services.AddSingleton<IMissingTranslationReporter, SupabaseMissingTranslationReporter>();
90+
services.AddSingleton<ITranslationService, JsonTranslationService>();
91+
services.AddLogging(logging =>
92+
{
93+
logging.ClearProviders();
94+
logging.Services.AddSingleton<ILoggerProvider, TranslatingSerilogLoggerProvider>();
95+
});
9096

9197
services.AddLocalization();
9298

BetterGenshinImpact/BetterGenshinImpact.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
8585
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
8686
<PackageReference Include="Serilog.Sinks.RichTextBoxEx.Wpf" Version="1.1.0.1" />
87+
<!-- <PackageReference Include="supabase-csharp" Version="0.16.2" />-->
8788
<PackageReference Include="System.Drawing.Common" Version="10.0.0" />
8889
<PackageReference Include="System.IO.Hashing" Version="9.0.4" />
8990
<PackageReference Include="TorchSharp" Version="0.105.0" />
@@ -200,6 +201,9 @@
200201
<None Update="GameTask\AutoDomain\Assets\1920x1080\**">
201202
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
202203
</None>
204+
<None Update="User\I18n\en.json">
205+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
206+
</None>
203207
</ItemGroup>
204208

205209
<ItemGroup>

BetterGenshinImpact/Core/Config/OtherConfig.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using BetterGenshinImpact.Core.Recognition;
33
using BetterGenshinImpact.Model;
44
using CommunityToolkit.Mvvm.ComponentModel;
@@ -121,4 +121,4 @@ public partial class Ocr : ObservableObject
121121
/// </summary>
122122
[ObservableProperty]
123123
private string _uiCultureInfoName = "zh-Hans";
124-
}
124+
}

BetterGenshinImpact/Core/Script/ScriptRepoUpdater.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
using System.Net.Http;
2222
using System.Threading.Tasks;
2323
using System.Windows;
24-
using Windows.UI.Xaml.Automation;
2524
using BetterGenshinImpact.View.Windows;
2625
using LibGit2Sharp;
2726
using LibGit2Sharp.Handlers;
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using BetterGenshinImpact.Service.Interface;
5+
using Microsoft.Extensions.Logging;
6+
using Serilog;
7+
using Serilog.Events;
8+
9+
namespace BetterGenshinImpact.Helpers;
10+
11+
public sealed class TranslatingSerilogLoggerProvider : ILoggerProvider
12+
{
13+
private readonly ITranslationService _translationService;
14+
15+
public TranslatingSerilogLoggerProvider(ITranslationService translationService)
16+
{
17+
_translationService = translationService;
18+
}
19+
20+
public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName)
21+
{
22+
return new TranslatingSerilogLogger(categoryName, _translationService);
23+
}
24+
25+
public void Dispose()
26+
{
27+
}
28+
29+
private sealed class TranslatingSerilogLogger : Microsoft.Extensions.Logging.ILogger
30+
{
31+
private readonly ITranslationService _translationService;
32+
private readonly Serilog.ILogger _logger;
33+
34+
public TranslatingSerilogLogger(string categoryName, ITranslationService translationService)
35+
{
36+
_translationService = translationService;
37+
_logger = Serilog.Log.Logger.ForContext("SourceContext", categoryName);
38+
}
39+
40+
public IDisposable BeginScope<TState>(TState state) where TState : notnull
41+
{
42+
return NullScope.Instance;
43+
}
44+
45+
public bool IsEnabled(LogLevel logLevel)
46+
{
47+
return logLevel != LogLevel.None;
48+
}
49+
50+
public void Log<TState>(
51+
LogLevel logLevel,
52+
EventId eventId,
53+
TState state,
54+
Exception? exception,
55+
Func<TState, Exception?, string> formatter)
56+
{
57+
if (!IsEnabled(logLevel))
58+
{
59+
return;
60+
}
61+
62+
var serilogLevel = ConvertLevel(logLevel);
63+
if (serilogLevel == null)
64+
{
65+
return;
66+
}
67+
68+
var (template, values) = ExtractTemplateAndValues(state, formatter, exception);
69+
var translatedTemplate = _translationService.Translate(template, TranslationSourceInfo.From(MissingTextSource.Log));
70+
71+
if (values.Length == 0)
72+
{
73+
_logger.Write(serilogLevel.Value, exception, translatedTemplate);
74+
return;
75+
}
76+
77+
_logger.Write(serilogLevel.Value, exception, translatedTemplate, values);
78+
}
79+
80+
private (string Template, object?[] Values) ExtractTemplateAndValues<TState>(
81+
TState state,
82+
Func<TState, Exception?, string> formatter,
83+
Exception? exception)
84+
{
85+
if (state is IReadOnlyList<KeyValuePair<string, object?>> kvps)
86+
{
87+
var original = kvps.FirstOrDefault(kv => string.Equals(kv.Key, "{OriginalFormat}", StringComparison.Ordinal));
88+
var template = original.Value as string;
89+
if (string.IsNullOrEmpty(template))
90+
{
91+
template = formatter(state, exception);
92+
}
93+
94+
var values = kvps
95+
.Where(kv =>
96+
!string.Equals(kv.Key, "{OriginalFormat}", StringComparison.Ordinal) &&
97+
!string.Equals(kv.Key, "EventId", StringComparison.Ordinal))
98+
.Select(kv => kv.Value)
99+
.ToArray();
100+
101+
return (template ?? string.Empty, values);
102+
}
103+
104+
return (formatter(state, exception), Array.Empty<object?>());
105+
}
106+
107+
private static LogEventLevel? ConvertLevel(LogLevel level)
108+
{
109+
return level switch
110+
{
111+
LogLevel.Trace => LogEventLevel.Verbose,
112+
LogLevel.Debug => LogEventLevel.Debug,
113+
LogLevel.Information => LogEventLevel.Information,
114+
LogLevel.Warning => LogEventLevel.Warning,
115+
LogLevel.Error => LogEventLevel.Error,
116+
LogLevel.Critical => LogEventLevel.Fatal,
117+
_ => null
118+
};
119+
}
120+
121+
private sealed class NullScope : IDisposable
122+
{
123+
public static NullScope Instance { get; } = new();
124+
125+
public void Dispose()
126+
{
127+
}
128+
}
129+
}
130+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace BetterGenshinImpact.Service.Interface;
2+
3+
public interface IMissingTranslationReporter
4+
{
5+
bool TryEnqueue(string language, string key, TranslationSourceInfo sourceInfo);
6+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System.Globalization;
2+
3+
namespace BetterGenshinImpact.Service.Interface;
4+
5+
public enum MissingTextSource
6+
{
7+
Log,
8+
UiStaticLiteral,
9+
UiDynamicBinding,
10+
Unknown
11+
}
12+
13+
public sealed class TranslationSourceInfo
14+
{
15+
public MissingTextSource Source { get; set; } = MissingTextSource.Unknown;
16+
public string? ViewXamlPath { get; set; }
17+
public string? ViewType { get; set; }
18+
public string? ElementType { get; set; }
19+
public string? ElementName { get; set; }
20+
public string? PropertyName { get; set; }
21+
public string? BindingPath { get; set; }
22+
public string? Notes { get; set; }
23+
24+
public static TranslationSourceInfo From(MissingTextSource source)
25+
{
26+
return new TranslationSourceInfo
27+
{
28+
Source = source
29+
};
30+
}
31+
}
32+
33+
public interface ITranslationService
34+
{
35+
string Translate(string text);
36+
string Translate(string text, TranslationSourceInfo sourceInfo);
37+
CultureInfo GetCurrentCulture();
38+
void Reload();
39+
}
40+

0 commit comments

Comments
 (0)