fix culture
This commit is contained in:
@@ -27,7 +27,7 @@ builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.
|
|||||||
|
|
||||||
// ע<>᱾<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>
|
// ע<>᱾<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 =>
|
builder.Services.AddScoped<IStringLocalizerFactory>(sp =>
|
||||||
new JsonStringLocalizerFactory("Localization", sp.GetRequiredService<HttpClient>()));
|
new JsonStringLocalizerFactory("localization", sp.GetRequiredService<HttpClient>()));
|
||||||
|
|
||||||
// ע<><D7A2>IStringLocalizer
|
// ע<><D7A2>IStringLocalizer
|
||||||
builder.Services.AddTransient(typeof(IStringLocalizer<>), typeof(StringLocalizer<>));
|
builder.Services.AddTransient(typeof(IStringLocalizer<>), typeof(StringLocalizer<>));
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace Atomx.Admin.Client.Services
|
|||||||
// 在 Blazor WebAssembly 场景下,会使用注入的 HttpClient 从 wwwroot/Localization/{culture}.json 获取资源
|
// 在 Blazor WebAssembly 场景下,会使用注入的 HttpClient 从 wwwroot/Localization/{culture}.json 获取资源
|
||||||
public JsonStringLocalizer(string resourcesPath, HttpClient? httpClient = null)
|
public JsonStringLocalizer(string resourcesPath, HttpClient? httpClient = null)
|
||||||
{
|
{
|
||||||
_resourcesPath = (resourcesPath ?? "Localization").Trim('/'); // 规范化
|
_resourcesPath = (resourcesPath ?? "localization").Trim('/'); // 规范化
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
using System.Net.Http;
|
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Services
|
namespace Atomx.Admin.Client.Services
|
||||||
{
|
{
|
||||||
@@ -10,7 +9,7 @@ namespace Atomx.Admin.Client.Services
|
|||||||
|
|
||||||
public JsonStringLocalizerFactory(string resourcesPath, HttpClient httpClient)
|
public JsonStringLocalizerFactory(string resourcesPath, HttpClient httpClient)
|
||||||
{
|
{
|
||||||
_resourcesPath = (resourcesPath ?? "Localization").Trim('/');
|
_resourcesPath = (resourcesPath ?? "localization").Trim('/');
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
using System.Globalization;
|
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Services
|
namespace Atomx.Admin.Client.Services
|
||||||
{
|
{
|
||||||
@@ -29,19 +28,6 @@ namespace Atomx.Admin.Client.Services
|
|||||||
if (_currentLanguage != value)
|
if (_currentLanguage != value)
|
||||||
{
|
{
|
||||||
_currentLanguage = value;
|
_currentLanguage = value;
|
||||||
|
|
||||||
// 设置全局线程文化,确保 IStringLocalizer 等在随后的渲染中读取到新文化
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var ci = new CultureInfo(value);
|
|
||||||
CultureInfo.DefaultThreadCurrentCulture = ci;
|
|
||||||
CultureInfo.DefaultThreadCurrentUICulture = ci;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// 忽略无效 culture 字符串
|
|
||||||
}
|
|
||||||
|
|
||||||
OnLanguageChanged?.Invoke();
|
OnLanguageChanged?.Invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,8 +45,10 @@ namespace Atomx.Admin.Client.Services
|
|||||||
public async Task InitializeAsync()
|
public async Task InitializeAsync()
|
||||||
{
|
{
|
||||||
// 尝试从本地存储获取保存的语言
|
// 尝试从本地存储获取保存的语言
|
||||||
|
Console.WriteLine("尝试从本地存储获取保存的语言 Initializing LanguageProvider...");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
var savedLanguage = await _jsRuntime.InvokeAsync<string>("localStorage.getItem", "preferred-language");
|
var savedLanguage = await _jsRuntime.InvokeAsync<string>("localStorage.getItem", "preferred-language");
|
||||||
if (!string.IsNullOrEmpty(savedLanguage) && SupportedLanguages.Contains(savedLanguage))
|
if (!string.IsNullOrEmpty(savedLanguage) && SupportedLanguages.Contains(savedLanguage))
|
||||||
{
|
{
|
||||||
@@ -85,6 +73,7 @@ namespace Atomx.Admin.Client.Services
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task ChangeLanguageAsync(string languageCode)
|
public async Task ChangeLanguageAsync(string languageCode)
|
||||||
{
|
{
|
||||||
|
Console.WriteLine("切换语言 ChangeLanguageAsync to " + languageCode);
|
||||||
if (SupportedLanguages.Contains(languageCode) && CurrentLanguage != languageCode)
|
if (SupportedLanguages.Contains(languageCode) && CurrentLanguage != languageCode)
|
||||||
{
|
{
|
||||||
CurrentLanguage = languageCode;
|
CurrentLanguage = languageCode;
|
||||||
@@ -99,7 +88,8 @@ namespace Atomx.Admin.Client.Services
|
|||||||
// 忽略错误
|
// 忽略错误
|
||||||
}
|
}
|
||||||
|
|
||||||
// setter 已触发 OnLanguageChanged
|
// 通知语言已更改
|
||||||
|
OnLanguageChanged?.Invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,10 +35,10 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Update="Localization\en-US.json">
|
<Content Update="wwwroot\localization\en-US.json">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Update="Localization\zh-Hans.json">
|
<Content Update="wwwroot\localization\zh-Hans.json">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -13,11 +13,11 @@
|
|||||||
<link rel="stylesheet" href="@Assets["Atomx.Admin.styles.css"]" />
|
<link rel="stylesheet" href="@Assets["Atomx.Admin.styles.css"]" />
|
||||||
<ImportMap />
|
<ImportMap />
|
||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
<link rel="icon" type="image/png" href="favicon.png" />
|
||||||
<HeadOutlet @rendermode="InteractiveServer" />
|
<HeadOutlet @rendermode="InteractiveAuto" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<Routes @rendermode="InteractiveServer" />
|
<Routes @rendermode="InteractiveAuto" />
|
||||||
|
|
||||||
<script src="_framework/blazor.web.js"></script>
|
<script src="_framework/blazor.web.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
namespace Atomx.Admin.Middlewares
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 从请求路径的首段提取短语言码(如 /zh/ 或 /en/),
|
||||||
|
/// 将其映射为 Culture(如 zh-Hans / en-US),设置线程 Culture,
|
||||||
|
/// 并在继续管线前把该段从 Request.Path 中移除以便路由匹配。
|
||||||
|
/// </summary>
|
||||||
|
public class RequestCultureMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
|
||||||
|
// 可扩展短码与完整 culture 的映射
|
||||||
|
private static readonly Dictionary<string, string> ShortToCulture = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
{ "zh", "zh-Hans" },
|
||||||
|
{ "en", "en-US" }
|
||||||
|
};
|
||||||
|
|
||||||
|
public RequestCultureMiddleware(RequestDelegate next)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InvokeAsync(HttpContext context)
|
||||||
|
{
|
||||||
|
var path = context.Request.Path.Value ?? "/";
|
||||||
|
var trimmed = path.Trim('/');
|
||||||
|
if (!string.IsNullOrEmpty(trimmed))
|
||||||
|
{
|
||||||
|
var segments = trimmed.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
var first = segments.FirstOrDefault();
|
||||||
|
if (!string.IsNullOrEmpty(first) && ShortToCulture.TryGetValue(first, out var cultureName))
|
||||||
|
{
|
||||||
|
// 设置线程 Culture(影响后续处理中 IStringLocalizer / 日期格式等)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var ci = new CultureInfo(cultureName);
|
||||||
|
CultureInfo.DefaultThreadCurrentCulture = ci;
|
||||||
|
CultureInfo.DefaultThreadCurrentUICulture = ci;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 忽略非法 culture
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将请求路径去掉首段语言前缀,便于后续路由匹配(例如 /zh/account -> /account)
|
||||||
|
var remaining = segments.Length > 1 ? "/" + string.Join('/', segments.Skip(1)) : "/";
|
||||||
|
|
||||||
|
var originalPath = context.Request.Path;
|
||||||
|
context.Request.Path = new PathString(remaining);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _next(context);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// 恢复原始 path(以防其他中间件依赖)
|
||||||
|
context.Request.Path = originalPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await _next(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -64,7 +64,7 @@ builder.Services.AddHttpClientApiService(builder.Configuration["WebApi:ServerUrl
|
|||||||
builder.Services.AddSingleton<IStringLocalizerFactory>(sp =>
|
builder.Services.AddSingleton<IStringLocalizerFactory>(sp =>
|
||||||
{
|
{
|
||||||
var env = sp.GetRequiredService<IWebHostEnvironment>();
|
var env = sp.GetRequiredService<IWebHostEnvironment>();
|
||||||
var resourcesPath = Path.Combine(env.ContentRootPath, "Localization");
|
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()
|
// <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>();
|
var httpFactory = sp.GetService<IHttpClientFactory>();
|
||||||
HttpClient httpClient;
|
HttpClient httpClient;
|
||||||
@@ -194,6 +194,10 @@ app.UseResponseCompression();
|
|||||||
// ʹ<><CAB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> CORS <20><><EFBFBD><EFBFBD>
|
// ʹ<><CAB9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> CORS <20><><EFBFBD><EFBFBD>
|
||||||
app.UseCors("DefaultCors");
|
app.UseCors("DefaultCors");
|
||||||
|
|
||||||
|
//// ע<><D7A2> RequestCultureMiddleware<72><65>ʹ<EFBFBD>ⲿ<EFBFBD><E2B2BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Culture <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ǰ
|
||||||
|
//app.UseMiddleware<RequestCultureMiddleware>();
|
||||||
|
|
||||||
|
|
||||||
app.UseStaticFiles(new StaticFileOptions
|
app.UseStaticFiles(new StaticFileOptions
|
||||||
{
|
{
|
||||||
OnPrepareResponse = ctx =>
|
OnPrepareResponse = ctx =>
|
||||||
|
|||||||
Reference in New Issue
Block a user