From 97b8ed4526b0ef6909767ffee5f4e0a49fe69527 Mon Sep 17 00:00:00 2001 From: Hisham Bin Ateya Date: Mon, 25 Mar 2024 10:27:31 +0300 Subject: [PATCH] Support key hierarchy --- .../Internal/JsonResourceLoader.cs | 69 ++++++++++++++++++- .../Internal/JsonResourceManager.cs | 42 ++++++++--- .../JsonStringLocalizerTests.cs | 21 +++++- .../Resources/Common/Test.fr-FR.json | 18 ++++- 4 files changed, 136 insertions(+), 14 deletions(-) diff --git a/src/My.Extensions.Localization.Json/Internal/JsonResourceLoader.cs b/src/My.Extensions.Localization.Json/Internal/JsonResourceLoader.cs index 1d4b50b..b33fd92 100644 --- a/src/My.Extensions.Localization.Json/Internal/JsonResourceLoader.cs +++ b/src/My.Extensions.Localization.Json/Internal/JsonResourceLoader.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.IO; -using System.Linq; using System.Text.Json; namespace My.Extensions.Localization.Json.Internal; @@ -23,9 +22,75 @@ public static IDictionary Load(string filePath) using var document = JsonDocument.Parse(reader.BaseStream, _jsonDocumentOptions); - resources = document.RootElement.EnumerateObject().ToDictionary(e => e.Name, e => e.Value.ToString()); + var rootELement = document.RootElement.Clone(); + + JsonElementToDictionary(rootELement, resources); } return resources; } + + private static void JsonElementToDictionary(JsonElement element, Dictionary result) + { + foreach (var item in element.EnumerateObject()) + { + JsonElementToObject(item.Value, result, item.Name); + } + } + + private static void JsonElementToObject(JsonElement element, Dictionary result, string path) + { + const char period = '.'; + if (element.ValueKind == JsonValueKind.Object) + { + foreach (var item in element.EnumerateObject()) + { + if (string.IsNullOrEmpty(path)) + { + path = item.Name; + } + else + { + path += period + item.Name; + } + + JsonElementToObject(item.Value, result, path); + + if (path.Contains(period)) + { + path = path[..path.LastIndexOf(period)]; + } + } + } + else if (element.ValueKind == JsonValueKind.Array) + { + JsonElementToArray(element, result, path); + } + else + { + JsonElementToValue(element, result, path); + } + } + + private static void JsonElementToArray(JsonElement element, Dictionary result, string path) + { + const char openBracket = '['; + var index = 0; + foreach (var item in element.EnumerateArray()) + { + path += $"[{index}]"; + + JsonElementToObject(item, result, path); + + if (path.Contains(openBracket)) + { + path = path[..path.LastIndexOf(openBracket)]; + } + + ++index; + } + } + + private static void JsonElementToValue(JsonElement element, Dictionary result, string path) + => result.Add(path, element.ToString()); } \ No newline at end of file diff --git a/src/My.Extensions.Localization.Json/Internal/JsonResourceManager.cs b/src/My.Extensions.Localization.Json/Internal/JsonResourceManager.cs index 3ce1547..0c1a06e 100644 --- a/src/My.Extensions.Localization.Json/Internal/JsonResourceManager.cs +++ b/src/My.Extensions.Localization.Json/Internal/JsonResourceManager.cs @@ -1,8 +1,10 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Text.Json; namespace My.Extensions.Localization.Json.Internal; @@ -30,7 +32,7 @@ public virtual ConcurrentDictionary GetResourceSet(CultureInfo c var allResources = new ConcurrentDictionary(); do { - if (_resourcesCache.TryGetValue(culture.Name, out ConcurrentDictionary resources)) + if (_resourcesCache.TryGetValue(culture.Name, out var resources)) { foreach (var entry in resources) { @@ -45,7 +47,7 @@ public virtual ConcurrentDictionary GetResourceSet(CultureInfo c } else { - _resourcesCache.TryGetValue(culture.Name, out ConcurrentDictionary resources); + _resourcesCache.TryGetValue(culture.Name, out var resources); return resources; } @@ -63,11 +65,11 @@ public virtual string GetString(string name) do { - if (_resourcesCache.TryGetValue(culture.Name, out ConcurrentDictionary resources)) + if (_resourcesCache.TryGetValue(culture.Name, out var resources)) { - if (resources.TryGetValue(name, out string value)) + if (resources.TryGetValue(name, out var value)) { - return value; + return value.ToString(); } } @@ -86,13 +88,13 @@ public virtual string GetString(string name, CultureInfo culture) return null; } - if (!_resourcesCache.TryGetValue(culture.Name, out ConcurrentDictionary resources)) + if (!_resourcesCache.TryGetValue(culture.Name, out var resources)) { return null; } - return resources.TryGetValue(name, out string value) - ? value + return resources.TryGetValue(name, out var value) + ? value.ToString() : null; } @@ -157,4 +159,26 @@ ConcurrentDictionary GetOrAddResourceCache(string resourceFile) }); } } + + private static string GetJsonElement(JsonElement jsonElement, string path) + { + if (jsonElement.ValueKind == JsonValueKind.Null || jsonElement.ValueKind == JsonValueKind.Undefined) + { + return default; + } + + string[] segments = path.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries); + + for (int n = 0; n < segments.Length; n++) + { + jsonElement = jsonElement.TryGetProperty(segments[n], out JsonElement value) ? value : default; + + if (jsonElement.ValueKind == JsonValueKind.Null || jsonElement.ValueKind == JsonValueKind.Undefined) + { + return default; + } + } + + return jsonElement.ToString(); + } } diff --git a/test/My.Extensions.Localization.Json.Tests/JsonStringLocalizerTests.cs b/test/My.Extensions.Localization.Json.Tests/JsonStringLocalizerTests.cs index efec31d..b2c91f1 100644 --- a/test/My.Extensions.Localization.Json.Tests/JsonStringLocalizerTests.cs +++ b/test/My.Extensions.Localization.Json.Tests/JsonStringLocalizerTests.cs @@ -113,8 +113,8 @@ public void GetTranslation_StronglyTypeResourceName() } [Theory] - [InlineData(true, 3)] - [InlineData(false, 2)] + [InlineData(true, 9)] + [InlineData(false, 8)] public void JsonStringLocalizer_GetAllStrings(bool includeParent, int expected) { // Arrange @@ -160,6 +160,23 @@ public async void CultureBasedResourcesUsesIStringLocalizer() var response = await client.GetAsync("/"); } + [Theory] + [InlineData("fr-FR", "Book.Page.One", "Page Un")] + [InlineData("fr-FR", "Book.Page.Two", "Page Deux")] + [InlineData("fr-FR", "Articles[0].Content", "Contenu 1")] + [InlineData("fr-FR", "Articles[1].Content", "Contenu 2")] + public void GetTranslationUsingKeyHeirarchy(string culture, string name, string expected) + { + // Arrange + LocalizationHelper.SetCurrentCulture(culture); + + // Act + string translation = _localizer[name]; + + // Assert + Assert.Equal(expected, translation); + } + private class SharedResource { public string Hello { get; set; } diff --git a/test/My.Extensions.Localization.Json.Tests/Resources/Common/Test.fr-FR.json b/test/My.Extensions.Localization.Json.Tests/Resources/Common/Test.fr-FR.json index f1480a1..b538d3a 100644 --- a/test/My.Extensions.Localization.Json.Tests/Resources/Common/Test.fr-FR.json +++ b/test/My.Extensions.Localization.Json.Tests/Resources/Common/Test.fr-FR.json @@ -1,4 +1,20 @@ { "Hello": "Bonjour", - "Hello, {0}": "Bonjour, {0}" + "Hello, {0}": "Bonjour, {0}", + "Book": { + "Page": { + "One": "Page Un", + "Two": "Page Deux" + } + }, + "Articles": [ + { + "Title": "Titre 1", + "Content": "Contenu 1" + }, + { + "Title": "Titre 2", + "Content": "Contenu 2" + } + ] } \ No newline at end of file