Skip to content
25 changes: 12 additions & 13 deletions src/BootstrapBlazor/Extensions/LocalizationOptionsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,20 @@ public static IEnumerable<IConfigurationSection> GetJsonStringFromAssembly(this
var builder = new ConfigurationBuilder();

// 获取程序集中的资源文件
var assemblies = new List<Assembly>()
{
assembly
};
var assemblies = new List<Assembly>() { assembly };

var entryAssembly = GetAssembly();
if (assembly != entryAssembly)
{
assemblies.Add(entryAssembly);
}

if (option.AdditionalJsonAssemblies != null)
{
assemblies.AddRange(option.AdditionalJsonAssemblies);
}

var streams = assemblies.SelectMany(i => option.GetResourceStream(i, cultureName));
var streams = assemblies.SelectMany(i => option.GetResourceStream(i, cultureName)).ToList();

// 添加 Json 文件流到配置
foreach (var s in streams)
Expand Down Expand Up @@ -120,16 +118,17 @@ void AddStream(string name)

// 开启回落机制并且当前文化信息与回落语言相同
bool EqualFallbackCulture(string name) => option.EnableFallbackCulture && option.FallbackCulture == name;
}

StringSegment GetParentCultureName(StringSegment cultureInfoName)
static StringSegment GetParentCultureName(StringSegment cultureInfoName)
{
var ret = new StringSegment();
var index = cultureInfoName.IndexOf('-');
if (index > 0)
{
var ret = new StringSegment();
var index = cultureInfoName.IndexOf('-');
if (index > 0)
{
ret = cultureInfoName.Subsegment(0, index);
}
return ret;
ret = cultureInfoName.Subsegment(0, index);
}

return ret;
}
}
13 changes: 2 additions & 11 deletions src/BootstrapBlazor/Localization/Json/JsonStringLocalizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ public override LocalizedString this[string name]
var cacheKey = $"{nameof(GetValueFromCache)}&name={name}&{Assembly.GetUniqueName()}&type={typeName}&culture={cultureName}";
if (!CacheManager.GetMissingLocalizerByKey(cacheKey))
{
var l = GetLocalizedString();
var l = localizerStrings?.FirstOrDefault(i => i.Name == name)
?? CacheManager.GetAllStringsFromResolve().FirstOrDefault(i => i.Name == name);
if (l is { ResourceNotFound: false })
{
ret = l.Value;
Expand All @@ -116,16 +117,6 @@ public override LocalizedString this[string name]
}
}
return ret;

LocalizedString? GetLocalizedString()
{
LocalizedString? localizer = null;
if (localizerStrings != null)
{
localizer = localizerStrings.FirstOrDefault(i => i.Name == name);
}
return localizer ?? CacheManager.GetAllStringsFromResolve().FirstOrDefault(i => i.Name == name);
}
}

private string? GetLocalizerValueFromCache(IStringLocalizer localizer, string name)
Expand Down
76 changes: 43 additions & 33 deletions src/BootstrapBlazor/Services/CacheManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
using System.Linq.Expressions;
using System.Reflection;

#if NET8_0_OR_GREATER
using System.Collections.Frozen;
#endif

namespace BootstrapBlazor.Components;

/// <summary>
Expand Down Expand Up @@ -118,7 +122,7 @@ public void Clear(object? key)
/// </summary>
private void SetStartTime(DateTimeOffset startDateTimeOffset)
{
GetOrCreate("BootstrapBlazor_StartTime", entry => startDateTimeOffset);
GetOrCreate("BootstrapBlazor_StartTime", _ => startDateTimeOffset);
}

/// <summary>
Expand Down Expand Up @@ -159,7 +163,7 @@ public static int ElementCount(object? value)

#region Localizer
/// <summary>
///
/// 通过 Type 获得 <see cref="IStringLocalizer"/> 实例
/// </summary>
/// <param name="resourceSource"></param>
/// <returns></returns>
Expand All @@ -168,10 +172,10 @@ public static int ElementCount(object? value)
: Instance.Provider.GetRequiredService<IStringLocalizerFactory>().Create(resourceSource);

/// <summary>
///
/// 获得 <see cref="JsonLocalizationOptions"/> 值
/// </summary>
/// <returns></returns>
public static JsonLocalizationOptions GetJsonLocalizationOption()
private static JsonLocalizationOptions GetJsonLocalizationOption()
{
var localizationOptions = Instance.Provider.GetRequiredService<IOptions<JsonLocalizationOptions>>();
return localizationOptions.Value;
Expand All @@ -185,20 +189,17 @@ public static JsonLocalizationOptions GetJsonLocalizationOption()
/// <returns></returns>
public static IStringLocalizer? GetStringLocalizerFromService(Assembly assembly, string typeName) => assembly.IsDynamic
? null
: Instance.GetOrCreate($"{nameof(GetStringLocalizerFromService)}-{CultureInfo.CurrentUICulture.Name}-{assembly.GetUniqueName()}-{typeName}", entry =>
: Instance.GetOrCreate($"{nameof(GetStringLocalizerFromService)}-{CultureInfo.CurrentUICulture.Name}-{assembly.GetUniqueName()}-{typeName}", _ =>
{
IStringLocalizer? ret = null;
var factories = Instance.Provider.GetServices<IStringLocalizerFactory>();
if (factories != null)
var factory = factories.LastOrDefault(a => a is not JsonStringLocalizerFactory);
if (factory != null)
{
var factory = factories.LastOrDefault(a => a is not JsonStringLocalizerFactory);
if (factory != null)
var type = assembly.GetType(typeName);
if (type != null)
{
var type = assembly.GetType(typeName);
if (type != null)
{
ret = factory.Create(type);
}
ret = factory.Create(type);
}
}
return ret;
Expand All @@ -209,8 +210,8 @@ public static JsonLocalizationOptions GetJsonLocalizationOption()
/// </summary>
/// <param name="assembly"></param>
/// <param name="typeName"></param>
/// <returns></returns>
public static IEnumerable<LocalizedString>? GetAllStringsByTypeName(Assembly assembly, string typeName) => GetJsonStringByTypeName(GetJsonLocalizationOption(), assembly, typeName, CultureInfo.CurrentUICulture.Name);
public static IEnumerable<LocalizedString>? GetAllStringsByTypeName(Assembly assembly, string typeName)
=> GetJsonStringByTypeName(GetJsonLocalizationOption(), assembly, typeName, CultureInfo.CurrentUICulture.Name);

/// <summary>
/// 通过指定程序集获取所有本地化信息键值集合
Expand All @@ -223,34 +224,43 @@ public static JsonLocalizationOptions GetJsonLocalizationOption()
/// <returns></returns>
public static IEnumerable<LocalizedString>? GetJsonStringByTypeName(JsonLocalizationOptions option, Assembly assembly, string typeName, string? cultureName = null, bool forceLoad = false)
{
return assembly.IsDynamic ? null : GetJsonStringByTypeName();
if (assembly.IsDynamic)
{
return null;
}

IEnumerable<LocalizedString>? GetJsonStringByTypeName()
cultureName ??= CultureInfo.CurrentUICulture.Name;
var key = $"{nameof(GetJsonStringByTypeName)}-{assembly.GetUniqueName()}-{cultureName}";
var typeKey = $"{key}-{typeName}";
if (forceLoad)
{
cultureName ??= CultureInfo.CurrentUICulture.Name;
var key = $"{nameof(GetJsonStringByTypeName)}-{assembly.GetUniqueName()}-{cultureName}";
var typeKey = $"{key}-{typeName}";
if (forceLoad)
{
Instance.Cache.Remove(key);
Instance.Cache.Remove(typeKey);
}
return Instance.GetOrCreate(typeKey, entry =>
{
var sections = Instance.GetOrCreate(key, entry => option.GetJsonStringFromAssembly(assembly, cultureName));
return sections.FirstOrDefault(kv => typeName.Equals(kv.Key, StringComparison.OrdinalIgnoreCase))?
.GetChildren()
.SelectMany(kv => new[] { new LocalizedString(kv.Key, kv.Value!, false, typeName) });
});
Instance.Cache.Remove(key);
Instance.Cache.Remove(typeKey);
}
return Instance.GetOrCreate(typeKey, entry =>
{
var sections = Instance.GetOrCreate(key, entry => option.GetJsonStringFromAssembly(assembly, cultureName));
var items = sections.FirstOrDefault(kv => typeName.Equals(kv.Key, StringComparison.OrdinalIgnoreCase))?
.GetChildren()
.SelectMany(kv => new[] { new LocalizedString(kv.Key, kv.Value!, false, typeName) });
#if NET8_0_OR_GREATER
return items?.ToFrozenSet();
#else
return items?.ToHashSet();
#endif
});
}

/// <summary>
/// 通过 ILocalizationResolve 接口实现类获得本地化键值集合
/// </summary>
/// <param name="includeParentCultures"></param>
/// <returns></returns>
public static IEnumerable<LocalizedString> GetAllStringsFromResolve(bool includeParentCultures = true) => Instance.GetOrCreate($"{nameof(GetAllStringsFromResolve)}-{CultureInfo.CurrentUICulture.Name}", entry => Instance.Provider.GetRequiredService<ILocalizationResolve>().GetAllStringsByCulture(includeParentCultures));
#if NET8_0_OR_GREATER
public static FrozenSet<LocalizedString> GetAllStringsFromResolve(bool includeParentCultures = true) => Instance.GetOrCreate($"{nameof(GetAllStringsFromResolve)}-{CultureInfo.CurrentUICulture.Name}", entry => Instance.Provider.GetRequiredService<ILocalizationResolve>().GetAllStringsByCulture(includeParentCultures).ToFrozenSet());
#else
public static HashSet<LocalizedString> GetAllStringsFromResolve(bool includeParentCultures = true) => Instance.GetOrCreate($"{nameof(GetAllStringsFromResolve)}-{CultureInfo.CurrentUICulture.Name}", entry => Instance.Provider.GetRequiredService<ILocalizationResolve>().GetAllStringsByCulture(includeParentCultures).ToHashSet());
#endif

/// <summary>
/// 查询缺失本地化资源项目
Expand Down
8 changes: 3 additions & 5 deletions src/BootstrapBlazor/Utils/Utility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ public static class Utility
/// <param name="typeName">类名称</param>
/// <param name="cultureName">cultureName 未空时使用 CultureInfo.CurrentUICulture.Name</param>
/// <param name="forceLoad">默认 false 使用缓存值 设置 true 时内部强制重新加载</param>
/// <returns></returns>
public static IEnumerable<LocalizedString> GetJsonStringByTypeName(JsonLocalizationOptions option, Assembly assembly, string typeName, string? cultureName = null, bool forceLoad = false) => CacheManager.GetJsonStringByTypeName(option, assembly, typeName, cultureName, forceLoad) ?? [];

/// <summary>
Expand Down Expand Up @@ -367,7 +366,7 @@ public static IEnumerable<ITableColumn> GetTableColumns(Type type, IEnumerable<I
return defaultOrderCallback?.Invoke(cols) ?? cols;
}

internal static IEnumerable<ITableColumn> OrderFunc(this IEnumerable<ITableColumn> cols) => cols
internal static IEnumerable<ITableColumn> OrderFunc(this List<ITableColumn> cols) => cols
.Where(a => a.Order > 0).OrderBy(a => a.Order)
.Concat(cols.Where(a => a.Order == 0))
.Concat(cols.Where(a => a.Order < 0).OrderBy(a => a.Order));
Expand Down Expand Up @@ -404,7 +403,6 @@ public static void CreateDisplayByFieldType(this RenderTreeBuilder builder, IEdi
builder.AddAttribute(50, "class", col.CssClass);
}
builder.AddMultipleAttributes(60, item.ComponentParameters);
builder.CloseComponent();
}
else if (item.ComponentType == typeof(Textarea) || item.Rows > 0)
{
Expand All @@ -422,7 +420,6 @@ public static void CreateDisplayByFieldType(this RenderTreeBuilder builder, IEdi
builder.AddAttribute(60, "class", col.CssClass);
}
builder.AddMultipleAttributes(70, item.ComponentParameters);
builder.CloseComponent();
}
else
{
Expand All @@ -448,8 +445,9 @@ public static void CreateDisplayByFieldType(this RenderTreeBuilder builder, IEdi
builder.AddAttribute(90, "class", col.CssClass);
}
builder.AddMultipleAttributes(100, item.ComponentParameters);
builder.CloseComponent();
}

builder.CloseComponent();
}

/// <summary>
Expand Down
30 changes: 30 additions & 0 deletions test/UnitTest/Performance/CacheTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

using Microsoft.Extensions.Localization;
using System.Collections.Concurrent;
using System.Collections.Frozen;
using System.Diagnostics;

namespace UnitTest.Performance;
Expand Down Expand Up @@ -53,6 +54,35 @@ public void Cache_Ok()
IEnumerable<Foo> CacheMethod() => cache.GetOrAdd("test", key => NoCacheMethod());
}

[Fact]
public void List_Perf()
{
var listItms = GetListLocalizedStrings();
var setItems = GetSetLocalizedStrings();
var frozenItems = GetFrozenLocalizedStrings();

var sw = Stopwatch.StartNew();
listItms.FirstOrDefault(i => i.Name == "500000");
sw.Stop();
var sp1 = sw.Elapsed;

sw.Restart();
setItems.FirstOrDefault(i => i.Name == "500000");
sw.Stop();
var sp2 = sw.Elapsed;

sw.Restart();
frozenItems.FirstOrDefault(i => i.Name == "500000");
sw.Stop();
var sp3 = sw.Elapsed;
}

private IEnumerable<LocalizedString> GetListLocalizedStrings() => Enumerable.Range(1, 1000000).Select(i => new LocalizedString($"{i}", $"{i}", false, nameof(CacheTest)));

private IEnumerable<LocalizedString> GetSetLocalizedStrings() => Enumerable.Range(1, 1000000).Select(i => new LocalizedString($"{i}", $"{i}", false, nameof(CacheTest))).ToHashSet();

private IEnumerable<LocalizedString> GetFrozenLocalizedStrings() => Enumerable.Range(1, 1000000).Select(i => new LocalizedString($"{i}", $"{i}", false, nameof(CacheTest))).ToFrozenSet();

private IEnumerable<Foo> NoCacheMethod() => Enumerable.Range(1, 80).Select(i => new Foo()
{
Id = i,
Expand Down
Loading