Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,28 @@ namespace My.Extensions.Localization.Json.Internal;
internal static class JsonStringLocalizerLoggerExtensions
{
private static readonly Action<ILogger, string, string, CultureInfo, Exception> _searchedLocation;
private static readonly Action<ILogger, string, string, CultureInfo, Exception> _missingLocalization;

static JsonStringLocalizerLoggerExtensions()
{
_searchedLocation = LoggerMessage.Define<string, string, CultureInfo>(
LogLevel.Debug,
1,
$"{nameof(JsonStringLocalizer)} searched for '{{Key}}' in '{{LocationSearched}}' with culture '{{Culture}}'.");

_missingLocalization = LoggerMessage.Define<string, string, CultureInfo>(
LogLevel.Warning,
2,
$"{nameof(JsonStringLocalizer)} could not find localization for '{{Key}}' in '{{LocationSearched}}' with culture '{{Culture}}'.");
}

public static void SearchedLocation(this ILogger logger, string key, string searchedLocation, CultureInfo culture)
{
_searchedLocation(logger, key, searchedLocation, culture, null);
}

public static void MissingLocalization(this ILogger logger, string key, string searchedLocation, CultureInfo culture)
{
_missingLocalization(logger, key, searchedLocation, culture, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,10 @@ namespace My.Extensions.Localization.Json;
public class JsonLocalizationOptions : LocalizationOptions
{
public ResourcesType ResourcesType { get; set; } = ResourcesType.TypeBased;

/// <summary>
/// Gets or sets the behavior when a localization resource is not found.
/// The default is <see cref="MissingLocalizationBehavior.Ignore"/>.
/// </summary>
public MissingLocalizationBehavior MissingLocalizationBehavior { get; set; } = MissingLocalizationBehavior.Ignore;
}
56 changes: 53 additions & 3 deletions src/My.Extensions.Localization.Json/JsonStringLocalizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class JsonStringLocalizer : IStringLocalizer
private readonly JsonResourceManager _jsonResourceManager;
private readonly IResourceStringProvider _resourceStringProvider;
private readonly ILogger _logger;
private readonly MissingLocalizationBehavior _missingLocalizationBehavior;

private string _searchedLocation = string.Empty;

Expand All @@ -27,21 +28,44 @@ public JsonStringLocalizer(
ILogger logger)
: this(jsonResourceManager,
new JsonStringProvider(resourceNamesCache, jsonResourceManager),
logger)
logger,
MissingLocalizationBehavior.Ignore)
{
}

public JsonStringLocalizer(
JsonResourceManager jsonResourceManager,
IResourceNamesCache resourceNamesCache,
ILogger logger,
MissingLocalizationBehavior missingLocalizationBehavior)
: this(jsonResourceManager,
new JsonStringProvider(resourceNamesCache, jsonResourceManager),
logger,
missingLocalizationBehavior)
{
}

public JsonStringLocalizer(
JsonResourceManager jsonResourceManager,
IResourceStringProvider resourceStringProvider,
ILogger logger)
: this(jsonResourceManager, resourceStringProvider, logger, MissingLocalizationBehavior.Ignore)
{
}

public JsonStringLocalizer(
JsonResourceManager jsonResourceManager,
IResourceStringProvider resourceStringProvider,
ILogger logger,
MissingLocalizationBehavior missingLocalizationBehavior)
{
_jsonResourceManager = jsonResourceManager ?? throw new ArgumentNullException(nameof(jsonResourceManager));
_resourceStringProvider = resourceStringProvider ?? throw new ArgumentNullException(nameof(resourceStringProvider));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_missingLocalizationBehavior = missingLocalizationBehavior;
}

[Obsolete("This constructor has been deprected and will be removed in the upcoming major release.")]
[Obsolete("This constructor has been deprecated and will be removed in the upcoming major release.")]
public JsonStringLocalizer(
JsonResourceManager jsonResourceManager,
IResourceStringProvider resourceStringProvider,
Expand All @@ -51,6 +75,7 @@ public JsonStringLocalizer(
_jsonResourceManager = jsonResourceManager ?? throw new ArgumentNullException(nameof(jsonResourceManager));
_resourceStringProvider = resourceStringProvider ?? throw new ArgumentNullException(nameof(resourceStringProvider));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_missingLocalizationBehavior = MissingLocalizationBehavior.Ignore;
}

public LocalizedString this[string name]
Expand Down Expand Up @@ -107,23 +132,48 @@ protected string GetStringSafely(string name, CultureInfo culture)

if (_missingManifestCache.ContainsKey(cacheKey))
{
HandleMissingLocalization(name, keyCulture);
return null;
}

try
{
return culture == null
var value = culture == null
? _jsonResourceManager.GetString(name)
: _jsonResourceManager.GetString(name, culture);

if (value == null)
{
_missingManifestCache.TryAdd(cacheKey, null);
HandleMissingLocalization(name, keyCulture);
}

return value;
}
catch (MissingManifestResourceException)
{
_missingManifestCache.TryAdd(cacheKey, null);
HandleMissingLocalization(name, keyCulture);

return null;
}
}

private void HandleMissingLocalization(string name, CultureInfo culture)
{
switch (_missingLocalizationBehavior)
{
case MissingLocalizationBehavior.LogWarning:
_logger.MissingLocalization(name, _jsonResourceManager.ResourcesFilePath, culture);
break;
case MissingLocalizationBehavior.ThrowException:
throw new MissingLocalizationException(name, culture.Name, _jsonResourceManager.ResourcesFilePath);
case MissingLocalizationBehavior.Ignore:
default:
break;
}
}

private HashSet<string> GetResourceNamesFromCultureHierarchy(CultureInfo startingCulture)
{
var currentCulture = startingCulture;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class JsonStringLocalizerFactory : IStringLocalizerFactory
private readonly ConcurrentDictionary<string, JsonStringLocalizer> _localizerCache = new();
private readonly string _resourcesRelativePath;
private readonly ResourcesType _resourcesType = ResourcesType.TypeBased;
private readonly MissingLocalizationBehavior _missingLocalizationBehavior = MissingLocalizationBehavior.Ignore;
private readonly ILoggerFactory _loggerFactory;

public JsonStringLocalizerFactory(
Expand All @@ -28,6 +29,7 @@ public JsonStringLocalizerFactory(

_resourcesRelativePath = localizationOptions.Value.ResourcesPath ?? string.Empty;
_resourcesType = localizationOptions.Value.ResourcesType;
_missingLocalizationBehavior = localizationOptions.Value.MissingLocalizationBehavior;
_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
}

Expand Down Expand Up @@ -95,7 +97,7 @@ protected virtual JsonStringLocalizer CreateJsonStringLocalizer(
: new JsonResourceManager(resourcesPath);
var logger = _loggerFactory.CreateLogger<JsonStringLocalizer>();

return new JsonStringLocalizer(resourceManager, _resourceNamesCache, logger);
return new JsonStringLocalizer(resourceManager, _resourceNamesCache, logger, _missingLocalizationBehavior);
}

private string GetResourcePath(Assembly assembly)
Expand Down
23 changes: 23 additions & 0 deletions src/My.Extensions.Localization.Json/MissingLocalizationBehavior.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace My.Extensions.Localization.Json;

/// <summary>
/// Specifies the behavior when a localization resource is not found.
/// </summary>
public enum MissingLocalizationBehavior
{
/// <summary>
/// Ignores the missing localization and uses the key as the value.
/// This is the default behavior.
/// </summary>
Ignore,

/// <summary>
/// Logs a warning when a localization is not found.
/// </summary>
LogWarning,

/// <summary>
/// Throws a <see cref="MissingLocalizationException"/> when a localization is not found.
/// </summary>
ThrowException
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System;

namespace My.Extensions.Localization.Json;

/// <summary>
/// The exception that is thrown when a localization resource is not found
/// and the <see cref="MissingLocalizationBehavior.ThrowException"/> behavior is configured.
/// </summary>
public class MissingLocalizationException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="MissingLocalizationException"/> class.
/// </summary>
public MissingLocalizationException()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="MissingLocalizationException"/> class
/// with a specified error message.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
public MissingLocalizationException(string message) : base(message)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="MissingLocalizationException"/> class
/// with a specified error message and a reference to the inner exception that is the cause of this exception.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception.</param>
public MissingLocalizationException(string message, Exception innerException) : base(message, innerException)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="MissingLocalizationException"/> class
/// with details about the missing localization.
/// </summary>
/// <param name="key">The localization key that was not found.</param>
/// <param name="culture">The culture for which the localization was not found.</param>
/// <param name="searchedLocation">The location where the localization was searched.</param>
public MissingLocalizationException(string key, string culture, string searchedLocation)
: base($"Localization for key '{key}' was not found for culture '{culture}' in '{searchedLocation}'.")
{
Key = key;
Culture = culture;
SearchedLocation = searchedLocation;
}

/// <summary>
/// Gets the localization key that was not found.
/// </summary>
public string Key { get; }

/// <summary>
/// Gets the culture for which the localization was not found.
/// </summary>
public string Culture { get; }

/// <summary>
/// Gets the location where the localization was searched.
/// </summary>
public string SearchedLocation { get; }
}
Loading