feat(ui): 添加更新UI语言文件功能

- 在 ITranslationService 接口中添加 Reload 方法
- 在 JsonTranslationService 中实现 Reload 方法,支持重新加载语言文件并发送变更通知
- 在通用设置页面添加“更新”按钮,点击后从远程仓库下载最新语言文件
- 实现 OnUpdateUiLanguageAsync 命令,支持从 GitHub 和镜像源下载语言文件
- 下载后自动替换本地文件并重新加载翻译服务
This commit is contained in:
辉鸭蛋
2026-02-13 20:54:57 +08:00
parent f245fb9ea6
commit aaea43984d
5 changed files with 817 additions and 743 deletions

View File

@@ -35,5 +35,6 @@ public interface ITranslationService
string Translate(string text);
string Translate(string text, TranslationSourceInfo sourceInfo);
CultureInfo GetCurrentCulture();
void Reload();
}

View File

@@ -58,6 +58,39 @@ public sealed class JsonTranslationService : ITranslationService, IDisposable
}
}
public void Reload()
{
var cultureName = _otherConfig.UiCultureInfoName ?? string.Empty;
string previousLoaded;
string currentLoaded;
lock (_sync)
{
previousLoaded = _loadedCultureName;
FlushMissingIfDirty(previousLoaded);
if (string.IsNullOrWhiteSpace(cultureName) || IsChineseCultureName(cultureName))
{
_loadedCultureName = string.Empty;
_map = new Dictionary<string, string>(StringComparer.Ordinal);
_existingMissingKeys = new HashSet<string>(StringComparer.Ordinal);
}
else
{
_loadedCultureName = cultureName;
_map = LoadMap(cultureName);
_existingMissingKeys = LoadExistingMissingKeys(cultureName);
}
_missingKeys.Clear();
Interlocked.Exchange(ref _dirtyMissing, 0);
currentLoaded = _loadedCultureName;
}
WeakReferenceMessenger.Default.Send(
new PropertyChangedMessage<object>(this, nameof(OtherConfig.UiCultureInfoName), previousLoaded, currentLoaded));
}
public string Translate(string text)
{
return Translate(text, TranslationSourceInfo.From(MissingTextSource.Unknown));

File diff suppressed because it is too large Load Diff

View File

@@ -32,6 +32,7 @@
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ui:TextBlock Grid.Row="0"
Grid.Column="0"
@@ -43,9 +44,15 @@
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
Text="UI Language"
TextWrapping="Wrap" />
<ui:Button Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Margin="0,0,12,0"
Command="{Binding UpdateUiLanguageCommand}"
Content="更新" />
<ComboBox Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
Grid.Column="2"
Width="200"
Margin="0,0,36,0"
DisplayMemberPath="Value"

View File

@@ -7,6 +7,9 @@ using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using Windows.System;
@@ -20,6 +23,7 @@ using BetterGenshinImpact.GameTask.AutoTrackPath;
using BetterGenshinImpact.GameTask.Common.Element.Assets;
using BetterGenshinImpact.GameTask.LogParse;
using BetterGenshinImpact.Helpers;
using BetterGenshinImpact.Helpers.Http;
using BetterGenshinImpact.Model;
using BetterGenshinImpact.Service.Interface;
using BetterGenshinImpact.Service.Notification;
@@ -34,6 +38,7 @@ using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Win32;
using Newtonsoft.Json;
using Wpf.Ui;
namespace BetterGenshinImpact.ViewModel.Pages;
@@ -89,6 +94,94 @@ public partial class CommonSettingsPageViewModel : ViewModel
}
);
[RelayCommand]
private async Task OnUpdateUiLanguageAsync()
{
var cultureName = Config.OtherConfig.UiCultureInfoName ?? string.Empty;
if (string.IsNullOrWhiteSpace(cultureName))
{
throw new InvalidOperationException("当前UI语言为空无法更新语言文件。");
}
if (cultureName == "zh-Hans")
{
await ThemedMessageBox.InformationAsync("zh-Hans 无语言文件,无需更新。");
return;
}
var urls = new[]
{
$"https://raw.githubusercontent.com/babalae/bettergi-i18n/refs/heads/main/i18n/{cultureName}.json",
$"https://cnb.cool/bettergi/bettergi-i18n/-/git/raw/main/i18n/{cultureName}.json"
};
using var httpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(30)
};
byte[]? bytes = null;
Exception? lastError = null;
var allNotFound = true;
foreach (var url in urls)
{
try
{
using var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.UserAgent.ParseAdd("BetterGenshinImpact");
using var response = await httpClient.SendAsync(request);
if (response.StatusCode == HttpStatusCode.NotFound)
{
lastError = new HttpRequestException("Language file not found.", null, response.StatusCode);
continue;
}
allNotFound = false;
response.EnsureSuccessStatusCode();
bytes = await response.Content.ReadAsByteArrayAsync();
var json = Encoding.UTF8.GetString(bytes);
_ = JsonConvert.DeserializeObject<Dictionary<string, string>>(json)
?? throw new JsonException("翻译文件不是有效的 JSON 字典。");
break;
}
catch (Exception e)
{
lastError = e;
allNotFound = false;
}
}
if (bytes == null)
{
if (allNotFound)
{
await ThemedMessageBox.WarningAsync($"语言文件不存在:{cultureName}.json");
return;
}
throw new Exception($"下载语言文件失败:{cultureName}.json", lastError);
}
var dir = Global.Absolute(@"User\I18n");
Directory.CreateDirectory(dir);
var path = Path.Combine(dir, $"{cultureName}.json");
var tmp = $"{path}.{Guid.NewGuid():N}.tmp";
await File.WriteAllBytesAsync(tmp, bytes);
if (File.Exists(path))
{
File.Replace(tmp, path, null);
}
else
{
File.Move(tmp, path);
}
var translator = App.GetService<ITranslationService>() ?? throw new NullReferenceException();
translator.Reload();
}
public string SelectedCountry
{
get => _selectedCountry;