diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/ILocalizationDictionary.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/ILocalizationDictionary.cs index e6879c0007..359ef6c553 100644 --- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/ILocalizationDictionary.cs +++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/ILocalizationDictionary.cs @@ -13,4 +13,6 @@ public interface ILocalizationDictionary LocalizedString? GetOrNull(string name); void Fill(Dictionary dictionary); + + void Merge(ILocalizationDictionary dictionary); } 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 f065f0e695..d39c06a1b4 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 @@ -60,7 +60,6 @@ public static class JsonLocalizationDictionaryBuilder } var dictionary = new Dictionary(); - var dublicateNames = new List(); foreach (var item in FlattenTexts(jsonFile.Texts)) { @@ -68,20 +67,10 @@ public static class JsonLocalizationDictionaryBuilder { 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()); } - if (dublicateNames.Count > 0) - { - throw new AbpException( - "A dictionary can not contain same key twice. There are some duplicated names: " + - dublicateNames.JoinAsString(", ")); - } - return new StaticLocalizationDictionary(cultureCode, dictionary); } diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/StaticLocalizationDictionary.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/StaticLocalizationDictionary.cs index b363076fe0..213627e1c6 100644 --- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/StaticLocalizationDictionary.cs +++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/StaticLocalizationDictionary.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Microsoft.Extensions.Localization; @@ -37,4 +38,22 @@ public class StaticLocalizationDictionary : ILocalizationDictionary dictionary[item.Key] = item.Value; } } + + public void Merge(ILocalizationDictionary dictionary) + { + if (dictionary is not StaticLocalizationDictionary staticLocalizationDictionary) + { + return; // do nothing, we have no idea what to do with such + } + + foreach (var item in staticLocalizationDictionary.Dictionary) + { + if (string.IsNullOrEmpty(item.Key)) + { + throw new AbpException("The key is empty in given json string."); + } + + Dictionary[item.Key] = item.Value; + } + } } diff --git a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/VirtualFiles/VirtualFileLocalizationResourceContributorBase.cs b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/VirtualFiles/VirtualFileLocalizationResourceContributorBase.cs index d151dc78df..0c3b26aa60 100644 --- a/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/VirtualFiles/VirtualFileLocalizationResourceContributorBase.cs +++ b/framework/src/Volo.Abp.Localization/Volo/Abp/Localization/VirtualFiles/VirtualFileLocalizationResourceContributorBase.cs @@ -104,12 +104,14 @@ public abstract class VirtualFileLocalizationResourceContributorBase : ILocaliza continue; } - if (dictionaries.ContainsKey(dictionary.CultureName)) + if (!dictionaries.ContainsKey(dictionary.CultureName)) { - throw new AbpException($"{file.GetVirtualOrPhysicalPathOrNull()} dictionary has a culture name '{dictionary.CultureName}' which is already defined! Localization resource: {_resource.ResourceName}"); + dictionaries[dictionary.CultureName] = dictionary; + } + else + { + dictionaries[dictionary.CultureName].Merge(dictionary); } - - dictionaries[dictionary.CultureName] = dictionary; } return dictionaries; diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalizationTestModule.cs b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalizationTestModule.cs index 32835cb8f4..d4ef509fd5 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalizationTestModule.cs +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalizationTestModule.cs @@ -29,6 +29,10 @@ public class AbpLocalizationTestModule : AbpModule .Add("LocalizationTestCountryNames") .AddVirtualJson("/Volo/Abp/Localization/TestResources/Base/CountryNames"); + options.Resources + .Add("LocalizationTestFilesSplit") + .AddVirtualJson("/Volo/Abp/Localization/TestResources/FilesSplit"); + options.Resources .Add("en") .AddVirtualJson("/Volo/Abp/Localization/TestResources/Source") 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 189c1b8614..424cb8af34 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 @@ -234,6 +234,27 @@ public class AbpLocalization_Tests : AbpIntegratedTest +/// Testing edge cases for +/// +public class JsonLocalizationDictionaryBuilder_Tests : AbpIntegratedTest +{ + [Fact] + public void JsonLocalizationDictionaryBuilder_Should_Handle_Duplicates() + { + var localizationDictionary = JsonLocalizationDictionaryBuilder + .BuildFromJsonString("{\r\n \"culture\": \"en\",\r\n \"texts\": {\r\n \"ThisFieldIsRequired\": \"This field is required\",\r\n \"MaxLenghtErrorMessage\": \"This field can be maximum of '{0}' chars\",\r\n \"Enum:BookType.Undefined\": \"Undefined from ValidationResource\",\r\n \"Enum:BookType.0\": \"Undefined with value 0 from ValidationResource\",\r\n \"BookType.Adventure\": \"Adventure from ValidationResource\",\r\n \"BookType.1\": \"Adventure with value 1 from ValidationResource\",\r\n \"Biography\": \"Biography from ValidationResource\",\r\n \"ThisFieldIsRequired\": \"This field is required again\"\r\n }\r\n}"); + + var localizationString = localizationDictionary.GetOrNull("ThisFieldIsRequired"); + localizationString.ShouldNotBeNull(); + + localizationString.Value.ShouldBe("This field is required again"); + } + + [Fact] + public void JsonLocalizationDictionaryBuilder_Should_Handle_Deep_Duplicates() + { + var input = @"{ +""culture"": ""en"", +""texts"": { + ""ThisFieldIsRequired"": ""This field is required"", + ""DeepLocaliaztionKey"": {""DeepKey"": ""DeepValue""}, + ""DeepLocaliaztionKey__DeepKey"": ""Another translation"" +} +}"; + + var localizationDictionary = JsonLocalizationDictionaryBuilder.BuildFromJsonString(input); + localizationDictionary.ShouldNotBeNull(); + var localizationString = localizationDictionary.GetOrNull("DeepLocaliaztionKey__DeepKey"); + localizationString.ShouldNotBeNull(); + localizationString.Value.ShouldBe("Another translation"); + } +} diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/FilesSplit/!en_First.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/FilesSplit/!en_First.json new file mode 100644 index 0000000000..681c77b5d5 --- /dev/null +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/FilesSplit/!en_First.json @@ -0,0 +1,6 @@ +{ + "culture": "en", + "texts": { + "ThisIsRequiredValue": "This is required value" + } +} diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/FilesSplit/en_Base.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/FilesSplit/en_Base.json new file mode 100644 index 0000000000..1bcfead01b --- /dev/null +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/FilesSplit/en_Base.json @@ -0,0 +1,7 @@ +{ + "culture": "en", + "texts": { + "Base.Id": "Id", + "Base.Name": "Name" + } +} diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/FilesSplit/en_Book.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/FilesSplit/en_Book.json new file mode 100644 index 0000000000..22d6039843 --- /dev/null +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/FilesSplit/en_Book.json @@ -0,0 +1,7 @@ +{ + "culture": "en", + "texts": { + "Book.Id": "ISBN", + "Book.Name": "Title" + } +} diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/FilesSplit/zen_Last.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/FilesSplit/zen_Last.json new file mode 100644 index 0000000000..a94dc23006 --- /dev/null +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/FilesSplit/zen_Last.json @@ -0,0 +1,6 @@ +{ + "culture": "en", + "texts": { + "ThisIsRequiredValue": "This is required value (this will be used)" + } +} diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/de.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/de.json index ee396f1341..4ef1655764 100644 --- a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/de.json +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/de.json @@ -6,11 +6,6 @@ "CarPlural": "Autos", "MaxLenghtErrorMessage": "Die Länge dieses Feldes kann maximal '{0}'-Zeichen betragen", "Universe": "Universum", - "FortyTwo": "Zweiundvierzig", - "Enum:BookType.Undefined": "Nicht definiert", - "Enum:BookType.0": "Undefiniert mit Wert 0", - "BookType.Adventure": "Abenteuer", - "BookType.1": "Abenteuer mit Wert 1", - "Biography": "Biografie" + "FortyTwo": "Zweiundvierzig" } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/de_Book.json b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/de_Book.json new file mode 100644 index 0000000000..1847c4e4fb --- /dev/null +++ b/framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/de_Book.json @@ -0,0 +1,10 @@ +{ + "culture": "de", + "texts": { + "Enum:BookType.Undefined": "Nicht definiert", + "Enum:BookType.0": "Undefiniert mit Wert 0", + "BookType.Adventure": "Abenteuer", + "BookType.1": "Abenteuer mit Wert 1", + "Biography": "Biografie" + } +} \ No newline at end of file