@@ -1,16 +1,20 @@
using Microsoft.JSInterop ;
using System.Collections.Concurrent ;
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> <EFBFBD> <EFBFBD>
/// - <20> <> WASM <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʹ <EFBFBD> <EFBFBD> ע<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> HttpClient <20> <> /localization/{culture}.json <20> <> <EFBFBD> ز<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
/// ͬʱ<EFBFBD> <EFBFBD> <EFBFBD> л <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʱд<EFBFBD> <EFBFBD> Cookie <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ҳ <EFBFBD> <EFBFBD> HTML lang <20> <> <EFBFBD> ԡ <EFBFBD>
/// <20> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ṩ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> JSON <20> ļ<EFBFBD> <C4BC> ļ <EFBFBD> <EFBFBD> ء<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʱ<EFBFBD> л<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ܡ<EFBFBD>
/// <EFBFBD> <EFBFBD> Ҫְ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
/// - <20> <> Server <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> prerender <20> <> Blazor Server<65> <72> ʱ<EFBFBD> <CAB1> <EFBFBD> Դ<EFBFBD> webroot ͬ<> <CDAC> <EFBFBD> <EFBFBD> ȡ<EFBFBD> <C8A1> <EFBFBD> ػ<EFBFBD> JSON <20> ļ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> 棬
/// <20> Ա <EFBFBD> <EFBFBD> ڷ <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ⱦ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> á<EFBFBD>
/// - <20> <> WASM <20> <> <EFBFBD> <EFBFBD> ʱʹ <CAB1> <CAB9> ע<EFBFBD> <D7A2> <EFBFBD> <EFBFBD> HttpClient <20> <> /localization/{culture}.json <20> <> <EFBFBD> ز<EFBFBD> <D8B2> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
/// - <20> <> <EFBFBD> л<EFBFBD> <D0BB> <EFBFBD> <EFBFBD> <EFBFBD> ʱд<CAB1> <D0B4> <EFBFBD> <EFBFBD> Ϊ `atomx.culture` <20> <> cookie<69> <65> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ˶<EFBFBD> ȡ<EFBFBD> <C8A1> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ҳ<EFBFBD> <D2B3> <EFBFBD> <EFBFBD> HTML lang <20> <> <EFBFBD> ԡ<EFBFBD>
/// - <20> ṩ<EFBFBD> ¼<EFBFBD> ֪ͨ LanguageChanged<65> <64> <EFBFBD> <EFBFBD> UI <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ӧ<EFBFBD> <D3A6> <EFBFBD> Ա<EFBFBD> <D4B1> <EFBFBD> <EFBFBD> <EFBFBD>
///
/// ˵<> <CBB5> <EFBFBD> <EFBFBD> Ϊ<EFBFBD> ˼<EFBFBD> <CBBC> <EFBFBD> Server <20> <> WASM<53> <4D> <EFBFBD> <EFBFBD> Provider <20> ᾡ<EFBFBD> <E1BEA1> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʱ<EFBFBD> <CAB1> <EFBFBD> <EFBFBD> ѡ <EFBFBD> <D1A1> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʵ<EFBFBD> <CAB5> <EFBFBD> Դ<EFBFBD> <D4B4> <EFBFBD> ط<EFBFBD> ʽ <EFBFBD> <CABD>
/// </summary>
public interface ILocalizationProvider
{
@@ -25,6 +29,12 @@ namespace Atomx.Admin.Client.Services
event EventHandler < string > ? LanguageChanged ;
}
/// <summary>
/// LocalizationProvider <20> <> ʵ<EFBFBD> ֣<EFBFBD>
/// - ά<> <CEAC> һ <EFBFBD> <D2BB> <EFBFBD> <EFBFBD> ̬<EFBFBD> <CCAC> <EFBFBD> 棬<EFBFBD> <E6A3AC> <EFBFBD> <EFBFBD> <EFBFBD> ظ<EFBFBD> <D8B8> <EFBFBD> <EFBFBD> <EFBFBD> /<2F> <> ȡ<EFBFBD> <C8A1> Դ<EFBFBD> <D4B4>
/// - ֧<> ֶ<EFBFBD> <D6B6> 루<EFBFBD> <EBA3A8> zh / en<65> <6E> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> culture<72> <65> <EFBFBD> <EFBFBD> zh-Hans / en-US<55> <53> ֮<EFBFBD> <D6AE> <EFBFBD> <EFBFBD> ӳ<EFBFBD> 䡣
/// - <20> <> Server <20> <> ֧<EFBFBD> <D6A7> ͬ<EFBFBD> <CDAC> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> prerender <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
/// </summary>
public class LocalizationProvider : ILocalizationProvider
{
private readonly IServiceProvider _sp ;
@@ -33,22 +43,26 @@ namespace Atomx.Admin.Client.Services
private readonly ILogger < LocalizationProvider > _logger ;
private readonly ILocalizationService _localizationService ;
// <20> <> <EFBFBD> 棺culture -> translations
// Use a static concurrent dictionary so files loaded during middleware/server prerender
// are visible to provider instances created later in the same request pipeline.
// <20> <> <EFBFBD> 棺culture -> translations<EFBFBD> <EFBFBD> ʹ <EFBFBD> <EFBFBD> ConcurrentDictionary <20> <> <EFBFBD> ̰߳<DFB3> ȫ<EFBFBD> ع<EFBFBD> <D8B9> <EFBFBD> <EFBFBD> <EFBFBD>
// ʹ <EFBFBD> þ<EFBFBD> ̬<EFBFBD> ֶ<EFBFBD> <EFBFBD> <EFBFBD> Ϊ<EFBFBD> <EFBFBD> ʹ <EFBFBD> м<EFBFBD> <EFBFBD> <EFBFBD> /<2F> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ͬһ <CDAC> <D2BB> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ܹ<EFBFBD> <DCB9> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ѽ<EFBFBD> <D1BC> صķ<D8B5> <C4B7> 룬<EFBFBD> <EBA3AC> <EFBFBD> <EFBFBD> <EFBFBD> ظ<EFBFBD> I/O<> <4F>
private static readonly ConcurrentDictionary < string , Dictionary < string , string > > _cache = new ( ) ;
// <20> <> <EFBFBD> 뵽 <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> culture <20> <> ӳ<EFBFBD> <EFBFBD>
// ֧<EFBFBD> ֵĶ<EFBFBD> <EFBFBD> <EFBFBD> ӳ<EFBFBD> 䣬<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> չ <EFBFBD> <EFBFBD> <EFBFBD> ڴ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ӡ <EFBFBD>
private static readonly Dictionary < string , string > ShortToCulture = new ( StringComparer . OrdinalIgnoreCase )
{
{ "zh" , "zh-Hans" } ,
{ "en" , "en-US" }
} ;
// Ĭ<> <C4AC> <EFBFBD> Ļ<EFBFBD> <C4BB> <EFBFBD> <EFBFBD> <EFBFBD> δ<EFBFBD> ܴ<EFBFBD> <DCB4> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> /URL/Cookie <20> н<EFBFBD> <D0BD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ļ<EFBFBD> ʱʹ <CAB1> ã<EFBFBD>
private string _currentCulture = "zh-Hans" ;
private const string CookieName = "atomx.culture" ;
/// <summary>
/// <20> <> <EFBFBD> 캯<EFBFBD> <ECBAAF> <EFBFBD> <EFBFBD> ͨ<EFBFBD> <CDA8> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ע<EFBFBD> <D7A2> <EFBFBD> <EFBFBD> ȡ<EFBFBD> <C8A1> Ҫ<EFBFBD> <D2AA> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
/// ע<> <EFBFBD> 캯<EFBFBD> <ECBAAF> <EFBFBD> в<EFBFBD> Ӧִ<D3A6> к<EFBFBD> ʱ<EFBFBD> <CAB1> JS <20> <> <EFBFBD> ص<EFBFBD> ͬ<EFBFBD> <CDAC> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
/// </summary>
public LocalizationProvider ( IServiceProvider sp , ILogger < LocalizationProvider > logger , IHttpClientFactory ? httpClientFactory , IJSRuntime ? jsRuntime , ILocalizationService localizationService )
{
_sp = sp ;
@@ -57,22 +71,22 @@ namespace Atomx.Admin.Client.Services
_logger = logger ;
_localizationService = localizationService ;
// <20> <> <EFBFBD> ڹ<EFBFBD> <EFBFBD> 캯<EFBFBD> <EFBFBD> <EFBFBD> н<EFBFBD> <EFBFBD> <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> <EFBFBD> <EFBFBD> <EFBFBD> culture
// <20> <> <EFBFBD> Ը<EFBFBD> <EFBFBD> ݵ<EFBFBD> ǰ<EFBFBD> ߳<EFBFBD> culture <20> <> ʼ <EFBFBD> <CABC> _currentCulture<72> <65> <EFBFBD> <EFBFBD> <EFBFBD> ׳ <EFBFBD> <EFBFBD> 쳣 <EFBFBD> <EFBFBD>
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 ) ;
_logger . LogDebug ( "LocalizationProvider <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ߳<EFBFBD> UI <20> Ļ<EFBFBD> : {Culture}" , _currentCulture ) ;
}
}
catch ( Exception ex )
{
_logger ? .LogDebug ( ex , "LocalizationProvider ctor failed to read thread culture " ) ;
_logger . LogDebug ( ex , "LocalizationProvider <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ȡ<EFBFBD> ߳<EFBFBD> <EFBFBD> Ļ<EFBFBD> ʧ<EFBFBD> <EFBFBD> " ) ;
}
// <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Server<65> <72> IWebHostEnvironment <EFBFBD> <EFBFBD> <EFBFBD> ã <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> JSRuntime <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ã<EFBFBD> ˵ <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ͬ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ļ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ر<EFBFBD> <EFBFBD> ػ<EFBFBD> <EFBFBD> ļ<EFBFBD>
// <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Server <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> δ <EFBFBD> <EFBFBD> ʹ <EFBFBD> <EFBFBD> JSRuntime<6D> <65> <EFBFBD> <EFBFBD> ζ<EFBFBD> ŷ <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ͬ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ļ<EFBFBD> ϵͳ<EFBFBD> <EFBFBD> ȡ <EFBFBD> <EFBFBD> <EFBFBD> ػ<EFBFBD> <EFBFBD> ļ<EFBFBD> <EFBFBD> <EFBFBD> ֧<EFBFBD> <EFBFBD> prerender
try
{
var envType = Type . GetType ( "Microsoft.AspNetCore.Hosting.IWebHostEnvironment, Microsoft.AspNetCore.Hosting.Abstractions" )
@@ -93,23 +107,23 @@ namespace Atomx.Admin.Client.Services
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 ) ;
_logger . LogInformation ( "(Server ͬ<> <CDAC> ) <20> <> ·<EFBFBD> <C2B7> <EFBFBD> <EFBFBD> <EFBFBD> ر<EFBFBD> <D8B1> ػ<EFBFBD> <D8BB> ļ<EFBFBD> {Path}<7D> <> Culture:{Culture}<7D> <> <EFBFBD> <EFBFBD> Ŀ<EFBFBD> <C4BF> : {Count}" , path , _currentCulture , dict . Count ) ;
}
catch ( Exception ex )
{
_logger ? .LogWarning ( ex , "Failed to read localization file synchronously : {Path}" , path ) ;
_logger . LogWarning ( ex , "(Server ͬ<> <CDAC> ) <20> <> ȡ<EFBFBD> <C8A1> <EFBFBD> ػ<EFBFBD> <D8BB> ļ<EFBFBD> ʧ<EFBFBD> <CAA7> : {Path}" , path ) ;
}
}
else
{
_logger ? .LogDebug ( "Localization file not found at {Path}" , path ) ;
_logger . LogDebug ( "<EFBFBD> <EFBFBD> <EFBFBD> ػ<EFBFBD> <EFBFBD> ļ<EFBFBD> δ<EFBFBD> <EFBFBD> ·<EFBFBD> <EFBFBD> <EFBFBD> ҵ<EFBFBD> : {Path}" , path ) ;
}
}
}
}
catch ( Exception ex )
{
_logger ? .LogDebug ( ex , "Synchronous file load attempt failed in ctor " ) ;
_logger . LogDebug ( ex , "LocalizationProvider <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ͬ<EFBFBD> <CDAC> <EFBFBD> ļ<EFBFBD> <C4BC> <EFBFBD> <EFBFBD> س<EFBFBD> <D8B3> <EFBFBD> ʧ<EFBFBD> <CAA7> " ) ;
}
}
@@ -117,6 +131,9 @@ namespace Atomx.Admin.Client.Services
public event EventHandler < string > ? LanguageChanged ;
/// <summary>
/// <20> ӻ<EFBFBD> <D3BB> <EFBFBD> <EFBFBD> ж<EFBFBD> ȡָ<C8A1> <D6B8> <EFBFBD> <EFBFBD> <EFBFBD> ı <EFBFBD> <C4B1> ػ<EFBFBD> <D8BB> ַ<EFBFBD> <D6B7> <EFBFBD> <EFBFBD> <EFBFBD> δ<EFBFBD> ҵ<EFBFBD> <D2B5> <EFBFBD> <EFBFBD> <EFBFBD> null<6C> <6C>
/// </summary>
public string? GetString ( string key )
{
if ( string . IsNullOrEmpty ( key ) ) return null ;
@@ -129,9 +146,14 @@ namespace Atomx.Admin.Client.Services
return null ;
}
/// <summary>
/// <20> <> ʼ <EFBFBD> <CABC> <EFBFBD> <EFBFBD> <EFBFBD> ̣<EFBFBD> <CCA3> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ˣ<EFBFBD>
/// <20> <> <EFBFBD> ȼ<EFBFBD> <C8BC> <EFBFBD> URL <20> <> <EFBFBD> <EFBFBD> ǰ -> Cookie(atomx.culture) -> <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> -> Ĭ<> <C4AC>
/// <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> 루<EFBFBD> <EBA3A8> zh/en<65> <6E> <EFBFBD> <EFBFBD> ӳ<EFBFBD> <D3B3> Ϊ<EFBFBD> <CEAA> <EFBFBD> <EFBFBD> <EFBFBD> Ļ<EFBFBD> <C4BB> <EFBFBD> <EFBFBD> <EFBFBD> zh-Hans/en-US<55> <53> <EFBFBD> <EFBFBD>
/// </summary>
public async Task InitializeAsync ( )
{
_logger ? .LogDebug ( "LocalizationProvider.InitializeAsync start . CurrentCulture={Culture}" , _currentCulture ) ;
_logger . LogDebug ( "LocalizationProvider.InitializeAsync <EFBFBD> <EFBFBD> ʼ . CurrentCulture={Culture}" , _currentCulture ) ;
string? urlFirstSegment = null ;
@@ -140,7 +162,7 @@ namespace Atomx.Admin.Client.Services
if ( _jsRuntime ! = null & & OperatingSystem . IsBrowser ( ) )
{
var path = await _jsRuntime . InvokeAsync < string > ( "eval" , "location.pathname" ) ;
_logger ? .LogDebug ( "JS location.pathname='{Path}'" , path ) ;
_logger . LogDebug ( "JS location.pathname='{Path}'" , path ) ;
if ( ! string . IsNullOrEmpty ( path ) )
{
var trimmed = path . Trim ( '/' ) ;
@@ -148,19 +170,19 @@ namespace Atomx.Admin.Client.Services
{
var seg = trimmed . Split ( '/' , StringSplitOptions . RemoveEmptyEntries ) . FirstOrDefault ( ) ;
urlFirstSegment = seg ;
_logger ? .LogDebug ( "Detected url first segment : {Segment}" , urlFirstSegment ) ;
_logger . LogDebug ( "<EFBFBD> <EFBFBD> <EFBFBD> URL <20> <EFBFBD> : {Segment}" , urlFirstSegment ) ;
}
}
}
}
catch ( Exception ex )
{
_logger ? .LogDebug ( ex , "<22> <> ȡ location.pathname ʧ<> <CAA7> " ) ;
_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 ) ;
_logger . LogDebug ( "URL <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> '{Seg}' ӳ<> <D3B3> Ϊ<EFBFBD> Ļ<EFBFBD> '{Culture}'" , urlFirstSegment , mapped ) ;
await SetCultureInternalAsync ( mapped , persistCookie : false ) ;
return ;
}
@@ -170,18 +192,18 @@ namespace Atomx.Admin.Client.Services
if ( _jsRuntime ! = null & & OperatingSystem . IsBrowser ( ) )
{
var cookieVal = await _jsRuntime . InvokeAsync < string > ( "cookies.Read" , CookieName ) ;
_logger ? .LogDebug ( "Cookie '{CookieName}'='{CookieVal}'" , CookieName , cookieVal ) ;
_logger . LogDebug ( "<EFBFBD> <EFBFBD> ȡ 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 ) ;
_logger . LogDebug ( "Cookie <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> '{Cookie}' ӳ<EFBFBD> <EFBFBD> Ϊ<EFBFBD> Ļ<EFBFBD> {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> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> һ <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʹ <EFBFBD> <EFBFBD>
// cookie <20> п<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ѿ <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> culture<72> <65> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ƽ <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> һ <EFBFBD> <EFBFBD>
await SetCultureInternalAsync ( MapToFullCulture ( cookieVal ) , persistCookie : false ) ;
return ;
}
@@ -190,7 +212,7 @@ namespace Atomx.Admin.Client.Services
}
catch ( Exception ex )
{
_logger ? .LogDebug ( ex , "<22> <> ȡ Cookie ʧ<> <CAA7> " ) ;
_logger . LogDebug ( ex , "<22> <> ȡ Cookie ʧ<> <CAA7> " ) ;
}
try
@@ -198,11 +220,11 @@ namespace Atomx.Admin.Client.Services
if ( _jsRuntime ! = null & & OperatingSystem . IsBrowser ( ) )
{
var browserLang = await _jsRuntime . InvokeAsync < string > ( "getBrowserLanguage" ) ;
_logger ? .LogDebug ( "Browser language : {BrowserLang}" , browserLang ) ;
_logger . LogDebug ( "<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> : {BrowserLang}" , browserLang ) ;
if ( ! string . IsNullOrEmpty ( browserLang ) )
{
var mappedFromBrowser = MapToFullCulture ( browserLang ) ;
_logger ? .LogDebug ( "Browser mapped to {Culture}" , mappedFromBrowser ) ;
_logger . LogDebug ( "<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ӳ<EFBFBD> <EFBFBD> Ϊ {Culture}" , mappedFromBrowser ) ;
await SetCultureInternalAsync ( mappedFromBrowser , persistCookie : false ) ;
return ;
}
@@ -210,15 +232,18 @@ namespace Atomx.Admin.Client.Services
}
catch ( Exception ex )
{
_logger ? .LogDebug ( ex , "<22> <> ȡ<EFBFBD> <C8A1> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʧ<EFBFBD> <CAA7> " ) ;
_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 ) ;
// <20> <> <EFBFBD> ˵<EFBFBD> <EFBFBD> <EFBFBD> ǰĬ<EFBFBD> <EFBFBD> <EFBFBD> Ļ<EFBFBD> <EFBFBD> <EFBFBD> ȷ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Դ
_logger . LogDebug ( "InitializeAsync <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʹ <EFBFBD> õ<EFBFBD> ǰ<EFBFBD> Ļ<EFBFBD> {Culture}" , _currentCulture ) ;
await EnsureCultureLoadedAsync ( _currentCulture ) ;
await SetCultureInternalAsync ( _currentCulture , persistCookie : false ) ;
}
/// <summary>
/// <20> <> <EFBFBD> <EFBFBD> <EFBFBD> 첽<EFBFBD> <ECB2BD> <EFBFBD> <EFBFBD> <EFBFBD> Ļ<EFBFBD> <C4BB> <EFBFBD> <EFBFBD> ɴ<EFBFBD> <C9B4> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> culture<72> <65> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ѡ <EFBFBD> <D1A1> <EFBFBD> Ƿ<EFBFBD> <C7B7> ־û<D6BE> <C3BB> <EFBFBD> Cookie
/// </summary>
public async Task SetCultureAsync ( string cultureShortOrFull )
{
if ( string . IsNullOrEmpty ( cultureShortOrFull ) ) return ;
@@ -235,10 +260,9 @@ namespace Atomx.Admin.Client.Services
public Task LoadCultureAsync ( string culture ) = > EnsureCultureLoadedAsync ( MapToFullCulture ( culture ) ) ;
/// <summary>
/// Server-side synchronous culture set used during prerender to ensure translations
/// are available immediately. This method will attempt to load localization
/// JSON from the server's webroot synchronously and set thread cultures.
/// </summary>
/// Server <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> prerender ʱ<> <CAB1> ͬ<EFBFBD> <CDAC> <EFBFBD> <EFBFBD> <EFBFBD> ã<EFBFBD> <C3A3> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ߳<EFBFBD> Culture <20> <> <EFBFBD> <EFBFBD> <EFBFBD> Դ<EFBFBD> webroot ͬ<> <CDAC> <EFBFBD> <EFBFBD> <EFBFBD> ر<EFBFBD> <D8B1> ػ<EFBFBD> <D8BB> ļ<EFBFBD> <C4BC> <EFBFBD>
/// <EFBFBD> ÷<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ᴥ<EFBFBD> <EFBFBD> JS <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʺ<EFBFBD> <CABA> <EFBFBD> <EFBFBD> м<EFBFBD> <D0BC> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʹ <EFBFBD> á<EFBFBD>
/// </summary>
public void SetCultureForServer ( string cultureShortOrFull )
{
try
@@ -246,7 +270,7 @@ namespace Atomx.Admin.Client.Services
var cultureFull = MapToFullCulture ( cultureShortOrFull ) ;
if ( string . IsNullOrEmpty ( cultureFull ) ) return ;
// set thread culture
// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ߳<EFBFBD> culture<72> <65> Ӱ<EFBFBD> <D3B0> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ˴ <EFBFBD> <CBB4> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> IStringLocalizer<65> <72>
try
{
var ci = new CultureInfo ( cultureFull ) ;
@@ -256,7 +280,7 @@ namespace Atomx.Admin.Client.Services
}
catch { }
// try load from webroot synchronously via IWebHostEnvironment if available
// ͬ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> webroot <20> <> <EFBFBD> <EFBFBD> JSON <20> <> <EFBFBD> ػ<EFBFBD> <D8BB> ļ<EFBFBD> <C4BC> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ڣ<EFBFBD>
try
{
var envType = Type . GetType ( "Microsoft.AspNetCore.Hosting.IWebHostEnvironment, Microsoft.AspNetCore.Hosting.Abstractions" )
@@ -277,11 +301,11 @@ namespace Atomx.Admin.Client.Services
var json = File . ReadAllText ( path ) ;
var dict = JsonSerializer . Deserialize < Dictionary < string , string > > ( json ) ? ? new Dictionary < string , string > ( ) ;
_cache [ cultureFull ] = dict ;
_logger ? .LogInformation ( "(Server sync) Loaded localization file for {Culture} from path {Path}, entries : {Count}" , cultureFull , path , dict . Count ) ;
_logger . LogInformation ( "(Server ͬ<EFBFBD> <EFBFBD> ) <20> <> Ϊ {Culture} <20> <> ·<EFBFBD> <C2B7> <EFBFBD> <EFBFBD> <EFBFBD> ر<EFBFBD> <D8B1> ػ<EFBFBD> <D8BB> ļ<EFBFBD> <C4BC> <EFBFBD> <EFBFBD> <EFBFBD> Ŀ<EFBFBD> <C4BF> : {Count}" , cultureFull , dict . Count ) ;
}
catch ( Exception ex )
{
_logger ? .LogWarning ( ex , "(Server sync) Failed to read localization file synchronously : {Path}" , path ) ;
_logger . LogWarning ( ex , "(Server ͬ<EFBFBD> <EFBFBD> ) <20> <> ȡ<EFBFBD> <C8A1> <EFBFBD> ػ<EFBFBD> <D8BB> ļ<EFBFBD> ʧ<EFBFBD> <CAA7> : {Path}" , path ) ;
}
}
}
@@ -289,18 +313,21 @@ namespace Atomx.Admin.Client.Services
}
catch ( Exception ex )
{
_logger ? .LogDebug ( ex , "SetCultureForServer failed to load file for {Culture}" , cultureFull ) ;
_logger . LogDebug ( ex , "SetCultureForServer <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ļ<EFBFBD> ʱ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> : {Culture}" , cultureFull ) ;
}
}
catch ( Exception ex )
{
_logger ? .LogDebug ( ex , "SetCultureForServer encountered error " ) ;
_logger . LogDebug ( ex , "SetCultureForServer ִ<EFBFBD> й<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> з<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> 쳣 " ) ;
}
}
/// <summary>
/// <20> ڲ<EFBFBD> <DAB2> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ļ<EFBFBD> <C4BB> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ҫʱ<D2AA> ־û<D6BE> Cookie<69> <65> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> localization service <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ¼<EFBFBD> <C2BC> <EFBFBD>
/// </summary>
private async Task SetCultureInternalAsync ( string cultureFull , bool persistCookie )
{
//_logger? .LogDebug("<22> <> <EFBFBD> <EFBFBD> <EFBFBD> ڲ<EFBFBD> <DAB2> Ļ<EFBFBD> <C4BB> 첽<EFBFBD> <ECB2BD> ʼ : {Culture}, <20> ־û<D6BE> ={Persist}", cultureFull, persistCookie);
//_logger.LogDebug("<22> <> <EFBFBD> <EFBFBD> <EFBFBD> ڲ<EFBFBD> <DAB2> Ļ<EFBFBD> <C4BB> 첽<EFBFBD> <ECB2BD> ʼ : {Culture}, <20> ־û<D6BE> ={Persist}", cultureFull, persistCookie);
await EnsureCultureLoadedAsync ( cultureFull ) ;
try
@@ -310,11 +337,11 @@ namespace Atomx.Admin.Client.Services
CultureInfo . DefaultThreadCurrentUICulture = ci ;
_currentCulture = cultureFull ;
_localizationService . SetLanguage ( ci ) ;
_logger ? .LogDebug ( "Culture set to {Culture}" , cultureFull ) ;
_logger . LogDebug ( "<EFBFBD> Ļ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ϊ {Culture}" , cultureFull ) ;
}
catch ( Exception ex )
{
_logger ? .LogWarning ( ex , "<22> <> <EFBFBD> <EFBFBD> Culture ʧ<> <CAA7> : {Culture}" , cultureFull ) ;
_logger . LogWarning ( ex , "<22> <> <EFBFBD> <EFBFBD> Culture ʧ<> <CAA7> : {Culture}" , cultureFull ) ;
}
if ( persistCookie & & _jsRuntime ! = null )
@@ -322,11 +349,12 @@ namespace Atomx.Admin.Client.Services
try
{
var shortKey = ShortToCulture . FirstOrDefault ( kv = > string . Equals ( kv . Value , cultureFull , StringComparison . OrdinalIgnoreCase ) ) . Key ? ? cultureFull ;
// <20> <> shortKey д<> <D0B4> cookie<69> <65> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ȡ<EFBFBD> <C8A1> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Server ģʽ <C4A3> µ<EFBFBD> <C2B5> м<EFBFBD> <D0BC> <EFBFBD> <EFBFBD> <EFBFBD>
await _jsRuntime . InvokeVoidAsync ( "cookies.Write" , CookieName , shortKey , DateTime . UtcNow . AddYears ( 1 ) . ToString ( "o" ) ) ;
}
catch ( Exception ex )
{
_logger ? .LogDebug ( ex , "д Cookie ʧ<> <CAA7> " ) ;
_logger . LogDebug ( ex , "д<EFBFBD> <EFBFBD> Cookie ʧ<> <CAA7> " ) ;
}
}
@@ -334,41 +362,46 @@ namespace Atomx.Admin.Client.Services
{
if ( _jsRuntime ! = null )
{
// <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> HTML <20> <> lang <20> <> <EFBFBD> ԣ<EFBFBD> <D4A3> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ϰ<EFBFBD> /SEO
await _jsRuntime . InvokeVoidAsync ( "setHtmlLang" , cultureFull ) ;
}
}
catch { }
// ֪ͨ<CDA8> <D6AA> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD>
LanguageChanged ? . Invoke ( this , cultureFull ) ;
}
/// <summary>
/// ȷ<> <C8B7> ָ<EFBFBD> <D6B8> <EFBFBD> Ļ<EFBFBD> <C4BB> <EFBFBD> JSON <20> ļ<EFBFBD> <C4BC> ѱ<EFBFBD> <D1B1> <EFBFBD> <EFBFBD> ص<EFBFBD> <D8B5> <EFBFBD> <EFBFBD> 档<EFBFBD> <E6A1A3> <EFBFBD> <EFBFBD> ˳<EFBFBD> <CBB3> <EFBFBD> <EFBFBD> WASM HttpClient -> <20> ļ<EFBFBD> ϵͳ -> <20> <> <EFBFBD> ֵ<EFBFBD> ռ λ<D5BC> <CEBB>
/// </summary>
private async Task EnsureCultureLoadedAsync ( string cultureFull )
{
// Normalize possible short codes (e.g. zh -> zh-Hans, en -> en-US) and variants (zh-CN -> zh-Hans)
// <EFBFBD> <EFBFBD> һ <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> 루<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> zh -> zh-Hans<6E> <73>
cultureFull = MapToFullCulture ( cultureFull ) ;
if ( string . IsNullOrEmpty ( cultureFull ) ) return ;
if ( _cache . ContainsKey ( cultureFull ) )
{
_logger ? .LogDebug ( "EnsureCultureLoadedAsync: culture {Culture} already cached " , cultureFull ) ;
_logger . LogDebug ( "EnsureCultureLoadedAsync: <EFBFBD> Ļ<EFBFBD> {Culture} <EFBFBD> ѻ<EFBFBD> <EFBFBD> <EFBFBD> " , cultureFull ) ;
return ;
}
// Prefer HttpClient when running in browser (WASM)
// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> WASM<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʹ <EFBFBD> <EFBFBD> HttpClient <20> <> <EFBFBD> ر<EFBFBD> <D8B1> ػ<EFBFBD> JSON
if ( _jsRuntime ! = null & & OperatingSystem . IsBrowser ( ) )
{
_logger ? .LogInformation ( "EnsureCultureLoadedAsync: running in browser, will attempt HttpClient for {Culture}" , cultureFull ) ;
_logger . LogInformation ( "EnsureCultureLoadedAsync: <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ͨ<EFBFBD> <EFBFBD> HttpClient <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> {Culture}" , cultureFull ) ;
try
{
var http = _sp . GetService ( typeof ( HttpClient ) ) as HttpClient ;
if ( http = = null & & _httpClientFactory ! = null )
{
_logger ? .LogDebug ( "HttpClient not found from s ervice p rovider, using f actory" ) ;
_logger . LogDebug ( "δ<EFBFBD> <EFBFBD> S erviceP rovider <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> HttpClient<6E> <74> ʹ <EFBFBD> <CAB9> IHttpClientF actory <20> <> <EFBFBD> <EFBFBD> " ) ;
http = _httpClientFactory . CreateClient ( ) ;
}
else
{
_logger ? .LogDebug ( "HttpClient resolved from s ervice p rovider: {HasClient}" , http ! = null ) ;
_logger . LogDebug ( "<EFBFBD> <EFBFBD> S erviceP rovider <20> <> <EFBFBD> <EFBFBD> HttpClient : {HasClient}" , http ! = null ) ;
}
if ( http ! = null )
@@ -376,7 +409,7 @@ namespace Atomx.Admin.Client.Services
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
// <EFBFBD> <EFBFBD> HttpClient <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> BaseAddress ʱʹ <CAB1> ã<EFBFBD> <C3A3> <EFBFBD> <EFBFBD> <EFBFBD> ͨ<EFBFBD> <CDA8> JS <20> <> ȡ location.origin <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> URI
if ( http . BaseAddress ! = null )
{
requestUri = new Uri ( http . BaseAddress , url ) ;
@@ -388,43 +421,42 @@ namespace Atomx.Admin.Client.Services
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 " ) ;
_logger . LogDebug ( jsEx , "<EFBFBD> <EFBFBD> JS <20> <> ȡ location.origin ʧ<EFBFBD> <EFBFBD> " ) ;
}
}
if ( requestUri ! = null )
{
_logger ? .LogInformation ( "Downloading localization from {Url} " , requestUri ) ;
_logger . LogInformation ( "<EFBFBD> <EFBFBD> {Url} <20> <> <EFBFBD> ر<EFBFBD> <D8B1> ػ<EFBFBD> <D8BB> <EFBFBD> Դ " , 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 ) ;
_logger . LogInformation ( "ͨ<EFBFBD> <EFBFBD> HttpClient Ϊ {Culture} <20> <> <EFBFBD> ص<EFBFBD> <D8B5> <EFBFBD> <EFBFBD> ػ<EFBFBD> <D8BB> <EFBFBD> <EFBFBD> ݣ<EFBFBD> <DDA3> <EFBFBD> Ŀ<EFBFBD> <C4BF> : {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 ) ;
_logger . LogWarning ( "HttpClient <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> URL<52> <4C> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ͨ<EFBFBD> <CDA8> HttpClient <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> {Culture}" , cultureFull ) ;
}
}
else
{
_logger ? .LogWarning ( "No HttpClient available to load localization for {Culture}" , cultureFull ) ;
_logger . LogWarning ( "δ<EFBFBD> ҵ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> õ<EFBFBD> HttpClient <EFBFBD> Լ<EFBFBD> <EFBFBD> <EFBFBD> {Culture}" , cultureFull ) ;
}
}
catch ( Exception ex )
{
_logger ? .LogDebug ( ex , "ͨ<> <CDA8> HttpClient <20> <> <EFBFBD> ر<EFBFBD> <D8B1> ػ<EFBFBD> <D8BB> ļ<EFBFBD> ʧ<EFBFBD> <CAA7> : {Culture}" , cultureFull ) ;
_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> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ȡ IWebHostEnvironment<6E> <74> Server ʱ<> <CAB1> <EFBFBD> ã<EFBFBD>
_logger . LogDebug ( "EnsureCultureLoadedAsync: <20> <> <EFBFBD> <EFBFBD> <EFBFBD> ļ<EFBFBD> ϵͳ<CFB5> <CDB3> <EFBFBD> <EFBFBD> {Culture}" , cultureFull ) ;
// <20> <> <EFBFBD> ˣ <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ͨ <EFBFBD> <EFBFBD> IWebHostEnvironment <20> <> <EFBFBD> ļ<EFBFBD> ϵͳ<CFB5> <CDB3> ȡ <EFBFBD> <EFBFBD> Server ʱ<> <CAB1> <EFBFBD> ã<EFBFBD>
try
{
var envType = Type . GetType ( "Microsoft.AspNetCore.Hosting.IWebHostEnvironment, Microsoft.AspNetCore.Hosting.Abstractions" )
@@ -438,67 +470,70 @@ namespace Atomx.Admin.Client.Services
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 ) ;
_logger . LogDebug ( "<EFBFBD> <EFBFBD> <EFBFBD> ұ<EFBFBD> <EFBFBD> ػ<EFBFBD> <EFBFBD> ļ<EFBFBD> ·<EFBFBD> <EFBFBD> : {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 ) ;
_logger . LogInformation ( "<EFBFBD> <EFBFBD> <EFBFBD> ļ<EFBFBD> Ϊ {Culture} <20> <> <EFBFBD> ر<EFBFBD> <D8B1> ػ<EFBFBD> <D8BB> <EFBFBD> <EFBFBD> <EFBFBD> Ŀ<EFBFBD> <C4BF> : {Count}" , cultureFull , dict . Count ) ;
return ;
}
else
{
_logger ? .LogDebug ( "Localization file not found at {Path}" , path ) ;
// Fallback: check build output wwwroot under AppContext.BaseDirectory
_logger . LogDebug ( "δ<EFBFBD> <EFBFBD> ·<EFBFBD> <EFBFBD> <EFBFBD> ҵ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ػ<EFBFBD> <EFBFBD> ļ<EFBFBD> : {Path}" , path ) ;
// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ·<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ʱ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> Ŀ¼<EFBFBD> <EFBFBD> wwwroot
try
{
var alt = Path . Combine ( AppContext . BaseDirectory ? ? "." , "wwwroot" , "localization" , cultureFull + ".json" ) ;
_logger ? .LogDebug ( "Looking for localization file at alternative path {AltPath}" , alt ) ;
_logger . LogDebug ( "<EFBFBD> <EFBFBD> <EFBFBD> ұ<EFBFBD> <EFBFBD> <EFBFBD> ·<EFBFBD> <EFBFBD> : {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 ) ;
_logger . LogInformation ( "<EFBFBD> ӱ<EFBFBD> <EFBFBD> <EFBFBD> ·<EFBFBD> <EFBFBD> Ϊ {Culture} <20> <> <EFBFBD> ص<EFBFBD> <D8B5> <EFBFBD> <EFBFBD> ػ<EFBFBD> <D8BB> ļ<EFBFBD> <C4BC> <EFBFBD> <EFBFBD> <EFBFBD> Ŀ<EFBFBD> <C4BF> : {Count}" , cultureFull , dict2 . Count ) ;
return ;
}
else
{
_logger ? .LogDebug ( "Localization file not found at alternative path {AltPath}" , alt ) ;
_logger . LogDebug ( "<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ·<EFBFBD> <EFBFBD> δ<EFBFBD> ҵ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ػ<EFBFBD> <EFBFBD> ļ<EFBFBD> : {AltPath}" , alt ) ;
}
}
catch ( Exception exAlt )
{
_logger ? .LogDebug ( exAlt , "Error while checking alternative localization path " ) ;
_logger . LogDebug ( exAlt , "<EFBFBD> <EFBFBD> <EFBFBD> 鱸<EFBFBD> ñ<EFBFBD> <EFBFBD> ػ<EFBFBD> ·<EFBFBD> <EFBFBD> ʱ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> " ) ;
}
}
}
else
{
_logger ? .LogDebug ( "IWebHostEnvironment not resolved from service provider " ) ;
_logger . LogDebug ( "<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ServiceProvider <20> <> ȡ IWebHostEnvironment ʵ<> <CAB5> " ) ;
}
}
else
{
_logger ? .LogDebug ( "IWebHostEnvironment type not found via reflection " ) ;
_logger . LogDebug ( "ͨ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> δ<EFBFBD> <EFBFBD> <EFBFBD> ҵ<EFBFBD> IWebHostEnvironment <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> " ) ;
}
}
catch ( Exception ex )
{
_logger ? .LogDebug ( ex , "<22> <> <EFBFBD> ļ<EFBFBD> ϵͳ<CFB5> <CDB3> <EFBFBD> ر<EFBFBD> <D8B1> ػ<EFBFBD> <D8BB> ļ<EFBFBD> ʧ<EFBFBD> <CAA7> : {Culture}" , cultureFull ) ;
_logger . LogDebug ( ex , "ͨ <EFBFBD> <EFBFBD> <EFBFBD> ļ<EFBFBD> ϵͳ<EFBFBD> <EFBFBD> <EFBFBD> ر<EFBFBD> <EFBFBD> ػ<EFBFBD> <EFBFBD> ļ<EFBFBD> ʧ<EFBFBD> <EFBFBD> : {Culture}" , cultureFull ) ;
}
_logger ? .LogDebug ( "EnsureCultureLoadedAsync fallback to empty dict for {Culture}" , cultureFull ) ;
_logger . LogDebug ( "EnsureCultureLoadedAsync: <20> <> <EFBFBD> <EFBFBD> Ϊ {Culture} <20> Ŀ<EFBFBD> <C4BF> ֵ<EFBFBD> ռ λ " , cultureFull ) ;
_cache [ cultureFull ] = new Dictionary < string , string > ( ) ;
}
/// <summary>
/// <20> <> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ַ<EFBFBD> <D6B7> <EFBFBD> ӳ<EFBFBD> <D3B3> Ϊ<EFBFBD> ڲ<EFBFBD> ʹ <EFBFBD> õ<EFBFBD> <C3B5> <EFBFBD> <EFBFBD> <EFBFBD> culture<72> <65> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> zh -> zh-Hans<6E> <73>
/// </summary>
private string MapToFullCulture ( string culture )
{
if ( string . IsNullOrEmpty ( culture ) ) return culture ;
// direct mapping
// ֱ<EFBFBD> <EFBFBD> ӳ<EFBFBD> <EFBFBD>
if ( ShortToCulture . TryGetValue ( culture , out var mapped ) ) return mapped ;
// consider prefix, e.g. zh-CN -> zh
// <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> ǰ<EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> <EFBFBD> zh-CN -> zh
var prefix = culture . Split ( '-' , StringSplitOptions . RemoveEmptyEntries ) . FirstOrDefault ( ) ;
if ( ! string . IsNullOrEmpty ( prefix ) & & ShortToCulture . TryGetValue ( prefix , out var mapped2 ) ) return mapped2 ;
return culture ;