From 99f024ba96ef65aeae966888d2df5f640d1d751c Mon Sep 17 00:00:00 2001 From: DR-lin-eng <52230594+DR-lin-eng@users.noreply.github.com> Date: Thu, 24 Jul 2025 00:12:06 +0800 Subject: [PATCH] =?UTF-8?q?=E7=B4=A7=E6=80=A5=E4=BF=AE=E6=AD=A3bug=20(#191?= =?UTF-8?q?7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: DR-lin-eng <@DR-lin-eng> Co-authored-by: yuzai <3020834774@qq.com> --- .../Core/Config/CommonConfig.cs | 2 +- BetterGenshinImpact/Languages/jp-JP.json | 2 +- BetterGenshinImpact/Languages/zh-CN.json | 2 - BetterGenshinImpact/Model/LanguageInfo.cs | 3 +- .../Service/LanguageManager.cs | 76 +++++---- .../Service/LocalizationService.cs | 152 +++++++++--------- BetterGenshinImpact/Service/ScriptService.cs | 30 ++-- .../View/Pages/CommonSettingsPage.xaml | 5 - .../View/Pages/ScriptControlPage.xaml | 3 +- .../Pages/View/ScriptGroupConfigView.xaml | 5 +- .../ViewModel/LocalizationViewModel.cs | 113 ++++++++++++- 11 files changed, 257 insertions(+), 136 deletions(-) diff --git a/BetterGenshinImpact/Core/Config/CommonConfig.cs b/BetterGenshinImpact/Core/Config/CommonConfig.cs index 86283a3a..5de01566 100644 --- a/BetterGenshinImpact/Core/Config/CommonConfig.cs +++ b/BetterGenshinImpact/Core/Config/CommonConfig.cs @@ -57,5 +57,5 @@ public partial class CommonConfig : ObservableObject /// 应用程序语言设置 /// [ObservableProperty] - private string _language = "en-US"; + private string _language = "zh-CN"; } diff --git a/BetterGenshinImpact/Languages/jp-JP.json b/BetterGenshinImpact/Languages/jp-JP.json index 56aba717..81637660 100644 --- a/BetterGenshinImpact/Languages/jp-JP.json +++ b/BetterGenshinImpact/Languages/jp-JP.json @@ -1,6 +1,6 @@ { "metadata": { - "code": "ja-JP", + "code": "jp-JP", "displayName": "日本語", "nativeName": "日本語", "version": "1.0.0" diff --git a/BetterGenshinImpact/Languages/zh-CN.json b/BetterGenshinImpact/Languages/zh-CN.json index b191f56d..ab96ed4d 100644 --- a/BetterGenshinImpact/Languages/zh-CN.json +++ b/BetterGenshinImpact/Languages/zh-CN.json @@ -12,7 +12,6 @@ "trigger.fastPickModeDescription": "打开时,将不再识别具体拾取内容,黑、白名单会失效", "trigger.autoSkip.selectOptionMethod": "选择选项的方式", "trigger.autoSkip.selectOptionMethodDescription": "后台模式下会自动切换到使用交互键", - "trigger.autoHangout.selectBranch": "选择邀约分支(不支持气泡联想选择)", "trigger.autoHangout.selectBranchDescription": "会按照选择分支的关键词进行选项选择", "trigger.autoHangout.optionDelay": "选择邀约选项前的延迟(毫秒)", @@ -1478,7 +1477,6 @@ "task.autoRedeemCode.description": "自动使用输入的兑换码", "task.autoStygianOnslaught.title": "自动幽境危战", "task.autoStygianOnslaught.description": "在接触钥匙后的界面启动本任务 -", - "oneDragon.serenitea": "尘歌壶配置", "oneDragon.enterPotMethod": "进壶方式选择", "oneDragon.purchaseDateAndItems": "购买日期和商品", diff --git a/BetterGenshinImpact/Model/LanguageInfo.cs b/BetterGenshinImpact/Model/LanguageInfo.cs index aaac693b..ad5bba59 100644 --- a/BetterGenshinImpact/Model/LanguageInfo.cs +++ b/BetterGenshinImpact/Model/LanguageInfo.cs @@ -34,7 +34,8 @@ public class LanguageInfo public override string ToString() { - return $"{NativeName} ({DisplayName})"; + // Return DisplayName for ComboBox display, fallback to Code if DisplayName is empty + return !string.IsNullOrEmpty(DisplayName) ? DisplayName : Code; } public override bool Equals(object? obj) diff --git a/BetterGenshinImpact/Service/LanguageManager.cs b/BetterGenshinImpact/Service/LanguageManager.cs index 51f753a1..cf8ce5db 100644 --- a/BetterGenshinImpact/Service/LanguageManager.cs +++ b/BetterGenshinImpact/Service/LanguageManager.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; -using System.Text.RegularExpressions; using System.Threading.Tasks; using BetterGenshinImpact.Model; using BetterGenshinImpact.Service.Interface; @@ -22,10 +21,7 @@ public class LanguageManager : ILanguageManager, IDisposable private readonly object _lockObject = new object(); private bool _disposed = false; - // Language file naming convention regex: language-region.json (e.g., en-US.json, zh-CN.json) - private static readonly Regex LanguageFilePattern = new Regex( - @"^[a-z]{2}(-[A-Z]{2})?\.json$", - RegexOptions.Compiled | RegexOptions.IgnoreCase); + // All JSON files in the Languages directory are considered language files public LanguageManager(ILogger logger) { @@ -54,35 +50,43 @@ public class LanguageManager : ILanguageManager, IDisposable } var languageFiles = Directory.GetFiles(_languagesDirectory, "*.json"); - _logger.LogInformation("Found {Count} language files", languageFiles.Length); + _logger.LogInformation("Found {Count} language files in {Directory}", languageFiles.Length, _languagesDirectory); foreach (var filePath in languageFiles) { + var fileName = Path.GetFileName(filePath); + _logger.LogDebug("Processing language file: {FileName}", fileName); + try { var languageInfo = await LoadLanguageInfoAsync(filePath); if (languageInfo != null) { languages.Add(languageInfo); - _logger.LogDebug("Loaded language: {Code} - {Name}", languageInfo.Code, languageInfo.DisplayName); + _logger.LogInformation("Successfully loaded language: {Code} - {DisplayName} from {FileName}", + languageInfo.Code, languageInfo.DisplayName, fileName); + } + else + { + _logger.LogWarning("Failed to load language info from {FileName} - returned null", fileName); } } catch (Exception ex) { - _logger.LogError(ex, "Failed to load language file: {FilePath}", filePath); + _logger.LogError(ex, "Exception while loading language file: {FilePath}", filePath); } } - // Ensure we have at least English as fallback - if (!languages.Any(l => l.Code.Equals("en-US", StringComparison.OrdinalIgnoreCase))) + // Ensure we have at least Chinese as fallback + if (!languages.Any(l => l.Code.Equals("zh-CN", StringComparison.OrdinalIgnoreCase))) { - _logger.LogWarning("No English language file found, creating default"); + _logger.LogWarning("No Chinese language file found, creating default"); languages.Add(new LanguageInfo { - Code = "en-US", - DisplayName = "English", - NativeName = "English", - FilePath = Path.Combine(_languagesDirectory, "en-US.json"), + Code = "zh-CN", + DisplayName = "简体中文", + NativeName = "简体中文", + FilePath = Path.Combine(_languagesDirectory, "zh-CN.json"), Version = "1.0.0" }); } @@ -423,10 +427,19 @@ public class LanguageManager : ILanguageManager, IDisposable } var jsonContent = await File.ReadAllTextAsync(filePath); - var languageData = JsonSerializer.Deserialize(jsonContent); + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + AllowTrailingCommas = true, + ReadCommentHandling = JsonCommentHandling.Skip + }; + var languageData = JsonSerializer.Deserialize(jsonContent, options); if (languageData?.Metadata != null) { + _logger.LogDebug("Loaded metadata for {FileName}: Code={Code}, DisplayName={DisplayName}, NativeName={NativeName}", + fileName, languageData.Metadata.Code, languageData.Metadata.DisplayName, languageData.Metadata.NativeName); + // Validate metadata if (!ValidateLanguageMetadata(languageData.Metadata, fileName)) { @@ -434,7 +447,7 @@ public class LanguageManager : ILanguageManager, IDisposable return null; } - return new LanguageInfo + var languageInfo = new LanguageInfo { Code = languageData.Metadata.Code ?? Path.GetFileNameWithoutExtension(filePath), DisplayName = languageData.Metadata.DisplayName ?? languageData.Metadata.Code ?? "Unknown", @@ -442,11 +455,19 @@ public class LanguageManager : ILanguageManager, IDisposable FilePath = filePath, Version = languageData.Metadata.Version ?? "1.0.0" }; + + _logger.LogDebug("Created LanguageInfo: Code={Code}, DisplayName={DisplayName}, NativeName={NativeName}", + languageInfo.Code, languageInfo.DisplayName, languageInfo.NativeName); + + return languageInfo; } else { // Fallback for files without metadata var fileNameWithoutExt = Path.GetFileNameWithoutExtension(filePath); + _logger.LogWarning("No metadata found in {FileName}, using fallback with DisplayName={DisplayName}", + fileName, fileNameWithoutExt); + return new LanguageInfo { Code = fileNameWithoutExt, @@ -566,7 +587,7 @@ public class LanguageManager : ILanguageManager, IDisposable /// Validates language file naming convention /// /// The file name to validate - /// True if the file name follows the convention, false otherwise + /// True if the file name is a JSON file, false otherwise private bool ValidateLanguageFileName(string fileName) { if (string.IsNullOrEmpty(fileName)) @@ -574,7 +595,8 @@ public class LanguageManager : ILanguageManager, IDisposable return false; } - return LanguageFilePattern.IsMatch(fileName); + // Accept all JSON files in the Languages directory as potential language files + return fileName.EndsWith(".json", StringComparison.OrdinalIgnoreCase); } /// @@ -593,22 +615,20 @@ public class LanguageManager : ILanguageManager, IDisposable // Check if code is provided if (string.IsNullOrEmpty(metadata.Code)) { + _logger.LogWarning("Language metadata missing code in file: {FileName}", fileName); return false; } - // Check if code matches file name (without extension) - var expectedCode = Path.GetFileNameWithoutExtension(fileName); - if (!metadata.Code.Equals(expectedCode, StringComparison.OrdinalIgnoreCase)) + // Check if displayName is provided + if (string.IsNullOrEmpty(metadata.DisplayName)) { - _logger.LogWarning("Language code mismatch: file={FileName}, metadata={Code}", expectedCode, metadata.Code); + _logger.LogWarning("Language metadata missing displayName in file: {FileName}", fileName); return false; } - // Validate language code format - if (!LanguageFilePattern.IsMatch(fileName)) - { - return false; - } + // Log successful validation + _logger.LogDebug("Language metadata validated successfully for {FileName}: Code={Code}, DisplayName={DisplayName}", + fileName, metadata.Code, metadata.DisplayName); return true; } diff --git a/BetterGenshinImpact/Service/LocalizationService.cs b/BetterGenshinImpact/Service/LocalizationService.cs index 7cb7ba25..4b311786 100644 --- a/BetterGenshinImpact/Service/LocalizationService.cs +++ b/BetterGenshinImpact/Service/LocalizationService.cs @@ -98,15 +98,15 @@ public class LocalizationService : ILocalizationService, IDisposable if (_availableLanguages.Count == 0) { _logger.LogWarning("No language files discovered, creating minimal language list"); - // Create minimal language list with at least English + // Create minimal language list with at least Chinese _availableLanguages = new List { new LanguageInfo { - Code = "en-US", - DisplayName = "English", - NativeName = "English", - FilePath = Path.Combine(_languageManager.LanguagesDirectory, "en-US.json"), + Code = "zh-CN", + DisplayName = "简体中文", + NativeName = "简体中文", + FilePath = Path.Combine(_languageManager.LanguagesDirectory, "zh-CN.json"), Version = "1.0.0" } }; @@ -122,9 +122,9 @@ public class LocalizationService : ILocalizationService, IDisposable { new LanguageInfo { - Code = "en-US", - DisplayName = "English", - NativeName = "English", + Code = "zh-CN", + DisplayName = "简体中文", + NativeName = "简体中文", FilePath = "fallback", Version = "1.0.0" } @@ -139,11 +139,11 @@ public class LocalizationService : ILocalizationService, IDisposable { try { - _fallbackTranslations = await _languageManager.LoadLanguageAsync("en-US"); + _fallbackTranslations = await _languageManager.LoadLanguageAsync("zh-CN"); if (_fallbackTranslations.Count == 0) { - _logger.LogWarning("No English translations loaded, creating minimal fallback translations"); + _logger.LogWarning("No Chinese translations loaded, creating minimal fallback translations"); CreateMinimalFallbackTranslations(); } else @@ -153,7 +153,7 @@ public class LocalizationService : ILocalizationService, IDisposable } catch (Exception ex) { - _logger.LogError(ex, "Failed to load English fallback translations, creating minimal set"); + _logger.LogError(ex, "Failed to load Chinese fallback translations, creating minimal set"); CreateMinimalFallbackTranslations(); } } @@ -165,18 +165,18 @@ public class LocalizationService : ILocalizationService, IDisposable { _fallbackTranslations = new Dictionary { - ["common.ok"] = "OK", - ["common.cancel"] = "Cancel", - ["common.save"] = "Save", - ["common.close"] = "Close", - ["common.error"] = "Error", - ["common.warning"] = "Warning", - ["common.information"] = "Information", - ["settings.title"] = "Settings", - ["settings.language"] = "Language", - ["error.translation_load_failed"] = "Failed to load translations", - ["error.language_not_available"] = "Language not available", - ["error.file_corrupted"] = "Language file corrupted" + ["common.ok"] = "确定", + ["common.cancel"] = "取消", + ["common.save"] = "保存", + ["common.close"] = "关闭", + ["common.error"] = "错误", + ["common.warning"] = "警告", + ["common.information"] = "信息", + ["settings.title"] = "设置", + ["settings.language"] = "语言", + ["error.translation_load_failed"] = "加载翻译失败", + ["error.language_not_available"] = "语言不可用", + ["error.file_corrupted"] = "语言文件损坏" }; _logger.LogInformation("Created {Count} minimal fallback translations", _fallbackTranslations.Count); @@ -192,14 +192,14 @@ public class LocalizationService : ILocalizationService, IDisposable _logger.LogWarning("Initializing emergency fallback mode"); // Set minimal state - CurrentLanguage = "en-US"; + CurrentLanguage = "zh-CN"; _availableLanguages = new List { new LanguageInfo { - Code = "en-US", - DisplayName = "English (Emergency)", - NativeName = "English (Emergency)", + Code = "zh-CN", + DisplayName = "简体中文 (紧急)", + NativeName = "简体中文 (紧急)", FilePath = "emergency", Version = "1.0.0" } @@ -214,7 +214,7 @@ public class LocalizationService : ILocalizationService, IDisposable { _logger.LogCritical(ex, "Even emergency fallback initialization failed - localization service may not function properly"); // Set absolute minimal state - CurrentLanguage = "en-US"; + CurrentLanguage = "zh-CN"; _availableLanguages = new List(); _fallbackTranslations = new Dictionary(); _currentTranslations = new Dictionary(); @@ -246,7 +246,7 @@ public class LocalizationService : ILocalizationService, IDisposable } } - // Fallback to English + // Fallback to Chinese if (_fallbackTranslations.TryGetValue(key, out var fallbackTranslation)) { _logger.LogDebug("Using fallback translation for key: {Key} (current language: {Language})", key, CurrentLanguage); @@ -326,8 +326,8 @@ public class LocalizationService : ILocalizationService, IDisposable } else { - _logger.LogWarning("All fallback strategies failed, using English"); - languageCode = "en-US"; + _logger.LogWarning("All fallback strategies failed, using Chinese"); + languageCode = "zh-CN"; } } @@ -343,7 +343,7 @@ public class LocalizationService : ILocalizationService, IDisposable if (_fallbackTranslations.Count > 0) { translations = new Dictionary(_fallbackTranslations); - languageCode = "en-US"; + languageCode = "zh-CN"; _logger.LogWarning("Using fallback translations due to empty translation set"); } else @@ -351,7 +351,7 @@ public class LocalizationService : ILocalizationService, IDisposable // Create absolute minimal translations CreateMinimalFallbackTranslations(); translations = new Dictionary(_fallbackTranslations); - languageCode = "en-US"; + languageCode = "zh-CN"; _logger.LogWarning("Created minimal translations due to complete translation failure"); } } @@ -370,7 +370,7 @@ public class LocalizationService : ILocalizationService, IDisposable { _logger.LogError(ex, "Critical error setting language: {LanguageCode}, attempting recovery", languageCode); - // Attempt to recover by reverting to original language or English + // Attempt to recover by reverting to original language or Chinese await AttemptLanguageRecovery(originalLanguage, ex); } } @@ -459,18 +459,18 @@ public class LocalizationService : ILocalizationService, IDisposable { var translations = await _languageManager.LoadLanguageAsync(languageCode); - if (translations.Count == 0 && !languageCode.Equals("en-US", StringComparison.OrdinalIgnoreCase)) + if (translations.Count == 0 && !languageCode.Equals("zh-CN", StringComparison.OrdinalIgnoreCase)) { - _logger.LogWarning("No translations loaded for {LanguageCode}, trying fallback to English", languageCode); + _logger.LogWarning("No translations loaded for {LanguageCode}, trying fallback to Chinese", languageCode); - // Try to reload English fallback - var fallbackTranslations = await _languageManager.LoadLanguageAsync("en-US"); + // Try to reload Chinese fallback + var fallbackTranslations = await _languageManager.LoadLanguageAsync("zh-CN"); if (fallbackTranslations.Count > 0) { return fallbackTranslations; } - // If even English failed, use our cached fallback + // If even Chinese failed, use our cached fallback if (_fallbackTranslations.Count > 0) { _logger.LogWarning("Using cached fallback translations"); @@ -520,28 +520,28 @@ public class LocalizationService : ILocalizationService, IDisposable } } - // Try to fall back to English + // Try to fall back to Chinese try { - var englishTranslations = await _languageManager.LoadLanguageAsync("en-US"); - if (englishTranslations.Count > 0) + var chineseTranslations = await _languageManager.LoadLanguageAsync("zh-CN"); + if (chineseTranslations.Count > 0) { - _currentTranslations = englishTranslations; - CurrentLanguage = "en-US"; - _logger.LogInformation("Successfully fell back to English"); + _currentTranslations = chineseTranslations; + CurrentLanguage = "zh-CN"; + _logger.LogInformation("Successfully fell back to Chinese"); return; } } catch (Exception ex) { - _logger.LogError(ex, "Failed to fall back to English"); + _logger.LogError(ex, "Failed to fall back to Chinese"); } // Use cached fallback translations if (_fallbackTranslations.Count > 0) { _currentTranslations = new Dictionary(_fallbackTranslations); - CurrentLanguage = "en-US"; + CurrentLanguage = "zh-CN"; _logger.LogWarning("Using cached fallback translations for recovery"); return; } @@ -549,7 +549,7 @@ public class LocalizationService : ILocalizationService, IDisposable // Create minimal emergency translations CreateMinimalFallbackTranslations(); _currentTranslations = new Dictionary(_fallbackTranslations); - CurrentLanguage = "en-US"; + CurrentLanguage = "zh-CN"; _logger.LogWarning("Created minimal emergency translations for recovery"); } catch (Exception recoveryEx) @@ -558,7 +558,7 @@ public class LocalizationService : ILocalizationService, IDisposable originalException.Message); // Set absolute minimal state - CurrentLanguage = "en-US"; + CurrentLanguage = "zh-CN"; _currentTranslations = new Dictionary(); _fallbackTranslations = new Dictionary(); } @@ -621,9 +621,9 @@ public class LocalizationService : ILocalizationService, IDisposable return matchingLanguage.Code; } - // Default to English - _logger.LogInformation("Using default language: en-US"); - return "en-US"; + // Default to Chinese + _logger.LogInformation("Using default language: zh-CN"); + return "zh-CN"; } /// @@ -668,15 +668,15 @@ public class LocalizationService : ILocalizationService, IDisposable _logger.LogInformation("Available languages updated. New count: {Count}", _availableLanguages.Count); - // Validate that we still have at least English available - if (!_availableLanguages.Any(l => l.Code.Equals("en-US", StringComparison.OrdinalIgnoreCase))) + // Validate that we still have at least Chinese available + if (!_availableLanguages.Any(l => l.Code.Equals("zh-CN", StringComparison.OrdinalIgnoreCase))) { - _logger.LogWarning("English language no longer available after file change, adding fallback entry"); + _logger.LogWarning("Chinese language no longer available after file change, adding fallback entry"); _availableLanguages.Add(new LanguageInfo { - Code = "en-US", - DisplayName = "English (Fallback)", - NativeName = "English (Fallback)", + Code = "zh-CN", + DisplayName = "简体中文 (回退)", + NativeName = "简体中文 (回退)", FilePath = "fallback", Version = "1.0.0" }); @@ -695,9 +695,9 @@ public class LocalizationService : ILocalizationService, IDisposable { new LanguageInfo { - Code = "en-US", - DisplayName = "English (Recovery)", - NativeName = "English (Recovery)", + Code = "zh-CN", + DisplayName = "简体中文 (恢复)", + NativeName = "简体中文 (恢复)", FilePath = "recovery", Version = "1.0.0" } @@ -752,19 +752,19 @@ public class LocalizationService : ILocalizationService, IDisposable { _logger.LogWarning("Current language file deleted: {Language}, attempting graceful fallback", CurrentLanguage); - // Try to find an alternative language or fall back to English + // Try to find an alternative language or fall back to Chinese var fallbackLanguage = await FindBestFallbackLanguage(deletedFileName); await SetLanguageAsync(fallbackLanguage); } - else if (deletedFileName.Equals("en-US", StringComparison.OrdinalIgnoreCase)) + else if (deletedFileName.Equals("zh-CN", StringComparison.OrdinalIgnoreCase)) { - _logger.LogWarning("English fallback file deleted, creating emergency fallback translations"); + _logger.LogWarning("Chinese fallback file deleted, creating emergency fallback translations"); // Recreate minimal fallback translations CreateMinimalFallbackTranslations(); - // If current language is English, update current translations too - if (CurrentLanguage.Equals("en-US", StringComparison.OrdinalIgnoreCase)) + // If current language is Chinese, update current translations too + if (CurrentLanguage.Equals("zh-CN", StringComparison.OrdinalIgnoreCase)) { _currentTranslations = new Dictionary(_fallbackTranslations); LanguageChanged?.Invoke(this, new LanguageChangedEventArgs(CurrentLanguage, CurrentLanguage)); @@ -800,13 +800,13 @@ public class LocalizationService : ILocalizationService, IDisposable _logger.LogWarning("Modified language file {Language} is empty or corrupted, keeping existing translations", CurrentLanguage); } } - else if (changedFileName.Equals("en-US", StringComparison.OrdinalIgnoreCase)) + else if (changedFileName.Equals("zh-CN", StringComparison.OrdinalIgnoreCase)) { - _logger.LogInformation("English fallback file modified, reloading fallback translations"); + _logger.LogInformation("Chinese fallback file modified, reloading fallback translations"); try { - var newFallbackTranslations = await _languageManager.LoadLanguageAsync("en-US"); + var newFallbackTranslations = await _languageManager.LoadLanguageAsync("zh-CN"); if (newFallbackTranslations.Count > 0) { _fallbackTranslations = newFallbackTranslations; @@ -814,12 +814,12 @@ public class LocalizationService : ILocalizationService, IDisposable } else { - _logger.LogWarning("Modified English file is empty, keeping existing fallback translations"); + _logger.LogWarning("Modified Chinese file is empty, keeping existing fallback translations"); } } catch (Exception ex) { - _logger.LogError(ex, "Error reloading English fallback file, keeping existing translations"); + _logger.LogError(ex, "Error reloading Chinese fallback file, keeping existing translations"); } } } @@ -884,14 +884,14 @@ public class LocalizationService : ILocalizationService, IDisposable return systemLanguage; } - // Default to English - _logger.LogInformation("Using English as final fallback"); - return "en-US"; + // Default to Chinese + _logger.LogInformation("Using Chinese as final fallback"); + return "zh-CN"; } catch (Exception ex) { - _logger.LogError(ex, "Error finding fallback language, defaulting to English"); - return "en-US"; + _logger.LogError(ex, "Error finding fallback language, defaulting to Chinese"); + return "zh-CN"; } } diff --git a/BetterGenshinImpact/Service/ScriptService.cs b/BetterGenshinImpact/Service/ScriptService.cs index 6c443052..d31a9705 100644 --- a/BetterGenshinImpact/Service/ScriptService.cs +++ b/BetterGenshinImpact/Service/ScriptService.cs @@ -118,7 +118,7 @@ public partial class ScriptService : IScriptService if (!string.IsNullOrEmpty(groupName)) { - var message = _localizationService?.GetString("script.groupLoadedAndStarting", groupName, list.Count) ?? $"脚本组 {groupName} 加载完成,共{list.Count}个脚本,开始执行"; + var message = _localizationService?.GetString("script.groupLoadedAndStarting", groupName, list.Count) ?? $"Script group {groupName} loaded successfully, {list.Count} scripts total, starting execution"; _logger.LogInformation(message); } @@ -152,14 +152,14 @@ public partial class ScriptService : IScriptService await _blessingOfTheWelkinMoonTask.Start(CancellationContext.Instance.Cts.Token); if (project.Status != "Enabled") { - var message = _localizationService?.GetString("script.scriptDisabledSkipping", project.Name) ?? $"脚本 {project.Name} 状态为禁用,跳过执行"; + var message = _localizationService?.GetString("script.scriptDisabledSkipping", project.Name) ?? $"Script {project.Name} is disabled, skipping execution"; _logger.LogInformation(message); continue; } if (CancellationContext.Instance.Cts.IsCancellationRequested) { - var message = _localizationService?.GetString("script.executionCancelled") ?? "执行被取消"; + var message = _localizationService?.GetString("script.executionCancelled") ?? "Execution cancelled"; _logger.LogInformation(message); break; } @@ -206,13 +206,13 @@ public partial class ScriptService : IScriptService } catch (TaskCanceledException e) { - var message = _localizationService?.GetString("script.cancellingTask", e.Message) ?? $"取消执行任务: {e.Message}"; + var message = _localizationService?.GetString("script.cancellingTask", e.Message) ?? $"Cancelling task execution: {e.Message}"; _logger.LogInformation(message); throw; } catch (Exception e) { - var errorMessage = _localizationService?.GetString("script.scriptExecutionError") ?? "执行脚本时发生异常"; + var errorMessage = _localizationService?.GetString("script.scriptExecutionError") ?? "An exception occurred while executing script"; _logger.LogDebug(e, errorMessage); _logger.LogError($"{errorMessage}: {{Msg}}", e.Message); if (taskProgress!=null && taskProgress.CurrentScriptGroupProjectInfo!=null ) @@ -225,7 +225,7 @@ public partial class ScriptService : IScriptService stopwatch.Stop(); var elapsedTime = TimeSpan.FromMilliseconds(stopwatch.ElapsedMilliseconds); var message = _localizationService?.GetString("script.scriptExecutionCompleted", project.Name, elapsedTime.Hours * 60 + elapsedTime.Minutes, elapsedTime.TotalSeconds % 60) ?? - $"√ 脚本执行结束: {project.Name}, 耗时: {elapsedTime.Hours * 60 + elapsedTime.Minutes}分{elapsedTime.TotalSeconds % 60:0.000}秒"; + $"√ Script execution completed: {project.Name}, elapsed time: {elapsedTime.Hours * 60 + elapsedTime.Minutes}min {elapsedTime.TotalSeconds % 60:0.000}sec"; _logger.LogInformation(message); _logger.LogInformation("------------------------------"); } @@ -260,7 +260,7 @@ public partial class ScriptService : IScriptService var autoconfig = TaskContext.Instance().Config.OtherConfig.AutoRestartConfig; if (autoconfig.Enabled && taskProgress.ConsecutiveFailureCount >= autoconfig.FailureCount) { - var message = _localizationService?.GetString("script.consecutiveUnexpectedErrors") ?? "连续多次出现未预期的异常,自动重启bgi"; + var message = _localizationService?.GetString("script.consecutiveUnexpectedErrors") ?? "Multiple consecutive unexpected errors occurred, automatically restarting BGI"; _logger.LogInformation(message); Notify.Event(NotificationEvent.GroupEnd).Error("notification.error.unexpectedError"); if (autoconfig.RestartGameTogether @@ -282,7 +282,7 @@ public partial class ScriptService : IScriptService if (!string.IsNullOrEmpty(groupName)) { - var message = _localizationService?.GetString("script.groupExecutionCompleted", groupName) ?? $"脚本组 {groupName} 执行结束"; + var message = _localizationService?.GetString("script.groupExecutionCompleted", groupName) ?? $"Script group {groupName} execution completed"; _logger.LogInformation(message); } @@ -349,29 +349,29 @@ public partial class ScriptService : IScriptService { if (project.Project == null) { - var errorMessage = _localizationService?.GetString("script.projectIsNull") ?? "Project 为空"; + var errorMessage = _localizationService?.GetString("script.projectIsNull") ?? "Project is null"; throw new Exception(errorMessage); } - var message = _localizationService?.GetString("script.startingJsScript", project.Name) ?? $"√ 开始执行JS脚本: {project.Name}"; + var message = _localizationService?.GetString("script.startingJsScript", project.Name) ?? $"√ Starting JS script: {project.Name}"; _logger.LogInformation(message); await project.Run(); } else if (project.Type == "KeyMouse") { - var message = _localizationService?.GetString("script.startingKeyMouseScript", project.Name) ?? $"√ 开始执行键鼠脚本: {project.Name}"; + var message = _localizationService?.GetString("script.startingKeyMouseScript", project.Name) ?? $"√ Starting key-mouse script: {project.Name}"; _logger.LogInformation(message); await project.Run(); } else if (project.Type == "Pathing") { - var message = _localizationService?.GetString("script.startingPathingTask", project.Name) ?? $"√ 开始执行地图追踪任务: {project.Name}"; + var message = _localizationService?.GetString("script.startingPathingTask", project.Name) ?? $"√ Starting pathing task: {project.Name}"; _logger.LogInformation(message); await project.Run(); } else if (project.Type == "Shell") { - var message = _localizationService?.GetString("script.startingShellScript", project.Name) ?? $"√ 开始执行shell: {project.Name}"; + var message = _localizationService?.GetString("script.startingShellScript", project.Name) ?? $"√ Starting shell script: {project.Name}"; _logger.LogInformation(message); await project.Run(); } @@ -430,8 +430,8 @@ public partial class ScriptService : IScriptService { first = false; var localizationService = App.GetService(); - var message1 = localizationService?.GetString("script.notInMainUiWaiting") ?? "当前不在游戏主界面,等待进入主界面后执行任务..."; - var message2 = localizationService?.GetString("script.mainUiInstructions") ?? "如果你已经在游戏内的主界面,请按下退出当前界面(ESC)或者等待30秒后将尝试自动点击空白区域,使前台任务能够正常执行!"; + var message1 = localizationService?.GetString("script.notInMainUiWaiting") ?? "Not currently in the game main UI, waiting to enter main UI before executing tasks..."; + var message2 = localizationService?.GetString("script.mainUiInstructions") ?? "If you are already in the game's main UI, please press ESC to exit the current interface or wait 30 seconds to attempt to automatically click an empty area, allowing foreground tasks to execute normally!"; TaskControl.Logger.LogInformation(message1); TaskControl.Logger.LogInformation(message2); } diff --git a/BetterGenshinImpact/View/Pages/CommonSettingsPage.xaml b/BetterGenshinImpact/View/Pages/CommonSettingsPage.xaml index 49b4fdb4..33d2efab 100644 --- a/BetterGenshinImpact/View/Pages/CommonSettingsPage.xaml +++ b/BetterGenshinImpact/View/Pages/CommonSettingsPage.xaml @@ -94,11 +94,6 @@ ItemsSource="{Binding LocalizationViewModel.AvailableLanguages}" SelectedItem="{Binding LocalizationViewModel.SelectedLanguage, Mode=TwoWay}" IsEnabled="{Binding LocalizationViewModel.IsLoading, Converter={StaticResource InverseBooleanConverter}}"> - - - - - - {markup:Localize Key=scheduler.rightClickToAddDragToReorder} + Text="{markup:Localize Key=scheduler.rightClickToAddDragToReorder}"> diff --git a/BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml b/BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml index 17dea316..0668dc42 100644 --- a/BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml +++ b/BetterGenshinImpact/View/Pages/View/ScriptGroupConfigView.xaml @@ -958,9 +958,8 @@ - {markup:Localize Key=scriptGroup.shellExecutionConfigDescription} - + Text="{markup:Localize Key=scriptGroup.shellExecutionConfigDescription}" + TextWrapping="Wrap" /> /// ViewModel for managing language selection and localization /// -public partial class LocalizationViewModel : ObservableObject +public partial class LocalizationViewModel : ObservableObject, IDisposable { private readonly ILocalizationService _localizationService; private readonly ILogger _logger; @@ -34,6 +35,9 @@ public partial class LocalizationViewModel : ObservableObject // Subscribe to language change events _localizationService.LanguageChanged += OnLanguageChanged; + + // Subscribe to property changes to detect when available languages change + _localizationService.PropertyChanged += OnLocalizationServicePropertyChanged; } /// @@ -52,6 +56,8 @@ public partial class LocalizationViewModel : ObservableObject foreach (var language in languages) { AvailableLanguages.Add(language); + _logger.LogDebug("Added language: Code={Code}, DisplayName={DisplayName}, NativeName={NativeName}", + language.Code, language.DisplayName, language.NativeName); } // Set the currently selected language @@ -59,7 +65,8 @@ public partial class LocalizationViewModel : ObservableObject SelectedLanguage = AvailableLanguages.FirstOrDefault(l => l.Code.Equals(currentLanguageCode, StringComparison.OrdinalIgnoreCase)); - _logger.LogInformation("LocalizationViewModel initialized with {Count} languages", AvailableLanguages.Count); + _logger.LogInformation("LocalizationViewModel initialized with {Count} languages. Selected: {SelectedLanguage}", + AvailableLanguages.Count, SelectedLanguage?.DisplayName ?? "None"); } catch (Exception ex) { @@ -117,6 +124,89 @@ public partial class LocalizationViewModel : ObservableObject } } + /// + /// Handles property change events from the localization service + /// + private async void OnLocalizationServicePropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(ILocalizationService.AvailableLanguages)) + { + try + { + _logger.LogInformation("Available languages changed, refreshing language list"); + + // Refresh the available languages list + var languages = _localizationService.AvailableLanguages.ToList(); + + // Update the collection on the UI thread + if (Application.Current?.Dispatcher != null) + { + await Application.Current.Dispatcher.InvokeAsync(() => + { + var currentSelection = SelectedLanguage; + + AvailableLanguages.Clear(); + foreach (var language in languages) + { + AvailableLanguages.Add(language); + } + + // Try to maintain the current selection + if (currentSelection != null) + { + var newSelection = AvailableLanguages.FirstOrDefault(l => + l.Code.Equals(currentSelection.Code, StringComparison.OrdinalIgnoreCase)); + SelectedLanguage = newSelection; + } + + // If no selection or selection is invalid, select current language from service + if (SelectedLanguage == null) + { + var currentLanguageCode = _localizationService.CurrentLanguage; + SelectedLanguage = AvailableLanguages.FirstOrDefault(l => + l.Code.Equals(currentLanguageCode, StringComparison.OrdinalIgnoreCase)); + } + + _logger.LogInformation("Language list refreshed with {Count} languages", AvailableLanguages.Count); + }); + } + else + { + // Fallback for non-UI thread scenarios + var currentSelection = SelectedLanguage; + + AvailableLanguages.Clear(); + foreach (var language in languages) + { + AvailableLanguages.Add(language); + } + + // Try to maintain the current selection + if (currentSelection != null) + { + var newSelection = AvailableLanguages.FirstOrDefault(l => + l.Code.Equals(currentSelection.Code, StringComparison.OrdinalIgnoreCase)); + SelectedLanguage = newSelection; + } + + // If no selection or selection is invalid, select current language from service + if (SelectedLanguage == null) + { + var currentLanguageCode = _localizationService.CurrentLanguage; + SelectedLanguage = AvailableLanguages.FirstOrDefault(l => + l.Code.Equals(currentLanguageCode, StringComparison.OrdinalIgnoreCase)); + } + + _logger.LogInformation("Language list refreshed with {Count} languages", AvailableLanguages.Count); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to refresh available languages"); + } + } + } + /// /// Gets the display text for a language (native name with fallback to display name) /// @@ -128,4 +218,23 @@ public partial class LocalizationViewModel : ObservableObject ? language.NativeName : language.DisplayName; } + + /// + /// Disposes the view model and cleans up event subscriptions + /// + public void Dispose() + { + try + { + if (_localizationService != null) + { + _localizationService.LanguageChanged -= OnLanguageChanged; + _localizationService.PropertyChanged -= OnLocalizationServicePropertyChanged; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Error disposing LocalizationViewModel"); + } + } } \ No newline at end of file