diff --git a/docs/en/framework/fundamentals/localization.md b/docs/en/framework/fundamentals/localization.md index 6e8a3f678a..47c4ca564a 100644 --- a/docs/en/framework/fundamentals/localization.md +++ b/docs/en/framework/fundamentals/localization.md @@ -91,6 +91,34 @@ A JSON localization file content is shown below: > ABP will ignore (skip) the JSON file if the `culture` section is missing. +You can also use nesting or array in localization files, like this: + +````json +{ + "culture": "en", + "texts": { + "HelloWorld": "Hello World!", + "Hello": { + "World": "Hello World!" + }, + "Hi":[ + "Bye": "Bye World!" + "Hello": "Hello World!" + ] + } +} +```` + +Then you can use it like this: + +> The double underscore (`__`) is used to separate the parent key from the child key. + +````csharp +var str = L["Hello__World"]; // Hello World! +var str2 = L["Hi__0"]; // Bye World! +var str3 = L["Hi__1"]; // Hello World! +```` + ### Default Resource `AbpLocalizationOptions.DefaultResourceType` can be set to a resource type, so it is used when the localization resource was not specified: diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Json/JsonLocalizationDictionaryBuilder.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Json/JsonLocalizationDictionaryBuilder.cs index e4b66148a5..f065f0e695 100644 --- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Json/JsonLocalizationDictionaryBuilder.cs +++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Json/JsonLocalizationDictionaryBuilder.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text.Json; using Microsoft.Extensions.Localization; @@ -47,12 +48,11 @@ public static class JsonLocalizationDictionaryBuilder { throw new AbpException("Can not parse json string. " + ex.Message); } - if (jsonFile == null) { return null; } - + var cultureCode = jsonFile.Culture; if (string.IsNullOrEmpty(cultureCode)) { @@ -61,18 +61,17 @@ public static class JsonLocalizationDictionaryBuilder var dictionary = new Dictionary(); var dublicateNames = new List(); - foreach (var item in jsonFile.Texts) + + foreach (var item in FlattenTexts(jsonFile.Texts)) { if (string.IsNullOrEmpty(item.Key)) { throw new AbpException("The key is empty in given json string."); } - if (dictionary.GetOrDefault(item.Key) != null) { dublicateNames.Add(item.Key); } - dictionary[item.Key] = new LocalizedString(item.Key, item.Value.NormalizeLineEndings()); } @@ -85,4 +84,67 @@ public static class JsonLocalizationDictionaryBuilder return new StaticLocalizationDictionary(cultureCode, dictionary); } + + private static Dictionary FlattenTexts(Dictionary texts, string prefix = "") + { + var result = new Dictionary(); + foreach (var text in texts) + { + var currentKey = string.IsNullOrEmpty(prefix) ? text.Key : $"{prefix}__{text.Key}"; + switch (text.Value) + { + case JsonElement jsonElement: + foreach (var item in FlattenJsonElement(jsonElement, currentKey)) + { + result[item.Key] = item.Value; + } + break; + case string str: + result[currentKey] = str; + break; + case null: + result[currentKey] = ""; + break; + default: + result[currentKey] = text.Value.ToString() ?? ""; + break; + } + } + return result; + } + + private static IEnumerable> FlattenJsonElement(JsonElement element, string prefix) + { + switch (element.ValueKind) + { + case JsonValueKind.String: + yield return new KeyValuePair(prefix, element.GetString() ?? ""); + break; + case JsonValueKind.Object: + foreach (var prop in element.EnumerateObject()) + { + var newKey = $"{prefix}__{prop.Name}"; + foreach (var item in FlattenJsonElement(prop.Value, newKey)) + { + yield return item; + } + } + break; + case JsonValueKind.Array: + var i = 0; + foreach (var prop in element.EnumerateArray()) + { + var newKey = $"{prefix}__{i}"; + foreach (var item in FlattenJsonElement(prop, newKey)) + { + yield return item; + } + i++; + } + break; + default: + yield return new KeyValuePair(prefix, element.ToString()); + break; + } + } } diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Json/JsonLocalizationFile.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Json/JsonLocalizationFile.cs index 8470e2696f..337a03a31e 100644 --- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Json/JsonLocalizationFile.cs +++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Json/JsonLocalizationFile.cs @@ -9,10 +9,5 @@ public class JsonLocalizationFile /// public string Culture { get; set; } = default!; - public Dictionary Texts { get; set; } - - public JsonLocalizationFile() - { - Texts = new Dictionary(); - } + public Dictionary Texts { get; set; } = []; } diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs index e5262bc8a2..189c1b8614 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs @@ -196,7 +196,7 @@ public class AbpLocalization_Tests : AbpIntegratedTest