diff --git a/src/My.Extensions.Localization.Json/JsonLocalizationOptions.cs b/src/My.Extensions.Localization.Json/JsonLocalizationOptions.cs
index 4eb1cae..8d56841 100644
--- a/src/My.Extensions.Localization.Json/JsonLocalizationOptions.cs
+++ b/src/My.Extensions.Localization.Json/JsonLocalizationOptions.cs
@@ -5,4 +5,11 @@ namespace My.Extensions.Localization.Json;
public class JsonLocalizationOptions : LocalizationOptions
{
public ResourcesType ResourcesType { get; set; } = ResourcesType.TypeBased;
+
+ ///
+ /// Gets or sets a value indicating whether to use the full generic type name for resource files.
+ /// When set to true (default), the full generic type name (e.g., "GenericClass`1[[...]]") is used.
+ /// When set to false, the generic type markers are stripped (e.g., "GenericClass"), making it easier to name resource files.
+ ///
+ public bool UseGenericResources { get; set; } = true;
}
\ No newline at end of file
diff --git a/src/My.Extensions.Localization.Json/JsonStringLocalizerFactory.cs b/src/My.Extensions.Localization.Json/JsonStringLocalizerFactory.cs
index a45a9ea..137227f 100644
--- a/src/My.Extensions.Localization.Json/JsonStringLocalizerFactory.cs
+++ b/src/My.Extensions.Localization.Json/JsonStringLocalizerFactory.cs
@@ -18,6 +18,7 @@ public class JsonStringLocalizerFactory : IStringLocalizerFactory
private readonly ConcurrentDictionary _localizerCache = new();
private readonly string _resourcesRelativePath;
private readonly ResourcesType _resourcesType = ResourcesType.TypeBased;
+ private readonly bool _useGenericResources;
private readonly ILoggerFactory _loggerFactory;
public JsonStringLocalizerFactory(
@@ -28,6 +29,7 @@ public JsonStringLocalizerFactory(
_resourcesRelativePath = localizationOptions.Value.ResourcesPath ?? string.Empty;
_resourcesType = localizationOptions.Value.ResourcesType;
+ _useGenericResources = localizationOptions.Value.UseGenericResources;
_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
}
@@ -52,8 +54,10 @@ public IStringLocalizer Create(Type resourceSource)
? typeInfo.Name
: TrimPrefix(typeInfo.FullName, assemblyName + ".");
- resourcesPath = Path.Combine(PathHelpers.GetApplicationRoot(), GetResourcePath(assembly));
typeName = TryFixInnerClassPath(typeName);
+ typeName = TryStripGenericTypeMarkers(typeName);
+
+ resourcesPath = Path.Combine(PathHelpers.GetApplicationRoot(), GetResourcePath(assembly));
return _localizerCache.GetOrAdd($"culture={CultureInfo.CurrentUICulture.Name}, typeName={typeName}", _ => CreateJsonStringLocalizer(resourcesPath, typeName));
}
@@ -79,6 +83,7 @@ public IStringLocalizer Create(string baseName, string location)
if (_resourcesType == ResourcesType.TypeBased)
{
baseName = TryFixInnerClassPath(baseName);
+ baseName = TryStripGenericTypeMarkers(baseName);
resourceName = TrimPrefix(baseName, location + ".");
}
@@ -129,4 +134,20 @@ private static string TryFixInnerClassPath(string path)
return fixedPath;
}
+
+ private string TryStripGenericTypeMarkers(string typeName)
+ {
+ if (_useGenericResources)
+ {
+ return typeName;
+ }
+
+ var backtickIndex = typeName.IndexOf('`', StringComparison.Ordinal);
+ if (backtickIndex >= 0)
+ {
+ return typeName.Substring(0, backtickIndex);
+ }
+
+ return typeName;
+ }
}
\ No newline at end of file
diff --git a/test/My.Extensions.Localization.Json.Tests/Common/GenericTest.cs b/test/My.Extensions.Localization.Json.Tests/Common/GenericTest.cs
new file mode 100644
index 0000000..9e8a027
--- /dev/null
+++ b/test/My.Extensions.Localization.Json.Tests/Common/GenericTest.cs
@@ -0,0 +1,5 @@
+namespace My.Extensions.Localization.Json.Tests.Common;
+
+internal class GenericTest
+{
+}
diff --git a/test/My.Extensions.Localization.Json.Tests/JsonStringLocalizerFactoryTests.cs b/test/My.Extensions.Localization.Json.Tests/JsonStringLocalizerFactoryTests.cs
index 8e230dc..1eef765 100644
--- a/test/My.Extensions.Localization.Json.Tests/JsonStringLocalizerFactoryTests.cs
+++ b/test/My.Extensions.Localization.Json.Tests/JsonStringLocalizerFactoryTests.cs
@@ -112,11 +112,48 @@ public async Task LocalizerReturnsTranslationFromInnerClass()
var response = await client.GetAsync("/");
}
- private void SetupLocalizationOptions(string resourcesPath, ResourcesType resourcesType = ResourcesType.TypeBased)
+ [Fact]
+ public void CreateLocalizerForGenericType_WithUseGenericResourcesFalse_StripsGenericMarkers()
+ {
+ SetupLocalizationOptions("Resources", ResourcesType.TypeBased, useGenericResources: false);
+ LocalizationHelper.SetCurrentCulture("fr");
+
+ // Arrange
+ var localizerFactory = new JsonStringLocalizerFactory(_localizationOptions.Object, _loggerFactory);
+
+ // Act - Create localizer for a generic type
+ var localizer = localizerFactory.Create(typeof(GenericTest));
+
+ // Assert
+ Assert.NotNull(localizer);
+ Assert.Equal("Bonjour Générique", localizer["Hello"]);
+ }
+
+ [Fact]
+ public void CreateLocalizerForGenericType_WithUseGenericResourcesTrue_UsesFullTypeName()
+ {
+ SetupLocalizationOptions("Resources", ResourcesType.TypeBased, useGenericResources: true);
+ LocalizationHelper.SetCurrentCulture("fr");
+
+ // Arrange
+ var localizerFactory = new JsonStringLocalizerFactory(_localizationOptions.Object, _loggerFactory);
+
+ // Act - Create localizer for a generic type (with default UseGenericResources = true,
+ // the resource name includes generic markers, so it won't find the resource file)
+ var localizer = localizerFactory.Create(typeof(GenericTest));
+
+ // Assert - Resource not found because the file is named GenericTest.fr.json, not GenericTest`1[[...]]
+ Assert.NotNull(localizer);
+ var result = localizer["Hello"];
+ Assert.True(result.ResourceNotFound);
+ }
+
+ private void SetupLocalizationOptions(string resourcesPath, ResourcesType resourcesType = ResourcesType.TypeBased, bool useGenericResources = true)
=> _localizationOptions.Setup(o => o.Value)
.Returns(() => new JsonLocalizationOptions {
ResourcesPath = resourcesPath,
- ResourcesType = resourcesType
+ ResourcesType = resourcesType,
+ UseGenericResources = useGenericResources
});
public class InnerClassStartup
diff --git a/test/My.Extensions.Localization.Json.Tests/My.Extensions.Localization.Json.Tests.csproj b/test/My.Extensions.Localization.Json.Tests/My.Extensions.Localization.Json.Tests.csproj
index 82785a0..33a92d5 100644
--- a/test/My.Extensions.Localization.Json.Tests/My.Extensions.Localization.Json.Tests.csproj
+++ b/test/My.Extensions.Localization.Json.Tests/My.Extensions.Localization.Json.Tests.csproj
@@ -39,6 +39,9 @@
PreserveNewest
+
+ PreserveNewest
+
diff --git a/test/My.Extensions.Localization.Json.Tests/Resources/Common/GenericTest.fr.json b/test/My.Extensions.Localization.Json.Tests/Resources/Common/GenericTest.fr.json
new file mode 100644
index 0000000..4232242
--- /dev/null
+++ b/test/My.Extensions.Localization.Json.Tests/Resources/Common/GenericTest.fr.json
@@ -0,0 +1,3 @@
+{
+ "Hello": "Bonjour Générique"
+}