完善语言文件的生成
This commit is contained in:
@@ -29,7 +29,7 @@ builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.
|
|||||||
|
|
||||||
// ע<><D7A2> LocalizationProvider (<28><><EFBFBD><EFBFBD> WASM)
|
// ע<><D7A2> LocalizationProvider (<28><><EFBFBD><EFBFBD> WASM)
|
||||||
// Use Scoped lifetime because LocalizationProvider depends on IJSRuntime (scoped) and IHttpClient etc.
|
// Use Scoped lifetime because LocalizationProvider depends on IJSRuntime (scoped) and IHttpClient etc.
|
||||||
builder.Services.AddScoped<ILocalizationProvider, WasmLocalizationProvider>();
|
builder.Services.AddScoped<ILocalizationProvider, LocalizationProvider>();
|
||||||
// ע<><D7A2> ILocalizationService <20><><EFBFBD><EFBFBD>ͬ<EFBFBD><CDAC> Culture <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>䴫<EFBFBD><E4B4AB>
|
// ע<><D7A2> ILocalizationService <20><><EFBFBD><EFBFBD>ͬ<EFBFBD><CDAC> Culture <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>䴫<EFBFBD><E4B4AB>
|
||||||
builder.Services.AddScoped<ILocalizationService, LocalizationService>();
|
builder.Services.AddScoped<ILocalizationService, LocalizationService>();
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace Atomx.Admin.Client.Services
|
|||||||
var value = _provider.GetString(name);
|
var value = _provider.GetString(name);
|
||||||
if (value == null)
|
if (value == null)
|
||||||
{
|
{
|
||||||
// Avoid synchronous blocking during server prerender. Start background load and return key.
|
// 避免在服务端 prerender 阶段进行同步阻塞。以后台方式启动加载并返回 key。
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_ = _provider.LoadCultureAsync(_provider.CurrentCulture);
|
_ = _provider.LoadCultureAsync(_provider.CurrentCulture);
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
|
using Microsoft.JSInterop;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Microsoft.JSInterop;
|
|
||||||
using Microsoft.Extensions.Localization;
|
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Services
|
namespace Atomx.Admin.Client.Services
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <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><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>
|
||||||
/// - <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>
|
/// <EFBFBD><EFBFBD>Ҫְ<EFBFBD><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>
|
/// - <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>棬
|
||||||
/// ͬʱ<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><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>
|
/// </summary>
|
||||||
public interface ILocalizationProvider
|
public interface ILocalizationProvider
|
||||||
{
|
{
|
||||||
@@ -25,6 +29,12 @@ namespace Atomx.Admin.Client.Services
|
|||||||
event EventHandler<string>? LanguageChanged;
|
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
|
public class LocalizationProvider : ILocalizationProvider
|
||||||
{
|
{
|
||||||
private readonly IServiceProvider _sp;
|
private readonly IServiceProvider _sp;
|
||||||
@@ -33,22 +43,26 @@ namespace Atomx.Admin.Client.Services
|
|||||||
private readonly ILogger<LocalizationProvider> _logger;
|
private readonly ILogger<LocalizationProvider> _logger;
|
||||||
private readonly ILocalizationService _localizationService;
|
private readonly ILocalizationService _localizationService;
|
||||||
|
|
||||||
// <20><><EFBFBD>棺culture -> translations
|
// <20><><EFBFBD>棺culture -> translations<EFBFBD><EFBFBD>ʹ<EFBFBD><EFBFBD> ConcurrentDictionary <20><><EFBFBD>̰߳<DFB3>ȫ<EFBFBD>ع<EFBFBD><D8B9><EFBFBD><EFBFBD><EFBFBD>
|
||||||
// Use a static concurrent dictionary so files loaded during middleware/server prerender
|
// ʹ<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>
|
||||||
// are visible to provider instances created later in the same request pipeline.
|
|
||||||
private static readonly ConcurrentDictionary<string, Dictionary<string, string>> _cache = new();
|
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)
|
private static readonly Dictionary<string, string> ShortToCulture = new(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
{ "zh", "zh-Hans" },
|
{ "zh", "zh-Hans" },
|
||||||
{ "en", "en-US" }
|
{ "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 string _currentCulture = "zh-Hans";
|
||||||
|
|
||||||
private const string CookieName = "atomx.culture";
|
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)
|
public LocalizationProvider(IServiceProvider sp, ILogger<LocalizationProvider> logger, IHttpClientFactory? httpClientFactory, IJSRuntime? jsRuntime, ILocalizationService localizationService)
|
||||||
{
|
{
|
||||||
_sp = sp;
|
_sp = sp;
|
||||||
@@ -57,22 +71,22 @@ namespace Atomx.Admin.Client.Services
|
|||||||
_logger = logger;
|
_logger = logger;
|
||||||
_localizationService = localizationService;
|
_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
|
try
|
||||||
{
|
{
|
||||||
var threadUi = CultureInfo.DefaultThreadCurrentUICulture ?? CultureInfo.CurrentUICulture;
|
var threadUi = CultureInfo.DefaultThreadCurrentUICulture ?? CultureInfo.CurrentUICulture;
|
||||||
if (!string.IsNullOrEmpty(threadUi?.Name))
|
if (!string.IsNullOrEmpty(threadUi?.Name))
|
||||||
{
|
{
|
||||||
_currentCulture = MapToFullCulture(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)
|
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
|
try
|
||||||
{
|
{
|
||||||
var envType = Type.GetType("Microsoft.AspNetCore.Hosting.IWebHostEnvironment, Microsoft.AspNetCore.Hosting.Abstractions")
|
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 json = File.ReadAllText(path);
|
||||||
var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(json) ?? new Dictionary<string, string>();
|
var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(json) ?? new Dictionary<string, string>();
|
||||||
_cache[_currentCulture] = dict;
|
_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)
|
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
|
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)
|
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;
|
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)
|
public string? GetString(string key)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(key)) return null;
|
if (string.IsNullOrEmpty(key)) return null;
|
||||||
@@ -129,9 +146,14 @@ namespace Atomx.Admin.Client.Services
|
|||||||
return null;
|
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()
|
public async Task InitializeAsync()
|
||||||
{
|
{
|
||||||
_logger?.LogDebug("LocalizationProvider.InitializeAsync start. CurrentCulture={Culture}", _currentCulture);
|
_logger.LogDebug("LocalizationProvider.InitializeAsync <EFBFBD><EFBFBD>ʼ. CurrentCulture={Culture}", _currentCulture);
|
||||||
|
|
||||||
string? urlFirstSegment = null;
|
string? urlFirstSegment = null;
|
||||||
|
|
||||||
@@ -140,7 +162,7 @@ namespace Atomx.Admin.Client.Services
|
|||||||
if (_jsRuntime != null && OperatingSystem.IsBrowser())
|
if (_jsRuntime != null && OperatingSystem.IsBrowser())
|
||||||
{
|
{
|
||||||
var path = await _jsRuntime.InvokeAsync<string>("eval", "location.pathname");
|
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))
|
if (!string.IsNullOrEmpty(path))
|
||||||
{
|
{
|
||||||
var trimmed = path.Trim('/');
|
var trimmed = path.Trim('/');
|
||||||
@@ -148,19 +170,19 @@ namespace Atomx.Admin.Client.Services
|
|||||||
{
|
{
|
||||||
var seg = trimmed.Split('/', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
|
var seg = trimmed.Split('/', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
|
||||||
urlFirstSegment = seg;
|
urlFirstSegment = seg;
|
||||||
_logger?.LogDebug("Detected url first segment: {Segment}", urlFirstSegment);
|
_logger.LogDebug("<EFBFBD><EFBFBD><EFBFBD> URL <20><EFBFBD>: {Segment}", urlFirstSegment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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))
|
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);
|
await SetCultureInternalAsync(mapped, persistCookie: false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -170,18 +192,18 @@ namespace Atomx.Admin.Client.Services
|
|||||||
if (_jsRuntime != null && OperatingSystem.IsBrowser())
|
if (_jsRuntime != null && OperatingSystem.IsBrowser())
|
||||||
{
|
{
|
||||||
var cookieVal = await _jsRuntime.InvokeAsync<string>("cookies.Read", CookieName);
|
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 (!string.IsNullOrEmpty(cookieVal))
|
||||||
{
|
{
|
||||||
if (ShortToCulture.TryGetValue(cookieVal, out var mappedFromCookie))
|
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);
|
await SetCultureInternalAsync(mappedFromCookie, persistCookie: false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
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);
|
await SetCultureInternalAsync(MapToFullCulture(cookieVal), persistCookie: false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -190,7 +212,7 @@ namespace Atomx.Admin.Client.Services
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger?.LogDebug(ex, "<22><>ȡ Cookie ʧ<><CAA7>");
|
_logger.LogDebug(ex, "<22><>ȡ Cookie ʧ<><CAA7>");
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -198,11 +220,11 @@ namespace Atomx.Admin.Client.Services
|
|||||||
if (_jsRuntime != null && OperatingSystem.IsBrowser())
|
if (_jsRuntime != null && OperatingSystem.IsBrowser())
|
||||||
{
|
{
|
||||||
var browserLang = await _jsRuntime.InvokeAsync<string>("getBrowserLanguage");
|
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))
|
if (!string.IsNullOrEmpty(browserLang))
|
||||||
{
|
{
|
||||||
var mappedFromBrowser = MapToFullCulture(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);
|
await SetCultureInternalAsync(mappedFromBrowser, persistCookie: false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -210,15 +232,18 @@ namespace Atomx.Admin.Client.Services
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
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
|
// <20><><EFBFBD>˵<EFBFBD><EFBFBD><EFBFBD>ǰĬ<EFBFBD><EFBFBD><EFBFBD>Ļ<EFBFBD><EFBFBD><EFBFBD>ȷ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Դ
|
||||||
_logger?.LogDebug("InitializeAsync falling back to current culture {Culture}", _currentCulture);
|
_logger.LogDebug("InitializeAsync <EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʹ<EFBFBD>õ<EFBFBD>ǰ<EFBFBD>Ļ<EFBFBD> {Culture}", _currentCulture);
|
||||||
await EnsureCultureLoadedAsync(_currentCulture);
|
await EnsureCultureLoadedAsync(_currentCulture);
|
||||||
await SetCultureInternalAsync(_currentCulture, persistCookie: false);
|
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)
|
public async Task SetCultureAsync(string cultureShortOrFull)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(cultureShortOrFull)) return;
|
if (string.IsNullOrEmpty(cultureShortOrFull)) return;
|
||||||
@@ -235,9 +260,8 @@ namespace Atomx.Admin.Client.Services
|
|||||||
public Task LoadCultureAsync(string culture) => EnsureCultureLoadedAsync(MapToFullCulture(culture));
|
public Task LoadCultureAsync(string culture) => EnsureCultureLoadedAsync(MapToFullCulture(culture));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Server-side synchronous culture set used during prerender to ensure translations
|
/// 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>
|
||||||
/// are available immediately. This method will attempt to load localization
|
/// <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>
|
||||||
/// JSON from the server's webroot synchronously and set thread cultures.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SetCultureForServer(string cultureShortOrFull)
|
public void SetCultureForServer(string cultureShortOrFull)
|
||||||
{
|
{
|
||||||
@@ -246,7 +270,7 @@ namespace Atomx.Admin.Client.Services
|
|||||||
var cultureFull = MapToFullCulture(cultureShortOrFull);
|
var cultureFull = MapToFullCulture(cultureShortOrFull);
|
||||||
if (string.IsNullOrEmpty(cultureFull)) return;
|
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
|
try
|
||||||
{
|
{
|
||||||
var ci = new CultureInfo(cultureFull);
|
var ci = new CultureInfo(cultureFull);
|
||||||
@@ -256,7 +280,7 @@ namespace Atomx.Admin.Client.Services
|
|||||||
}
|
}
|
||||||
catch { }
|
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
|
try
|
||||||
{
|
{
|
||||||
var envType = Type.GetType("Microsoft.AspNetCore.Hosting.IWebHostEnvironment, Microsoft.AspNetCore.Hosting.Abstractions")
|
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 json = File.ReadAllText(path);
|
||||||
var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(json) ?? new Dictionary<string, string>();
|
var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(json) ?? new Dictionary<string, string>();
|
||||||
_cache[cultureFull] = dict;
|
_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)
|
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)
|
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)
|
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)
|
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);
|
await EnsureCultureLoadedAsync(cultureFull);
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -310,11 +337,11 @@ namespace Atomx.Admin.Client.Services
|
|||||||
CultureInfo.DefaultThreadCurrentUICulture = ci;
|
CultureInfo.DefaultThreadCurrentUICulture = ci;
|
||||||
_currentCulture = cultureFull;
|
_currentCulture = cultureFull;
|
||||||
_localizationService.SetLanguage(ci);
|
_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)
|
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)
|
if (persistCookie && _jsRuntime != null)
|
||||||
@@ -322,11 +349,12 @@ namespace Atomx.Admin.Client.Services
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var shortKey = ShortToCulture.FirstOrDefault(kv => string.Equals(kv.Value, cultureFull, StringComparison.OrdinalIgnoreCase)).Key ?? cultureFull;
|
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"));
|
await _jsRuntime.InvokeVoidAsync("cookies.Write", CookieName, shortKey, DateTime.UtcNow.AddYears(1).ToString("o"));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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)
|
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);
|
await _jsRuntime.InvokeVoidAsync("setHtmlLang", cultureFull);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
|
|
||||||
|
// ֪ͨ<CDA8><D6AA><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||||
LanguageChanged?.Invoke(this, cultureFull);
|
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)
|
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);
|
cultureFull = MapToFullCulture(cultureFull);
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(cultureFull)) return;
|
if (string.IsNullOrEmpty(cultureFull)) return;
|
||||||
if (_cache.ContainsKey(cultureFull))
|
if (_cache.ContainsKey(cultureFull))
|
||||||
{
|
{
|
||||||
_logger?.LogDebug("EnsureCultureLoadedAsync: culture {Culture} already cached", cultureFull);
|
_logger.LogDebug("EnsureCultureLoadedAsync: <EFBFBD>Ļ<EFBFBD> {Culture} <EFBFBD>ѻ<EFBFBD><EFBFBD><EFBFBD>", cultureFull);
|
||||||
return;
|
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())
|
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
|
try
|
||||||
{
|
{
|
||||||
var http = _sp.GetService(typeof(HttpClient)) as HttpClient;
|
var http = _sp.GetService(typeof(HttpClient)) as HttpClient;
|
||||||
if (http == null && _httpClientFactory != null)
|
if (http == null && _httpClientFactory != null)
|
||||||
{
|
{
|
||||||
_logger?.LogDebug("HttpClient not found from service provider, using factory");
|
_logger.LogDebug("δ<EFBFBD><EFBFBD> ServiceProvider <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> HttpClient<6E><74>ʹ<EFBFBD><CAB9> IHttpClientFactory <20><><EFBFBD><EFBFBD>");
|
||||||
http = _httpClientFactory.CreateClient();
|
http = _httpClientFactory.CreateClient();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger?.LogDebug("HttpClient resolved from service provider: {HasClient}", http != null);
|
_logger.LogDebug("<EFBFBD><EFBFBD> ServiceProvider <20><><EFBFBD><EFBFBD> HttpClient: {HasClient}", http != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (http != null)
|
if (http != null)
|
||||||
@@ -376,7 +409,7 @@ namespace Atomx.Admin.Client.Services
|
|||||||
var url = $"/localization/{cultureFull}.json";
|
var url = $"/localization/{cultureFull}.json";
|
||||||
Uri? requestUri = null;
|
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)
|
if (http.BaseAddress != null)
|
||||||
{
|
{
|
||||||
requestUri = new Uri(http.BaseAddress, url);
|
requestUri = new Uri(http.BaseAddress, url);
|
||||||
@@ -388,43 +421,42 @@ namespace Atomx.Admin.Client.Services
|
|||||||
var origin = await _jsRuntime.InvokeAsync<string>("eval", "location.origin");
|
var origin = await _jsRuntime.InvokeAsync<string>("eval", "location.origin");
|
||||||
if (!string.IsNullOrEmpty(origin))
|
if (!string.IsNullOrEmpty(origin))
|
||||||
{
|
{
|
||||||
// ensure no double slashes
|
|
||||||
requestUri = new Uri(new Uri(origin), url);
|
requestUri = new Uri(new Uri(origin), url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception jsEx)
|
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)
|
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 txt = await http.GetStringAsync(requestUri);
|
||||||
var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(txt) ?? new Dictionary<string, string>();
|
var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(txt) ?? new Dictionary<string, string>();
|
||||||
_cache[cultureFull] = dict;
|
_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;
|
return;
|
||||||
}
|
}
|
||||||
else
|
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
|
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)
|
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);
|
_logger.LogDebug("EnsureCultureLoadedAsync: <20><><EFBFBD><EFBFBD><EFBFBD>ļ<EFBFBD>ϵͳ<CFB5><CDB3><EFBFBD><EFBFBD> {Culture}", cultureFull);
|
||||||
// <20><><EFBFBD><EFBFBD>ͨ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȡ IWebHostEnvironment<6E><74>Server ʱ<><CAB1><EFBFBD>ã<EFBFBD>
|
// <20><><EFBFBD>ˣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͨ<EFBFBD><EFBFBD> IWebHostEnvironment <20><><EFBFBD>ļ<EFBFBD>ϵͳ<CFB5><CDB3>ȡ<EFBFBD><EFBFBD>Server ʱ<><CAB1><EFBFBD>ã<EFBFBD>
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var envType = Type.GetType("Microsoft.AspNetCore.Hosting.IWebHostEnvironment, Microsoft.AspNetCore.Hosting.Abstractions")
|
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 contentRootProp = envType.GetProperty("ContentRootPath");
|
||||||
var webRoot = webRootProp?.GetValue(env) as string ?? Path.Combine(contentRootProp?.GetValue(env) as string ?? ".", "wwwroot");
|
var webRoot = webRootProp?.GetValue(env) as string ?? Path.Combine(contentRootProp?.GetValue(env) as string ?? ".", "wwwroot");
|
||||||
var path = Path.Combine(webRoot ?? ".", "localization", cultureFull + ".json");
|
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))
|
if (File.Exists(path))
|
||||||
{
|
{
|
||||||
var json = await File.ReadAllTextAsync(path);
|
var json = await File.ReadAllTextAsync(path);
|
||||||
var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(json) ?? new Dictionary<string, string>();
|
var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(json) ?? new Dictionary<string, string>();
|
||||||
_cache[cultureFull] = dict;
|
_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;
|
return;
|
||||||
}
|
}
|
||||||
else
|
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);
|
||||||
// Fallback: check build output wwwroot under AppContext.BaseDirectory
|
// <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
|
try
|
||||||
{
|
{
|
||||||
var alt = Path.Combine(AppContext.BaseDirectory ?? ".", "wwwroot", "localization", cultureFull + ".json");
|
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))
|
if (File.Exists(alt))
|
||||||
{
|
{
|
||||||
var json2 = await File.ReadAllTextAsync(alt);
|
var json2 = await File.ReadAllTextAsync(alt);
|
||||||
var dict2 = JsonSerializer.Deserialize<Dictionary<string, string>>(json2) ?? new Dictionary<string, string>();
|
var dict2 = JsonSerializer.Deserialize<Dictionary<string, string>>(json2) ?? new Dictionary<string, string>();
|
||||||
_cache[cultureFull] = dict2;
|
_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;
|
return;
|
||||||
}
|
}
|
||||||
else
|
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)
|
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
|
else
|
||||||
{
|
{
|
||||||
_logger?.LogDebug("IWebHostEnvironment not resolved from service provider");
|
_logger.LogDebug("<EFBFBD><EFBFBD><EFBFBD><EFBFBD> ServiceProvider <20><>ȡ IWebHostEnvironment ʵ<><CAB5>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
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)
|
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>();
|
_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)
|
private string MapToFullCulture(string culture)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(culture)) return culture;
|
if (string.IsNullOrEmpty(culture)) return culture;
|
||||||
// direct mapping
|
// ֱ<EFBFBD><EFBFBD>ӳ<EFBFBD><EFBFBD>
|
||||||
if (ShortToCulture.TryGetValue(culture, out var mapped)) return mapped;
|
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();
|
var prefix = culture.Split('-', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
|
||||||
if (!string.IsNullOrEmpty(prefix) && ShortToCulture.TryGetValue(prefix, out var mapped2)) return mapped2;
|
if (!string.IsNullOrEmpty(prefix) && ShortToCulture.TryGetValue(prefix, out var mapped2)) return mapped2;
|
||||||
return culture;
|
return culture;
|
||||||
|
|||||||
@@ -1,239 +0,0 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Text.Json;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.JSInterop;
|
|
||||||
using Blazored.LocalStorage;
|
|
||||||
|
|
||||||
namespace Atomx.Admin.Client.Services
|
|
||||||
{
|
|
||||||
public class WasmLocalizationProvider : ILocalizationProvider
|
|
||||||
{
|
|
||||||
private readonly IJSRuntime _jsRuntime;
|
|
||||||
private readonly HttpClient _httpClient;
|
|
||||||
private readonly ILogger<WasmLocalizationProvider> _logger;
|
|
||||||
private readonly ILocalStorageService _localStorage;
|
|
||||||
private readonly ILocalizationService _localizationService;
|
|
||||||
private static readonly ConcurrentDictionary<string, Dictionary<string, string>> _cache = new();
|
|
||||||
private readonly HashSet<string> _loadingCultures = new();
|
|
||||||
private string _currentCulture = "zh-Hans";
|
|
||||||
private bool _isInitialized = false;
|
|
||||||
|
|
||||||
private const string LocalizationStorageKey = "Localization_{0}";
|
|
||||||
private const string LocalizationVersionKey = "LocalizationVersion_{0}";
|
|
||||||
|
|
||||||
public WasmLocalizationProvider(IJSRuntime jsRuntime, HttpClient httpClient, ILogger<WasmLocalizationProvider> logger, ILocalStorageService localStorage, ILocalizationService localizationService)
|
|
||||||
{
|
|
||||||
_jsRuntime = jsRuntime;
|
|
||||||
_httpClient = httpClient;
|
|
||||||
_logger = logger;
|
|
||||||
_localStorage = localStorage;
|
|
||||||
_localizationService = localizationService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string CurrentCulture => _currentCulture;
|
|
||||||
public bool IsInitialized => _isInitialized;
|
|
||||||
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
if (_isInitialized) return;
|
|
||||||
|
|
||||||
await LoadCultureAsync(_currentCulture);
|
|
||||||
|
|
||||||
// ensure thread cultures and notify localization service
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var ci = new CultureInfo(_currentCulture);
|
|
||||||
CultureInfo.DefaultThreadCurrentCulture = ci;
|
|
||||||
CultureInfo.DefaultThreadCurrentUICulture = ci;
|
|
||||||
_localizationService.SetLanguage(ci);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogDebug(ex, "Failed to set culture after initialize: {Culture}", _currentCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
_isInitialized = true;
|
|
||||||
LanguageChanged?.Invoke(this, _currentCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SetCultureAsync(string cultureShortOrFull)
|
|
||||||
{
|
|
||||||
var full = MapToFullCulture(cultureShortOrFull);
|
|
||||||
_currentCulture = full;
|
|
||||||
|
|
||||||
await LoadCultureAsync(_currentCulture);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var ci = new CultureInfo(_currentCulture);
|
|
||||||
CultureInfo.DefaultThreadCurrentCulture = ci;
|
|
||||||
CultureInfo.DefaultThreadCurrentUICulture = ci;
|
|
||||||
_localizationService.SetLanguage(ci);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogDebug(ex, "Failed to set culture in SetCultureAsync: {Culture}", _currentCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
LanguageChanged?.Invoke(this, _currentCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task LoadCultureAsync(string culture)
|
|
||||||
{
|
|
||||||
var cultureFull = MapToFullCulture(culture);
|
|
||||||
|
|
||||||
// Step 1: Check in-memory cache
|
|
||||||
if (_cache.ContainsKey(cultureFull)) return;
|
|
||||||
|
|
||||||
lock (_loadingCultures)
|
|
||||||
{
|
|
||||||
if (_loadingCultures.Contains(cultureFull))
|
|
||||||
{
|
|
||||||
_logger.LogDebug("Culture {Culture} is already being loaded.", cultureFull);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_loadingCultures.Add(cultureFull);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Step 2: Check local storage for cached data
|
|
||||||
var localDataKey = string.Format(LocalizationStorageKey, cultureFull);
|
|
||||||
var localVersionKey = string.Format(LocalizationVersionKey, cultureFull);
|
|
||||||
|
|
||||||
var cachedVersion = await _localStorage.GetItemAsync<string>(localVersionKey);
|
|
||||||
var cachedData = await _localStorage.GetItemAsync<Dictionary<string, string>>(localDataKey);
|
|
||||||
|
|
||||||
if (cachedData != null)
|
|
||||||
{
|
|
||||||
_cache[cultureFull] = cachedData;
|
|
||||||
_logger.LogInformation("Loaded localization for {Culture} from local storage.", cultureFull);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 3: Validate version with server
|
|
||||||
var versionUrl = $"api/localeresource/version/{cultureFull}";
|
|
||||||
var serverVersion = await _httpClient.GetStringAsync(versionUrl);
|
|
||||||
|
|
||||||
if (cachedVersion == serverVersion && cachedData != null)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Localization data for {Culture} is up-to-date.", cultureFull);
|
|
||||||
|
|
||||||
// ensure thread cultures and notify localization service when using cached data
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var ci = new CultureInfo(cultureFull);
|
|
||||||
CultureInfo.DefaultThreadCurrentCulture = ci;
|
|
||||||
CultureInfo.DefaultThreadCurrentUICulture = ci;
|
|
||||||
_localizationService.SetLanguage(ci);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogDebug(ex, "Failed to set culture after loading from cache: {Culture}", cultureFull);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 4: Fetch from server if version mismatch or no cached data
|
|
||||||
var url = $"/localization/{cultureFull}.json";
|
|
||||||
var json = await _httpClient.GetStringAsync(url);
|
|
||||||
var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(json) ?? new Dictionary<string, string>();
|
|
||||||
_cache[cultureFull] = dict;
|
|
||||||
|
|
||||||
// Step 5: Update local storage
|
|
||||||
await _localStorage.SetItemAsync(localVersionKey, serverVersion);
|
|
||||||
await _localStorage.SetItemAsync(localDataKey, dict);
|
|
||||||
|
|
||||||
_logger.LogInformation("Loaded localization file for {Culture} from server and updated local storage.", cultureFull);
|
|
||||||
|
|
||||||
// ensure thread cultures and notify localization service after fetching
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var ci = new CultureInfo(cultureFull);
|
|
||||||
CultureInfo.DefaultThreadCurrentCulture = ci;
|
|
||||||
CultureInfo.DefaultThreadCurrentUICulture = ci;
|
|
||||||
_localizationService.SetLanguage(ci);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogDebug(ex, "Failed to set culture after fetching: {Culture}", cultureFull);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Failed to load localization file for {Culture}", cultureFull);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
lock (_loadingCultures)
|
|
||||||
{
|
|
||||||
_loadingCultures.Remove(cultureFull);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify listeners that the culture has been loaded
|
|
||||||
LanguageChanged?.Invoke(this, cultureFull);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string MapToFullCulture(string culture)
|
|
||||||
{
|
|
||||||
return culture switch
|
|
||||||
{
|
|
||||||
"zh" => "zh-Hans",
|
|
||||||
"en" => "en-US",
|
|
||||||
_ => culture
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// <20>ӷ<EFBFBD><D3B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȡ<EFBFBD><C8A1><EFBFBD>ػ<EFBFBD><D8BB><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="culture"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private Dictionary<string, string> FetchFromServer(string culture)
|
|
||||||
{
|
|
||||||
var url = $"/localization/{culture}.json";
|
|
||||||
var json = _httpClient.GetStringAsync(url).Result;
|
|
||||||
var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(json) ?? new Dictionary<string, string>();
|
|
||||||
return dict;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// <20><><EFBFBD>鱾<EFBFBD>ػ<EFBFBD><D8BB>汾<EFBFBD>Ƿ<EFBFBD><C7B7><EFBFBD><EFBFBD><EFBFBD>
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="versionKey"></param>
|
|
||||||
/// <param name="culture"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private async Task<bool> CheckVersionAsync(string versionKey, string culture)
|
|
||||||
{
|
|
||||||
var cachedVersion = await _localStorage.GetItemAsync<string>(versionKey);
|
|
||||||
if(string.IsNullOrEmpty(cachedVersion))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var versionUrl = $"api/localeresource/version/{culture}";
|
|
||||||
var serverVersion = await _httpClient.GetStringAsync(versionUrl);
|
|
||||||
|
|
||||||
if (cachedVersion != serverVersion)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,6 @@ using Atomx.Utils.Models;
|
|||||||
using MapsterMapper;
|
using MapsterMapper;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
|
|
||||||
namespace Atomx.Admin.Controllers
|
namespace Atomx.Admin.Controllers
|
||||||
{
|
{
|
||||||
@@ -206,17 +205,65 @@ namespace Atomx.Admin.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
//异步更新对应的json文件
|
//异步更新对应的json文件
|
||||||
|
var language = await _cacheService.GetLanguageById(model.LanguageId);
|
||||||
|
var culture = language?.Culture ?? string.Empty;
|
||||||
|
if (!string.IsNullOrEmpty(culture))
|
||||||
|
{
|
||||||
var wwwroot = _environment.WebRootPath;
|
var wwwroot = _environment.WebRootPath;
|
||||||
|
var path = Path.Combine(wwwroot, "localization");
|
||||||
var dic = new Dictionary<string, string>();
|
var dic = new Dictionary<string, string>();
|
||||||
dic.Add(data.Name, data.Value);
|
dic.Add(data.Name, data.Value);
|
||||||
|
_backgroundService.UpdateLocalizationFile(path, culture, dic.ToJson());
|
||||||
_backgroundService.UpdateLocalizationFile(wwwroot, model.Culture, dic.ToJson());
|
}
|
||||||
|
|
||||||
result = result.IsSuccess(true);
|
result = result.IsSuccess(true);
|
||||||
|
|
||||||
return new JsonResult(result);
|
return new JsonResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除多语言翻译数据
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpPost("delete")]
|
||||||
|
public async Task<IActionResult> DeleteAsync(long id)
|
||||||
|
{
|
||||||
|
var result = new ApiResult<bool>();
|
||||||
|
var data = _dbContext.LocaleResources.SingleOrDefault(p => p.Id == id);
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
result = result.IsFail("数据不存在,请更换!");
|
||||||
|
return new JsonResult(result);
|
||||||
|
}
|
||||||
|
_dbContext.LocaleResources.Remove(data);
|
||||||
|
_dbContext.SaveChanges();
|
||||||
|
result = result.IsSuccess(true);
|
||||||
|
return new JsonResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重构多语言文件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpPost("rebuild")]
|
||||||
|
public async Task<IActionResult> RebuidCultureFile(long id)
|
||||||
|
{
|
||||||
|
var result = new ApiResult<bool>();
|
||||||
|
var data = _dbContext.Languages.SingleOrDefault(p => p.Id == id);
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
result = result.IsFail("数据不存在,请更换!");
|
||||||
|
return new JsonResult(result);
|
||||||
|
}
|
||||||
|
var wwwroot = _environment.WebRootPath;
|
||||||
|
var path = Path.Combine(wwwroot, "localization");
|
||||||
|
_backgroundService.RebuildLocalizationFile(path, data.Culture);
|
||||||
|
result = result.IsSuccess(true);
|
||||||
|
return new JsonResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取对应的多语言文化数据的版本
|
/// 获取对应的多语言文化数据的版本
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ namespace Atomx.Admin.Extensions
|
|||||||
language.Culture = "zh-cn";
|
language.Culture = "zh-cn";
|
||||||
language.CreateTime = DateTime.UtcNow;
|
language.CreateTime = DateTime.UtcNow;
|
||||||
language.UpdateTime = DateTime.UtcNow;
|
language.UpdateTime = DateTime.UtcNow;
|
||||||
language.Id = 1;
|
|
||||||
language.Name = "中文";
|
language.Name = "中文";
|
||||||
language.DisplayOrder = 0;
|
language.DisplayOrder = 0;
|
||||||
language.Enabled = true;
|
language.Enabled = true;
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ namespace Atomx.Admin.Middlewares
|
|||||||
{
|
{
|
||||||
logger?.LogDebug("ILocalizationProvider not registered in RequestServices");
|
logger?.LogDebug("ILocalizationProvider not registered in RequestServices");
|
||||||
}
|
}
|
||||||
else if (providerObj is Atomx.Admin.Services.ServerLocalizationProvider provider)
|
else if (providerObj is Atomx.Admin.Client.Services.LocalizationProvider provider)
|
||||||
{
|
{
|
||||||
logger?.LogDebug("Calling SetCultureForServer on LocalizationProvider with {Culture}", cultureName);
|
logger?.LogDebug("Calling SetCultureForServer on LocalizationProvider with {Culture}", cultureName);
|
||||||
_= provider.SetCultureAsync(cultureName);
|
_= provider.SetCultureAsync(cultureName);
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ builder.Services.AddHttpContextAccessor();
|
|||||||
builder.Services.AddHttpClientApiService(builder.Configuration["WebApi:ServerUrl"] ?? "http://localhost");
|
builder.Services.AddHttpClientApiService(builder.Configuration["WebApi:ServerUrl"] ?? "http://localhost");
|
||||||
|
|
||||||
// ע<>뱾<EFBFBD>ػ<EFBFBD><D8BB>ṩ<EFBFBD><E1B9A9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
// ע<>뱾<EFBFBD>ػ<EFBFBD><D8BB>ṩ<EFBFBD><E1B9A9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||||
builder.Services.AddScoped<ILocalizationProvider, ServerLocalizationProvider>();
|
builder.Services.AddScoped<ILocalizationProvider, LocalizationProvider>();
|
||||||
builder.Services.AddScoped<ILocalizationService, LocalizationService>();
|
builder.Services.AddScoped<ILocalizationService, LocalizationService>();
|
||||||
builder.Services.AddTransient(typeof(IStringLocalizer<>), typeof(JsonStringLocalizer<>));
|
builder.Services.AddTransient(typeof(IStringLocalizer<>), typeof(JsonStringLocalizer<>));
|
||||||
|
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
using Atomx.Admin.Client.Services;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
|
||||||
|
|
||||||
namespace Atomx.Admin.Services
|
|
||||||
{
|
|
||||||
public class ServerLocalizationProvider : ILocalizationProvider
|
|
||||||
{
|
|
||||||
private readonly IServiceProvider _serviceProvider;
|
|
||||||
private readonly ILogger<ServerLocalizationProvider> _logger;
|
|
||||||
private static readonly ConcurrentDictionary<string, Dictionary<string, string>> _cache = new();
|
|
||||||
private string _currentCulture = "zh-Hans";
|
|
||||||
|
|
||||||
public ServerLocalizationProvider(IServiceProvider serviceProvider, ILogger<ServerLocalizationProvider> logger)
|
|
||||||
{
|
|
||||||
_serviceProvider = serviceProvider;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
await LoadCultureAsync(_currentCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SetCultureAsync(string cultureShortOrFull)
|
|
||||||
{
|
|
||||||
_currentCulture = MapToFullCulture(cultureShortOrFull);
|
|
||||||
await LoadCultureAsync(_currentCulture);
|
|
||||||
LanguageChanged?.Invoke(this, _currentCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task LoadCultureAsync(string culture)
|
|
||||||
{
|
|
||||||
var cultureFull = MapToFullCulture(culture);
|
|
||||||
if (_cache.ContainsKey(cultureFull)) return Task.CompletedTask;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var env = _serviceProvider.GetService(typeof(Microsoft.AspNetCore.Hosting.IWebHostEnvironment)) as Microsoft.AspNetCore.Hosting.IWebHostEnvironment;
|
|
||||||
if (env != null)
|
|
||||||
{
|
|
||||||
var path = Path.Combine(env.WebRootPath, "localization", cultureFull + ".json");
|
|
||||||
if (File.Exists(path))
|
|
||||||
{
|
|
||||||
var json = File.ReadAllText(path);
|
|
||||||
var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(json) ?? new Dictionary<string, string>();
|
|
||||||
_cache[cultureFull] = dict;
|
|
||||||
_logger.LogInformation("Loaded localization file for {Culture} from {Path}", cultureFull, path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Failed to load localization file for {Culture}", cultureFull);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string MapToFullCulture(string culture)
|
|
||||||
{
|
|
||||||
return culture switch
|
|
||||||
{
|
|
||||||
"zh" => "zh-Hans",
|
|
||||||
"en" => "en-US",
|
|
||||||
_ => culture
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,6 +6,12 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="Utils\**" />
|
||||||
|
<EmbeddedResource Remove="Utils\**" />
|
||||||
|
<None Remove="Utils\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Mapster.DependencyInjection" Version="1.0.1" />
|
<PackageReference Include="Mapster.DependencyInjection" Version="1.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -14,8 +20,4 @@
|
|||||||
<ProjectReference Include="..\Atomx.Utils\Atomx.Utils.csproj" />
|
<ProjectReference Include="..\Atomx.Utils\Atomx.Utils.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Utils\" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 邮件地址
|
/// 邮件地址
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string Email { get; set; } = string.Empty;
|
public string Email { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -42,7 +42,7 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 公司
|
/// 公司
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string Company { get; set; } = string.Empty;
|
public string Company { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace Atomx.Common.Entities
|
|||||||
/// 地区名称
|
/// 地区名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -45,31 +45,31 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Meta描述介绍
|
/// Meta描述介绍
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string MetaDescription { get; set; } = string.Empty;
|
public string MetaDescription { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Meta关键词
|
/// Meta关键词
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string MetaKeywords { get; set; } = string.Empty;
|
public string MetaKeywords { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 过滤属性IDs
|
/// 过滤属性IDs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string FilterAttributes { get; set; } = string.Empty;
|
public string FilterAttributes { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 分类图片
|
/// 分类图片
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string Image { get; set; } = string.Empty;
|
public string Image { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 分类页 banner
|
/// 分类页 banner
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string Banner { get; set; } = string.Empty;
|
public string Banner { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 国旗
|
/// 国旗
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string FlagImage { get; set; } = string.Empty;
|
public string FlagImage { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -53,7 +53,7 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 多语言资源的版本,可以是时间戳或哈希
|
/// 多语言资源的版本,可以是时间戳或哈希
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(25)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string ResourceVersion { get; set; } = string.Empty;
|
public string ResourceVersion { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 资源名称
|
/// 资源名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 数据KEY
|
/// 数据KEY
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string Key { get; set; } = string.Empty;
|
public string Key { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Meta标题
|
/// Meta标题
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string MetaKeywords { get; set; } = string.Empty;
|
public string MetaKeywords { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 备注说明
|
/// 备注说明
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string Note { get; set; } = string.Empty;
|
public string Note { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 分类图标
|
/// 分类图标
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string Icon { get; set; } = string.Empty;
|
public string Icon { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -47,7 +47,7 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 菜单链接的URL
|
/// 菜单链接的URL
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string Url { get; set; } = string.Empty;
|
public string Url { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 邮件地址
|
/// 邮件地址
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string Email { get; set; } = string.Empty;
|
public string Email { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -87,7 +87,7 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 公司
|
/// 公司
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string Company { get; set; } = string.Empty;
|
public string Company { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -303,13 +303,13 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 订单备注说明
|
/// 订单备注说明
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string? Remark { get; set; } = string.Empty;
|
public string? Remark { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 卖家下单备注信息
|
/// 卖家下单备注信息
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string? Message { get; set; } = string.Empty;
|
public string? Message { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 商品名称
|
/// 商品名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string Title { get; set; } = string.Empty;
|
public string Title { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 收款账号
|
/// 收款账号
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string Account { get; set; } = string.Empty;
|
public string Account { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 权限点说明
|
/// 权限点说明
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string Description { get; set; } = string.Empty;
|
public string Description { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 产品名称
|
/// 产品名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string Title { get; set; } = string.Empty;
|
public string Title { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -80,13 +80,13 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 产品卖点
|
/// 产品卖点
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string? Feature { get; set; } = string.Empty;
|
public string? Feature { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 产品简介
|
/// 产品简介
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string? Description { get; set; } = string.Empty;
|
public string? Description { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 备注说明
|
/// 备注说明
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string Note { get; set; } = string.Empty;
|
public string Note { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 属性图片
|
/// 属性图片
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string Image { get; set; } = string.Empty;
|
public string Image { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 备注说明
|
/// 备注说明
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string Note { get; set; } = string.Empty;
|
public string Note { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// SEO Title
|
/// SEO Title
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string MetaTitle { get; set; } = string.Empty;
|
public string MetaTitle { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 说明
|
/// 说明
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string Description { get; set; } = string.Empty;
|
public string Description { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 字段KEY
|
/// 字段KEY
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string MetaKey { get; set; } = string.Empty;
|
public string MetaKey { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ namespace Atomx.Common.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 备注说明
|
/// 备注说明
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Column(TypeName = "varchar(255)")]
|
[Column(TypeName = "varchar(256)")]
|
||||||
public string Note { get; set; } = string.Empty;
|
public string Note { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -34,6 +34,12 @@ namespace Atomx.Core.Jos
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var translations = data.FromJson<Dictionary<string, string>>();
|
var translations = data.FromJson<Dictionary<string, string>>();
|
||||||
|
if (translations == null)
|
||||||
|
{
|
||||||
|
_logger.LogError("No translations provided for culture: {Culture}", culture);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
var fileName = $"{culture}.json";
|
var fileName = $"{culture}.json";
|
||||||
var filePath = Path.Combine(path, fileName);
|
var filePath = Path.Combine(path, fileName);
|
||||||
@@ -73,6 +79,7 @@ namespace Atomx.Core.Jos
|
|||||||
{
|
{
|
||||||
var hashBytes = sha256.ComputeHash(stream);
|
var hashBytes = sha256.ComputeHash(stream);
|
||||||
fileHash = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
|
fileHash = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
|
||||||
|
Console.WriteLine(fileHash);
|
||||||
}
|
}
|
||||||
var language = _dbContext.Languages.FirstOrDefault(l => l.Culture == culture);
|
var language = _dbContext.Languages.FirstOrDefault(l => l.Culture == culture);
|
||||||
if (language != null)
|
if (language != null)
|
||||||
@@ -87,10 +94,37 @@ namespace Atomx.Core.Jos
|
|||||||
_logger.LogInformation("Saved localization file for culture: {Culture} with {Count} translations. File hash: {Hash}",
|
_logger.LogInformation("Saved localization file for culture: {Culture} with {Count} translations. File hash: {Hash}",
|
||||||
culture, translations.Count, fileHash);
|
culture, translations.Count, fileHash);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error saving localization file for culture: {Culture}", culture);
|
_logger.LogError(ex, "Error saving localization file for culture: {Culture}", culture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重构指定文化的本地化文件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path"></param>
|
||||||
|
/// <param name="culture"></param>
|
||||||
|
[AutomaticRetry(Attempts = 3, OnAttemptsExceeded = AttemptsExceededAction.Delete)]
|
||||||
|
[DisableConcurrentExecution(60)]
|
||||||
|
public void RebuildCultureFile(string path, string culture)
|
||||||
|
{
|
||||||
|
var language = _dbContext.Languages.FirstOrDefault(l => l.Culture == culture);
|
||||||
|
if (language != null)
|
||||||
|
{
|
||||||
|
var translations = _dbContext.LocaleResources
|
||||||
|
.Where(lr => lr.LanguageId == language.Id)
|
||||||
|
.ToDictionary(lr => lr.Name, lr => lr.Value);
|
||||||
|
var data = JsonSerializer.Serialize(translations, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||||
|
WriteIndented = true
|
||||||
|
});
|
||||||
|
|
||||||
|
_ = ExecuteAsync(path, culture, data);
|
||||||
|
}
|
||||||
|
_logger.LogError("Language not found for culture: {Culture}, cannot rebuild localization file.", culture);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,16 @@ namespace Atomx.Core.Jos
|
|||||||
/// <param name="data"></param>
|
/// <param name="data"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
string UpdateLocalizationFile(string path, string culture, string data);
|
string UpdateLocalizationFile(string path, string culture, string data);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重构翻译本地化文件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path"></param>
|
||||||
|
/// <param name="culture"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
string RebuildLocalizationFile(string path, string culture);
|
||||||
|
|
||||||
|
|
||||||
string SendSMSVerificationCode(string phoneNumber, string code, TimeSpan validDuration);
|
string SendSMSVerificationCode(string phoneNumber, string code, TimeSpan validDuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +51,18 @@ namespace Atomx.Core.Jos
|
|||||||
return jobId;
|
return jobId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重构翻译本地化文件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path"></param>
|
||||||
|
/// <param name="culture"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string RebuildLocalizationFile(string path, string culture)
|
||||||
|
{
|
||||||
|
var jobId = _backgroundJobClient.Enqueue<LocalizationJob>(job => job.RebuildCultureFile(path, culture));
|
||||||
|
return jobId;
|
||||||
|
}
|
||||||
|
|
||||||
public string SendSMSVerificationCode(string phoneNumber, string code, TimeSpan validDuration)
|
public string SendSMSVerificationCode(string phoneNumber, string code, TimeSpan validDuration)
|
||||||
{
|
{
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|||||||
namespace Atomx.Data.Migrations
|
namespace Atomx.Data.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(DataContext))]
|
[DbContext(typeof(DataContext))]
|
||||||
[Migration("20251203190956_0.1")]
|
[Migration("20251214094758_0.1")]
|
||||||
partial class _01
|
partial class _01
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -20,7 +20,7 @@ namespace Atomx.Data.Migrations
|
|||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "10.0.0")
|
.HasAnnotation("ProductVersion", "10.0.1")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
@@ -43,7 +43,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Company")
|
b.Property<string>("Company")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<int>("Count")
|
b.Property<int>("Count")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
@@ -60,7 +60,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Email")
|
b.Property<string>("Email")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<string>("FullAddress")
|
b.Property<string>("FullAddress")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@@ -254,7 +254,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<int>("NumericISOCode")
|
b.Property<int>("NumericISOCode")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
@@ -282,7 +282,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Banner")
|
b.Property<string>("Banner")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<int>("Count")
|
b.Property<int>("Count")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
@@ -301,22 +301,22 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("FilterAttributes")
|
b.Property<string>("FilterAttributes")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<string>("Image")
|
b.Property<string>("Image")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<bool>("IsNode")
|
b.Property<bool>("IsNode")
|
||||||
.HasColumnType("boolean");
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
b.Property<string>("MetaDescription")
|
b.Property<string>("MetaDescription")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<string>("MetaKeywords")
|
b.Property<string>("MetaKeywords")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@@ -470,7 +470,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("FlagImage")
|
b.Property<string>("FlagImage")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@@ -478,7 +478,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("ResourceVersion")
|
b.Property<string>("ResourceVersion")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(25)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<string>("Title")
|
b.Property<string>("Title")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@@ -502,7 +502,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<DateTime?>("UpdateTime")
|
b.Property<DateTime?>("UpdateTime")
|
||||||
.HasColumnType("timestamptz");
|
.HasColumnType("timestamptz");
|
||||||
@@ -526,7 +526,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Key")
|
b.Property<string>("Key")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<int>("LanguageNumber")
|
b.Property<int>("LanguageNumber")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
@@ -566,7 +566,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Icon")
|
b.Property<string>("Icon")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<bool>("IsLink")
|
b.Property<bool>("IsLink")
|
||||||
.HasColumnType("boolean");
|
.HasColumnType("boolean");
|
||||||
@@ -594,7 +594,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Url")
|
b.Property<string>("Url")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
@@ -646,7 +646,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Account")
|
b.Property<string>("Account")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<string>("Config")
|
b.Property<string>("Config")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@@ -698,7 +698,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Description")
|
b.Property<string>("Description")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@@ -736,7 +736,7 @@ namespace Atomx.Data.Migrations
|
|||||||
.HasColumnType("timestamptz");
|
.HasColumnType("timestamptz");
|
||||||
|
|
||||||
b.Property<string>("Description")
|
b.Property<string>("Description")
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<int>("DisplayOrder")
|
b.Property<int>("DisplayOrder")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
@@ -746,7 +746,7 @@ namespace Atomx.Data.Migrations
|
|||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<string>("Feature")
|
b.Property<string>("Feature")
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<string>("Image")
|
b.Property<string>("Image")
|
||||||
.HasColumnType("varchar(256)");
|
.HasColumnType("varchar(256)");
|
||||||
@@ -804,7 +804,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Title")
|
b.Property<string>("Title")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<DateTime?>("UpdateTime")
|
b.Property<DateTime?>("UpdateTime")
|
||||||
.HasColumnType("timestamptz");
|
.HasColumnType("timestamptz");
|
||||||
@@ -890,7 +890,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Note")
|
b.Property<string>("Note")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<decimal>("Price")
|
b.Property<decimal>("Price")
|
||||||
.HasColumnType("decimal(18,4)");
|
.HasColumnType("decimal(18,4)");
|
||||||
@@ -1001,7 +1001,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Image")
|
b.Property<string>("Image")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<bool>("IsAddWeight")
|
b.Property<bool>("IsAddWeight")
|
||||||
.HasColumnType("boolean");
|
.HasColumnType("boolean");
|
||||||
@@ -1086,7 +1086,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Note")
|
b.Property<string>("Note")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<long?>("Operator")
|
b.Property<long?>("Operator")
|
||||||
.HasColumnType("bigint");
|
.HasColumnType("bigint");
|
||||||
@@ -1170,7 +1170,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Description")
|
b.Property<string>("Description")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<bool>("Enabled")
|
b.Property<bool>("Enabled")
|
||||||
.HasColumnType("boolean");
|
.HasColumnType("boolean");
|
||||||
@@ -19,9 +19,9 @@ namespace Atomx.Data.Migrations
|
|||||||
Id = table.Column<long>(type: "bigint", nullable: false),
|
Id = table.Column<long>(type: "bigint", nullable: false),
|
||||||
UserId = table.Column<long>(type: "bigint", nullable: false),
|
UserId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
Name = table.Column<string>(type: "varchar(128)", nullable: false),
|
Name = table.Column<string>(type: "varchar(128)", nullable: false),
|
||||||
Email = table.Column<string>(type: "varchar(255)", nullable: false),
|
Email = table.Column<string>(type: "varchar(256)", nullable: false),
|
||||||
Phone = table.Column<string>(type: "varchar(20)", nullable: false),
|
Phone = table.Column<string>(type: "varchar(20)", nullable: false),
|
||||||
Company = table.Column<string>(type: "varchar(255)", nullable: false),
|
Company = table.Column<string>(type: "varchar(256)", nullable: false),
|
||||||
CountryId = table.Column<long>(type: "bigint", nullable: false),
|
CountryId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
Country = table.Column<string>(type: "varchar(50)", nullable: false),
|
Country = table.Column<string>(type: "varchar(50)", nullable: false),
|
||||||
ProvinceId = table.Column<long>(type: "bigint", nullable: false),
|
ProvinceId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
@@ -102,7 +102,7 @@ namespace Atomx.Data.Migrations
|
|||||||
{
|
{
|
||||||
Id = table.Column<long>(type: "bigint", nullable: false),
|
Id = table.Column<long>(type: "bigint", nullable: false),
|
||||||
ParentId = table.Column<long>(type: "bigint", nullable: false),
|
ParentId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
Name = table.Column<string>(type: "varchar(255)", nullable: false),
|
Name = table.Column<string>(type: "varchar(256)", nullable: false),
|
||||||
Initial = table.Column<string>(type: "varchar(1)", nullable: false),
|
Initial = table.Column<string>(type: "varchar(1)", nullable: false),
|
||||||
TwoLetterISOCode = table.Column<string>(type: "varchar(2)", nullable: false),
|
TwoLetterISOCode = table.Column<string>(type: "varchar(2)", nullable: false),
|
||||||
ThreeLetterISOCode = table.Column<string>(type: "varchar(3)", nullable: false),
|
ThreeLetterISOCode = table.Column<string>(type: "varchar(3)", nullable: false),
|
||||||
@@ -126,11 +126,11 @@ namespace Atomx.Data.Migrations
|
|||||||
ParentId = table.Column<long>(type: "bigint", nullable: false),
|
ParentId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
Name = table.Column<string>(type: "varchar(25)", nullable: false),
|
Name = table.Column<string>(type: "varchar(25)", nullable: false),
|
||||||
Slug = table.Column<string>(type: "varchar(50)", nullable: false),
|
Slug = table.Column<string>(type: "varchar(50)", nullable: false),
|
||||||
MetaDescription = table.Column<string>(type: "varchar(255)", nullable: false),
|
MetaDescription = table.Column<string>(type: "varchar(256)", nullable: false),
|
||||||
MetaKeywords = table.Column<string>(type: "varchar(255)", nullable: false),
|
MetaKeywords = table.Column<string>(type: "varchar(256)", nullable: false),
|
||||||
FilterAttributes = table.Column<string>(type: "varchar(255)", nullable: false),
|
FilterAttributes = table.Column<string>(type: "varchar(256)", nullable: false),
|
||||||
Image = table.Column<string>(type: "varchar(255)", nullable: false),
|
Image = table.Column<string>(type: "varchar(256)", nullable: false),
|
||||||
Banner = table.Column<string>(type: "varchar(255)", nullable: false),
|
Banner = table.Column<string>(type: "varchar(256)", nullable: false),
|
||||||
IsNode = table.Column<bool>(type: "boolean", nullable: false),
|
IsNode = table.Column<bool>(type: "boolean", nullable: false),
|
||||||
Enabled = table.Column<bool>(type: "boolean", nullable: false),
|
Enabled = table.Column<bool>(type: "boolean", nullable: false),
|
||||||
Depth = table.Column<int>(type: "integer", nullable: false),
|
Depth = table.Column<int>(type: "integer", nullable: false),
|
||||||
@@ -200,10 +200,10 @@ namespace Atomx.Data.Migrations
|
|||||||
Name = table.Column<string>(type: "varchar(50)", nullable: false),
|
Name = table.Column<string>(type: "varchar(50)", nullable: false),
|
||||||
Title = table.Column<string>(type: "varchar(50)", nullable: false),
|
Title = table.Column<string>(type: "varchar(50)", nullable: false),
|
||||||
Culture = table.Column<string>(type: "varchar(25)", nullable: false),
|
Culture = table.Column<string>(type: "varchar(25)", nullable: false),
|
||||||
FlagImage = table.Column<string>(type: "varchar(255)", nullable: false),
|
FlagImage = table.Column<string>(type: "varchar(256)", nullable: false),
|
||||||
DisplayOrder = table.Column<int>(type: "integer", nullable: false),
|
DisplayOrder = table.Column<int>(type: "integer", nullable: false),
|
||||||
Enabled = table.Column<bool>(type: "boolean", nullable: false),
|
Enabled = table.Column<bool>(type: "boolean", nullable: false),
|
||||||
ResourceVersion = table.Column<string>(type: "varchar(25)", nullable: false),
|
ResourceVersion = table.Column<string>(type: "varchar(256)", nullable: false),
|
||||||
CreateTime = table.Column<DateTime>(type: "timestamptz", nullable: false),
|
CreateTime = table.Column<DateTime>(type: "timestamptz", nullable: false),
|
||||||
UpdateTime = table.Column<DateTime>(type: "timestamptz", nullable: true)
|
UpdateTime = table.Column<DateTime>(type: "timestamptz", nullable: true)
|
||||||
},
|
},
|
||||||
@@ -218,7 +218,7 @@ namespace Atomx.Data.Migrations
|
|||||||
{
|
{
|
||||||
Id = table.Column<long>(type: "bigint", nullable: false),
|
Id = table.Column<long>(type: "bigint", nullable: false),
|
||||||
LanguageId = table.Column<int>(type: "integer", nullable: false),
|
LanguageId = table.Column<int>(type: "integer", nullable: false),
|
||||||
Name = table.Column<string>(type: "varchar(255)", nullable: false),
|
Name = table.Column<string>(type: "varchar(256)", nullable: false),
|
||||||
Value = table.Column<string>(type: "varchar(1000)", nullable: false),
|
Value = table.Column<string>(type: "varchar(1000)", nullable: false),
|
||||||
UpdateTime = table.Column<DateTime>(type: "timestamptz", nullable: true)
|
UpdateTime = table.Column<DateTime>(type: "timestamptz", nullable: true)
|
||||||
},
|
},
|
||||||
@@ -235,7 +235,7 @@ namespace Atomx.Data.Migrations
|
|||||||
LanguageNumber = table.Column<int>(type: "integer", nullable: false),
|
LanguageNumber = table.Column<int>(type: "integer", nullable: false),
|
||||||
Type = table.Column<int>(type: "integer", nullable: false),
|
Type = table.Column<int>(type: "integer", nullable: false),
|
||||||
EntityId = table.Column<long>(type: "bigint", nullable: false),
|
EntityId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
Key = table.Column<string>(type: "varchar(255)", nullable: false),
|
Key = table.Column<string>(type: "varchar(256)", nullable: false),
|
||||||
Value = table.Column<string>(type: "text", nullable: false)
|
Value = table.Column<string>(type: "text", nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
@@ -251,9 +251,9 @@ namespace Atomx.Data.Migrations
|
|||||||
Type = table.Column<int>(type: "integer", nullable: false),
|
Type = table.Column<int>(type: "integer", nullable: false),
|
||||||
ParentId = table.Column<long>(type: "bigint", nullable: false),
|
ParentId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
Name = table.Column<string>(type: "varchar(50)", nullable: false),
|
Name = table.Column<string>(type: "varchar(50)", nullable: false),
|
||||||
Icon = table.Column<string>(type: "varchar(255)", nullable: false),
|
Icon = table.Column<string>(type: "varchar(256)", nullable: false),
|
||||||
Key = table.Column<string>(type: "varchar(50)", nullable: false),
|
Key = table.Column<string>(type: "varchar(50)", nullable: false),
|
||||||
Url = table.Column<string>(type: "varchar(255)", nullable: false),
|
Url = table.Column<string>(type: "varchar(256)", nullable: false),
|
||||||
Code = table.Column<string>(type: "varchar(50)", nullable: false),
|
Code = table.Column<string>(type: "varchar(50)", nullable: false),
|
||||||
IsLink = table.Column<bool>(type: "boolean", nullable: false),
|
IsLink = table.Column<bool>(type: "boolean", nullable: false),
|
||||||
Enabled = table.Column<bool>(type: "boolean", nullable: false),
|
Enabled = table.Column<bool>(type: "boolean", nullable: false),
|
||||||
@@ -297,7 +297,7 @@ namespace Atomx.Data.Migrations
|
|||||||
Name = table.Column<string>(type: "varchar(20)", nullable: false),
|
Name = table.Column<string>(type: "varchar(20)", nullable: false),
|
||||||
Title = table.Column<string>(type: "varchar(20)", nullable: false),
|
Title = table.Column<string>(type: "varchar(20)", nullable: false),
|
||||||
Description = table.Column<string>(type: "varchar(512)", nullable: false),
|
Description = table.Column<string>(type: "varchar(512)", nullable: false),
|
||||||
Account = table.Column<string>(type: "varchar(255)", nullable: false),
|
Account = table.Column<string>(type: "varchar(256)", nullable: false),
|
||||||
Config = table.Column<string>(type: "text", nullable: false),
|
Config = table.Column<string>(type: "text", nullable: false),
|
||||||
DisplayOrder = table.Column<int>(type: "integer", nullable: false),
|
DisplayOrder = table.Column<int>(type: "integer", nullable: false),
|
||||||
Status = table.Column<int>(type: "integer", nullable: false),
|
Status = table.Column<int>(type: "integer", nullable: false),
|
||||||
@@ -315,7 +315,7 @@ namespace Atomx.Data.Migrations
|
|||||||
{
|
{
|
||||||
Id = table.Column<long>(type: "bigint", nullable: false),
|
Id = table.Column<long>(type: "bigint", nullable: false),
|
||||||
Name = table.Column<string>(type: "varchar(128)", nullable: false),
|
Name = table.Column<string>(type: "varchar(128)", nullable: false),
|
||||||
Description = table.Column<string>(type: "varchar(255)", nullable: false),
|
Description = table.Column<string>(type: "varchar(256)", nullable: false),
|
||||||
Category = table.Column<string>(type: "varchar(128)", nullable: false)
|
Category = table.Column<string>(type: "varchar(128)", nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
@@ -344,7 +344,7 @@ namespace Atomx.Data.Migrations
|
|||||||
MarketPrice = table.Column<decimal>(type: "numeric(18,4)", nullable: false),
|
MarketPrice = table.Column<decimal>(type: "numeric(18,4)", nullable: false),
|
||||||
Price = table.Column<decimal>(type: "numeric(18,4)", nullable: false),
|
Price = table.Column<decimal>(type: "numeric(18,4)", nullable: false),
|
||||||
Mark = table.Column<int>(type: "integer", nullable: false),
|
Mark = table.Column<int>(type: "integer", nullable: false),
|
||||||
Note = table.Column<string>(type: "varchar(255)", nullable: false),
|
Note = table.Column<string>(type: "varchar(256)", nullable: false),
|
||||||
Enabled = table.Column<bool>(type: "boolean", nullable: false),
|
Enabled = table.Column<bool>(type: "boolean", nullable: false),
|
||||||
UpdateTime = table.Column<DateTime>(type: "timestamptz", nullable: true)
|
UpdateTime = table.Column<DateTime>(type: "timestamptz", nullable: true)
|
||||||
},
|
},
|
||||||
@@ -419,7 +419,7 @@ namespace Atomx.Data.Migrations
|
|||||||
OptionId = table.Column<long>(type: "bigint", nullable: false),
|
OptionId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
ProductAttributeRelationId = table.Column<long>(type: "bigint", nullable: false),
|
ProductAttributeRelationId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
Name = table.Column<string>(type: "varchar(50)", nullable: false),
|
Name = table.Column<string>(type: "varchar(50)", nullable: false),
|
||||||
Image = table.Column<string>(type: "varchar(255)", nullable: false),
|
Image = table.Column<string>(type: "varchar(256)", nullable: false),
|
||||||
Weight = table.Column<decimal>(type: "numeric(18,4)", nullable: false),
|
Weight = table.Column<decimal>(type: "numeric(18,4)", nullable: false),
|
||||||
WeightUnit = table.Column<int>(type: "integer", nullable: false),
|
WeightUnit = table.Column<int>(type: "integer", nullable: false),
|
||||||
IsAddWeight = table.Column<bool>(type: "boolean", nullable: false),
|
IsAddWeight = table.Column<bool>(type: "boolean", nullable: false),
|
||||||
@@ -466,7 +466,7 @@ namespace Atomx.Data.Migrations
|
|||||||
Weight = table.Column<decimal>(type: "numeric(18,4)", nullable: false),
|
Weight = table.Column<decimal>(type: "numeric(18,4)", nullable: false),
|
||||||
BeforeStock = table.Column<int>(type: "integer", nullable: false),
|
BeforeStock = table.Column<int>(type: "integer", nullable: false),
|
||||||
AfterStock = table.Column<int>(type: "integer", nullable: false),
|
AfterStock = table.Column<int>(type: "integer", nullable: false),
|
||||||
Note = table.Column<string>(type: "varchar(255)", nullable: false),
|
Note = table.Column<string>(type: "varchar(256)", nullable: false),
|
||||||
CreateTime = table.Column<DateTime>(type: "timestamptz", nullable: false)
|
CreateTime = table.Column<DateTime>(type: "timestamptz", nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
@@ -486,12 +486,12 @@ namespace Atomx.Data.Migrations
|
|||||||
CategoryPath = table.Column<string>(type: "varchar(200)", nullable: true),
|
CategoryPath = table.Column<string>(type: "varchar(200)", nullable: true),
|
||||||
CategoryId = table.Column<long>(type: "bigint", nullable: false),
|
CategoryId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
ManufacturerId = table.Column<long>(type: "bigint", nullable: false),
|
ManufacturerId = table.Column<long>(type: "bigint", nullable: false),
|
||||||
Title = table.Column<string>(type: "varchar(255)", nullable: false),
|
Title = table.Column<string>(type: "varchar(256)", nullable: false),
|
||||||
SIN = table.Column<string>(type: "varchar(20)", nullable: true),
|
SIN = table.Column<string>(type: "varchar(20)", nullable: true),
|
||||||
Image = table.Column<string>(type: "varchar(256)", nullable: true),
|
Image = table.Column<string>(type: "varchar(256)", nullable: true),
|
||||||
Photos = table.Column<string>(type: "text", nullable: true),
|
Photos = table.Column<string>(type: "text", nullable: true),
|
||||||
Feature = table.Column<string>(type: "varchar(255)", nullable: true),
|
Feature = table.Column<string>(type: "varchar(256)", nullable: true),
|
||||||
Description = table.Column<string>(type: "varchar(255)", nullable: true),
|
Description = table.Column<string>(type: "varchar(256)", nullable: true),
|
||||||
SpecificationJson = table.Column<string>(type: "text", nullable: true),
|
SpecificationJson = table.Column<string>(type: "text", nullable: true),
|
||||||
Weight = table.Column<decimal>(type: "numeric(6,2)", nullable: true),
|
Weight = table.Column<decimal>(type: "numeric(6,2)", nullable: true),
|
||||||
MinWeight = table.Column<decimal>(type: "numeric(6,2)", nullable: true),
|
MinWeight = table.Column<decimal>(type: "numeric(6,2)", nullable: true),
|
||||||
@@ -544,7 +544,7 @@ namespace Atomx.Data.Migrations
|
|||||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
Type = table.Column<int>(type: "integer", nullable: false),
|
Type = table.Column<int>(type: "integer", nullable: false),
|
||||||
Name = table.Column<string>(type: "text", nullable: false),
|
Name = table.Column<string>(type: "text", nullable: false),
|
||||||
Description = table.Column<string>(type: "varchar(255)", nullable: false),
|
Description = table.Column<string>(type: "varchar(256)", nullable: false),
|
||||||
Permission = table.Column<string>(type: "text", nullable: false),
|
Permission = table.Column<string>(type: "text", nullable: false),
|
||||||
IsSystemRole = table.Column<bool>(type: "boolean", nullable: false),
|
IsSystemRole = table.Column<bool>(type: "boolean", nullable: false),
|
||||||
Count = table.Column<int>(type: "integer", nullable: false),
|
Count = table.Column<int>(type: "integer", nullable: false),
|
||||||
@@ -17,7 +17,7 @@ namespace Atomx.Data.Migrations
|
|||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "10.0.0")
|
.HasAnnotation("ProductVersion", "10.0.1")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
@@ -40,7 +40,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Company")
|
b.Property<string>("Company")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<int>("Count")
|
b.Property<int>("Count")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
@@ -57,7 +57,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Email")
|
b.Property<string>("Email")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<string>("FullAddress")
|
b.Property<string>("FullAddress")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@@ -251,7 +251,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<int>("NumericISOCode")
|
b.Property<int>("NumericISOCode")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
@@ -279,7 +279,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Banner")
|
b.Property<string>("Banner")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<int>("Count")
|
b.Property<int>("Count")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
@@ -298,22 +298,22 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("FilterAttributes")
|
b.Property<string>("FilterAttributes")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<string>("Image")
|
b.Property<string>("Image")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<bool>("IsNode")
|
b.Property<bool>("IsNode")
|
||||||
.HasColumnType("boolean");
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
b.Property<string>("MetaDescription")
|
b.Property<string>("MetaDescription")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<string>("MetaKeywords")
|
b.Property<string>("MetaKeywords")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@@ -467,7 +467,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("FlagImage")
|
b.Property<string>("FlagImage")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@@ -475,7 +475,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("ResourceVersion")
|
b.Property<string>("ResourceVersion")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(25)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<string>("Title")
|
b.Property<string>("Title")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@@ -499,7 +499,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<DateTime?>("UpdateTime")
|
b.Property<DateTime?>("UpdateTime")
|
||||||
.HasColumnType("timestamptz");
|
.HasColumnType("timestamptz");
|
||||||
@@ -523,7 +523,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Key")
|
b.Property<string>("Key")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<int>("LanguageNumber")
|
b.Property<int>("LanguageNumber")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
@@ -563,7 +563,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Icon")
|
b.Property<string>("Icon")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<bool>("IsLink")
|
b.Property<bool>("IsLink")
|
||||||
.HasColumnType("boolean");
|
.HasColumnType("boolean");
|
||||||
@@ -591,7 +591,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Url")
|
b.Property<string>("Url")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
@@ -643,7 +643,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Account")
|
b.Property<string>("Account")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<string>("Config")
|
b.Property<string>("Config")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@@ -695,7 +695,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Description")
|
b.Property<string>("Description")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<string>("Name")
|
b.Property<string>("Name")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@@ -733,7 +733,7 @@ namespace Atomx.Data.Migrations
|
|||||||
.HasColumnType("timestamptz");
|
.HasColumnType("timestamptz");
|
||||||
|
|
||||||
b.Property<string>("Description")
|
b.Property<string>("Description")
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<int>("DisplayOrder")
|
b.Property<int>("DisplayOrder")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
@@ -743,7 +743,7 @@ namespace Atomx.Data.Migrations
|
|||||||
.HasColumnType("text");
|
.HasColumnType("text");
|
||||||
|
|
||||||
b.Property<string>("Feature")
|
b.Property<string>("Feature")
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<string>("Image")
|
b.Property<string>("Image")
|
||||||
.HasColumnType("varchar(256)");
|
.HasColumnType("varchar(256)");
|
||||||
@@ -801,7 +801,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Title")
|
b.Property<string>("Title")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<DateTime?>("UpdateTime")
|
b.Property<DateTime?>("UpdateTime")
|
||||||
.HasColumnType("timestamptz");
|
.HasColumnType("timestamptz");
|
||||||
@@ -887,7 +887,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Note")
|
b.Property<string>("Note")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<decimal>("Price")
|
b.Property<decimal>("Price")
|
||||||
.HasColumnType("decimal(18,4)");
|
.HasColumnType("decimal(18,4)");
|
||||||
@@ -998,7 +998,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Image")
|
b.Property<string>("Image")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<bool>("IsAddWeight")
|
b.Property<bool>("IsAddWeight")
|
||||||
.HasColumnType("boolean");
|
.HasColumnType("boolean");
|
||||||
@@ -1083,7 +1083,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Note")
|
b.Property<string>("Note")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<long?>("Operator")
|
b.Property<long?>("Operator")
|
||||||
.HasColumnType("bigint");
|
.HasColumnType("bigint");
|
||||||
@@ -1167,7 +1167,7 @@ namespace Atomx.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Description")
|
b.Property<string>("Description")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("varchar(255)");
|
.HasColumnType("varchar(256)");
|
||||||
|
|
||||||
b.Property<bool>("Enabled")
|
b.Property<bool>("Enabled")
|
||||||
.HasColumnType("boolean");
|
.HasColumnType("boolean");
|
||||||
|
|||||||
Reference in New Issue
Block a user