323 lines
11 KiB
C#
323 lines
11 KiB
C#
using Atomx.Common.Models;
|
|
using Microsoft.JSInterop;
|
|
using System.Text.Json;
|
|
|
|
namespace Atomx.Admin.Client.Services
|
|
{
|
|
public interface ILocalizationService
|
|
{
|
|
/// <summary>
|
|
/// 根据name获取制定文化语言的译文
|
|
/// </summary>
|
|
/// <param name="name"></param>
|
|
/// <param name="culture"></param>
|
|
/// <returns></returns>
|
|
Task<string?> GetStringAsync(string name, string? culture = null);
|
|
|
|
/// <summary>
|
|
/// 把本地化文化语言加载到内存中
|
|
/// </summary>
|
|
/// <param name="culture"></param>
|
|
/// <returns></returns>
|
|
Task<bool> LoadResourcesAsync(string culture);
|
|
|
|
|
|
event EventHandler<string>? ResourcesUpdated;
|
|
}
|
|
|
|
public class LocalizationClientService : ILocalizationService, IAsyncDisposable
|
|
{
|
|
private readonly HttpService _httpService;
|
|
private readonly IJSRuntime _jsRuntime;
|
|
private readonly ILogger<LocalizationClientService> _logger;
|
|
|
|
private readonly Dictionary<string, Dictionary<string, string>> _resources = new();
|
|
private readonly Dictionary<string, string> _versions = new();
|
|
private readonly SemaphoreSlim _semaphore = new(1, 1);
|
|
|
|
public event EventHandler<string>? ResourcesUpdated;
|
|
|
|
public LocalizationClientService(
|
|
HttpService httpService,
|
|
IJSRuntime jsRuntime,
|
|
ILogger<LocalizationClientService> logger)
|
|
{
|
|
_httpService = httpService;
|
|
_jsRuntime = jsRuntime;
|
|
_logger = logger;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 根据name获取制定文化语言的译文
|
|
/// </summary>
|
|
/// <param name="name"></param>
|
|
/// <param name="culture"></param>
|
|
/// <returns></returns>
|
|
public async Task<string?> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 把本地化文化语言加载到内存中
|
|
/// </summary>
|
|
/// <param name="culture"></param>
|
|
/// <returns></returns>
|
|
public async Task<bool> 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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 从单例对象中获取版本记录
|
|
/// </summary>
|
|
/// <param name="culture"></param>
|
|
/// <returns></returns>
|
|
private async Task<string?> GetResourceVersionAsync(string culture)
|
|
{
|
|
if (_versions.TryGetValue(culture, out var version))
|
|
{
|
|
return version;
|
|
}
|
|
|
|
var storedVersion = await GetStoredVersion(culture);
|
|
return storedVersion;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 检查更新资源
|
|
/// </summary>
|
|
/// <param name="culture"></param>
|
|
/// <returns></returns>
|
|
private async Task<bool> CheckAndUpdateResourcesAsync(string culture)
|
|
{
|
|
return await CheckAndUpdateFromServer(culture);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 从本地加载文化语言数据
|
|
/// </summary>
|
|
/// <param name="culture"></param>
|
|
/// <returns></returns>
|
|
private async Task<bool> TryLoadFromLocalStorage(string culture)
|
|
{
|
|
try
|
|
{
|
|
var resourcesJson = await _jsRuntime.InvokeAsync<string?>("localStorage.getItem", $"locales_{culture}");
|
|
var version = await _jsRuntime.InvokeAsync<string?>("localStorage.getItem", $"locales_version_{culture}");
|
|
|
|
if (string.IsNullOrEmpty(resourcesJson) || string.IsNullOrEmpty(version))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var resources = JsonSerializer.Deserialize<Dictionary<string, string>>(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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 从服务器更新获取文化语言数据
|
|
/// </summary>
|
|
/// <param name="culture"></param>
|
|
/// <returns></returns>
|
|
private async Task<bool> LoadFromServer(string culture)
|
|
{
|
|
try
|
|
{
|
|
var response = await _httpService.Get<ApiResult<LocalizationFile>>($"/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;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 根据版本信息上从服务器数据对比版本更新数据
|
|
/// </summary>
|
|
/// <param name="culture"></param>
|
|
/// <returns></returns>
|
|
private async Task<bool> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 从服务器获取当前多语言文化数据版本信息
|
|
/// </summary>
|
|
/// <param name="culture"></param>
|
|
/// <returns></returns>
|
|
private async Task<string?> GetServerVersion(string culture)
|
|
{
|
|
try
|
|
{
|
|
var api = $"/api/localeresource/version/{culture}";
|
|
var result = await _httpService.Get<ApiResult<string>>(api);
|
|
if (result.Success)
|
|
{
|
|
return result.Data;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to get server version for culture: {Culture}", culture);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取存储在本地的文化语言版本信息
|
|
/// </summary>
|
|
/// <param name="culture"></param>
|
|
/// <returns></returns>
|
|
private async Task<string?> GetStoredVersion(string culture)
|
|
{
|
|
try
|
|
{
|
|
var version = await _jsRuntime.InvokeAsync<string?>("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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 存储版本和文化语言信息
|
|
/// </summary>
|
|
/// <param name="culture"></param>
|
|
/// <param name="resources"></param>
|
|
/// <param name="version"></param>
|
|
/// <returns></returns>
|
|
private async Task SaveToLocalStorage(string culture, Dictionary<string, string> 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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取浏览器上的文化语言信息
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private async Task<string> GetCurrentCultureAsync()
|
|
{
|
|
try
|
|
{
|
|
return await _jsRuntime.InvokeAsync<string>("blazorCulture.get");
|
|
}
|
|
catch
|
|
{
|
|
return "en-US"; // 默认文化
|
|
}
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
_semaphore?.Dispose();
|
|
}
|
|
}
|
|
}
|