Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/BootstrapBlazor/BootstrapBlazor.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<Version>10.5.0-beta02</Version>
<Version>10.5.0-beta03</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
17 changes: 15 additions & 2 deletions src/BootstrapBlazor/Localization/Json/JsonStringLocalizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ public override LocalizedString this[string name]
}

private readonly ConcurrentDictionary<string, object?> _missingManifestCache = [];

/// <summary>
/// <para lang="zh">清除缓存方法</para>
/// <para lang="en">Reset cache method</para>
/// <para>v<version>10.5.0</version></para>
/// </summary>
public void ResetMissingMainifestCache() => _missingManifestCache.Clear();
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
Outdated
Comment thread
ArgoZhang marked this conversation as resolved.
Outdated

private string? GetStringFromJson(string name)
{
// <para lang="zh">从 json 本地化文件中获取字符串</para>
Expand Down Expand Up @@ -172,11 +180,16 @@ private List<LocalizedString> MergeResolveLocalizers(IEnumerable<LocalizedString
private void HandleMissingResourceItem(string name)
{
localizationMissingItemHandler.HandleMissingItem(name, typeName, CultureInfo.CurrentUICulture.Name);
if (jsonLocalizationOptions.IgnoreLocalizerMissing == false)
if (jsonLocalizationOptions.IgnoreLocalizerMissing)
{
_missingManifestCache.TryAdd($"name={name}&culture={CultureInfo.CurrentUICulture.Name}", null);
return;
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
}

if (Logger.IsEnabled(LogLevel.Information))
{
Logger.LogInformation("{JsonStringLocalizerName} searched for '{Name}' in '{TypeName}' with culture '{CultureName}' not found.", nameof(JsonStringLocalizer), name, typeName, CultureInfo.CurrentUICulture.Name);
}
_missingManifestCache.TryAdd($"name={name}&culture={CultureInfo.CurrentUICulture.Name}", null);
}
Comment thread
ArgoZhang marked this conversation as resolved.

private List<LocalizedString>? _allLocalizedStrings;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ internal class JsonStringLocalizerFactory : ResourceManagerStringLocalizerFactor
private readonly JsonLocalizationOptions _jsonLocalizationOptions;
private readonly ILocalizationMissingItemHandler _localizationMissingItemHandler;
private string? _typeName;
private JsonStringLocalizer? _localizer;

/// <summary>
/// <para lang="zh">构造函数</para>
Expand Down Expand Up @@ -108,5 +109,19 @@ protected override string GetResourcePrefix(string baseResourceName, string base
/// </summary>
/// <param name="assembly"><para lang="zh">The assembly to create a <see cref="ResourceManagerStringLocalizer"/> for</para><para lang="en">The assembly to create a <see cref="ResourceManagerStringLocalizer"/> for</para></param>
/// <param name="baseName"><para lang="zh">The base name of the resource to search for</para><para lang="en">The base name of the resource to search for</para></param>
protected override ResourceManagerStringLocalizer CreateResourceManagerStringLocalizer(Assembly assembly, string baseName) => new JsonStringLocalizer(assembly, _typeName!, baseName, _jsonLocalizationOptions, _loggerFactory.CreateLogger<JsonStringLocalizer>(), ResourceNamesCache, _localizationMissingItemHandler);
protected override ResourceManagerStringLocalizer CreateResourceManagerStringLocalizer(Assembly assembly, string baseName)
{
_localizer = new JsonStringLocalizer(assembly, _typeName!, baseName, _jsonLocalizationOptions, _loggerFactory.CreateLogger<JsonStringLocalizer>(), ResourceNamesCache, _localizationMissingItemHandler);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Tracking only the last created JsonStringLocalizer means Reset() will not affect earlier instances and may be racy.

Because the factory only keeps a single _localizer reference and Reset() delegates to it, any previously created JsonStringLocalizer instances (for other baseName/assembly or consumers) will never be reset. In a concurrent scenario, _localizer may also be overwritten while another thread calls Reset() expecting to target a different instance.

To align Reset() with the intended cache-clearing semantics, consider either:

  • Tracking all created localizers (e.g., in a thread-safe collection) and resetting them all, or
  • Centralizing cache reset in a shared/global cache, or
  • Clearly scoping the factory so that only one localizer instance can exist per scope/lifetime.

return _localizer;
}

/// <summary>
/// <para lang="zh">清除缓存方法</para>
/// <para lang="en">Reset cache method</para>
/// <para>v<version>10.5.0</version></para>
/// </summary>
public void Reset()
{
_localizer?.ResetMissingMainifestCache();
}
Comment thread
ArgoZhang marked this conversation as resolved.
Outdated
}
10 changes: 10 additions & 0 deletions src/BootstrapBlazor/Services/CacheManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,16 @@ public void Clear(object? key)
else if (Cache is MemoryCache c)
{
c.Compact(100);

// 资源文件
var factories = Provider.GetServices<IStringLocalizerFactory>();
foreach (var factory in factories)
{
if (factory is JsonStringLocalizerFactory f)
{
f.Reset();
}
}
}
}

Expand Down
30 changes: 29 additions & 1 deletion test/UnitTest/Localization/JsonStringLocalizerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Localization;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using System.Reflection;

Expand Down Expand Up @@ -215,7 +216,8 @@ public void GetAllStrings_FromResource()
Assert.NotEmpty(items);
Assert.Equal("test-name", items.First(i => i.Name == "Name").Value);

var name = Utility.GetDisplayName(typeof(Dummy), "DummyName");
var type = typeof(Dummy);
var name = Utility.GetDisplayName(type, "DummyName");
Assert.Equal("test-name", name);
}

Expand Down Expand Up @@ -498,5 +500,31 @@ public void GetAllStrings_FromInject()
var localizer = provider.GetRequiredService<IStringLocalizer<Foo>>();
var item = localizer["Foo.Name"];
Assert.NotEqual("Foo.Name", item);

item = localizer["missing-item"];
Assert.True(item.ResourceNotFound);

// 测试 Reset
var cacheManager = provider.GetRequiredService<ICacheManager>();
cacheManager.Clear();

// 测试内部缓存值为空集合
var localizerInfo = localizer.GetType().GetField("_localizer", BindingFlags.Instance | BindingFlags.NonPublic);
Assert.NotNull(localizerInfo);
var v = localizerInfo.GetValue(localizer);
Assert.NotNull(v);

var fieldInfo = v.GetType().GetField("_missingManifestCache", BindingFlags.Instance | BindingFlags.NonPublic);
Assert.NotNull(fieldInfo);

var val = fieldInfo.GetValue(v);
Assert.NotNull(val);

bool useCache = false;
if (val is ConcurrentDictionary<string, object?> cache)
{
useCache = cache.IsEmpty;
}
Assert.True(useCache);
}
}
Loading