实现多语言切换
This commit is contained in:
517
Atomx.Admin/Atomx.Admin.Client/Services/LocalizationProvider.cs
Normal file
517
Atomx.Admin/Atomx.Admin.Client/Services/LocalizationProvider.cs
Normal file
@@ -0,0 +1,517 @@
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using Microsoft.JSInterop;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace Atomx.Admin.Client.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// <20>ṩ<EFBFBD><E1B9A9><EFBFBD><EFBFBD><EFBFBD><EFBFBD> JSON <20>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ء<EFBFBD><D8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD>л<EFBFBD><D0BB><EFBFBD>ʵ<EFBFBD>֡<EFBFBD>
|
||||
/// - <20><> Server <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͨ<EFBFBD><CDA8><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> IWebHostEnvironment <20><> webroot<6F><74><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>ϵͳ<CFB5><CDB3>ȡ {culture}.json <20>ļ<EFBFBD><C4BC><EFBFBD>
|
||||
/// - <20><> WASM <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>ע<EFBFBD><D7A2><EFBFBD><EFBFBD> HttpClient <20><> /localization/{culture}.json <20><><EFBFBD>ز<EFBFBD><D8B2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
/// ͬʱ<CDAC><CAB1><EFBFBD>л<EFBFBD><D0BB><EFBFBD><EFBFBD><EFBFBD>ʱд<CAB1><D0B4> Cookie <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ҳ<EFBFBD><D2B3> HTML lang <20><><EFBFBD>ԡ<EFBFBD>
|
||||
/// </summary>
|
||||
public interface ILocalizationProvider
|
||||
{
|
||||
string CurrentCulture { get; }
|
||||
string? GetString(string key);
|
||||
Task SetCultureAsync(string cultureShortOrFull);
|
||||
Task InitializeAsync();
|
||||
/// <summary>
|
||||
/// <20><>ָ֤<D6A4><D6B8><EFBFBD>Ļ<EFBFBD><C4BB><EFBFBD><EFBFBD><EFBFBD>Դ<EFBFBD>ѱ<EFBFBD><D1B1><EFBFBD><EFBFBD>أ<EFBFBD><D8A3>첽<EFBFBD><ECB2BD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ⲿ<EFBFBD><E2B2BF><EFBFBD><EFBFBD>Ҫʱ<D2AA><CAB1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ء<EFBFBD>
|
||||
/// </summary>
|
||||
Task LoadCultureAsync(string culture);
|
||||
event EventHandler<string>? LanguageChanged;
|
||||
}
|
||||
|
||||
public class LocalizationProvider : ILocalizationProvider
|
||||
{
|
||||
private readonly IServiceProvider _sp;
|
||||
private readonly IHttpClientFactory? _httpClientFactory;
|
||||
private readonly IJSRuntime? _jsRuntime;
|
||||
private readonly ILogger<LocalizationProvider> _logger;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
|
||||
// <20><><EFBFBD>棺culture -> translations
|
||||
private readonly Dictionary<string, Dictionary<string, string>> _cache = new();
|
||||
|
||||
// <20><><EFBFBD>뵽<EFBFBD><EBB5BD><EFBFBD><EFBFBD> culture <20><>ӳ<EFBFBD><D3B3>
|
||||
private static readonly Dictionary<string, string> ShortToCulture = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "zh", "zh-Hans" },
|
||||
{ "en", "en-US" }
|
||||
};
|
||||
|
||||
private string _currentCulture = "zh-Hans";
|
||||
|
||||
private const string CookieName = "atomx.culture";
|
||||
|
||||
public LocalizationProvider(IServiceProvider sp, IHttpClientFactory? httpClientFactory, IJSRuntime? jsRuntime, ILogger<LocalizationProvider> logger, ILocalizationService localizationService)
|
||||
{
|
||||
_sp = sp;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_jsRuntime = jsRuntime;
|
||||
_logger = logger;
|
||||
_localizationService = localizationService;
|
||||
|
||||
// <20><><EFBFBD>ڹ<EFBFBD><DAB9>캯<EFBFBD><ECBAAF><EFBFBD>н<EFBFBD><D0BD><EFBFBD> JS <20><><EFBFBD><EFBFBD>ͬ<EFBFBD><CDAC> IO<49><4F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ը<EFBFBD><D4B8><EFBFBD><EFBFBD>߳<EFBFBD> culture <20><><EFBFBD>ó<EFBFBD>ʼֵ<CABC><D6B5><EFBFBD><EFBFBD>һ<EFBFBD><D2BB>Ϊ<EFBFBD><CEAA><EFBFBD><EFBFBD> culture
|
||||
try
|
||||
{
|
||||
var threadUi = CultureInfo.DefaultThreadCurrentUICulture ?? CultureInfo.CurrentUICulture;
|
||||
if (!string.IsNullOrEmpty(threadUi?.Name))
|
||||
{
|
||||
_currentCulture = MapToFullCulture(threadUi!.Name);
|
||||
_logger?.LogDebug("LocalizationProvider ctor detected thread UI culture: {Culture}", _currentCulture);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogDebug(ex, "LocalizationProvider ctor failed to read thread culture");
|
||||
}
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Server<65><72>IWebHostEnvironment <20><><EFBFBD>ã<EFBFBD><C3A3><EFBFBD><EFBFBD><EFBFBD> JSRuntime <20><><EFBFBD><EFBFBD><EFBFBD>ã<EFBFBD>˵<EFBFBD><CBB5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͬ<EFBFBD><CDAC><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD><C4BC><EFBFBD><EFBFBD>ر<EFBFBD><D8B1>ػ<EFBFBD><D8BB>ļ<EFBFBD>
|
||||
try
|
||||
{
|
||||
var envType = Type.GetType("Microsoft.AspNetCore.Hosting.IWebHostEnvironment, Microsoft.AspNetCore.Hosting.Abstractions")
|
||||
?? Type.GetType("Microsoft.AspNetCore.Hosting.IWebHostEnvironment");
|
||||
if (envType != null)
|
||||
{
|
||||
var env = _sp.GetService(envType);
|
||||
if (env != null && _jsRuntime == null)
|
||||
{
|
||||
var webRootProp = envType.GetProperty("WebRootPath");
|
||||
var contentRootProp = envType.GetProperty("ContentRootPath");
|
||||
var webRoot = webRootProp?.GetValue(env) as string ?? Path.Combine(contentRootProp?.GetValue(env) as string ?? ".", "wwwroot");
|
||||
var path = Path.Combine(webRoot ?? ".", "localization", _currentCulture + ".json");
|
||||
if (File.Exists(path))
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(path);
|
||||
var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(json) ?? new Dictionary<string, string>();
|
||||
_cache[_currentCulture] = dict;
|
||||
_logger?.LogInformation("Loaded localization file for {Culture} from path {Path}, entries: {Count}", _currentCulture, path, dict.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogWarning(ex, "Failed to read localization file synchronously: {Path}", path);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger?.LogDebug("Localization file not found at {Path}", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogDebug(ex, "Synchronous file load attempt failed in ctor");
|
||||
}
|
||||
}
|
||||
|
||||
public string CurrentCulture => _currentCulture;
|
||||
|
||||
public event EventHandler<string>? LanguageChanged;
|
||||
|
||||
public string? GetString(string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key)) return null;
|
||||
|
||||
if (_cache.TryGetValue(_currentCulture, out var dict) && dict.TryGetValue(key, out var val))
|
||||
{
|
||||
return val;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
_logger?.LogDebug("LocalizationProvider.InitializeAsync start. CurrentCulture={Culture}", _currentCulture);
|
||||
|
||||
string? urlFirstSegment = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (_jsRuntime != null && OperatingSystem.IsBrowser())
|
||||
{
|
||||
var path = await _jsRuntime.InvokeAsync<string>("eval", "location.pathname");
|
||||
_logger?.LogDebug("JS location.pathname='{Path}'", path);
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
var trimmed = path.Trim('/');
|
||||
if (!string.IsNullOrEmpty(trimmed))
|
||||
{
|
||||
var seg = trimmed.Split('/', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
|
||||
urlFirstSegment = seg;
|
||||
_logger?.LogDebug("Detected url first segment: {Segment}", urlFirstSegment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogDebug(ex, "<22><>ȡ location.pathname ʧ<><CAA7>");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(urlFirstSegment) && ShortToCulture.TryGetValue(urlFirstSegment, out var mapped))
|
||||
{
|
||||
_logger?.LogDebug("URL short segment '{Seg}' mapped to culture '{Culture}'", urlFirstSegment, mapped);
|
||||
await SetCultureInternalAsync(mapped, persistCookie: false);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_jsRuntime != null && OperatingSystem.IsBrowser())
|
||||
{
|
||||
var cookieVal = await _jsRuntime.InvokeAsync<string>("CookieReader.Read", CookieName);
|
||||
_logger?.LogDebug("Cookie '{CookieName}'='{CookieVal}'", CookieName, cookieVal);
|
||||
if (!string.IsNullOrEmpty(cookieVal))
|
||||
{
|
||||
if (ShortToCulture.TryGetValue(cookieVal, out var mappedFromCookie))
|
||||
{
|
||||
_logger?.LogDebug("Cookie short '{Cookie}' mapped to {Culture}", cookieVal, mappedFromCookie);
|
||||
await SetCultureInternalAsync(mappedFromCookie, persistCookie: false);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// <20><><EFBFBD><EFBFBD> cookie <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> culture<72><65><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʽ<EFBFBD><CABD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD><EFBFBD>ʹ<EFBFBD><CAB9>
|
||||
await SetCultureInternalAsync(MapToFullCulture(cookieVal), persistCookie: false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogDebug(ex, "<22><>ȡ Cookie ʧ<><CAA7>");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_jsRuntime != null && OperatingSystem.IsBrowser())
|
||||
{
|
||||
var browserLang = await _jsRuntime.InvokeAsync<string>("getBrowserLanguage");
|
||||
_logger?.LogDebug("Browser language: {BrowserLang}", browserLang);
|
||||
if (!string.IsNullOrEmpty(browserLang))
|
||||
{
|
||||
var mappedFromBrowser = MapToFullCulture(browserLang);
|
||||
_logger?.LogDebug("Browser mapped to {Culture}", mappedFromBrowser);
|
||||
await SetCultureInternalAsync(mappedFromBrowser, persistCookie: false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogDebug(ex, "<22><>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD><CAA7>");
|
||||
}
|
||||
|
||||
// <20><><EFBFBD><EFBFBD>ȷ<EFBFBD><C8B7><EFBFBD><EFBFBD><EFBFBD>ص<EFBFBD>ǰ culture
|
||||
_logger?.LogDebug("InitializeAsync falling back to current culture {Culture}", _currentCulture);
|
||||
await EnsureCultureLoadedAsync(_currentCulture);
|
||||
await SetCultureInternalAsync(_currentCulture, persistCookie: false);
|
||||
}
|
||||
|
||||
public async Task SetCultureAsync(string cultureShortOrFull)
|
||||
{
|
||||
if (string.IsNullOrEmpty(cultureShortOrFull)) return;
|
||||
if (ShortToCulture.TryGetValue(cultureShortOrFull, out var mapped))
|
||||
{
|
||||
await SetCultureInternalAsync(mapped, persistCookie: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
await SetCultureInternalAsync(MapToFullCulture(cultureShortOrFull), persistCookie: true);
|
||||
}
|
||||
}
|
||||
|
||||
public Task LoadCultureAsync(string culture) => EnsureCultureLoadedAsync(MapToFullCulture(culture));
|
||||
|
||||
private async Task SetCultureInternalAsync(string cultureFull, bool persistCookie)
|
||||
{
|
||||
_logger?.LogDebug("SetCultureInternalAsync start: {Culture}, persist={Persist}", cultureFull, persistCookie);
|
||||
await EnsureCultureLoadedAsync(cultureFull);
|
||||
|
||||
try
|
||||
{
|
||||
var ci = new CultureInfo(cultureFull);
|
||||
CultureInfo.DefaultThreadCurrentCulture = ci;
|
||||
CultureInfo.DefaultThreadCurrentUICulture = ci;
|
||||
_currentCulture = cultureFull;
|
||||
_localizationService.SetLanguage(ci);
|
||||
_logger?.LogDebug("Culture set to {Culture}", cultureFull);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogWarning(ex, "<22><><EFBFBD><EFBFBD> Culture ʧ<><CAA7>: {Culture}", cultureFull);
|
||||
}
|
||||
|
||||
if (persistCookie && _jsRuntime != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var shortKey = ShortToCulture.FirstOrDefault(kv => string.Equals(kv.Value, cultureFull, StringComparison.OrdinalIgnoreCase)).Key ?? cultureFull;
|
||||
await _jsRuntime.InvokeVoidAsync("cookies.Write", CookieName, shortKey, DateTime.UtcNow.AddYears(1).ToString("o"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogDebug(ex, "д Cookie ʧ<><CAA7>");
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (_jsRuntime != null)
|
||||
{
|
||||
await _jsRuntime.InvokeVoidAsync("setHtmlLang", cultureFull);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
LanguageChanged?.Invoke(this, cultureFull);
|
||||
}
|
||||
|
||||
private async Task EnsureCultureLoadedAsync(string cultureFull)
|
||||
{
|
||||
// Normalize possible short codes (e.g. zh -> zh-Hans, en -> en-US) and variants (zh-CN -> zh-Hans)
|
||||
cultureFull = MapToFullCulture(cultureFull);
|
||||
|
||||
if (string.IsNullOrEmpty(cultureFull)) return;
|
||||
if (_cache.ContainsKey(cultureFull))
|
||||
{
|
||||
_logger?.LogDebug("EnsureCultureLoadedAsync: culture {Culture} already cached", cultureFull);
|
||||
return;
|
||||
}
|
||||
|
||||
// Prefer HttpClient when running in browser (WASM)
|
||||
if (_jsRuntime != null && OperatingSystem.IsBrowser())
|
||||
{
|
||||
_logger?.LogInformation("EnsureCultureLoadedAsync: running in browser, will attempt HttpClient for {Culture}", cultureFull);
|
||||
try
|
||||
{
|
||||
var http = _sp.GetService(typeof(HttpClient)) as HttpClient;
|
||||
if (http == null && _httpClientFactory != null)
|
||||
{
|
||||
_logger?.LogDebug("HttpClient not found from service provider, using factory");
|
||||
http = _httpClientFactory.CreateClient();
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger?.LogDebug("HttpClient resolved from service provider: {HasClient}", http != null);
|
||||
}
|
||||
|
||||
if (http != null)
|
||||
{
|
||||
var url = $"/localization/{cultureFull}.json";
|
||||
Uri? requestUri = null;
|
||||
|
||||
// If HttpClient has a BaseAddress, use it. Otherwise, if running in browser, build absolute URI from location.origin
|
||||
if (http.BaseAddress != null)
|
||||
{
|
||||
requestUri = new Uri(http.BaseAddress, url);
|
||||
}
|
||||
else if (_jsRuntime != null && OperatingSystem.IsBrowser())
|
||||
{
|
||||
try
|
||||
{
|
||||
var origin = await _jsRuntime.InvokeAsync<string>("eval", "location.origin");
|
||||
if (!string.IsNullOrEmpty(origin))
|
||||
{
|
||||
// ensure no double slashes
|
||||
requestUri = new Uri(new Uri(origin), url);
|
||||
}
|
||||
}
|
||||
catch (Exception jsEx)
|
||||
{
|
||||
_logger?.LogDebug(jsEx, "Failed to get location.origin from JS");
|
||||
}
|
||||
}
|
||||
|
||||
if (requestUri != null)
|
||||
{
|
||||
_logger?.LogInformation("Downloading localization from {Url}", requestUri);
|
||||
var txt = await http.GetStringAsync(requestUri);
|
||||
var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(txt) ?? new Dictionary<string, string>();
|
||||
_cache[cultureFull] = dict;
|
||||
_logger?.LogInformation("Loaded localization via HttpClient for {Culture}, entries: {Count}", cultureFull, dict.Count);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger?.LogWarning("HttpClient has no BaseAddress and JSRuntime unavailable to construct absolute URL; skipping HttpClient load for {Culture}", cultureFull);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger?.LogWarning("No HttpClient available to load localization for {Culture}", cultureFull);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogDebug(ex, "ͨ<><CDA8> HttpClient <20><><EFBFBD>ر<EFBFBD><D8B1>ػ<EFBFBD><D8BB>ļ<EFBFBD>ʧ<EFBFBD><CAA7>: {Culture}", cultureFull);
|
||||
}
|
||||
}
|
||||
|
||||
_logger?.LogDebug("EnsureCultureLoadedAsync trying filesystem for {Culture}", cultureFull);
|
||||
// <20><><EFBFBD><EFBFBD>ͨ<EFBFBD><CDA8><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȡ IWebHostEnvironment<6E><74>Server ʱ<><CAB1><EFBFBD>ã<EFBFBD>
|
||||
try
|
||||
{
|
||||
var envType = Type.GetType("Microsoft.AspNetCore.Hosting.IWebHostEnvironment, Microsoft.AspNetCore.Hosting.Abstractions")
|
||||
?? Type.GetType("Microsoft.AspNetCore.Hosting.IWebHostEnvironment");
|
||||
if (envType != null)
|
||||
{
|
||||
var env = _sp.GetService(envType);
|
||||
if (env != null)
|
||||
{
|
||||
var webRootProp = envType.GetProperty("WebRootPath");
|
||||
var contentRootProp = envType.GetProperty("ContentRootPath");
|
||||
var webRoot = webRootProp?.GetValue(env) as string ?? Path.Combine(contentRootProp?.GetValue(env) as string ?? ".", "wwwroot");
|
||||
var path = Path.Combine(webRoot ?? ".", "localization", cultureFull + ".json");
|
||||
_logger?.LogDebug("Looking for localization file at {Path}", path);
|
||||
if (File.Exists(path))
|
||||
{
|
||||
var json = await File.ReadAllTextAsync(path);
|
||||
var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(json) ?? new Dictionary<string, string>();
|
||||
_cache[cultureFull] = dict;
|
||||
_logger?.LogInformation("Loaded localization from file for {Culture}, entries: {Count}", cultureFull, dict.Count);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger?.LogDebug("Localization file not found at {Path}", path);
|
||||
// Fallback: check build output wwwroot under AppContext.BaseDirectory
|
||||
try
|
||||
{
|
||||
var alt = Path.Combine(AppContext.BaseDirectory ?? ".", "wwwroot", "localization", cultureFull + ".json");
|
||||
_logger?.LogDebug("Looking for localization file at alternative path {AltPath}", alt);
|
||||
if (File.Exists(alt))
|
||||
{
|
||||
var json2 = await File.ReadAllTextAsync(alt);
|
||||
var dict2 = JsonSerializer.Deserialize<Dictionary<string, string>>(json2) ?? new Dictionary<string, string>();
|
||||
_cache[cultureFull] = dict2;
|
||||
_logger?.LogInformation("Loaded localization from alternative file path for {Culture}, entries: {Count}", cultureFull, dict2.Count);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger?.LogDebug("Localization file not found at alternative path {AltPath}", alt);
|
||||
}
|
||||
}
|
||||
catch (Exception exAlt)
|
||||
{
|
||||
_logger?.LogDebug(exAlt, "Error while checking alternative localization path");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger?.LogDebug("IWebHostEnvironment not resolved from service provider");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger?.LogDebug("IWebHostEnvironment type not found via reflection");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger?.LogDebug(ex, "<22><><EFBFBD>ļ<EFBFBD>ϵͳ<CFB5><CDB3><EFBFBD>ر<EFBFBD><D8B1>ػ<EFBFBD><D8BB>ļ<EFBFBD>ʧ<EFBFBD><CAA7>: {Culture}", cultureFull);
|
||||
}
|
||||
|
||||
_logger?.LogDebug("EnsureCultureLoadedAsync fallback to empty dict for {Culture}", cultureFull);
|
||||
_cache[cultureFull] = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
private string MapToFullCulture(string culture)
|
||||
{
|
||||
if (string.IsNullOrEmpty(culture)) return culture;
|
||||
// direct mapping
|
||||
if (ShortToCulture.TryGetValue(culture, out var mapped)) return mapped;
|
||||
// consider prefix, e.g. zh-CN -> zh
|
||||
var prefix = culture.Split('-', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
|
||||
if (!string.IsNullOrEmpty(prefix) && ShortToCulture.TryGetValue(prefix, out var mapped2)) return mapped2;
|
||||
return culture;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <20><><EFBFBD><EFBFBD> ILocalizationProvider <20><> IStringLocalizer ʵ<>֣<EFBFBD>
|
||||
/// ʹ<><CAB9> JSON <20>ļ<EFBFBD><C4BC>еļ<D0B5>ֵ<EFBFBD><D6B5>δ<EFBFBD>ҵ<EFBFBD><D2B5><EFBFBD><EFBFBD><EFBFBD> key <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
/// <20><><EFBFBD>Ƹ<EFBFBD>Ϊ JsonStringLocalizer <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ܵ<EFBFBD> StringLocalizer <20><>ͻ<EFBFBD><CDBB>
|
||||
/// </summary>
|
||||
public class JsonStringLocalizer<T> : IStringLocalizer<T>
|
||||
{
|
||||
private readonly ILocalizationProvider _provider;
|
||||
|
||||
public JsonStringLocalizer(ILocalizationProvider provider)
|
||||
{
|
||||
_provider = provider;
|
||||
}
|
||||
|
||||
public LocalizedString this[string name]
|
||||
{
|
||||
get
|
||||
{
|
||||
var value = _provider.GetString(name);
|
||||
if (value == null)
|
||||
{
|
||||
// Avoid synchronous blocking during server prerender. Start background load and return key.
|
||||
try
|
||||
{
|
||||
_ = _provider.LoadCultureAsync(_provider.CurrentCulture);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
var result = value ?? name;
|
||||
return new LocalizedString(name, result, resourceNotFound: result == name);
|
||||
}
|
||||
}
|
||||
|
||||
public LocalizedString this[string name, params object[] arguments]
|
||||
{
|
||||
get
|
||||
{
|
||||
var fmt = _provider.GetString(name);
|
||||
if (fmt == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = _provider.LoadCultureAsync(_provider.CurrentCulture);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
var format = fmt ?? name;
|
||||
var value = string.Format(format, arguments);
|
||||
return new LocalizedString(name, value, resourceNotFound: format == name);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures)
|
||||
{
|
||||
var list = new List<LocalizedString>();
|
||||
var providerType = _provider.GetType();
|
||||
var currentProp = providerType.GetProperty("CurrentCulture");
|
||||
var culture = currentProp?.GetValue(_provider) as string ?? string.Empty;
|
||||
var cacheField = providerType.GetField("_cache", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
if (!string.IsNullOrEmpty(culture) && cacheField?.GetValue(_provider) is Dictionary<string, Dictionary<string, string>> cache && cache.TryGetValue(culture, out var dict))
|
||||
{
|
||||
foreach (var kv in dict)
|
||||
{
|
||||
list.Add(new LocalizedString(kv.Key, kv.Value, resourceNotFound: false));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public IStringLocalizer WithCulture(CultureInfo culture)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user