Skip to content

Commit 99bf2c0

Browse files
committed
Support key hierarchy
1 parent 43ccf24 commit 99bf2c0

File tree

4 files changed

+136
-14
lines changed

4 files changed

+136
-14
lines changed

src/My.Extensions.Localization.Json/Internal/JsonResourceLoader.cs

+67-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System.Collections.Generic;
22
using System.IO;
3-
using System.Linq;
43
using System.Text.Json;
54

65
namespace My.Extensions.Localization.Json.Internal;
@@ -23,9 +22,75 @@ public static IDictionary<string, string> Load(string filePath)
2322

2423
using var document = JsonDocument.Parse(reader.BaseStream, _jsonDocumentOptions);
2524

26-
resources = document.RootElement.EnumerateObject().ToDictionary(e => e.Name, e => e.Value.ToString());
25+
var rootELement = document.RootElement.Clone();
26+
27+
JsonElementToDictionary(rootELement, resources);
2728
}
2829

2930
return resources;
3031
}
32+
33+
private static void JsonElementToDictionary(JsonElement element, Dictionary<string, string> result)
34+
{
35+
foreach (var item in element.EnumerateObject())
36+
{
37+
JsonElementToObject(item.Value, result, item.Name);
38+
}
39+
}
40+
41+
private static void JsonElementToObject(JsonElement element, Dictionary<string, string> result, string path)
42+
{
43+
const char period = '.';
44+
if (element.ValueKind == JsonValueKind.Object)
45+
{
46+
foreach (var item in element.EnumerateObject())
47+
{
48+
if (string.IsNullOrEmpty(path))
49+
{
50+
path = item.Name;
51+
}
52+
else
53+
{
54+
path += period + item.Name;
55+
}
56+
57+
JsonElementToObject(item.Value, result, path);
58+
59+
if (path.Contains(period))
60+
{
61+
path = path[..path.LastIndexOf(period)];
62+
}
63+
}
64+
}
65+
else if (element.ValueKind == JsonValueKind.Array)
66+
{
67+
JsonElementToArray(element, result, path);
68+
}
69+
else
70+
{
71+
JsonElementToValue(element, result, path);
72+
}
73+
}
74+
75+
private static void JsonElementToArray(JsonElement element, Dictionary<string, string> result, string path)
76+
{
77+
const char openBracket = '[';
78+
var index = 0;
79+
foreach (var item in element.EnumerateArray())
80+
{
81+
path += $"[{index}]";
82+
83+
JsonElementToObject(item, result, path);
84+
85+
if (path.Contains(openBracket))
86+
{
87+
path = path[..path.LastIndexOf(openBracket)];
88+
}
89+
90+
++index;
91+
}
92+
}
93+
94+
private static void JsonElementToValue(JsonElement element, Dictionary<string, string> result, string path)
95+
=> result.Add(path, element.ToString());
3196
}

src/My.Extensions.Localization.Json/Internal/JsonResourceManager.cs

+33-9
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
using System.Collections.Concurrent;
1+
using System;
2+
using System.Collections.Concurrent;
23
using System.Collections.Generic;
34
using System.Globalization;
45
using System.IO;
56
using System.Linq;
7+
using System.Text.Json;
68

79
namespace My.Extensions.Localization.Json.Internal;
810

@@ -30,7 +32,7 @@ public virtual ConcurrentDictionary<string, string> GetResourceSet(CultureInfo c
3032
var allResources = new ConcurrentDictionary<string, string>();
3133
do
3234
{
33-
if (_resourcesCache.TryGetValue(culture.Name, out ConcurrentDictionary<string, string> resources))
35+
if (_resourcesCache.TryGetValue(culture.Name, out var resources))
3436
{
3537
foreach (var entry in resources)
3638
{
@@ -45,7 +47,7 @@ public virtual ConcurrentDictionary<string, string> GetResourceSet(CultureInfo c
4547
}
4648
else
4749
{
48-
_resourcesCache.TryGetValue(culture.Name, out ConcurrentDictionary<string, string> resources);
50+
_resourcesCache.TryGetValue(culture.Name, out var resources);
4951

5052
return resources;
5153
}
@@ -63,11 +65,11 @@ public virtual string GetString(string name)
6365

6466
do
6567
{
66-
if (_resourcesCache.TryGetValue(culture.Name, out ConcurrentDictionary<string, string> resources))
68+
if (_resourcesCache.TryGetValue(culture.Name, out var resources))
6769
{
68-
if (resources.TryGetValue(name, out string value))
70+
if (resources.TryGetValue(name, out var value))
6971
{
70-
return value;
72+
return value.ToString();
7173
}
7274
}
7375

@@ -86,13 +88,13 @@ public virtual string GetString(string name, CultureInfo culture)
8688
return null;
8789
}
8890

89-
if (!_resourcesCache.TryGetValue(culture.Name, out ConcurrentDictionary<string, string> resources))
91+
if (!_resourcesCache.TryGetValue(culture.Name, out var resources))
9092
{
9193
return null;
9294
}
9395

94-
return resources.TryGetValue(name, out string value)
95-
? value
96+
return resources.TryGetValue(name, out var value)
97+
? value.ToString()
9698
: null;
9799
}
98100

@@ -157,4 +159,26 @@ ConcurrentDictionary<string, string> GetOrAddResourceCache(string resourceFile)
157159
});
158160
}
159161
}
162+
163+
private static string GetJsonElement(JsonElement jsonElement, string path)
164+
{
165+
if (jsonElement.ValueKind == JsonValueKind.Null || jsonElement.ValueKind == JsonValueKind.Undefined)
166+
{
167+
return default;
168+
}
169+
170+
string[] segments = path.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
171+
172+
for (int n = 0; n < segments.Length; n++)
173+
{
174+
jsonElement = jsonElement.TryGetProperty(segments[n], out JsonElement value) ? value : default;
175+
176+
if (jsonElement.ValueKind == JsonValueKind.Null || jsonElement.ValueKind == JsonValueKind.Undefined)
177+
{
178+
return default;
179+
}
180+
}
181+
182+
return jsonElement.ToString();
183+
}
160184
}

test/My.Extensions.Localization.Json.Tests/JsonStringLocalizerTests.cs

+19-2
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ public void GetTranslation_StronglyTypeResourceName()
113113
}
114114

115115
[Theory]
116-
[InlineData(true, 3)]
117-
[InlineData(false, 2)]
116+
[InlineData(true, 9)]
117+
[InlineData(false, 8)]
118118
public void JsonStringLocalizer_GetAllStrings(bool includeParent, int expected)
119119
{
120120
// Arrange
@@ -160,6 +160,23 @@ public async void CultureBasedResourcesUsesIStringLocalizer()
160160
var response = await client.GetAsync("/");
161161
}
162162

163+
[Theory]
164+
[InlineData("fr-FR", "Book.Page.One", "Page Un")]
165+
[InlineData("fr-FR", "Book.Page.Two", "Page Deux")]
166+
[InlineData("fr-FR", "Articles[0].Content", "Contenu 1")]
167+
[InlineData("fr-FR", "Articles[1].Content", "Contenu 2")]
168+
public void GetTranslationUsingKeyHeirarchy(string culture, string name, string expected)
169+
{
170+
// Arrange
171+
LocalizationHelper.SetCurrentCulture(culture);
172+
173+
// Act
174+
string translation = _localizer[name];
175+
176+
// Assert
177+
Assert.Equal(expected, translation);
178+
}
179+
163180
private class SharedResource
164181
{
165182
public string Hello { get; set; }
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,20 @@
11
{
22
"Hello": "Bonjour",
3-
"Hello, {0}": "Bonjour, {0}"
3+
"Hello, {0}": "Bonjour, {0}",
4+
"Book": {
5+
"Page": {
6+
"One": "Page Un",
7+
"Two": "Page Deux"
8+
}
9+
},
10+
"Articles": [
11+
{
12+
"Title": "Titre 1",
13+
"Content": "Contenu 1"
14+
},
15+
{
16+
"Title": "Titre 2",
17+
"Content": "Contenu 2"
18+
}
19+
]
420
}

0 commit comments

Comments
 (0)