using System.Collections.Concurrent; using System.Globalization; using System.Text.Json; using Microsoft.Extensions.Logging; using Microsoft.JSInterop; using Blazored.LocalStorage; namespace Atomx.Admin.Client.Services { public class WasmLocalizationProvider : ILocalizationProvider { private readonly IJSRuntime _jsRuntime; private readonly HttpClient _httpClient; private readonly ILogger _logger; private readonly ILocalStorageService _localStorage; private static readonly ConcurrentDictionary> _cache = new(); private readonly HashSet _loadingCultures = new(); private string _currentCulture = "zh-Hans"; private bool _isInitialized = false; private const string LocalizationStorageKey = "Localization_{0}"; private const string LocalizationVersionKey = "LocalizationVersion_{0}"; public WasmLocalizationProvider(IJSRuntime jsRuntime, HttpClient httpClient, ILogger logger, ILocalStorageService localStorage) { _jsRuntime = jsRuntime; _httpClient = httpClient; _logger = logger; _localStorage = localStorage; } public string CurrentCulture => _currentCulture; public bool IsInitialized => _isInitialized; public event EventHandler? LanguageChanged; public string? GetString(string key) { if (string.IsNullOrEmpty(key)) return null; if (_cache.TryGetValue(_currentCulture, out var dict) && dict.TryGetValue(key, out var val)) { return val; } return null; } public async Task InitializeAsync() { if (_isInitialized) return; await LoadCultureAsync(_currentCulture); _isInitialized = true; LanguageChanged?.Invoke(this, _currentCulture); } public async Task SetCultureAsync(string cultureShortOrFull) { _currentCulture = MapToFullCulture(cultureShortOrFull); await LoadCultureAsync(_currentCulture); LanguageChanged?.Invoke(this, _currentCulture); } public async Task LoadCultureAsync(string culture) { var cultureFull = MapToFullCulture(culture); // Step 1: Check in-memory cache if (_cache.ContainsKey(cultureFull)) return; lock (_loadingCultures) { if (_loadingCultures.Contains(cultureFull)) { _logger.LogDebug("Culture {Culture} is already being loaded.", cultureFull); return; } _loadingCultures.Add(cultureFull); } try { // Step 2: Check local storage for cached data var localDataKey = string.Format(LocalizationStorageKey, cultureFull); var localVersionKey = string.Format(LocalizationVersionKey, cultureFull); var cachedVersion = await _localStorage.GetItemAsync(localVersionKey); var cachedData = await _localStorage.GetItemAsync>(localDataKey); if (cachedData != null) { _cache[cultureFull] = cachedData; _logger.LogInformation("Loaded localization for {Culture} from local storage.", cultureFull); } // Step 3: Validate version with server var versionUrl = $"api/localeresource/version/{cultureFull}"; var serverVersion = await _httpClient.GetStringAsync(versionUrl); if (cachedVersion == serverVersion && cachedData != null) { _logger.LogInformation("Localization data for {Culture} is up-to-date.", cultureFull); return; } // Step 4: Fetch from server if version mismatch or no cached data var url = $"/localization/{cultureFull}.json"; var json = await _httpClient.GetStringAsync(url); var dict = JsonSerializer.Deserialize>(json) ?? new Dictionary(); _cache[cultureFull] = dict; // Step 5: Update local storage await _localStorage.SetItemAsync(localVersionKey, serverVersion); await _localStorage.SetItemAsync(localDataKey, dict); _logger.LogInformation("Loaded localization file for {Culture} from server and updated local storage.", cultureFull); } catch (Exception ex) { _logger.LogError(ex, "Failed to load localization file for {Culture}", cultureFull); } finally { lock (_loadingCultures) { _loadingCultures.Remove(cultureFull); } } } private string MapToFullCulture(string culture) { return culture switch { "zh" => "zh-Hans", "en" => "en-US", _ => culture }; } /// /// 从服务器获取本地化数据 /// /// /// private Dictionary FetchFromServer(string culture) { var url = $"/localization/{culture}.json"; var json = _httpClient.GetStringAsync(url).Result; var dict = JsonSerializer.Deserialize>(json) ?? new Dictionary(); return dict; } /// /// 检查本地化版本是否最新 /// /// /// /// private async Task CheckVersionAsync(string versionKey, string culture) { var cachedVersion = await _localStorage.GetItemAsync(versionKey); if(string.IsNullOrEmpty(cachedVersion)) { return false; } var versionUrl = $"api/localeresource/version/{culture}"; var serverVersion = await _httpClient.GetStringAsync(versionUrl); if (cachedVersion != serverVersion) { return false; } return true; } } }