Browse Source

Add support for multiple localization files with same culture

pull/25227/head
Anton Gritsenko 2 months ago
parent
commit
67f523e454
  1. 2
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/ILocalizationDictionary.cs
  2. 13
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Json/JsonLocalizationDictionaryBuilder.cs
  3. 19
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/StaticLocalizationDictionary.cs
  4. 10
      framework/src/Volo.Abp.Localization/Volo/Abp/Localization/VirtualFiles/VirtualFileLocalizationResourceContributorBase.cs
  5. 4
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalizationTestModule.cs
  6. 21
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs
  7. 13
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpStringLocalizerFactory_Tests.cs
  8. 47
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/JsonLocalizationDictionaryBuilder_Tests.cs
  9. 6
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/FilesSplit/!en_First.json
  10. 7
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/FilesSplit/en_Base.json
  11. 7
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/FilesSplit/en_Book.json
  12. 6
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/FilesSplit/zen_Last.json
  13. 7
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/de.json
  14. 10
      framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/TestResources/Source/de_Book.json

2
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/ILocalizationDictionary.cs

@ -13,4 +13,6 @@ public interface ILocalizationDictionary
LocalizedString? GetOrNull(string name);
void Fill(Dictionary<string, LocalizedString> dictionary);
void Merge(ILocalizationDictionary dictionary);
}

13
framework/src/Volo.Abp.Localization/Volo/Abp/Localization/Json/JsonLocalizationDictionaryBuilder.cs

@ -60,7 +60,6 @@ public static class JsonLocalizationDictionaryBuilder
}
var dictionary = new Dictionary<string, LocalizedString>();
var dublicateNames = new List<string>();
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);
}

19
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;
}
}
}

10
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;

4
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<LocalizationTestResource>("en")
.AddVirtualJson("/Volo/Abp/Localization/TestResources/Source")

21
framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs

@ -234,6 +234,27 @@ public class AbpLocalization_Tests : AbpIntegratedTest<AbpLocalizationTestModule
}
}
[Fact]
public void Should_Get_Localized_Text_If_Defined_In_Requested_Culture_Defined_In_Different_Files()
{
using (CultureHelper.Use(CultureInfo.GetCultureInfo("de")))
{
_localizer["Car"].Value.ShouldBe("Auto");
}
using (CultureHelper.Use(CultureInfo.GetCultureInfo("de")))
{
_localizer["CarPlural"].Value.ShouldBe("Autos");
}
using (CultureHelper.Use(CultureInfo.GetCultureInfo("de")))
{
_localizer["Biography"].Value.ShouldBe("Biografie");
}
using (CultureHelper.Use(CultureInfo.GetCultureInfo("de")))
{
_localizer["Enum:BookType.Undefined"].Value.ShouldBe("Nicht definiert");
}
}
[Fact]
public void GetAllStrings_With_Parents()
{

13
framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/AbpStringLocalizerFactory_Tests.cs

@ -90,4 +90,17 @@ public class AbpStringLocalizerFactory_Tests : AbpIntegratedTest<AbpLocalization
localizer2["CarPlural"].Value.ShouldBe("CarPlural");
}
}
[Fact]
public async Task Should_Create_Resource_By_Name_FromSplitFiles()
{
using (CultureHelper.Use("en"))
{
var localizer = _factory.CreateByResourceNameOrNull("LocalizationTestFilesSplit");
localizer.ShouldNotBeNull();
localizer["Base.Id"].Value.ShouldBe("Id");
localizer["Book.Id"].Value.ShouldBe("ISBN");
localizer["ThisIsRequiredValue"].Value.ShouldBe("This is required value (this will be used)");
}
}
}

47
framework/test/Volo.Abp.Localization.Tests/Volo/Abp/Localization/JsonLocalizationDictionaryBuilder_Tests.cs

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Text;
using NSubstitute.ExceptionExtensions;
using Shouldly;
using Volo.Abp.Localization.Json;
using Volo.Abp.Testing;
using Xunit;
namespace Volo.Abp.Localization;
/// <summary>
/// Testing edge cases for <see cref="JsonLocalizationDictionaryBuilder"/>
/// </summary>
public class JsonLocalizationDictionaryBuilder_Tests : AbpIntegratedTest<AbpLocalizationTestModule>
{
[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");
}
}

6
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"
}
}

7
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"
}
}

7
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"
}
}

6
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)"
}
}

7
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"
}
}

10
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"
}
}
Loading…
Cancel
Save