From 1ea0592c8783f9250be2e182b3ae4d41395a553b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 18 Dec 2017 00:00:55 +0300 Subject: [PATCH] Inherit from existing resources. Thus we can share common localizations accross modules. #171. --- .../AbpDictionaryBasedStringLocalizer.cs | 30 +++++++++++++-- .../Localization/AbpStringLocalizerFactory.cs | 5 ++- .../Abp/Localization/LocalizationResource.cs | 14 ++----- .../LocalizationResourceExtensions.cs | 18 +++++++++ .../LocalizationResourceListExtensions.cs | 4 +- test/Volo.Abp.Tests/Volo.Abp.Tests.csproj | 3 +- .../Abp/Localization/AbpLocalization_Tests.cs | 38 ++++++++++++++++++- .../LocalizationTestCountryNamesResource.cs | 7 ++++ .../Localization/Base/CountryNames/en.json | 6 +++ .../Localization/Base/CountryNames/tr.json | 6 +++ .../LocalizationTestValidationResource.cs | 7 ++++ .../Abp/Localization/Base/Validation/en.json | 7 ++++ .../Volo/Abp/Localization/Source/en.json | 3 +- 13 files changed, 125 insertions(+), 23 deletions(-) create mode 100644 src/Volo.Abp/Volo/Abp/Localization/LocalizationResourceExtensions.cs create mode 100644 test/Volo.Abp.Tests/Volo/Abp/Localization/Base/CountryNames/LocalizationTestCountryNamesResource.cs create mode 100644 test/Volo.Abp.Tests/Volo/Abp/Localization/Base/CountryNames/en.json create mode 100644 test/Volo.Abp.Tests/Volo/Abp/Localization/Base/CountryNames/tr.json create mode 100644 test/Volo.Abp.Tests/Volo/Abp/Localization/Base/Validation/LocalizationTestValidationResource.cs create mode 100644 test/Volo.Abp.Tests/Volo/Abp/Localization/Base/Validation/en.json diff --git a/src/Volo.Abp/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs b/src/Volo.Abp/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs index 16a046bd8f..e24b2f2266 100644 --- a/src/Volo.Abp/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs +++ b/src/Volo.Abp/Volo/Abp/Localization/AbpDictionaryBasedStringLocalizer.cs @@ -10,13 +10,16 @@ namespace Volo.Abp.Localization { public LocalizationResource Resource { get; } - public LocalizedString this[string name] => GetLocalizedString(name, CultureInfo.CurrentUICulture.Name); + public List BaseLocalizers { get; } - public LocalizedString this[string name, params object[] arguments] => GetLocalizedStringFormatted(name, CultureInfo.CurrentUICulture.Name, arguments); + public virtual LocalizedString this[string name] => GetLocalizedString(name); - public AbpDictionaryBasedStringLocalizer(LocalizationResource resource) + public virtual LocalizedString this[string name, params object[] arguments] => GetLocalizedStringFormatted(name, arguments); + + public AbpDictionaryBasedStringLocalizer(LocalizationResource resource, List baseLocalizers) { Resource = resource; + BaseLocalizers = baseLocalizers; } public IEnumerable GetAllStrings(bool includeParentCultures) @@ -29,19 +32,38 @@ namespace Volo.Abp.Localization return new CultureWrapperStringLocalizer(culture.Name, this); } + protected virtual LocalizedString GetLocalizedStringFormatted(string name, params object[] arguments) + { + return GetLocalizedStringFormatted(name, CultureInfo.CurrentUICulture.Name, arguments); + } + protected virtual LocalizedString GetLocalizedStringFormatted(string name, string cultureName, params object[] arguments) { var localizedString = GetLocalizedString(name, cultureName); return new LocalizedString(name, string.Format(localizedString.Value, arguments), localizedString.ResourceNotFound, localizedString.SearchedLocation); } + protected virtual LocalizedString GetLocalizedString(string name) + { + return GetLocalizedString(name, CultureInfo.CurrentUICulture.Name); + } + protected virtual LocalizedString GetLocalizedString(string name, string cultureName) { var value = GetLocalizedStringOrNull(name, cultureName); if (value == null) { - return new LocalizedString(name, name, true); + foreach (var baseLocalizer in BaseLocalizers) + { + var baseLocalizedString = baseLocalizer.WithCulture(CultureInfo.GetCultureInfo(cultureName))[name]; + if (baseLocalizedString != null && !baseLocalizedString.ResourceNotFound) + { + return baseLocalizedString; + } + } + + return new LocalizedString(name, name, resourceNotFound: true); } return value; diff --git a/src/Volo.Abp/Volo/Abp/Localization/AbpStringLocalizerFactory.cs b/src/Volo.Abp/Volo/Abp/Localization/AbpStringLocalizerFactory.cs index 481fa530e4..932b8fca76 100644 --- a/src/Volo.Abp/Volo/Abp/Localization/AbpStringLocalizerFactory.cs +++ b/src/Volo.Abp/Volo/Abp/Localization/AbpStringLocalizerFactory.cs @@ -44,12 +44,13 @@ namespace Volo.Abp.Localization { resource.Initialize(_serviceProvider); - //Extend dictionary with extensions //Wrap reader by wrappers (like db wrapper which implement multitenancy/regions and so on...) //Notes: Localizer will be cached, so wrappers are responsible to cache/invalidate themselves! - var localizer = new AbpDictionaryBasedStringLocalizer(resource); + var baseLocalizers = resource.BaseResourceTypes.Select(Create).ToList(); + + var localizer = new AbpDictionaryBasedStringLocalizer(resource, baseLocalizers); //TODO: Wrap with DB provider or other premium sources diff --git a/src/Volo.Abp/Volo/Abp/Localization/LocalizationResource.cs b/src/Volo.Abp/Volo/Abp/Localization/LocalizationResource.cs index 2e2dbd6fd4..a881cac337 100644 --- a/src/Volo.Abp/Volo/Abp/Localization/LocalizationResource.cs +++ b/src/Volo.Abp/Volo/Abp/Localization/LocalizationResource.cs @@ -10,19 +10,12 @@ namespace Volo.Abp.Localization public string DefaultCultureName { get; set; } - public ILocalizationDictionaryProvider DictionaryProvider - { - get => _dictionaryProvider; - set - { - Check.NotNull(value, nameof(value)); - _dictionaryProvider = value; - } - } - private ILocalizationDictionaryProvider _dictionaryProvider; + public ILocalizationDictionaryProvider DictionaryProvider { get; } public List Extensions { get; } + public List BaseResourceTypes { get; } + public LocalizationResource([NotNull] Type resourceType, [NotNull] string defaultCultureName, [NotNull] ILocalizationDictionaryProvider dictionaryProvider) { Check.NotNull(resourceType, nameof(resourceType)); @@ -33,6 +26,7 @@ namespace Volo.Abp.Localization DefaultCultureName = defaultCultureName; DictionaryProvider = dictionaryProvider; + BaseResourceTypes = new List(); Extensions = new List(); } diff --git a/src/Volo.Abp/Volo/Abp/Localization/LocalizationResourceExtensions.cs b/src/Volo.Abp/Volo/Abp/Localization/LocalizationResourceExtensions.cs new file mode 100644 index 0000000000..9ec0fc5b68 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Localization/LocalizationResourceExtensions.cs @@ -0,0 +1,18 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.Localization +{ + public static class LocalizationResourceExtensions + { + public static LocalizationResource InheritFrom([NotNull] this LocalizationResource resource, [NotNull] params Type[] baseResourceTypes) + { + Check.NotNull(resource, nameof(resource)); + Check.NotNull(baseResourceTypes, nameof(baseResourceTypes)); + + resource.BaseResourceTypes.AddRange(baseResourceTypes); + + return resource; + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Localization/LocalizationResourceListExtensions.cs b/src/Volo.Abp/Volo/Abp/Localization/LocalizationResourceListExtensions.cs index f02881035e..84ee44e478 100644 --- a/src/Volo.Abp/Volo/Abp/Localization/LocalizationResourceListExtensions.cs +++ b/src/Volo.Abp/Volo/Abp/Localization/LocalizationResourceListExtensions.cs @@ -6,7 +6,7 @@ namespace Volo.Abp.Localization { public static class LocalizationResourceListExtensions { - public static void AddJson([NotNull] this LocalizationResourceDictionary resourceDictionary, [NotNull] string defaultCultureName) + public static LocalizationResource AddJson([NotNull] this LocalizationResourceDictionary resourceDictionary, [NotNull] string defaultCultureName) { Check.NotNull(resourceDictionary, nameof(resourceDictionary)); Check.NotNull(defaultCultureName, nameof(defaultCultureName)); @@ -18,7 +18,7 @@ namespace Volo.Abp.Localization throw new AbpException("There is already a resource with given type: " + resourceType.AssemblyQualifiedName); } - resourceDictionary[resourceType] = new LocalizationResource( + return resourceDictionary[resourceType] = new LocalizationResource( resourceType, defaultCultureName, new JsonEmbeddedFileLocalizationDictionaryProvider( diff --git a/test/Volo.Abp.Tests/Volo.Abp.Tests.csproj b/test/Volo.Abp.Tests/Volo.Abp.Tests.csproj index 693dc2a605..e4eaaedf1b 100644 --- a/test/Volo.Abp.Tests/Volo.Abp.Tests.csproj +++ b/test/Volo.Abp.Tests/Volo.Abp.Tests.csproj @@ -12,8 +12,7 @@ - - + diff --git a/test/Volo.Abp.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs b/test/Volo.Abp.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs index 3697b6aa17..a947f3d47a 100644 --- a/test/Volo.Abp.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs +++ b/test/Volo.Abp.Tests/Volo/Abp/Localization/AbpLocalization_Tests.cs @@ -2,6 +2,8 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using Shouldly; +using Volo.Abp.Localization.Base.CountryNames; +using Volo.Abp.Localization.Base.Validation; using Volo.Abp.Localization.Source; using Volo.Abp.Localization.SourceExt; using Volo.Abp.Modularity; @@ -58,7 +60,7 @@ namespace Volo.Abp.Localization using (AbpCultureHelper.Use("tr")) { - _localizer["SeeYou"].Value.ShouldBe("See you"); //Not defined in tr + _localizer["SeeYou"].Value.ShouldBe("See you"); //Not defined in tr, getting from default lang } using (AbpCultureHelper.Use("it")) @@ -67,6 +69,30 @@ namespace Volo.Abp.Localization } } + [Fact] + public void Should_Get_From_Inherited_Texts() + { + using (AbpCultureHelper.Use("en")) + { + _localizer["USA"].Value.ShouldBe("United States of America"); //Inherited from CountryNames/en.json + _localizer["ThisFieldIsRequired"].Value.ShouldBe("This field is required"); //Inherited from Validation/en.json + } + + using (AbpCultureHelper.Use("tr")) + { + _localizer["USA"].Value.ShouldBe("Amerika Birleşik Devletleri"); //Inherited from CountryNames/tr.json + } + } + + [Fact] + public void Should_Override_Inherited_Text() + { + using (AbpCultureHelper.Use("en")) + { + _localizer["MaxLenghtErrorMessage", 42].Value.ShouldBe("This field's length can be maximum of '42' chars"); //Overriden in Source/en.json + } + } + [Fact] public void Should_Get_Localized_Text_If_Defined_In_Requested_Culture() { @@ -84,7 +110,15 @@ namespace Volo.Abp.Localization { services.Configure(options => { - options.Resources.AddJson("en"); + options.Resources.AddJson("en"); + options.Resources.AddJson("en"); + + options.Resources.AddJson("en") + .InheritFrom( + typeof(LocalizationTestValidationResource), + typeof(LocalizationTestCountryNamesResource) + ); + options.Resources.ExtendWithJson(); }); } diff --git a/test/Volo.Abp.Tests/Volo/Abp/Localization/Base/CountryNames/LocalizationTestCountryNamesResource.cs b/test/Volo.Abp.Tests/Volo/Abp/Localization/Base/CountryNames/LocalizationTestCountryNamesResource.cs new file mode 100644 index 0000000000..dc05118996 --- /dev/null +++ b/test/Volo.Abp.Tests/Volo/Abp/Localization/Base/CountryNames/LocalizationTestCountryNamesResource.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Localization.Base.CountryNames +{ + public sealed class LocalizationTestCountryNamesResource + { + + } +} diff --git a/test/Volo.Abp.Tests/Volo/Abp/Localization/Base/CountryNames/en.json b/test/Volo.Abp.Tests/Volo/Abp/Localization/Base/CountryNames/en.json new file mode 100644 index 0000000000..0fafc3ee42 --- /dev/null +++ b/test/Volo.Abp.Tests/Volo/Abp/Localization/Base/CountryNames/en.json @@ -0,0 +1,6 @@ +{ + "culture": "en", + "texts": { + "USA": "United States of America" + } +} \ No newline at end of file diff --git a/test/Volo.Abp.Tests/Volo/Abp/Localization/Base/CountryNames/tr.json b/test/Volo.Abp.Tests/Volo/Abp/Localization/Base/CountryNames/tr.json new file mode 100644 index 0000000000..1f38b5c8d4 --- /dev/null +++ b/test/Volo.Abp.Tests/Volo/Abp/Localization/Base/CountryNames/tr.json @@ -0,0 +1,6 @@ +{ + "culture": "tr", + "texts": { + "USA": "Amerika Birleşik Devletleri" + } +} \ No newline at end of file diff --git a/test/Volo.Abp.Tests/Volo/Abp/Localization/Base/Validation/LocalizationTestValidationResource.cs b/test/Volo.Abp.Tests/Volo/Abp/Localization/Base/Validation/LocalizationTestValidationResource.cs new file mode 100644 index 0000000000..23202a6bda --- /dev/null +++ b/test/Volo.Abp.Tests/Volo/Abp/Localization/Base/Validation/LocalizationTestValidationResource.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Localization.Base.Validation +{ + public sealed class LocalizationTestValidationResource + { + + } +} diff --git a/test/Volo.Abp.Tests/Volo/Abp/Localization/Base/Validation/en.json b/test/Volo.Abp.Tests/Volo/Abp/Localization/Base/Validation/en.json new file mode 100644 index 0000000000..ca45061cca --- /dev/null +++ b/test/Volo.Abp.Tests/Volo/Abp/Localization/Base/Validation/en.json @@ -0,0 +1,7 @@ +{ + "culture": "en", + "texts": { + "ThisFieldIsRequired": "This field is required", + "MaxLenghtErrorMessage": "This field can be maximum of '{0}' chars" + } +} \ No newline at end of file diff --git a/test/Volo.Abp.Tests/Volo/Abp/Localization/Source/en.json b/test/Volo.Abp.Tests/Volo/Abp/Localization/Source/en.json index 988c2f6278..974f7081d0 100644 --- a/test/Volo.Abp.Tests/Volo/Abp/Localization/Source/en.json +++ b/test/Volo.Abp.Tests/Volo/Abp/Localization/Source/en.json @@ -3,6 +3,7 @@ "texts": { "Hello {0}.": "Hello {0}.", "Car": "Car", - "CarPlural": "Cars" + "CarPlural": "Cars", + "MaxLenghtErrorMessage": "This field's length can be maximum of '{0}' chars" } } \ No newline at end of file