紧急修正bug (#1917)

Co-authored-by: DR-lin-eng <@DR-lin-eng>
Co-authored-by: yuzai <3020834774@qq.com>
This commit is contained in:
DR-lin-eng
2025-07-24 00:12:06 +08:00
committed by GitHub
parent 976a1591c7
commit 99f024ba96
11 changed files with 257 additions and 136 deletions

View File

@@ -57,5 +57,5 @@ public partial class CommonConfig : ObservableObject
/// 应用程序语言设置
/// </summary>
[ObservableProperty]
private string _language = "en-US";
private string _language = "zh-CN";
}

View File

@@ -1,6 +1,6 @@
{
"metadata": {
"code": "ja-JP",
"code": "jp-JP",
"displayName": "日本語",
"nativeName": "日本語",
"version": "1.0.0"

View File

@@ -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": "购买日期和商品",

View File

@@ -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)

View File

@@ -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<LanguageManager> 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<LanguageFileData>(jsonContent);
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
AllowTrailingCommas = true,
ReadCommentHandling = JsonCommentHandling.Skip
};
var languageData = JsonSerializer.Deserialize<LanguageFileData>(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
/// </summary>
/// <param name="fileName">The file name to validate</param>
/// <returns>True if the file name follows the convention, false otherwise</returns>
/// <returns>True if the file name is a JSON file, false otherwise</returns>
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);
}
/// <summary>
@@ -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;
}

View File

@@ -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<LanguageInfo>
{
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<string, string>
{
["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<LanguageInfo>
{
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<LanguageInfo>();
_fallbackTranslations = new Dictionary<string, string>();
_currentTranslations = new Dictionary<string, string>();
@@ -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<string, string>(_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<string, string>(_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<string, string>(_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<string, string>(_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<string, string>();
_fallbackTranslations = new Dictionary<string, string>();
}
@@ -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";
}
/// <summary>
@@ -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<string, string>(_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";
}
}

View File

@@ -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<ILocalizationService>();
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);
}

View File

@@ -94,11 +94,6 @@
ItemsSource="{Binding LocalizationViewModel.AvailableLanguages}"
SelectedItem="{Binding LocalizationViewModel.SelectedLanguage, Mode=TwoWay}"
IsEnabled="{Binding LocalizationViewModel.IsLoading, Converter={StaticResource InverseBooleanConverter}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding NativeName}" />
</DataTemplate>
</ComboBox.ItemTemplate>
<b:Interaction.Triggers>
<b:EventTrigger EventName="SelectionChanged">
<b:InvokeCommandAction Command="{Binding LocalizationViewModel.ChangeLanguageCommand}"

View File

@@ -216,8 +216,7 @@
<ui:TextBlock Grid.Row="1"
Margin="0,0,0,8"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
TextWrapping="Wrap">
{markup:Localize Key=scheduler.rightClickToAddDragToReorder}
Text="{markup:Localize Key=scheduler.rightClickToAddDragToReorder}">
</ui:TextBlock>

View File

@@ -958,9 +958,8 @@
<ui:TextBlock Grid.Row="1"
Grid.Column="0"
Foreground="{ui:ThemeResource TextFillColorTertiaryBrush}"
TextWrapping="Wrap">
{markup:Localize Key=scriptGroup.shellExecutionConfigDescription}
</ui:TextBlock>
Text="{markup:Localize Key=scriptGroup.shellExecutionConfigDescription}"
TextWrapping="Wrap" />
<ui:ToggleSwitch Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using BetterGenshinImpact.Model;
using BetterGenshinImpact.Service.Interface;
using CommunityToolkit.Mvvm.ComponentModel;
@@ -13,7 +14,7 @@ namespace BetterGenshinImpact.ViewModel;
/// <summary>
/// ViewModel for managing language selection and localization
/// </summary>
public partial class LocalizationViewModel : ObservableObject
public partial class LocalizationViewModel : ObservableObject, IDisposable
{
private readonly ILocalizationService _localizationService;
private readonly ILogger<LocalizationViewModel> _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;
}
/// <summary>
@@ -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
}
}
/// <summary>
/// Handles property change events from the localization service
/// </summary>
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");
}
}
}
/// <summary>
/// Gets the display text for a language (native name with fallback to display name)
/// </summary>
@@ -128,4 +218,23 @@ public partial class LocalizationViewModel : ObservableObject
? language.NativeName
: language.DisplayName;
}
/// <summary>
/// Disposes the view model and cleans up event subscriptions
/// </summary>
public void Dispose()
{
try
{
if (_localizationService != null)
{
_localizationService.LanguageChanged -= OnLanguageChanged;
_localizationService.PropertyChanged -= OnLocalizationServicePropertyChanged;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error disposing LocalizationViewModel");
}
}
}