chore
This commit is contained in:
@@ -1,141 +0,0 @@
|
||||
using Microsoft.Extensions.Localization;
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Atomx.Admin.Client.Services
|
||||
{
|
||||
public class JsonStringLocalizer : IStringLocalizer
|
||||
{
|
||||
private readonly string _resourcesPath;
|
||||
private readonly Dictionary<string, Dictionary<string, string>> _resourcesCache = new();
|
||||
private readonly object _lock = new();
|
||||
private readonly HttpClient? _httpClient;
|
||||
|
||||
// resourcesPath 应为相对于站点根的“目录”名称,例如 "Localization"
|
||||
// 在 Blazor WebAssembly 场景下,会使用注入的 HttpClient 从 wwwroot/Localization/{culture}.json 获取资源
|
||||
public JsonStringLocalizer(string resourcesPath, HttpClient? httpClient = null)
|
||||
{
|
||||
_resourcesPath = (resourcesPath ?? "localization").Trim('/'); // 规范化
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public LocalizedString this[string name]
|
||||
{
|
||||
get
|
||||
{
|
||||
var value = GetString(name);
|
||||
return new LocalizedString(name, value ?? name, resourceNotFound: value == null);
|
||||
}
|
||||
}
|
||||
|
||||
public LocalizedString this[string name, params object[] arguments]
|
||||
{
|
||||
get
|
||||
{
|
||||
var format = GetString(name);
|
||||
var value = string.Format(format ?? name, arguments);
|
||||
return new LocalizedString(name, value, resourceNotFound: format == null);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures)
|
||||
{
|
||||
var culture = CultureInfo.CurrentUICulture.Name;
|
||||
var resources = LoadResources(culture);
|
||||
return resources.Select(r => new LocalizedString(r.Key, r.Value, false));
|
||||
}
|
||||
|
||||
private string? GetString(string name)
|
||||
{
|
||||
var culture = CultureInfo.CurrentUICulture.Name;
|
||||
var resources = LoadResources(culture);
|
||||
|
||||
// 尝试当前文化
|
||||
if (resources.TryGetValue(name, out var value))
|
||||
return value;
|
||||
|
||||
// 如果还找不到,尝试英文作为后备
|
||||
if (culture != "en-US")
|
||||
{
|
||||
var enResources = LoadResources("en-US");
|
||||
if (enResources.TryGetValue(name, out var enValue))
|
||||
return enValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Dictionary<string, string> LoadResources(string culture)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_resourcesCache.TryGetValue(culture, out var cachedResources))
|
||||
return cachedResources;
|
||||
|
||||
var resources = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
var fileName = $"{culture}.json";
|
||||
|
||||
// 在浏览器(WASM)环境下,通过 HttpClient 从静态资源目录获取
|
||||
if (OperatingSystem.IsBrowser() && _httpClient != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 构造相对 URL,例如 "Localization/zh-Hans.json"
|
||||
var relativeUrl = $"{_resourcesPath}/{fileName}";
|
||||
var json = _httpClient.GetStringAsync(relativeUrl).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
var jsonResources = JsonSerializer.Deserialize<Dictionary<string, string>>(json);
|
||||
if (jsonResources != null)
|
||||
{
|
||||
foreach (var item in jsonResources)
|
||||
{
|
||||
resources[item.Key] = item.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error loading localization file {fileName} via HttpClient: {ex.Message}");
|
||||
}
|
||||
|
||||
_resourcesCache[culture] = resources;
|
||||
return resources;
|
||||
}
|
||||
|
||||
// 非浏览器(例如 Server 或在 prerender 阶段)尝试从文件系统读取。
|
||||
// 尝试几种可能的路径:基路径为 AppContext.BaseDirectory 或当前工作目录,或直接使用传入的路径。
|
||||
try
|
||||
{
|
||||
var candidates = new[]
|
||||
{
|
||||
Path.Combine(AppContext.BaseDirectory, _resourcesPath, fileName),
|
||||
Path.Combine(Directory.GetCurrentDirectory(), _resourcesPath, fileName),
|
||||
Path.Combine(_resourcesPath, fileName),
|
||||
};
|
||||
|
||||
var filePath = candidates.FirstOrDefault(File.Exists);
|
||||
|
||||
if (!string.IsNullOrEmpty(filePath))
|
||||
{
|
||||
var json = File.ReadAllText(filePath);
|
||||
var jsonResources = JsonSerializer.Deserialize<Dictionary<string, string>>(json);
|
||||
if (jsonResources != null)
|
||||
{
|
||||
foreach (var item in jsonResources)
|
||||
{
|
||||
resources[item.Key] = item.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error loading localization file {fileName} from disk: {ex.Message}");
|
||||
}
|
||||
|
||||
_resourcesCache[culture] = resources;
|
||||
return resources;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace Atomx.Admin.Client.Services
|
||||
{
|
||||
public class JsonStringLocalizerFactory : IStringLocalizerFactory
|
||||
{
|
||||
private readonly string _resourcesPath;
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public JsonStringLocalizerFactory(string resourcesPath, HttpClient httpClient)
|
||||
{
|
||||
_resourcesPath = (resourcesPath ?? "localization").Trim('/');
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public IStringLocalizer Create(Type resourceSource)
|
||||
{
|
||||
return new JsonStringLocalizer(_resourcesPath, _httpClient);
|
||||
}
|
||||
|
||||
public IStringLocalizer Create(string baseName, string location)
|
||||
{
|
||||
return new JsonStringLocalizer(_resourcesPath, _httpClient);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace Atomx.Admin.Client.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 语言提供者服务
|
||||
/// </summary>
|
||||
public class LanguageProvider
|
||||
{
|
||||
private readonly IJSRuntime _jsRuntime;
|
||||
private readonly NavigationManager _navigationManager;
|
||||
private string _currentLanguage = "zh-Hans";
|
||||
|
||||
public event Action? OnLanguageChanged;
|
||||
|
||||
public LanguageProvider(IJSRuntime jsRuntime, NavigationManager navigationManager)
|
||||
{
|
||||
_jsRuntime = jsRuntime;
|
||||
_navigationManager = navigationManager;
|
||||
}
|
||||
|
||||
public string CurrentLanguage
|
||||
{
|
||||
get => _currentLanguage;
|
||||
private set
|
||||
{
|
||||
if (_currentLanguage != value)
|
||||
{
|
||||
_currentLanguage = value;
|
||||
OnLanguageChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> SupportedLanguages { get; } = new()
|
||||
{
|
||||
"zh-Hans", // 简体中文
|
||||
"en-US" // 英文(美国)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 初始化语言
|
||||
/// </summary>
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
// 尝试从本地存储获取保存的语言
|
||||
Console.WriteLine("尝试从本地存储获取保存的语言 Initializing LanguageProvider...");
|
||||
try
|
||||
{
|
||||
|
||||
var savedLanguage = await _jsRuntime.InvokeAsync<string>("localStorage.getItem", "preferred-language");
|
||||
if (!string.IsNullOrEmpty(savedLanguage) && SupportedLanguages.Contains(savedLanguage))
|
||||
{
|
||||
CurrentLanguage = savedLanguage;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 从浏览器获取语言
|
||||
var browserLanguage = await _jsRuntime.InvokeAsync<string>("getBrowserLanguage");
|
||||
CurrentLanguage = GetSupportedLanguage(browserLanguage);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// JS互操作可能不可用(在预渲染时)
|
||||
CurrentLanguage = "zh-Hans";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换语言
|
||||
/// </summary>
|
||||
public async Task ChangeLanguageAsync(string languageCode)
|
||||
{
|
||||
Console.WriteLine("切换语言 ChangeLanguageAsync to " + languageCode);
|
||||
if (SupportedLanguages.Contains(languageCode) && CurrentLanguage != languageCode)
|
||||
{
|
||||
CurrentLanguage = languageCode;
|
||||
|
||||
// 保存到本地存储
|
||||
try
|
||||
{
|
||||
await _jsRuntime.InvokeVoidAsync("localStorage.setItem", "preferred-language", languageCode);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略错误
|
||||
}
|
||||
|
||||
// 通知语言已更改
|
||||
OnLanguageChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取支持的语言
|
||||
/// </summary>
|
||||
private string GetSupportedLanguage(string browserLanguage)
|
||||
{
|
||||
if (string.IsNullOrEmpty(browserLanguage))
|
||||
return "zh-Hans";
|
||||
|
||||
// 检查完全匹配
|
||||
if (SupportedLanguages.Contains(browserLanguage))
|
||||
return browserLanguage;
|
||||
|
||||
// 检查中性语言匹配
|
||||
var neutralLanguage = browserLanguage.Split('-')[0];
|
||||
foreach (var supported in SupportedLanguages)
|
||||
{
|
||||
if (supported.StartsWith(neutralLanguage))
|
||||
return supported;
|
||||
}
|
||||
|
||||
return "zh-Hans"; // 默认语言
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace Atomx.Admin.Client.Services
|
||||
{
|
||||
public interface ILocalizationService
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前文化环境
|
||||
/// </summary>d
|
||||
CultureInfo CurrentCulture { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 当语言发生改变时触发的事件。
|
||||
/// </summary>
|
||||
event EventHandler<CultureInfo> LanguageChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 当语言发生改变时触发的事件。调用 <see cref="InteractiveStringLocalizer"/> 来更改语言环境。
|
||||
/// </summary>
|
||||
/// <param name="culture"></param>
|
||||
void SetLanguage(CultureInfo culture);
|
||||
}
|
||||
|
||||
|
||||
public class LocalizationService : ILocalizationService
|
||||
{
|
||||
private CultureInfo? _currentCulture;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前文化环境
|
||||
/// </summary>
|
||||
public CultureInfo CurrentCulture => _currentCulture ?? CultureInfo.CurrentCulture;
|
||||
|
||||
public event EventHandler<CultureInfo> LanguageChanged = default!;
|
||||
|
||||
public void SetLanguage(CultureInfo culture)
|
||||
{
|
||||
if (!culture.Equals(CultureInfo.CurrentCulture))
|
||||
{
|
||||
CultureInfo.CurrentCulture = culture;
|
||||
}
|
||||
|
||||
if (_currentCulture == null || !_currentCulture.Equals(culture))
|
||||
{
|
||||
_currentCulture = culture;
|
||||
CultureInfo.DefaultThreadCurrentCulture = culture;
|
||||
CultureInfo.DefaultThreadCurrentUICulture = culture;
|
||||
|
||||
LanguageChanged?.Invoke(this, culture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user