chore
This commit is contained in:
@@ -1,61 +0,0 @@
|
|||||||
@inject LanguageProvider LanguageProvider
|
|
||||||
@inject IStringLocalizer<LanguageSelector> Localizer
|
|
||||||
|
|
||||||
<div class="dropdown">
|
|
||||||
<button class="btn btn-outline-secondary dropdown-toggle"
|
|
||||||
type="button"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
aria-expanded="false">
|
|
||||||
@GetLanguageDisplayName(LanguageProvider.CurrentLanguage)
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
@foreach (var language in LanguageProvider.SupportedLanguages)
|
|
||||||
{
|
|
||||||
<li>
|
|
||||||
<button class="dropdown-item @(language == LanguageProvider.CurrentLanguage ? "active" : "")"
|
|
||||||
@onclick="() => ChangeLanguage(language)"
|
|
||||||
type="button">
|
|
||||||
@GetLanguageDisplayName(language)
|
|
||||||
@if (language == LanguageProvider.CurrentLanguage)
|
|
||||||
{
|
|
||||||
<span class="badge bg-primary">✓</span>
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
@page "/account/login"
|
@page "/account/login"
|
||||||
|
@page "/{locale}/account/login"
|
||||||
@using System.Text.Json
|
@using System.Text.Json
|
||||||
@layout EmptyLayout
|
@layout EmptyLayout
|
||||||
@inject ILogger<Login> Logger
|
@inject ILogger<Login> Logger
|
||||||
@inject IJSRuntime JS
|
@inject IJSRuntime JS
|
||||||
@inject IStringLocalizer<Login> Localizer
|
|
||||||
|
|
||||||
<PageTitle>登录</PageTitle>
|
<PageTitle>登录</PageTitle>
|
||||||
|
|
||||||
@@ -13,10 +13,6 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<LanguageSelector></LanguageSelector>
|
|
||||||
<p>
|
|
||||||
@LanguageProvider.CurrentLanguage 网站名称 @Localizer["site.name"]
|
|
||||||
</p>
|
|
||||||
<Flex Style="height:100vh" Justify="FlexJustify.Center" Align="FlexAlign.Center" Direction="FlexDirection.Vertical">
|
<Flex Style="height:100vh" Justify="FlexJustify.Center" Align="FlexAlign.Center" Direction="FlexDirection.Vertical">
|
||||||
<GridRow Justify="RowJustify.Center" Class="">
|
<GridRow Justify="RowJustify.Center" Class="">
|
||||||
<GridCol>
|
<GridCol>
|
||||||
@@ -63,6 +59,9 @@ else
|
|||||||
@code {
|
@code {
|
||||||
string handler = "Server";
|
string handler = "Server";
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string Locale { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
[SupplyParameterFromQuery(Name = "ReturnUrl")]
|
[SupplyParameterFromQuery(Name = "ReturnUrl")]
|
||||||
public string? ReturnUrl { get; set; }
|
public string? ReturnUrl { get; set; }
|
||||||
|
|||||||
@@ -25,17 +25,12 @@ builder.Services.AddScoped<IconsExtension>();
|
|||||||
// <20><> HttpClient <20><><EFBFBD>ڼ<EFBFBD><DABC><EFBFBD> wwwroot/Localization/{culture}.json <20>Ⱦ<EFBFBD>̬<EFBFBD><CCAC><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>
|
// <20><> HttpClient <20><><EFBFBD>ڼ<EFBFBD><DABC><EFBFBD> wwwroot/Localization/{culture}.json <20>Ⱦ<EFBFBD>̬<EFBFBD><CCAC><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>
|
||||||
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
||||||
|
|
||||||
// ע<>᱾<EFBFBD>ػ<EFBFBD><D8BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>վ<EFBFBD><D5BE><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ¼<C4BF><C2BC> "Localization" <20><>ע<EFBFBD><D7A2><EFBFBD><EFBFBD><EFBFBD>ڼ<EFBFBD><DABC>ؾ<EFBFBD>̬<EFBFBD>ļ<EFBFBD><C4BC><EFBFBD> HttpClient<6E><74>
|
|
||||||
builder.Services.AddScoped<IStringLocalizerFactory>(sp =>
|
|
||||||
new JsonStringLocalizerFactory("localization", sp.GetRequiredService<HttpClient>()));
|
|
||||||
|
|
||||||
// ע<><D7A2>IStringLocalizer
|
// ע<><D7A2>IStringLocalizer
|
||||||
builder.Services.AddTransient(typeof(IStringLocalizer<>), typeof(StringLocalizer<>));
|
builder.Services.AddTransient(typeof(IStringLocalizer<>), typeof(StringLocalizer<>));
|
||||||
// <20><><EFBFBD>ӱ<EFBFBD><D3B1>ػ<EFBFBD><D8BB><EFBFBD><EFBFBD><EFBFBD>
|
// <20><><EFBFBD>ӱ<EFBFBD><D3B1>ػ<EFBFBD><D8BB><EFBFBD><EFBFBD><EFBFBD>
|
||||||
builder.Services.AddLocalization();
|
builder.Services.AddLocalization();
|
||||||
|
|
||||||
// ע<><D7A2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ṩ<EFBFBD><E1B9A9>
|
|
||||||
builder.Services.AddScoped<LanguageProvider>();
|
|
||||||
|
|
||||||
// ע<><D7A2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD><EFBFBD> token & ˢ<>µ<EFBFBD> DelegatingHandler
|
// ע<><D7A2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Զ<EFBFBD><D4B6><EFBFBD><EFBFBD><EFBFBD> token & ˢ<>µ<EFBFBD> DelegatingHandler
|
||||||
builder.Services.AddScoped<AuthHeaderHandler>();
|
builder.Services.AddScoped<AuthHeaderHandler>();
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
using Atomx.Admin.Client.Services;
|
|
||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Utils
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 继承此基类的组件会自动订阅 LanguageProvider 的语言变更事件并在变更时重新渲染。
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -39,4 +39,3 @@
|
|||||||
@inject HttpService HttpService
|
@inject HttpService HttpService
|
||||||
@inject MessageService MessageService
|
@inject MessageService MessageService
|
||||||
@inject ModalService ModalService
|
@inject ModalService ModalService
|
||||||
@inject LanguageProvider LanguageProvider
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using AntDesign;
|
using Atomx.Admin.Client.Models;
|
||||||
using Atomx.Admin.Client.Models;
|
|
||||||
using Atomx.Admin.Client.Validators;
|
using Atomx.Admin.Client.Validators;
|
||||||
using Atomx.Admin.Services;
|
using Atomx.Admin.Services;
|
||||||
using Atomx.Common.Entities;
|
using Atomx.Common.Entities;
|
||||||
@@ -8,10 +7,8 @@ using Atomx.Data;
|
|||||||
using Atomx.Data.CacheServices;
|
using Atomx.Data.CacheServices;
|
||||||
using Atomx.Data.Services;
|
using Atomx.Data.Services;
|
||||||
using Atomx.Utils.Models;
|
using Atomx.Utils.Models;
|
||||||
using IdGen;
|
|
||||||
using MapsterMapper;
|
using MapsterMapper;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
|
||||||
|
|
||||||
namespace Atomx.Admin.Controllers
|
namespace Atomx.Admin.Controllers
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
using Atomx.Admin.Client.Services;
|
|
||||||
|
|
||||||
namespace Atomx.Admin.Middlewares
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 文化设置中间件
|
|
||||||
/// </summary>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -60,26 +60,6 @@ builder.Services.AddHttpContextAccessor();
|
|||||||
// HttpClient <20><><EFBFBD><EFBFBD><EFBFBD>ݷ<EFBFBD><DDB7><EFBFBD>
|
// HttpClient <20><><EFBFBD><EFBFBD><EFBFBD>ݷ<EFBFBD><DDB7><EFBFBD>
|
||||||
builder.Services.AddHttpClientApiService(builder.Configuration["WebApi:ServerUrl"] ?? "http://localhost");
|
builder.Services.AddHttpClientApiService(builder.Configuration["WebApi:ServerUrl"] ?? "http://localhost");
|
||||||
|
|
||||||
// ע<><D7A2>JSON<4F><4E><EFBFBD>ػ<EFBFBD><D8BB><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
builder.Services.AddSingleton<IStringLocalizerFactory>(sp =>
|
|
||||||
{
|
|
||||||
var env = sp.GetRequiredService<IWebHostEnvironment>();
|
|
||||||
var resourcesPath = Path.Combine(env.WebRootPath, "localization");
|
|
||||||
// <20><><EFBFBD>Դ<EFBFBD> DI <20><>ȡ IHttpClientFactory<72><79><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʹ<EFBFBD>ã<EFBFBD><C3A3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˵<EFBFBD> new HttpClient()
|
|
||||||
var httpFactory = sp.GetService<IHttpClientFactory>();
|
|
||||||
HttpClient httpClient;
|
|
||||||
if (httpFactory != null)
|
|
||||||
{
|
|
||||||
httpClient = httpFactory.CreateClient();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// <20>ڷ<EFBFBD><DAB7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫʹ<D2AA><CAB9><EFBFBD>ļ<EFBFBD>ϵͳ<CFB5><CDB3>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>˴<EFBFBD><CBB4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> HttpClient ֻ<><D6BB>Ϊ<EFBFBD><CEAA><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҫ httpClient <20><><EFBFBD>캯<EFBFBD><ECBAAF><EFBFBD>Ĺ<EFBFBD><C4B9><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
httpClient = new HttpClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new JsonStringLocalizerFactory(resourcesPath, httpClient);
|
|
||||||
});
|
|
||||||
|
|
||||||
// ע<><D7A2>IStringLocalizer
|
// ע<><D7A2>IStringLocalizer
|
||||||
builder.Services.AddTransient(typeof(IStringLocalizer<>), typeof(StringLocalizer<>));
|
builder.Services.AddTransient(typeof(IStringLocalizer<>), typeof(StringLocalizer<>));
|
||||||
@@ -99,8 +79,7 @@ builder.Services.AddScoped<PersistentAuthenticationStateProvider>();
|
|||||||
builder.Services.AddScoped<IIdCreatorService, IdCreatorService>();
|
builder.Services.AddScoped<IIdCreatorService, IdCreatorService>();
|
||||||
builder.Services.AddScoped<IIdentityService, IdentityService>();
|
builder.Services.AddScoped<IIdentityService, IdentityService>();
|
||||||
builder.Services.AddScoped<LocalizationFile, LocalizationFile>();
|
builder.Services.AddScoped<LocalizationFile, LocalizationFile>();
|
||||||
// ע<><D7A2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ṩ<EFBFBD>ߣ<EFBFBD><DFA3><EFBFBD>ΪScoped<65><64><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
builder.Services.AddScoped<LanguageProvider>();
|
|
||||||
builder.Services.AddScoped<AuthHeaderHandler>();
|
builder.Services.AddScoped<AuthHeaderHandler>();
|
||||||
|
|
||||||
// SignalR<6C><52><EFBFBD><EFBFBD><EFBFBD>÷<EFBFBD><C3B7><EFBFBD><EFBFBD><EFBFBD> Hub ֧<>֣<EFBFBD>ע<EFBFBD>⣺JWT <20><> OnMessageReceived <20><><EFBFBD><EFBFBD> AuthorizationExtension <20>д<EFBFBD><D0B4><EFBFBD><EFBFBD><EFBFBD>
|
// SignalR<6C><52><EFBFBD><EFBFBD><EFBFBD>÷<EFBFBD><C3B7><EFBFBD><EFBFBD><EFBFBD> Hub ֧<>֣<EFBFBD>ע<EFBFBD>⣺JWT <20><> OnMessageReceived <20><><EFBFBD><EFBFBD> AuthorizationExtension <20>д<EFBFBD><D0B4><EFBFBD><EFBFBD><EFBFBD>
|
||||||
|
|||||||
Reference in New Issue
Block a user