using Atomx.Common.Models; using Microsoft.JSInterop; using System.Text.Json; namespace Atomx.Admin.Client.Services { public interface ILocalizationService { /// /// 根据name获取制定文化语言的译文 /// /// /// /// Task GetStringAsync(string name, string? culture = null); /// /// 把本地化文化语言加载到内存中 /// /// /// Task LoadResourcesAsync(string culture); event EventHandler? ResourcesUpdated; } public class LocalizationClientService : ILocalizationService, IAsyncDisposable { private readonly HttpService _httpService; private readonly IJSRuntime _jsRuntime; private readonly ILogger _logger; private readonly Dictionary> _resources = new(); private readonly Dictionary _versions = new(); private readonly SemaphoreSlim _semaphore = new(1, 1); public event EventHandler? ResourcesUpdated; public LocalizationClientService( HttpService httpService, IJSRuntime jsRuntime, ILogger logger) { _httpService = httpService; _jsRuntime = jsRuntime; _logger = logger; } /// /// 根据name获取制定文化语言的译文 /// /// /// /// public async Task GetStringAsync(string name, string? culture = null) { culture ??= await GetCurrentCultureAsync(); if (_resources.TryGetValue(culture, out var cultureResources)) { if (cultureResources.TryGetValue(name, out var value)) { return value; } // 键不存在,触发资源更新检查 _ = Task.Run(async () => await CheckAndUpdateResourcesAsync(culture)); } else { // 资源未加载,立即加载 await LoadResourcesAsync(culture); // 重试获取 if (_resources.TryGetValue(culture, out cultureResources) && cultureResources.TryGetValue(name, out var value)) { return value; } } _logger.LogWarning("Localization key not found: {Key} for culture: {Culture}", name, culture); return null; } /// /// 把本地化文化语言加载到内存中 /// /// /// public async Task LoadResourcesAsync(string culture) { await _semaphore.WaitAsync(); try { // 先尝试从localStorage加载 if (await TryLoadFromLocalStorage(culture)) { // 检查服务器版本,如果需要更新则从服务器加载 if (await CheckAndUpdateFromServer(culture)) { return true; } return true; // 本地版本仍然有效 } // 从服务器加载 return await LoadFromServer(culture); } finally { _semaphore.Release(); } } /// /// 从单例对象中获取版本记录 /// /// /// private async Task GetResourceVersionAsync(string culture) { if (_versions.TryGetValue(culture, out var version)) { return version; } var storedVersion = await GetStoredVersion(culture); return storedVersion; } /// /// 检查更新资源 /// /// /// private async Task CheckAndUpdateResourcesAsync(string culture) { return await CheckAndUpdateFromServer(culture); } /// /// 从本地加载文化语言数据 /// /// /// private async Task TryLoadFromLocalStorage(string culture) { try { var resourcesJson = await _jsRuntime.InvokeAsync("localStorage.getItem", $"locales_{culture}"); var version = await _jsRuntime.InvokeAsync("localStorage.getItem", $"locales_version_{culture}"); if (string.IsNullOrEmpty(resourcesJson) || string.IsNullOrEmpty(version)) { return false; } var resources = JsonSerializer.Deserialize>(resourcesJson); if (resources != null && version != null) { _resources[culture] = resources; _versions[culture] = version; return true; } } catch (Exception ex) { _logger.LogWarning(ex, "Failed to load resources from localStorage for culture: {Culture}", culture); } return false; } /// /// 从服务器更新获取文化语言数据 /// /// /// private async Task LoadFromServer(string culture) { try { var response = await _httpService.Get>($"/api/localeresource/resources/{culture}"); if (response.Success) { _resources[culture] = response.Data.Translations; _versions[culture] = response.Data.ResourceVersion; // 保存到localStorage await SaveToLocalStorage(culture, response.Data.Translations, response.Data.ResourceVersion); ResourcesUpdated?.Invoke(this, culture); return true; } } catch (Exception ex) { _logger.LogError(ex, "Failed to load resources from server for culture: {Culture}", culture); } return false; } /// /// 根据版本信息上从服务器数据对比版本更新数据 /// /// /// private async Task CheckAndUpdateFromServer(string culture) { try { var currentVersion = await GetResourceVersionAsync(culture); var serverVersion = await GetServerVersion(culture); if (serverVersion != null && (currentVersion == null || serverVersion != currentVersion)) { _logger.LogInformation("Updating resources for culture: {Culture}", culture); return await LoadFromServer(culture); } } catch (Exception ex) { _logger.LogError(ex, "Failed to check and update resources for culture: {Culture}", culture); } return false; } /// /// 从服务器获取当前多语言文化数据版本信息 /// /// /// private async Task GetServerVersion(string culture) { try { var api = $"/api/localeresource/version/{culture}"; var result = await _httpService.Get>(api); if (result.Success) { return result.Data; } } catch (Exception ex) { _logger.LogWarning(ex, "Failed to get server version for culture: {Culture}", culture); } return null; } /// /// 获取存储在本地的文化语言版本信息 /// /// /// private async Task GetStoredVersion(string culture) { try { var version = await _jsRuntime.InvokeAsync("localStorage.getItem", $"locales_version_{culture}"); if (!string.IsNullOrEmpty(version)) { return version; } } catch (Exception ex) { _logger.LogWarning(ex, "Failed to get stored version for culture: {Culture}", culture); } return null; } /// /// 存储版本和文化语言信息 /// /// /// /// /// private async Task SaveToLocalStorage(string culture, Dictionary resources, string version) { try { var resourcesJson = JsonSerializer.Serialize(resources); await _jsRuntime.InvokeVoidAsync("localStorage.setItem", $"locales_{culture}", resourcesJson); await _jsRuntime.InvokeVoidAsync("localStorage.setItem", $"locales_version_{culture}", version); } catch (Exception ex) { _logger.LogWarning(ex, "Failed to save resources to localStorage for culture: {Culture}", culture); } } /// /// 获取浏览器上的文化语言信息 /// /// private async Task GetCurrentCultureAsync() { try { return await _jsRuntime.InvokeAsync("blazorCulture.get"); } catch { return "en-US"; // 默认文化 } } public async ValueTask DisposeAsync() { _semaphore?.Dispose(); } } }