Files
better-genshin-impact/BetterGenshinImpact/Service/LocalizationService.cs
DR-lin-eng 99f024ba96 紧急修正bug (#1917)
Co-authored-by: DR-lin-eng <@DR-lin-eng>
Co-authored-by: yuzai <3020834774@qq.com>
2025-07-24 00:12:06 +08:00

1014 lines
39 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using BetterGenshinImpact.Model;
using BetterGenshinImpact.Service.Interface;
using Microsoft.Extensions.Logging;
namespace BetterGenshinImpact.Service;
/// <summary>
/// Central service for managing application localization
/// </summary>
public class LocalizationService : ILocalizationService, IDisposable
{
private readonly ILanguageManager _languageManager;
private readonly IConfigService _configService;
private readonly ILogger<LocalizationService> _logger;
private string _currentLanguage = "zh-CN";
private List<LanguageInfo> _availableLanguages = new();
private Dictionary<string, string> _currentTranslations = new();
private Dictionary<string, string> _fallbackTranslations = new();
public LocalizationService(
ILanguageManager languageManager,
IConfigService configService,
ILogger<LocalizationService> logger)
{
_languageManager = languageManager;
_configService = configService;
_logger = logger;
}
public string CurrentLanguage
{
get => _currentLanguage;
private set
{
if (_currentLanguage != value)
{
var previousLanguage = _currentLanguage;
_currentLanguage = value;
OnPropertyChanged(nameof(CurrentLanguage));
LanguageChanged?.Invoke(this, new LanguageChangedEventArgs(previousLanguage, value));
}
}
}
public IEnumerable<LanguageInfo> AvailableLanguages => _availableLanguages;
public event PropertyChangedEventHandler? PropertyChanged;
public event EventHandler<LanguageChangedEventArgs>? LanguageChanged;
public async Task InitializeAsync()
{
try
{
_logger.LogInformation("Initializing localization service");
// Subscribe to language file changes for dynamic updates
_languageManager.LanguageFilesChanged += OnLanguageFilesChanged;
// Discover available languages with error recovery
await InitializeLanguageDiscovery();
// Load fallback translations with error recovery
await InitializeFallbackTranslations();
// Get saved language preference or use system default
var savedLanguage = GetSavedLanguage();
var targetLanguage = DetermineInitialLanguage(savedLanguage);
await SetLanguageAsync(targetLanguage);
_logger.LogInformation("Localization service initialized with language: {Language}", CurrentLanguage);
}
catch (Exception ex)
{
_logger.LogCritical(ex, "Critical failure during localization service initialization");
await InitializeEmergencyFallback();
}
}
/// <summary>
/// Initializes language discovery with error recovery
/// </summary>
private async Task InitializeLanguageDiscovery()
{
try
{
var languages = await _languageManager.DiscoverLanguagesAsync();
_availableLanguages = languages.ToList();
if (_availableLanguages.Count == 0)
{
_logger.LogWarning("No language files discovered, creating minimal language list");
// Create minimal language list with at least Chinese
_availableLanguages = new List<LanguageInfo>
{
new LanguageInfo
{
Code = "zh-CN",
DisplayName = "简体中文",
NativeName = "简体中文",
FilePath = Path.Combine(_languageManager.LanguagesDirectory, "zh-CN.json"),
Version = "1.0.0"
}
};
}
_logger.LogInformation("Found {Count} available languages", _availableLanguages.Count);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to discover languages, using minimal fallback");
// Create absolute minimal fallback
_availableLanguages = new List<LanguageInfo>
{
new LanguageInfo
{
Code = "zh-CN",
DisplayName = "简体中文",
NativeName = "简体中文",
FilePath = "fallback",
Version = "1.0.0"
}
};
}
}
/// <summary>
/// Initializes fallback translations with error recovery
/// </summary>
private async Task InitializeFallbackTranslations()
{
try
{
_fallbackTranslations = await _languageManager.LoadLanguageAsync("zh-CN");
if (_fallbackTranslations.Count == 0)
{
_logger.LogWarning("No Chinese translations loaded, creating minimal fallback translations");
CreateMinimalFallbackTranslations();
}
else
{
_logger.LogInformation("Loaded {Count} fallback translations", _fallbackTranslations.Count);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to load Chinese fallback translations, creating minimal set");
CreateMinimalFallbackTranslations();
}
}
/// <summary>
/// Creates minimal fallback translations for critical UI elements
/// </summary>
private void CreateMinimalFallbackTranslations()
{
_fallbackTranslations = new Dictionary<string, string>
{
["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);
}
/// <summary>
/// Emergency fallback initialization when all else fails
/// </summary>
private async Task InitializeEmergencyFallback()
{
try
{
_logger.LogWarning("Initializing emergency fallback mode");
// Set minimal state
CurrentLanguage = "zh-CN";
_availableLanguages = new List<LanguageInfo>
{
new LanguageInfo
{
Code = "zh-CN",
DisplayName = "简体中文 (紧急)",
NativeName = "简体中文 (紧急)",
FilePath = "emergency",
Version = "1.0.0"
}
};
CreateMinimalFallbackTranslations();
_currentTranslations = new Dictionary<string, string>(_fallbackTranslations);
_logger.LogWarning("Localization service initialized in emergency fallback mode");
}
catch (Exception ex)
{
_logger.LogCritical(ex, "Even emergency fallback initialization failed - localization service may not function properly");
// Set absolute minimal state
CurrentLanguage = "zh-CN";
_availableLanguages = new List<LanguageInfo>();
_fallbackTranslations = new Dictionary<string, string>();
_currentTranslations = new Dictionary<string, string>();
}
}
public string GetString(string key, params object[] args)
{
if (string.IsNullOrEmpty(key))
{
_logger.LogWarning("Attempted to get translation for empty or null key");
return string.Empty;
}
try
{
// Try current language first
if (_currentTranslations.TryGetValue(key, out var translation))
{
try
{
return args != null && args.Length > 0 ? string.Format(translation, args) : translation;
}
catch (FormatException ex)
{
_logger.LogError(ex, "Format error in translation for key: {Key}, translation: {Translation}", key, translation);
// Try fallback without formatting
return translation;
}
}
// Fallback to Chinese
if (_fallbackTranslations.TryGetValue(key, out var fallbackTranslation))
{
_logger.LogDebug("Using fallback translation for key: {Key} (current language: {Language})", key, CurrentLanguage);
try
{
return args != null && args.Length > 0 ? string.Format(fallbackTranslation, args) : fallbackTranslation;
}
catch (FormatException ex)
{
_logger.LogError(ex, "Format error in fallback translation for key: {Key}, translation: {Translation}", key, fallbackTranslation);
// Return unformatted fallback
return fallbackTranslation;
}
}
// Log missing translation for monitoring and translation team
LogMissingTranslation(key);
// Return key with indicator if no translation found
return $"[KEY_NOT_FOUND: {key}]";
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error getting translation for key: {Key}", key);
return $"[ERROR: {key}]";
}
}
/// <summary>
/// Logs missing translation keys for monitoring and translation team awareness
/// </summary>
private void LogMissingTranslation(string key)
{
// Log as warning for immediate attention
_logger.LogWarning("Missing translation key: {Key} for language: {Language}", key, CurrentLanguage);
// Also log structured data for potential automated collection
using var scope = _logger.BeginScope(new Dictionary<string, object>
{
["TranslationKey"] = key,
["Language"] = CurrentLanguage,
["EventType"] = "MissingTranslation",
["Timestamp"] = DateTime.UtcNow
});
_logger.LogInformation("Translation gap detected - Key: {Key}, Language: {Language}", key, CurrentLanguage);
}
public async Task SetLanguageAsync(string languageCode)
{
if (string.IsNullOrEmpty(languageCode))
{
_logger.LogWarning("Attempted to set empty language code");
return;
}
var originalLanguage = CurrentLanguage;
try
{
_logger.LogInformation("Attempting to set language to: {LanguageCode}", languageCode);
// Check if language is available
var targetLanguage = _availableLanguages.FirstOrDefault(l =>
l.Code.Equals(languageCode, StringComparison.OrdinalIgnoreCase));
if (targetLanguage == null)
{
_logger.LogWarning("Language not available: {LanguageCode}, attempting fallback strategies", languageCode);
// Try fallback strategies
var fallbackLanguage = await AttemptLanguageFallback(languageCode);
if (fallbackLanguage != null)
{
languageCode = fallbackLanguage;
_logger.LogInformation("Using fallback language: {LanguageCode}", languageCode);
}
else
{
_logger.LogWarning("All fallback strategies failed, using Chinese");
languageCode = "zh-CN";
}
}
// Load translations for the target language with error recovery
var translations = await LoadLanguageWithErrorRecovery(languageCode);
// Validate that we have some translations
if (translations.Count == 0)
{
_logger.LogError("No translations available for {LanguageCode}, attempting emergency recovery", languageCode);
// Emergency fallback - try to use fallback translations or create minimal set
if (_fallbackTranslations.Count > 0)
{
translations = new Dictionary<string, string>(_fallbackTranslations);
languageCode = "zh-CN";
_logger.LogWarning("Using fallback translations due to empty translation set");
}
else
{
// Create absolute minimal translations
CreateMinimalFallbackTranslations();
translations = new Dictionary<string, string>(_fallbackTranslations);
languageCode = "zh-CN";
_logger.LogWarning("Created minimal translations due to complete translation failure");
}
}
// Apply the language change
_currentTranslations = translations;
CurrentLanguage = languageCode;
// Save language preference with error handling
SaveLanguagePreference(languageCode);
_logger.LogInformation("Language successfully changed to: {Language} ({Count} translations loaded)",
languageCode, translations.Count);
}
catch (Exception ex)
{
_logger.LogError(ex, "Critical error setting language: {LanguageCode}, attempting recovery", languageCode);
// Attempt to recover by reverting to original language or Chinese
await AttemptLanguageRecovery(originalLanguage, ex);
}
}
/// <summary>
/// Attempts various fallback strategies when a requested language is not available
/// </summary>
private async Task<string?> AttemptLanguageFallback(string requestedLanguageCode)
{
try
{
// Strategy 1: Try language without region (e.g., "en" instead of "en-US")
var languageOnly = requestedLanguageCode.Split('-')[0];
var matchingLanguage = _availableLanguages.FirstOrDefault(l =>
l.Code.StartsWith(languageOnly, StringComparison.OrdinalIgnoreCase));
if (matchingLanguage != null)
{
_logger.LogInformation("Found language match without region: {RequestedCode} -> {FoundCode}",
requestedLanguageCode, matchingLanguage.Code);
return matchingLanguage.Code;
}
// Strategy 2: Try common language variants
var commonVariants = GetCommonLanguageVariants(requestedLanguageCode);
foreach (var variant in commonVariants)
{
var variantLanguage = _availableLanguages.FirstOrDefault(l =>
l.Code.Equals(variant, StringComparison.OrdinalIgnoreCase));
if (variantLanguage != null)
{
_logger.LogInformation("Found language variant: {RequestedCode} -> {FoundCode}",
requestedLanguageCode, variantLanguage.Code);
return variantLanguage.Code;
}
}
// Strategy 3: Re-scan for languages in case files were added recently
_logger.LogInformation("Re-scanning for languages in case new files were added");
var freshLanguages = await _languageManager.DiscoverLanguagesAsync();
var freshLanguagesList = freshLanguages.ToList();
var foundInFresh = freshLanguagesList.FirstOrDefault(l =>
l.Code.Equals(requestedLanguageCode, StringComparison.OrdinalIgnoreCase));
if (foundInFresh != null)
{
_logger.LogInformation("Found language in fresh scan: {LanguageCode}", requestedLanguageCode);
_availableLanguages = freshLanguagesList; // Update our cache
OnPropertyChanged(nameof(AvailableLanguages));
return requestedLanguageCode;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during language fallback attempt for: {LanguageCode}", requestedLanguageCode);
}
return null;
}
/// <summary>
/// Gets common language variants for fallback attempts
/// </summary>
private string[] GetCommonLanguageVariants(string languageCode)
{
return languageCode.ToLowerInvariant() switch
{
"en" or "en-us" => new[] { "en-US", "en-GB", "en" },
"zh" or "zh-cn" => new[] { "zh-CN", "zh-TW", "zh-HK", "zh" },
"es" or "es-es" => new[] { "es-ES", "es-MX", "es-AR", "es" },
"fr" or "fr-fr" => new[] { "fr-FR", "fr-CA", "fr" },
"de" or "de-de" => new[] { "de-DE", "de-AT", "de-CH", "de" },
"pt" or "pt-pt" => new[] { "pt-PT", "pt-BR", "pt" },
_ => new[] { languageCode }
};
}
/// <summary>
/// Loads a language with comprehensive error recovery
/// </summary>
private async Task<Dictionary<string, string>> LoadLanguageWithErrorRecovery(string languageCode)
{
try
{
var translations = await _languageManager.LoadLanguageAsync(languageCode);
if (translations.Count == 0 && !languageCode.Equals("zh-CN", StringComparison.OrdinalIgnoreCase))
{
_logger.LogWarning("No translations loaded for {LanguageCode}, trying fallback to Chinese", languageCode);
// Try to reload Chinese fallback
var fallbackTranslations = await _languageManager.LoadLanguageAsync("zh-CN");
if (fallbackTranslations.Count > 0)
{
return fallbackTranslations;
}
// If even Chinese failed, use our cached fallback
if (_fallbackTranslations.Count > 0)
{
_logger.LogWarning("Using cached fallback translations");
return new Dictionary<string, string>(_fallbackTranslations);
}
}
return translations;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error loading language {LanguageCode}, using fallback", languageCode);
// Return cached fallback or empty dictionary
return _fallbackTranslations.Count > 0
? new Dictionary<string, string>(_fallbackTranslations)
: new Dictionary<string, string>();
}
}
/// <summary>
/// Attempts to recover from a critical language setting error
/// </summary>
private async Task AttemptLanguageRecovery(string originalLanguage, Exception originalException)
{
try
{
_logger.LogWarning("Attempting language recovery, original language: {OriginalLanguage}", originalLanguage);
// Try to revert to original language
if (!string.IsNullOrEmpty(originalLanguage) && originalLanguage != CurrentLanguage)
{
try
{
var originalTranslations = await _languageManager.LoadLanguageAsync(originalLanguage);
if (originalTranslations.Count > 0)
{
_currentTranslations = originalTranslations;
CurrentLanguage = originalLanguage;
_logger.LogInformation("Successfully reverted to original language: {Language}", originalLanguage);
return;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to revert to original language: {Language}", originalLanguage);
}
}
// Try to fall back to Chinese
try
{
var chineseTranslations = await _languageManager.LoadLanguageAsync("zh-CN");
if (chineseTranslations.Count > 0)
{
_currentTranslations = chineseTranslations;
CurrentLanguage = "zh-CN";
_logger.LogInformation("Successfully fell back to Chinese");
return;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to fall back to Chinese");
}
// Use cached fallback translations
if (_fallbackTranslations.Count > 0)
{
_currentTranslations = new Dictionary<string, string>(_fallbackTranslations);
CurrentLanguage = "zh-CN";
_logger.LogWarning("Using cached fallback translations for recovery");
return;
}
// Create minimal emergency translations
CreateMinimalFallbackTranslations();
_currentTranslations = new Dictionary<string, string>(_fallbackTranslations);
CurrentLanguage = "zh-CN";
_logger.LogWarning("Created minimal emergency translations for recovery");
}
catch (Exception recoveryEx)
{
_logger.LogCritical(recoveryEx, "Language recovery failed completely. Original error: {OriginalError}",
originalException.Message);
// Set absolute minimal state
CurrentLanguage = "zh-CN";
_currentTranslations = new Dictionary<string, string>();
_fallbackTranslations = new Dictionary<string, string>();
}
}
private string GetSavedLanguage()
{
try
{
var config = _configService.Get();
return config.CommonConfig.Language ?? string.Empty;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to get saved language preference");
return string.Empty;
}
}
private void SaveLanguagePreference(string languageCode)
{
try
{
var config = _configService.Get();
config.CommonConfig.Language = languageCode;
_configService.Save();
_logger.LogDebug("Saved language preference: {Language}", languageCode);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to save language preference");
}
}
private string DetermineInitialLanguage(string savedLanguage)
{
// Use saved language if available and valid
if (!string.IsNullOrEmpty(savedLanguage) &&
_availableLanguages.Any(l => l.Code.Equals(savedLanguage, StringComparison.OrdinalIgnoreCase)))
{
return savedLanguage;
}
// Try to use system language
var systemLanguage = CultureInfo.CurrentUICulture.Name;
if (_availableLanguages.Any(l => l.Code.Equals(systemLanguage, StringComparison.OrdinalIgnoreCase)))
{
_logger.LogInformation("Using system language: {Language}", systemLanguage);
return systemLanguage;
}
// Try language without region (e.g., "en" instead of "en-US")
var languageOnly = systemLanguage.Split('-')[0];
var matchingLanguage = _availableLanguages.FirstOrDefault(l =>
l.Code.StartsWith(languageOnly, StringComparison.OrdinalIgnoreCase));
if (matchingLanguage != null)
{
_logger.LogInformation("Using closest matching language: {Language}", matchingLanguage.Code);
return matchingLanguage.Code;
}
// Default to Chinese
_logger.LogInformation("Using default language: zh-CN");
return "zh-CN";
}
/// <summary>
/// Handles language file changes and updates available languages dynamically
/// </summary>
private async void OnLanguageFilesChanged(object? sender, LanguageFilesChangedEventArgs e)
{
try
{
_logger.LogInformation("Language files changed: {ChangeType} - {FilePath}", e.ChangeType, e.FilePath);
// Re-discover available languages with error recovery
await HandleLanguageDiscoveryChange();
// Handle specific file change types with error recovery
await HandleSpecificFileChange(e);
}
catch (Exception ex)
{
_logger.LogError(ex, "Critical error handling language file change: {FilePath}", e.FilePath);
// Attempt recovery to ensure the service remains functional
await AttemptFileChangeRecovery(e, ex);
}
}
/// <summary>
/// Handles language discovery changes with error recovery
/// </summary>
private async Task HandleLanguageDiscoveryChange()
{
try
{
var languages = await _languageManager.DiscoverLanguagesAsync();
var newLanguagesList = languages.ToList();
// Check if the list actually changed
if (!LanguageListsEqual(_availableLanguages, newLanguagesList))
{
_availableLanguages = newLanguagesList;
OnPropertyChanged(nameof(AvailableLanguages));
_logger.LogInformation("Available languages updated. New count: {Count}", _availableLanguages.Count);
// Validate that we still have at least Chinese available
if (!_availableLanguages.Any(l => l.Code.Equals("zh-CN", StringComparison.OrdinalIgnoreCase)))
{
_logger.LogWarning("Chinese language no longer available after file change, adding fallback entry");
_availableLanguages.Add(new LanguageInfo
{
Code = "zh-CN",
DisplayName = "简体中文 (回退)",
NativeName = "简体中文 (回退)",
FilePath = "fallback",
Version = "1.0.0"
});
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during language discovery update");
// Ensure we maintain a minimal language list
if (_availableLanguages.Count == 0)
{
_logger.LogWarning("Language list became empty, restoring minimal fallback");
_availableLanguages = new List<LanguageInfo>
{
new LanguageInfo
{
Code = "zh-CN",
DisplayName = "简体中文 (恢复)",
NativeName = "简体中文 (恢复)",
FilePath = "recovery",
Version = "1.0.0"
}
};
OnPropertyChanged(nameof(AvailableLanguages));
}
}
}
/// <summary>
/// Handles specific file change types with comprehensive error recovery
/// </summary>
private async Task HandleSpecificFileChange(LanguageFilesChangedEventArgs e)
{
try
{
var changedFileName = Path.GetFileNameWithoutExtension(e.FilePath);
switch (e.ChangeType)
{
case System.IO.WatcherChangeTypes.Deleted:
await HandleFileDeleted(changedFileName, e.FilePath);
break;
case System.IO.WatcherChangeTypes.Changed:
await HandleFileChanged(changedFileName, e.FilePath);
break;
case System.IO.WatcherChangeTypes.Created:
await HandleFileCreated(changedFileName, e.FilePath);
break;
case System.IO.WatcherChangeTypes.Renamed:
// Handled by separate rename event
break;
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error handling specific file change: {ChangeType} - {FilePath}", e.ChangeType, e.FilePath);
}
}
/// <summary>
/// Handles file deletion with fallback mechanisms
/// </summary>
private async Task HandleFileDeleted(string deletedFileName, string filePath)
{
try
{
if (CurrentLanguage.Equals(deletedFileName, StringComparison.OrdinalIgnoreCase))
{
_logger.LogWarning("Current language file deleted: {Language}, attempting graceful fallback", CurrentLanguage);
// Try to find an alternative language or fall back to Chinese
var fallbackLanguage = await FindBestFallbackLanguage(deletedFileName);
await SetLanguageAsync(fallbackLanguage);
}
else if (deletedFileName.Equals("zh-CN", StringComparison.OrdinalIgnoreCase))
{
_logger.LogWarning("Chinese fallback file deleted, creating emergency fallback translations");
// Recreate minimal fallback translations
CreateMinimalFallbackTranslations();
// 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));
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error handling file deletion: {FilePath}", filePath);
}
}
/// <summary>
/// Handles file changes with validation and error recovery
/// </summary>
private async Task HandleFileChanged(string changedFileName, string filePath)
{
try
{
if (CurrentLanguage.Equals(changedFileName, StringComparison.OrdinalIgnoreCase))
{
_logger.LogInformation("Current language file modified: {Language}, reloading translations", CurrentLanguage);
var newTranslations = await _languageManager.LoadLanguageAsync(CurrentLanguage);
if (newTranslations.Count > 0)
{
_currentTranslations = newTranslations;
LanguageChanged?.Invoke(this, new LanguageChangedEventArgs(CurrentLanguage, CurrentLanguage));
_logger.LogInformation("Successfully reloaded {Count} translations for {Language}", newTranslations.Count, CurrentLanguage);
}
else
{
_logger.LogWarning("Modified language file {Language} is empty or corrupted, keeping existing translations", CurrentLanguage);
}
}
else if (changedFileName.Equals("zh-CN", StringComparison.OrdinalIgnoreCase))
{
_logger.LogInformation("Chinese fallback file modified, reloading fallback translations");
try
{
var newFallbackTranslations = await _languageManager.LoadLanguageAsync("zh-CN");
if (newFallbackTranslations.Count > 0)
{
_fallbackTranslations = newFallbackTranslations;
_logger.LogInformation("Successfully reloaded {Count} fallback translations", newFallbackTranslations.Count);
}
else
{
_logger.LogWarning("Modified Chinese file is empty, keeping existing fallback translations");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error reloading Chinese fallback file, keeping existing translations");
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error handling file change: {FilePath}", filePath);
}
}
/// <summary>
/// Handles new file creation
/// </summary>
private async Task HandleFileCreated(string createdFileName, string filePath)
{
try
{
_logger.LogInformation("New language file created: {FileName}", createdFileName);
// Validate the new file by attempting to load it
var testTranslations = await _languageManager.LoadLanguageAsync(createdFileName);
if (testTranslations.Count > 0)
{
_logger.LogInformation("New language file {FileName} validated successfully with {Count} translations",
createdFileName, testTranslations.Count);
}
else
{
_logger.LogWarning("New language file {FileName} appears to be empty or invalid", createdFileName);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error handling file creation: {FilePath}", filePath);
}
}
/// <summary>
/// Finds the best fallback language when the current language is no longer available
/// </summary>
private async Task<string> FindBestFallbackLanguage(string unavailableLanguage)
{
try
{
// Try to find a similar language (same language family)
var languagePrefix = unavailableLanguage.Split('-')[0];
var similarLanguage = _availableLanguages.FirstOrDefault(l =>
l.Code.StartsWith(languagePrefix, StringComparison.OrdinalIgnoreCase) &&
!l.Code.Equals(unavailableLanguage, StringComparison.OrdinalIgnoreCase));
if (similarLanguage != null)
{
_logger.LogInformation("Found similar language fallback: {UnavailableLanguage} -> {FallbackLanguage}",
unavailableLanguage, similarLanguage.Code);
return similarLanguage.Code;
}
// Try system language
var systemLanguage = CultureInfo.CurrentUICulture.Name;
if (_availableLanguages.Any(l => l.Code.Equals(systemLanguage, StringComparison.OrdinalIgnoreCase)))
{
_logger.LogInformation("Using system language as fallback: {SystemLanguage}", systemLanguage);
return systemLanguage;
}
// 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 Chinese");
return "zh-CN";
}
}
/// <summary>
/// Attempts recovery when file change handling fails
/// </summary>
private async Task AttemptFileChangeRecovery(LanguageFilesChangedEventArgs e, Exception originalException)
{
try
{
_logger.LogWarning("Attempting recovery from file change handling failure: {FilePath}", e.FilePath);
// Ensure we still have a functional language service
if (_currentTranslations.Count == 0)
{
_logger.LogWarning("Current translations lost, attempting to reload current language");
try
{
var recoveryTranslations = await _languageManager.LoadLanguageAsync(CurrentLanguage);
if (recoveryTranslations.Count > 0)
{
_currentTranslations = recoveryTranslations;
_logger.LogInformation("Successfully recovered current language translations");
}
else
{
// Fall back to cached fallback translations
if (_fallbackTranslations.Count > 0)
{
_currentTranslations = new Dictionary<string, string>(_fallbackTranslations);
CurrentLanguage = "en-US";
_logger.LogWarning("Recovered using cached fallback translations");
}
else
{
// Create emergency translations
CreateMinimalFallbackTranslations();
_currentTranslations = new Dictionary<string, string>(_fallbackTranslations);
CurrentLanguage = "en-US";
_logger.LogWarning("Created emergency translations for recovery");
}
}
}
catch (Exception recoveryEx)
{
_logger.LogError(recoveryEx, "Recovery attempt failed, creating minimal emergency state");
// Absolute minimal recovery
CreateMinimalFallbackTranslations();
_currentTranslations = new Dictionary<string, string>(_fallbackTranslations);
CurrentLanguage = "en-US";
}
}
// Ensure we have at least one available language
if (_availableLanguages.Count == 0)
{
_logger.LogWarning("No available languages after recovery, creating minimal list");
_availableLanguages = new List<LanguageInfo>
{
new LanguageInfo
{
Code = "en-US",
DisplayName = "English (Recovery)",
NativeName = "English (Recovery)",
FilePath = "recovery",
Version = "1.0.0"
}
};
OnPropertyChanged(nameof(AvailableLanguages));
}
}
catch (Exception recoveryEx)
{
_logger.LogCritical(recoveryEx, "File change recovery failed completely. Original error: {OriginalError}",
originalException.Message);
}
}
/// <summary>
/// Compares two language lists to determine if they are equal
/// </summary>
private bool LanguageListsEqual(List<LanguageInfo> list1, List<LanguageInfo> list2)
{
if (list1.Count != list2.Count)
{
return false;
}
var codes1 = list1.Select(l => l.Code).OrderBy(c => c).ToList();
var codes2 = list2.Select(l => l.Code).OrderBy(c => c).ToList();
return codes1.SequenceEqual(codes2, StringComparer.OrdinalIgnoreCase);
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// Disposes the localization service and cleans up event subscriptions
/// </summary>
public void Dispose()
{
try
{
if (_languageManager != null)
{
_languageManager.LanguageFilesChanged -= OnLanguageFilesChanged;
}
_logger.LogDebug("LocalizationService disposed");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error disposing LocalizationService");
}
}
}