From 9631e00a128b1c62ccdb89f8b1d8f37dffbd88e6 Mon Sep 17 00:00:00 2001 From: yxw <17074267@qq.com> Date: Sun, 7 Dec 2025 18:31:58 +0800 Subject: [PATCH] chore --- .../Components/LanguageSelector.razor | 61 -------- .../Atomx.Admin.Client/Pages/Login.razor | 9 +- Atomx.Admin/Atomx.Admin.Client/Program.cs | 5 - .../Services/JsonStringLocalizer.cs | 141 ------------------ .../Services/JsonStringLocalizerFactory.cs | 26 ---- .../Services/LanguageProvider.cs | 119 --------------- .../Services/LocalizationService.cs | 53 +++++++ .../Utils/LocalizedComponentBase.cs | 31 ---- Atomx.Admin/Atomx.Admin.Client/_Imports.razor | 3 +- .../Controllers/LocaleResourceController.cs | 5 +- .../Middlewares/CultureMiddleware.cs | 48 ------ Atomx.Admin/Atomx.Admin/Program.cs | 23 +-- 12 files changed, 60 insertions(+), 464 deletions(-) delete mode 100644 Atomx.Admin/Atomx.Admin.Client/Components/LanguageSelector.razor delete mode 100644 Atomx.Admin/Atomx.Admin.Client/Services/JsonStringLocalizer.cs delete mode 100644 Atomx.Admin/Atomx.Admin.Client/Services/JsonStringLocalizerFactory.cs delete mode 100644 Atomx.Admin/Atomx.Admin.Client/Services/LanguageProvider.cs create mode 100644 Atomx.Admin/Atomx.Admin.Client/Services/LocalizationService.cs delete mode 100644 Atomx.Admin/Atomx.Admin.Client/Utils/LocalizedComponentBase.cs delete mode 100644 Atomx.Admin/Atomx.Admin/Middlewares/CultureMiddleware.cs diff --git a/Atomx.Admin/Atomx.Admin.Client/Components/LanguageSelector.razor b/Atomx.Admin/Atomx.Admin.Client/Components/LanguageSelector.razor deleted file mode 100644 index 686d8df..0000000 --- a/Atomx.Admin/Atomx.Admin.Client/Components/LanguageSelector.razor +++ /dev/null @@ -1,61 +0,0 @@ -@inject LanguageProvider LanguageProvider -@inject IStringLocalizer Localizer - - - -@code { - protected override void OnInitialized() - { - // 订阅变更以便在语言切换时即时更新该组件 - LanguageProvider.OnLanguageChanged += OnLanguageChanged; - } - - private void OnLanguageChanged() - { - // 在 UI 线程上下文中触发 StateHasChanged - _ = InvokeAsync(StateHasChanged); - } - - public void Dispose() - { - LanguageProvider.OnLanguageChanged -= OnLanguageChanged; - } - - private string GetLanguageDisplayName(string languageCode) - { - return languageCode switch - { - "zh-Hans" => "简体中文", - "en-US" => "English (US)", - _ => languageCode - }; - } - - private async Task ChangeLanguage(string languageCode) - { - await LanguageProvider.ChangeLanguageAsync(languageCode); - } -} \ No newline at end of file diff --git a/Atomx.Admin/Atomx.Admin.Client/Pages/Login.razor b/Atomx.Admin/Atomx.Admin.Client/Pages/Login.razor index 922d01a..03d8af1 100644 --- a/Atomx.Admin/Atomx.Admin.Client/Pages/Login.razor +++ b/Atomx.Admin/Atomx.Admin.Client/Pages/Login.razor @@ -1,9 +1,9 @@ @page "/account/login" +@page "/{locale}/account/login" @using System.Text.Json @layout EmptyLayout @inject ILogger Logger @inject IJSRuntime JS -@inject IStringLocalizer Localizer 登录 @@ -13,10 +13,6 @@ } else { - -

- @LanguageProvider.CurrentLanguage 网站名称 @Localizer["site.name"] -

@@ -63,6 +59,9 @@ else @code { string handler = "Server"; + [Parameter] + public string Locale { get; set; } = string.Empty; + [Parameter] [SupplyParameterFromQuery(Name = "ReturnUrl")] public string? ReturnUrl { get; set; } diff --git a/Atomx.Admin/Atomx.Admin.Client/Program.cs b/Atomx.Admin/Atomx.Admin.Client/Program.cs index 3bf8f73..448ccd1 100644 --- a/Atomx.Admin/Atomx.Admin.Client/Program.cs +++ b/Atomx.Admin/Atomx.Admin.Client/Program.cs @@ -25,17 +25,12 @@ builder.Services.AddScoped(); // HttpClient ڼ wwwroot/Localization/{culture}.json Ⱦ̬ļ builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); -// ע᱾ػʹվĿ¼ "Localization" עڼؾ̬ļ HttpClient -builder.Services.AddScoped(sp => - new JsonStringLocalizerFactory("localization", sp.GetRequiredService())); // עIStringLocalizer builder.Services.AddTransient(typeof(IStringLocalizer<>), typeof(StringLocalizer<>)); // ӱػ builder.Services.AddLocalization(); -// עṩ -builder.Services.AddScoped(); // עԶ token & ˢµ DelegatingHandler builder.Services.AddScoped(); diff --git a/Atomx.Admin/Atomx.Admin.Client/Services/JsonStringLocalizer.cs b/Atomx.Admin/Atomx.Admin.Client/Services/JsonStringLocalizer.cs deleted file mode 100644 index c1ea153..0000000 --- a/Atomx.Admin/Atomx.Admin.Client/Services/JsonStringLocalizer.cs +++ /dev/null @@ -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> _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 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 LoadResources(string culture) - { - lock (_lock) - { - if (_resourcesCache.TryGetValue(culture, out var cachedResources)) - return cachedResources; - - var resources = new Dictionary(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>(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>(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; - } - } - } -} \ No newline at end of file diff --git a/Atomx.Admin/Atomx.Admin.Client/Services/JsonStringLocalizerFactory.cs b/Atomx.Admin/Atomx.Admin.Client/Services/JsonStringLocalizerFactory.cs deleted file mode 100644 index 4f44a41..0000000 --- a/Atomx.Admin/Atomx.Admin.Client/Services/JsonStringLocalizerFactory.cs +++ /dev/null @@ -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); - } - } -} diff --git a/Atomx.Admin/Atomx.Admin.Client/Services/LanguageProvider.cs b/Atomx.Admin/Atomx.Admin.Client/Services/LanguageProvider.cs deleted file mode 100644 index f079134..0000000 --- a/Atomx.Admin/Atomx.Admin.Client/Services/LanguageProvider.cs +++ /dev/null @@ -1,119 +0,0 @@ -using Microsoft.AspNetCore.Components; -using Microsoft.JSInterop; - -namespace Atomx.Admin.Client.Services -{ - /// - /// 语言提供者服务 - /// - 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 SupportedLanguages { get; } = new() - { - "zh-Hans", // 简体中文 - "en-US" // 英文(美国) - }; - - /// - /// 初始化语言 - /// - public async Task InitializeAsync() - { - // 尝试从本地存储获取保存的语言 - Console.WriteLine("尝试从本地存储获取保存的语言 Initializing LanguageProvider..."); - try - { - - var savedLanguage = await _jsRuntime.InvokeAsync("localStorage.getItem", "preferred-language"); - if (!string.IsNullOrEmpty(savedLanguage) && SupportedLanguages.Contains(savedLanguage)) - { - CurrentLanguage = savedLanguage; - } - else - { - // 从浏览器获取语言 - var browserLanguage = await _jsRuntime.InvokeAsync("getBrowserLanguage"); - CurrentLanguage = GetSupportedLanguage(browserLanguage); - } - } - catch - { - // JS互操作可能不可用(在预渲染时) - CurrentLanguage = "zh-Hans"; - } - } - - /// - /// 切换语言 - /// - 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(); - } - } - - /// - /// 获取支持的语言 - /// - 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"; // 默认语言 - } - } -} diff --git a/Atomx.Admin/Atomx.Admin.Client/Services/LocalizationService.cs b/Atomx.Admin/Atomx.Admin.Client/Services/LocalizationService.cs new file mode 100644 index 0000000..47f0f17 --- /dev/null +++ b/Atomx.Admin/Atomx.Admin.Client/Services/LocalizationService.cs @@ -0,0 +1,53 @@ +using System.Globalization; + +namespace Atomx.Admin.Client.Services +{ + public interface ILocalizationService + { + /// + /// 获取当前文化环境 + /// d + CultureInfo CurrentCulture { get; } + + /// + /// 当语言发生改变时触发的事件。 + /// + event EventHandler LanguageChanged; + + /// + /// 当语言发生改变时触发的事件。调用 来更改语言环境。 + /// + /// + void SetLanguage(CultureInfo culture); + } + + + public class LocalizationService : ILocalizationService + { + private CultureInfo? _currentCulture; + + /// + /// 获取当前文化环境 + /// + public CultureInfo CurrentCulture => _currentCulture ?? CultureInfo.CurrentCulture; + + public event EventHandler 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); + } + } + } +} diff --git a/Atomx.Admin/Atomx.Admin.Client/Utils/LocalizedComponentBase.cs b/Atomx.Admin/Atomx.Admin.Client/Utils/LocalizedComponentBase.cs deleted file mode 100644 index a51c906..0000000 --- a/Atomx.Admin/Atomx.Admin.Client/Utils/LocalizedComponentBase.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Atomx.Admin.Client.Services; -using Microsoft.AspNetCore.Components; - -namespace Atomx.Admin.Client.Utils -{ - /// - /// 继承此基类的组件会自动订阅 LanguageProvider 的语言变更事件并在变更时重新渲染。 - /// - public abstract class LocalizedComponentBase : ComponentBase, IDisposable - { - [Inject] - protected LanguageProvider LanguageProvider { get; set; } = null!; - - protected override void OnInitialized() - { - base.OnInitialized(); - LanguageProvider.OnLanguageChanged += LanguageChangedHandler; - } - - private void LanguageChangedHandler() - { - // 在组件上下文中安全调用 StateHasChanged - _ = InvokeAsync(StateHasChanged); - } - - public void Dispose() - { - LanguageProvider.OnLanguageChanged -= LanguageChangedHandler; - } - } -} diff --git a/Atomx.Admin/Atomx.Admin.Client/_Imports.razor b/Atomx.Admin/Atomx.Admin.Client/_Imports.razor index f107e44..91cc495 100644 --- a/Atomx.Admin/Atomx.Admin.Client/_Imports.razor +++ b/Atomx.Admin/Atomx.Admin.Client/_Imports.razor @@ -38,5 +38,4 @@ @inject NavigationManager Navigation @inject HttpService HttpService @inject MessageService MessageService -@inject ModalService ModalService -@inject LanguageProvider LanguageProvider \ No newline at end of file +@inject ModalService ModalService \ No newline at end of file diff --git a/Atomx.Admin/Atomx.Admin/Controllers/LocaleResourceController.cs b/Atomx.Admin/Atomx.Admin/Controllers/LocaleResourceController.cs index 3c5a844..d9765a2 100644 --- a/Atomx.Admin/Atomx.Admin/Controllers/LocaleResourceController.cs +++ b/Atomx.Admin/Atomx.Admin/Controllers/LocaleResourceController.cs @@ -1,5 +1,4 @@ -using AntDesign; -using Atomx.Admin.Client.Models; +using Atomx.Admin.Client.Models; using Atomx.Admin.Client.Validators; using Atomx.Admin.Services; using Atomx.Common.Entities; @@ -8,10 +7,8 @@ using Atomx.Data; using Atomx.Data.CacheServices; using Atomx.Data.Services; using Atomx.Utils.Models; -using IdGen; using MapsterMapper; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; namespace Atomx.Admin.Controllers { diff --git a/Atomx.Admin/Atomx.Admin/Middlewares/CultureMiddleware.cs b/Atomx.Admin/Atomx.Admin/Middlewares/CultureMiddleware.cs deleted file mode 100644 index 5a68045..0000000 --- a/Atomx.Admin/Atomx.Admin/Middlewares/CultureMiddleware.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Atomx.Admin.Client.Services; - -namespace Atomx.Admin.Middlewares -{ - /// - /// 文化设置中间件 - /// - public class CultureMiddleware - { - private readonly RequestDelegate _next; - - public CultureMiddleware(RequestDelegate next) - { - _next = next; - } - - public async Task InvokeAsync(HttpContext context, LanguageProvider languageProvider) - { - // 从查询字符串、Cookie或Header中获取语言设置 - var cultureQuery = context.Request.Query["culture"]; - var cultureCookie = context.Request.Cookies["preferred-language"]; - - string? culture = null; - - if (!string.IsNullOrEmpty(cultureQuery)) - { - culture = cultureQuery; - } - else if (!string.IsNullOrEmpty(cultureCookie)) - { - culture = cultureCookie; - } - - if (!string.IsNullOrEmpty(culture)) - { - var supportedCultures = new[] { "zh-Hans", "en-US" }; - if (supportedCultures.Contains(culture)) - { - var cultureInfo = new System.Globalization.CultureInfo(culture); - System.Threading.Thread.CurrentThread.CurrentCulture = cultureInfo; - System.Threading.Thread.CurrentThread.CurrentUICulture = cultureInfo; - } - } - - await _next(context); - } - } -} diff --git a/Atomx.Admin/Atomx.Admin/Program.cs b/Atomx.Admin/Atomx.Admin/Program.cs index 4fb1109..fb8643a 100644 --- a/Atomx.Admin/Atomx.Admin/Program.cs +++ b/Atomx.Admin/Atomx.Admin/Program.cs @@ -60,26 +60,6 @@ builder.Services.AddHttpContextAccessor(); // HttpClient ݷ builder.Services.AddHttpClientApiService(builder.Configuration["WebApi:ServerUrl"] ?? "http://localhost"); -// עJSONػ -builder.Services.AddSingleton(sp => -{ - var env = sp.GetRequiredService(); - var resourcesPath = Path.Combine(env.WebRootPath, "localization"); - // Դ DI ȡ IHttpClientFactoryʹã˵ new HttpClient() - var httpFactory = sp.GetService(); - HttpClient httpClient; - if (httpFactory != null) - { - httpClient = httpFactory.CreateClient(); - } - else - { - // ڷҪʹļϵͳȡļ˴ HttpClient ֻΪҪ httpClient 캯Ĺ - httpClient = new HttpClient(); - } - - return new JsonStringLocalizerFactory(resourcesPath, httpClient); -}); // עIStringLocalizer builder.Services.AddTransient(typeof(IStringLocalizer<>), typeof(StringLocalizer<>)); @@ -99,8 +79,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -// עṩߣΪScoped -builder.Services.AddScoped(); + builder.Services.AddScoped(); // SignalR÷ Hub ֧֣ע⣺JWT OnMessageReceived AuthorizationExtension д