mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-05-09 00:34:14 +08:00
紧急修正bug (#1917)
Co-authored-by: DR-lin-eng <@DR-lin-eng> Co-authored-by: yuzai <3020834774@qq.com>
This commit is contained in:
@@ -57,5 +57,5 @@ public partial class CommonConfig : ObservableObject
|
||||
/// 应用程序语言设置
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
private string _language = "en-US";
|
||||
private string _language = "zh-CN";
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"metadata": {
|
||||
"code": "ja-JP",
|
||||
"code": "jp-JP",
|
||||
"displayName": "日本語",
|
||||
"nativeName": "日本語",
|
||||
"version": "1.0.0"
|
||||
|
||||
@@ -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": "购买日期和商品",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user