diff --git a/src/My.Extensions.Localization.Json/JsonStringLocalizer.cs b/src/My.Extensions.Localization.Json/JsonStringLocalizer.cs index 2abc9e6..02ce8e5 100644 --- a/src/My.Extensions.Localization.Json/JsonStringLocalizer.cs +++ b/src/My.Extensions.Localization.Json/JsonStringLocalizer.cs @@ -96,7 +96,7 @@ protected virtual IEnumerable GetAllStrings(bool includeParentC } } - protected string GetStringSafely(string name, CultureInfo culture) + protected virtual string GetStringSafely(string name, CultureInfo culture) { ArgumentNullException.ThrowIfNull(name); diff --git a/src/My.Extensions.Localization.Json/JsonStringLocalizerFactory.cs b/src/My.Extensions.Localization.Json/JsonStringLocalizerFactory.cs index a63e550..19c247b 100644 --- a/src/My.Extensions.Localization.Json/JsonStringLocalizerFactory.cs +++ b/src/My.Extensions.Localization.Json/JsonStringLocalizerFactory.cs @@ -43,6 +43,31 @@ public JsonStringLocalizerFactory( _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); } + /// + /// Gets the cache used to store resource names. + /// + protected IResourceNamesCache ResourceNamesCache => _resourceNamesCache; + + /// + /// Gets the cache used to store localizer instances. + /// + protected ConcurrentDictionary LocalizerCache => _localizerCache; + + /// + /// Gets the resources relative path. + /// + protected string ResourcesRelativePath => _resourcesRelativePath; + + /// + /// Gets the resources type. + /// + protected ResourcesType ResourcesType => _resourcesType; + + /// + /// Gets the logger factory. + /// + protected ILoggerFactory LoggerFactory => _loggerFactory; + public IStringLocalizer Create(Type resourceSource) { ArgumentNullException.ThrowIfNull(resourceSource); diff --git a/test/My.Extensions.Localization.Json.Tests/ExtensibilityTests.cs b/test/My.Extensions.Localization.Json.Tests/ExtensibilityTests.cs new file mode 100644 index 0000000..0bb076a --- /dev/null +++ b/test/My.Extensions.Localization.Json.Tests/ExtensibilityTests.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Concurrent; +using System.Globalization; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Moq; +using My.Extensions.Localization.Json.Caching; +using My.Extensions.Localization.Json.Internal; +using My.Extensions.Localization.Json.Tests.Common; +using Xunit; + +namespace My.Extensions.Localization.Json.Tests; + +public class ExtensibilityTests +{ + private readonly Mock> _localizationOptions; + private readonly ILoggerFactory _loggerFactory; + + public ExtensibilityTests() + { + _localizationOptions = new Mock>(); + _localizationOptions.Setup(o => o.Value) + .Returns(() => new JsonLocalizationOptions { ResourcesPath = "Resources" }); + _loggerFactory = NullLoggerFactory.Instance; + } + + [Fact] + public void CustomJsonStringLocalizerFactory_CanOverrideCreateJsonStringLocalizer() + { + // Arrange + LocalizationHelper.SetCurrentCulture("fr-FR"); + var factory = new CustomJsonStringLocalizerFactory(_localizationOptions.Object, _loggerFactory); + + // Act + var localizer = factory.Create(typeof(Test)); + + // Assert + Assert.NotNull(localizer); + Assert.True(factory.CreateJsonStringLocalizerWasCalled); + } + + [Fact] + public void CustomJsonStringLocalizerFactory_CanAccessProtectedProperties() + { + // Arrange + var factory = new CustomJsonStringLocalizerFactory(_localizationOptions.Object, _loggerFactory); + + // Assert + Assert.NotNull(factory.GetResourceNamesCache()); + Assert.NotNull(factory.GetLocalizerCache()); + Assert.Equal("Resources", factory.GetResourcesRelativePath()); + Assert.Equal(ResourcesType.TypeBased, factory.GetResourcesType()); + Assert.NotNull(factory.GetLoggerFactory()); + } + + [Fact] + public void CustomJsonStringLocalizer_CanOverrideGetStringSafely() + { + // Arrange + LocalizationHelper.SetCurrentCulture("fr-FR"); + var factory = new CustomLocalizerFactory(_localizationOptions.Object, _loggerFactory); + var localizer = factory.Create(typeof(Test)) as CustomJsonStringLocalizer; + + // Act + var result = localizer["Hello"]; + + // Assert + Assert.NotNull(localizer); + Assert.True(localizer.GetStringSafelyWasCalled); + } + + [Fact] + public void CustomJsonStringLocalizer_CanOverrideGetAllStrings() + { + // Arrange + LocalizationHelper.SetCurrentCulture("fr-FR"); + var factory = new CustomLocalizerFactory(_localizationOptions.Object, _loggerFactory); + var localizer = factory.Create(typeof(Test)) as CustomJsonStringLocalizer; + + // Act + var result = localizer.GetAllStrings(true); + + // Assert + Assert.NotNull(localizer); + Assert.True(localizer.GetAllStringsWasCalled); + } + + /// + /// Custom factory that overrides CreateJsonStringLocalizer to demonstrate extensibility. + /// + private class CustomJsonStringLocalizerFactory : JsonStringLocalizerFactory + { + public bool CreateJsonStringLocalizerWasCalled { get; private set; } + + public CustomJsonStringLocalizerFactory( + IOptions localizationOptions, + ILoggerFactory loggerFactory) + : base(localizationOptions, loggerFactory) + { + } + + protected override JsonStringLocalizer CreateJsonStringLocalizer(string resourcesPath, string resourceName) + { + CreateJsonStringLocalizerWasCalled = true; + return base.CreateJsonStringLocalizer(resourcesPath, resourceName); + } + + // Expose protected properties for testing + public IResourceNamesCache GetResourceNamesCache() => ResourceNamesCache; + public ConcurrentDictionary GetLocalizerCache() => LocalizerCache; + public string GetResourcesRelativePath() => ResourcesRelativePath; + public ResourcesType GetResourcesType() => ResourcesType; + public ILoggerFactory GetLoggerFactory() => LoggerFactory; + } + + /// + /// Custom factory that creates CustomJsonStringLocalizer instances. + /// + private class CustomLocalizerFactory : JsonStringLocalizerFactory + { + public CustomLocalizerFactory( + IOptions localizationOptions, + ILoggerFactory loggerFactory) + : base(localizationOptions, loggerFactory) + { + } + + protected override JsonStringLocalizer CreateJsonStringLocalizer(string resourcesPath, string resourceName) + { + var resourceManager = ResourcesType == ResourcesType.TypeBased + ? new JsonResourceManager(resourcesPath, resourceName) + : new JsonResourceManager(resourcesPath); + var logger = LoggerFactory.CreateLogger(); + + return new CustomJsonStringLocalizer(resourceManager, ResourceNamesCache, logger); + } + } + + /// + /// Custom localizer that overrides GetStringSafely to demonstrate extensibility. + /// + private class CustomJsonStringLocalizer : JsonStringLocalizer + { + public bool GetStringSafelyWasCalled { get; private set; } + public bool GetAllStringsWasCalled { get; private set; } + + public CustomJsonStringLocalizer( + JsonResourceManager jsonResourceManager, + IResourceNamesCache resourceNamesCache, + ILogger logger) + : base(jsonResourceManager, resourceNamesCache, logger) + { + } + + protected override string GetStringSafely(string name, CultureInfo culture) + { + GetStringSafelyWasCalled = true; + return base.GetStringSafely(name, culture); + } + + public override System.Collections.Generic.IEnumerable GetAllStrings(bool includeParentCultures) + { + GetAllStringsWasCalled = true; + return base.GetAllStrings(includeParentCultures); + } + } +}