diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index 823b84910a..357e48b156 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -331,6 +331,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.GlobalFeatures", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.GlobalFeatures.Tests", "test\Volo.Abp.GlobalFeatures.Tests\Volo.Abp.GlobalFeatures.Tests.csproj", "{231F1581-AA21-44C3-BF27-51EB3AD5355C}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.MultiLingualObject", "src\Volo.Abp.MultiLingualObject\Volo.Abp.MultiLingualObject.csproj", "{C9142DED-1F6C-4385-A37D-81E46B233306}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.MultiLingualObject.Tests", "test\Volo.Abp.MultiLingualObject.Tests\Volo.Abp.MultiLingualObject.Tests.csproj", "{A30D63B0-E952-4052-BAEE-38B8BF924093}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Http.Client.IdentityModel.WebAssembly", "src\Volo.Abp.Http.Client.IdentityModel.WebAssembly\Volo.Abp.Http.Client.IdentityModel.WebAssembly.csproj", "{3D35A1E0-A9A1-404F-9B55-5F1A7EB6D5B8}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.Client.Common", "src\Volo.Abp.AspNetCore.Mvc.Client.Common\Volo.Abp.AspNetCore.Mvc.Client.Common.csproj", "{8A22D962-016E-474A-8BB7-F831F0ABF3AC}" @@ -1087,6 +1091,10 @@ Global {A37BFEB5-7C57-4CDC-93B8-B5CE4BB9ACE1}.Debug|Any CPU.Build.0 = Debug|Any CPU {A37BFEB5-7C57-4CDC-93B8-B5CE4BB9ACE1}.Release|Any CPU.ActiveCfg = Release|Any CPU {A37BFEB5-7C57-4CDC-93B8-B5CE4BB9ACE1}.Release|Any CPU.Build.0 = Release|Any CPU + {C9142DED-1F6C-4385-A37D-81E46B233306}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9142DED-1F6C-4385-A37D-81E46B233306}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A30D63B0-E952-4052-BAEE-38B8BF924093}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A30D63B0-E952-4052-BAEE-38B8BF924093}.Debug|Any CPU.Build.0 = Debug|Any CPU {F03A1CEA-FA44-4F30-BFC2-00BC2EAAB4E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F03A1CEA-FA44-4F30-BFC2-00BC2EAAB4E2}.Debug|Any CPU.Build.0 = Debug|Any CPU {F03A1CEA-FA44-4F30-BFC2-00BC2EAAB4E2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1304,6 +1312,8 @@ Global {DD9519E0-5A68-48DC-A051-7BF2AC922F3E} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {00D07595-993C-40FC-BD90-0DD6331414D3} = {447C8A77-E5F0-4538-8687-7383196D04EA} {A37BFEB5-7C57-4CDC-93B8-B5CE4BB9ACE1} = {447C8A77-E5F0-4538-8687-7383196D04EA} + {C9142DED-1F6C-4385-A37D-81E46B233306} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {A30D63B0-E952-4052-BAEE-38B8BF924093} = {447C8A77-E5F0-4538-8687-7383196D04EA} {F03A1CEA-FA44-4F30-BFC2-00BC2EAAB4E2} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {5AD34048-5DAA-4067-9C7D-09295617A057} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {B9133C38-AC24-4E2F-B581-D124CF410CDF} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} diff --git a/framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperMultiLingualDtoExtensions.cs b/framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperMultiLingualDtoExtensions.cs new file mode 100644 index 0000000000..83cb2b35b9 --- /dev/null +++ b/framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperMultiLingualDtoExtensions.cs @@ -0,0 +1,54 @@ +using Volo.Abp.MultiLingualObject; +using Volo.Abp.Threading; + +namespace AutoMapper +{ + public static class AbpAutoMapperMultiLingualDtoExtensions + { + public static CreateMultiLingualMapResult CreateMultiLingualMap(this Profile profile) + where TTranslation : class, IMultiLingualTranslation + where TSource : IHasMultiLingual + { + + return new( + profile.CreateMap().BeforeMap>(), + profile.CreateMap()); + } + } + + public class AbpMultiLingualMapperAction : IMappingAction + where TTranslation : class, IMultiLingualTranslation + where TSource : IHasMultiLingual + { + private readonly IMultiLingualObjectManager _multiLingualObjectManager; + + public AbpMultiLingualMapperAction(IMultiLingualObjectManager multiLingualObjectManager) + { + _multiLingualObjectManager = multiLingualObjectManager; + } + + public void Process(TSource source, TDestination destination, ResolutionContext context) + { + var translation = AsyncHelper.RunSync(() => _multiLingualObjectManager.GetTranslationAsync(source)); + if (translation != null) + { + context.Mapper.Map(translation, destination); + } + } + } + + public class CreateMultiLingualMapResult + { + public IMappingExpression EntityMap { get; } + + public IMappingExpression TranslateMap { get; } + + public CreateMultiLingualMapResult( + IMappingExpression entityMap, + IMappingExpression translateMap) + { + EntityMap = entityMap; + TranslateMap = translateMap; + } + } +} diff --git a/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj b/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj index 9f028d31ee..4a5f44f937 100644 --- a/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj +++ b/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj @@ -16,6 +16,7 @@ + diff --git a/framework/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs b/framework/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs index cb576ba1de..3969a04a1a 100644 --- a/framework/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs +++ b/framework/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs @@ -27,6 +27,8 @@ namespace Volo.Abp.AutoMapper context.Services.AddSingleton(CreateMappings); context.Services.AddSingleton(provider => provider.GetRequiredService()); + + context.Services.AddTransient(typeof(AbpMultiLingualMapperAction<,,>)); } private MapperAccessor CreateMappings(IServiceProvider serviceProvider) diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo.Abp.Ddd.Domain.csproj b/framework/src/Volo.Abp.Ddd.Domain/Volo.Abp.Ddd.Domain.csproj index 87879334af..e676f24313 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo.Abp.Ddd.Domain.csproj +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo.Abp.Ddd.Domain.csproj @@ -20,6 +20,7 @@ + diff --git a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs index 523a6ee4f2..0ebbe859bd 100644 --- a/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs +++ b/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/AbpDddDomainModule.cs @@ -6,6 +6,7 @@ using Volo.Abp.EventBus; using Volo.Abp.ExceptionHandling; using Volo.Abp.Guids; using Volo.Abp.Modularity; +using Volo.Abp.MultiLingualObject; using Volo.Abp.MultiTenancy; using Volo.Abp.ObjectMapping; using Volo.Abp.Specifications; @@ -25,6 +26,7 @@ namespace Volo.Abp.Domain typeof(AbpTimingModule), typeof(AbpUnitOfWorkModule), typeof(AbpObjectMappingModule), + typeof(AbpMultiLingualObjectModule), typeof(AbpExceptionHandlingModule), typeof(AbpSpecificationsModule) )] diff --git a/framework/src/Volo.Abp.MultiLingualObject/FodyWeavers.xml b/framework/src/Volo.Abp.MultiLingualObject/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/framework/src/Volo.Abp.MultiLingualObject/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.MultiLingualObject/FodyWeavers.xsd b/framework/src/Volo.Abp.MultiLingualObject/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.MultiLingualObject/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.MultiLingualObject/Volo.Abp.MultiLingualObject.csproj b/framework/src/Volo.Abp.MultiLingualObject/Volo.Abp.MultiLingualObject.csproj new file mode 100644 index 0000000000..d6df31e1ed --- /dev/null +++ b/framework/src/Volo.Abp.MultiLingualObject/Volo.Abp.MultiLingualObject.csproj @@ -0,0 +1,21 @@ + + + + + + + netstandard2.0 + Volo.Abp.MultiLingualObject + Volo.Abp.MultiLingualObject + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + diff --git a/framework/src/Volo.Abp.MultiLingualObject/Volo/Abp/MultiLingualObject/AbpMultiLingualObjectModule.cs b/framework/src/Volo.Abp.MultiLingualObject/Volo/Abp/MultiLingualObject/AbpMultiLingualObjectModule.cs new file mode 100644 index 0000000000..120cf7bac1 --- /dev/null +++ b/framework/src/Volo.Abp.MultiLingualObject/Volo/Abp/MultiLingualObject/AbpMultiLingualObjectModule.cs @@ -0,0 +1,11 @@ +using Volo.Abp.Localization; +using Volo.Abp.Modularity; + +namespace Volo.Abp.MultiLingualObject +{ + [DependsOn( + typeof(AbpLocalizationModule))] + public class AbpMultiLingualObjectModule : AbpModule + { + } +} diff --git a/framework/src/Volo.Abp.MultiLingualObject/Volo/Abp/MultiLingualObject/IHasMultiLingual.cs b/framework/src/Volo.Abp.MultiLingualObject/Volo/Abp/MultiLingualObject/IHasMultiLingual.cs new file mode 100644 index 0000000000..8ea05ecabd --- /dev/null +++ b/framework/src/Volo.Abp.MultiLingualObject/Volo/Abp/MultiLingualObject/IHasMultiLingual.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Volo.Abp.MultiLingualObject +{ + public interface IHasMultiLingual + where TTranslation : class, IMultiLingualTranslation + { + ICollection Translations { get; set; } + } +} diff --git a/framework/src/Volo.Abp.MultiLingualObject/Volo/Abp/MultiLingualObject/IMultiLingualObjectManager.cs b/framework/src/Volo.Abp.MultiLingualObject/Volo/Abp/MultiLingualObject/IMultiLingualObjectManager.cs new file mode 100644 index 0000000000..e6ddc840e5 --- /dev/null +++ b/framework/src/Volo.Abp.MultiLingualObject/Volo/Abp/MultiLingualObject/IMultiLingualObjectManager.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.MultiLingualObject +{ + public interface IMultiLingualObjectManager + { + TTranslation GetTranslation( + TMultiLingual multiLingual, + bool fallbackToParentCultures = true, + string culture = null) + where TMultiLingual : IHasMultiLingual + where TTranslation : class, IMultiLingualTranslation; + + Task GetTranslationAsync( + TMultiLingual multiLingual, + bool fallbackToParentCultures = true, + string culture = null) + where TMultiLingual : IHasMultiLingual + where TTranslation : class, IMultiLingualTranslation; + } +} diff --git a/framework/src/Volo.Abp.MultiLingualObject/Volo/Abp/MultiLingualObject/IMultiLingualTranslation.cs b/framework/src/Volo.Abp.MultiLingualObject/Volo/Abp/MultiLingualObject/IMultiLingualTranslation.cs new file mode 100644 index 0000000000..3676e08b44 --- /dev/null +++ b/framework/src/Volo.Abp.MultiLingualObject/Volo/Abp/MultiLingualObject/IMultiLingualTranslation.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.MultiLingualObject +{ + public interface IMultiLingualTranslation + { + string Language { get; set; } + } +} diff --git a/framework/src/Volo.Abp.MultiLingualObject/Volo/Abp/MultiLingualObject/MultiLingualObjectManager.cs b/framework/src/Volo.Abp.MultiLingualObject/Volo/Abp/MultiLingualObject/MultiLingualObjectManager.cs new file mode 100644 index 0000000000..dc6a82a28d --- /dev/null +++ b/framework/src/Volo.Abp.MultiLingualObject/Volo/Abp/MultiLingualObject/MultiLingualObjectManager.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Localization; +using Volo.Abp.Settings; +using Volo.Abp.Threading; + +namespace Volo.Abp.MultiLingualObject +{ + public class MultiLingualObjectManager : IMultiLingualObjectManager, ITransientDependency + { + protected ISettingProvider SettingProvider { get; } + + protected const int MaxCultureFallbackDepth = 5; + + public MultiLingualObjectManager(ISettingProvider settingProvider) + { + SettingProvider = settingProvider; + } + + public TTranslation GetTranslation( + TMultiLingual multiLingual, + bool fallbackToParentCultures = true, + string culture = null) + where TMultiLingual : IHasMultiLingual + where TTranslation : class, IMultiLingualTranslation + { + return AsyncHelper.RunSync(() => + GetTranslationAsync(multiLingual, fallbackToParentCultures, culture)); + } + + public virtual async Task GetTranslationAsync( + TMultiLingual multiLingual, + bool fallbackToParentCultures = true, + string culture = null) + where TMultiLingual : IHasMultiLingual + where TTranslation : class, IMultiLingualTranslation + { + culture ??= CultureInfo.CurrentUICulture.Name; + + if (multiLingual.Translations == null || !multiLingual.Translations.Any()) + { + return null; + } + + var translation = multiLingual.Translations.FirstOrDefault(pt => pt.Language == culture); + if (translation != null) + { + return translation; + } + + if (fallbackToParentCultures) + { + translation = + GeTranslationBasedOnCulturalRecursive( + CultureInfo.CurrentUICulture.Parent, multiLingual.Translations, 0); + if (translation != null) + { + return translation; + } + } + + var defaultLanguage = await SettingProvider.GetOrNullAsync(LocalizationSettingNames.DefaultLanguage); + + translation = multiLingual.Translations.FirstOrDefault(pt => pt.Language == defaultLanguage); + if (translation != null) + { + return translation; + } + + translation = multiLingual.Translations.FirstOrDefault(); + return translation; + } + + protected virtual TTranslation GeTranslationBasedOnCulturalRecursive( + CultureInfo culture, ICollection translations, int currentDepth) + where TTranslation : class, IMultiLingualTranslation + { + if (culture == null || culture.Name.IsNullOrWhiteSpace() || translations.IsNullOrEmpty() || + currentDepth > MaxCultureFallbackDepth) + { + return null; + } + + var translation = translations.FirstOrDefault(pt => + pt.Language.Equals(culture.Name, StringComparison.OrdinalIgnoreCase)); + return translation ?? + GeTranslationBasedOnCulturalRecursive(culture.Parent, + translations, currentDepth + 1); + } + } +} diff --git a/framework/test/Volo.Abp.AutoMapper.Tests/AutoMapper/AbpAutoMapperMultiLingualDto_Tests.cs b/framework/test/Volo.Abp.AutoMapper.Tests/AutoMapper/AbpAutoMapperMultiLingualDto_Tests.cs new file mode 100644 index 0000000000..0efb72625b --- /dev/null +++ b/framework/test/Volo.Abp.AutoMapper.Tests/AutoMapper/AbpAutoMapperMultiLingualDto_Tests.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp; +using Volo.Abp.AutoMapper; +using Volo.Abp.Localization; +using Volo.Abp.MultiLingualObject.TestObjects; +using Volo.Abp.Testing; +using Xunit; + +namespace AutoMapper +{ + public class AbpAutoMapperMultiLingualDto_Tests : AbpIntegratedTest + { + private readonly Volo.Abp.ObjectMapping.IObjectMapper _objectMapper; + private readonly MultiLingualBook _book; + + public AbpAutoMapperMultiLingualDto_Tests() + { + _objectMapper = ServiceProvider.GetRequiredService(); + + var id = Guid.NewGuid(); + _book = new MultiLingualBook(id, 100) + { + Translations = new List() + }; + + var en = new MultiLingualBookTranslation + { + Language = "en", + Name = "C# in Depth", + }; + var zh = new MultiLingualBookTranslation + { + Language = "zh-Hans", + Name = "深入理解C#", + }; + + _book.Translations.Add(en); + _book.Translations.Add(zh); + } + + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + + [Fact] + public void Should_Map_Current_UI_Culture() + { + using (CultureHelper.Use("zh-Hans")) + { + var bookDto = _objectMapper.Map(_book); + + bookDto.Name.ShouldBe("深入理解C#"); + bookDto.Price.ShouldBe(_book.Price); + bookDto.Id.ShouldBe(_book.Id); + } + } + + [Fact] + public void Should_Map_Fallback_UI_Culture() + { + using (CultureHelper.Use("en-us")) + { + var bookDto = _objectMapper.Map(_book); + + bookDto.Name.ShouldBe("C# in Depth"); + bookDto.Price.ShouldBe(_book.Price); + bookDto.Id.ShouldBe(_book.Id); + } + } + + [Fact] + public void Should_Map_Default_Language() + { + using (CultureHelper.Use("tr")) + { + var bookDto = _objectMapper.Map(_book); + + bookDto.Name.ShouldBe("C# in Depth"); + bookDto.Price.ShouldBe(_book.Price); + bookDto.Id.ShouldBe(_book.Id); + } + } + + [Fact] + public void NoTranslations_ShouldStillMapObject() + { + _book.Translations.Clear(); + + using (CultureHelper.Use("tr")) + { + var bookDto = _objectMapper.Map(_book); + + bookDto.Name.ShouldBeNull(); + bookDto.Price.ShouldBe(_book.Price); + bookDto.Id.ShouldBe(_book.Id); + } + } + } + + public class BookProfile : Profile + { + public BookProfile() + { + var mapResult = this.CreateMultiLingualMap(); + + mapResult.EntityMap.ValidateMemberList(MemberList.None); + mapResult.TranslateMap.ValidateMemberList(MemberList.None); + } + } +} diff --git a/framework/test/Volo.Abp.AutoMapper.Tests/Volo.Abp.AutoMapper.Tests.csproj b/framework/test/Volo.Abp.AutoMapper.Tests/Volo.Abp.AutoMapper.Tests.csproj index 1f11e8fc34..37ffca1657 100644 --- a/framework/test/Volo.Abp.AutoMapper.Tests/Volo.Abp.AutoMapper.Tests.csproj +++ b/framework/test/Volo.Abp.AutoMapper.Tests/Volo.Abp.AutoMapper.Tests.csproj @@ -11,8 +11,9 @@ - + + diff --git a/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapperTestModule.cs b/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapperTestModule.cs index 44331522a7..6e9fb10bf2 100644 --- a/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapperTestModule.cs +++ b/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapperTestModule.cs @@ -1,12 +1,14 @@ using Volo.Abp.Modularity; +using Volo.Abp.MultiLingualObject; using Volo.Abp.ObjectExtending; namespace Volo.Abp.AutoMapper { [DependsOn( typeof(AbpAutoMapperModule), - typeof(AbpObjectExtendingTestModule) - )] + typeof(AbpObjectExtendingTestModule), + typeof(AbpMultiLingualObjectModule) + )] public class AutoMapperTestModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) @@ -17,4 +19,4 @@ namespace Volo.Abp.AutoMapper }); } } -} \ No newline at end of file +} diff --git a/framework/test/Volo.Abp.MultiLingualObject.Tests/Volo.Abp.MultiLingualObject.Tests.csproj b/framework/test/Volo.Abp.MultiLingualObject.Tests/Volo.Abp.MultiLingualObject.Tests.csproj new file mode 100644 index 0000000000..52d2f7ac2f --- /dev/null +++ b/framework/test/Volo.Abp.MultiLingualObject.Tests/Volo.Abp.MultiLingualObject.Tests.csproj @@ -0,0 +1,18 @@ + + + + + + net5.0 + + + + + + + + + + + + diff --git a/framework/test/Volo.Abp.MultiLingualObject.Tests/Volo/Abp/MultiLingualObject/AbpMultiLingualObjectTestModule.cs b/framework/test/Volo.Abp.MultiLingualObject.Tests/Volo/Abp/MultiLingualObject/AbpMultiLingualObjectTestModule.cs new file mode 100644 index 0000000000..f8f79a3065 --- /dev/null +++ b/framework/test/Volo.Abp.MultiLingualObject.Tests/Volo/Abp/MultiLingualObject/AbpMultiLingualObjectTestModule.cs @@ -0,0 +1,21 @@ +using Autofac.Extensions.DependencyInjection; +using Volo.Abp.Autofac; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.ObjectMapping; +using Volo.Abp.Settings; + +namespace Volo.Abp.MultiLingualObject +{ + [DependsOn( + typeof(AbpAutofacModule), + typeof(AbpLocalizationModule), + typeof(AbpSettingsModule), + typeof(AbpObjectMappingModule), + typeof(AbpMultiLingualObjectModule), + typeof(AbpTestBaseModule) + )] + public class AbpMultiLingualObjectTestModule : AbpModule + { + } +} diff --git a/framework/test/Volo.Abp.MultiLingualObject.Tests/Volo/Abp/MultiLingualObject/MultiLingualObjectManager_Tests.cs b/framework/test/Volo.Abp.MultiLingualObject.Tests/Volo/Abp/MultiLingualObject/MultiLingualObjectManager_Tests.cs new file mode 100644 index 0000000000..269bf91b54 --- /dev/null +++ b/framework/test/Volo.Abp.MultiLingualObject.Tests/Volo/Abp/MultiLingualObject/MultiLingualObjectManager_Tests.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Localization; +using Volo.Abp.MultiLingualObject.TestObjects; +using Volo.Abp.Testing; +using Xunit; + +namespace Volo.Abp.MultiLingualObject +{ + public class MultiLingualObjectManager_Tests : AbpIntegratedTest + { + private readonly IMultiLingualObjectManager _multiLingualObjectManager; + private readonly MultiLingualBook _book; + + public MultiLingualObjectManager_Tests() + { + _multiLingualObjectManager = ServiceProvider.GetRequiredService(); + + var id = Guid.NewGuid(); + _book = new MultiLingualBook(id, 100) + { + Translations = new List() + }; + + var en = new MultiLingualBookTranslation + { + Language = "en", + Name = "C# in Depth", + }; + var zh = new MultiLingualBookTranslation + { + Language = "zh-Hans", + Name = "深入理解C#", + }; + + _book.Translations.Add(en); + _book.Translations.Add(zh); + } + + [Fact] + public void GetTranslation() + { + using (CultureHelper.Use("en-us")) + { + var translation = _multiLingualObjectManager.GetTranslation(_book); + + translation.Name.ShouldBe("C# in Depth"); + } + } + + [Fact] + public async Task GetTranslationAsync() + { + using (CultureHelper.Use("en-us")) + { + var translation = await _multiLingualObjectManager.GetTranslationAsync(_book); + + translation.Name.ShouldBe("C# in Depth"); + } + } + + [Fact] + public async Task Should_Get_Specified_Language() + { + using (CultureHelper.Use("zh-Hans")) + { + var translation = await _multiLingualObjectManager.GetTranslationAsync(_book, culture: "en"); + + translation.Name.ShouldBe("C# in Depth"); + } + } + } +} diff --git a/framework/test/Volo.Abp.MultiLingualObject.Tests/Volo/Abp/MultiLingualObject/MultiLingualObject_Tests.cs b/framework/test/Volo.Abp.MultiLingualObject.Tests/Volo/Abp/MultiLingualObject/MultiLingualObject_Tests.cs new file mode 100644 index 0000000000..917a11a2a8 --- /dev/null +++ b/framework/test/Volo.Abp.MultiLingualObject.Tests/Volo/Abp/MultiLingualObject/MultiLingualObject_Tests.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Localization; +using Volo.Abp.MultiLingualObject.TestObjects; +using Volo.Abp.ObjectMapping; +using Volo.Abp.Testing; +using Xunit; + +namespace Volo.Abp.MultiLingualObject +{ + public class MultiLingualObject_Tests : AbpIntegratedTest + { + private readonly IObjectMapper _objectMapper; + private readonly MultiLingualBook _book; + + public MultiLingualObject_Tests() + { + _objectMapper = ServiceProvider.GetRequiredService(); + + var id = Guid.NewGuid(); + _book = new MultiLingualBook(id, 100) + { + Translations = new List() + }; + + var en = new MultiLingualBookTranslation + { + Language = "en", + Name = "C# in Depth", + }; + var zh = new MultiLingualBookTranslation + { + Language = "zh-Hans", + Name = "深入理解C#", + }; + + _book.Translations.Add(en); + _book.Translations.Add(zh); + } + + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + + [Fact] + public void Should_Map_Current_UI_Culture() + { + using (CultureHelper.Use("zh-Hans")) + { + var bookDto = _objectMapper.Map(_book); + + bookDto.Name.ShouldBe("深入理解C#"); + bookDto.Price.ShouldBe(_book.Price); + bookDto.Id.ShouldBe(_book.Id); + } + } + + [Fact] + public void Should_Map_Fallback_UI_Culture() + { + using (CultureHelper.Use("en-us")) + { + var bookDto = _objectMapper.Map(_book); + + bookDto.Name.ShouldBe("C# in Depth"); + bookDto.Price.ShouldBe(_book.Price); + bookDto.Id.ShouldBe(_book.Id); + } + } + + [Fact] + public void Should_Map_Default_Language() + { + using (CultureHelper.Use("tr")) + { + var bookDto = _objectMapper.Map(_book); + + bookDto.Name.ShouldBe("C# in Depth"); + bookDto.Price.ShouldBe(_book.Price); + bookDto.Id.ShouldBe(_book.Id); + } + } + + [Fact] + public void NoTranslations_ShouldStillMapObject() + { + _book.Translations.Clear(); + + using (CultureHelper.Use("tr")) + { + var bookDto = _objectMapper.Map(_book); + + bookDto.Name.ShouldBeNull(); + bookDto.Price.ShouldBe(_book.Price); + bookDto.Id.ShouldBe(_book.Id); + } + } + } + + public class MultiLingualBookObjectMapper : IObjectMapper, + ITransientDependency + { + private readonly IMultiLingualObjectManager _multiLingualObjectManager; + + public MultiLingualBookObjectMapper(IMultiLingualObjectManager multiLingualObjectManager) + { + _multiLingualObjectManager = multiLingualObjectManager; + } + + public MultiLingualBookDto Map(MultiLingualBook source) + { + var translation = + _multiLingualObjectManager.GetTranslation(source); + + return new MultiLingualBookDto + { + Price = source.Price, + Id = source.Id, + Name = translation?.Name + }; + } + + public MultiLingualBookDto Map(MultiLingualBook source, MultiLingualBookDto destination) + { + return default; + } + } +} diff --git a/framework/test/Volo.Abp.MultiLingualObject.Tests/Volo/Abp/MultiLingualObject/TestObjects/MultiLingualBook.cs b/framework/test/Volo.Abp.MultiLingualObject.Tests/Volo/Abp/MultiLingualObject/TestObjects/MultiLingualBook.cs new file mode 100644 index 0000000000..d12022a44b --- /dev/null +++ b/framework/test/Volo.Abp.MultiLingualObject.Tests/Volo/Abp/MultiLingualObject/TestObjects/MultiLingualBook.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.MultiLingualObject.TestObjects +{ + public class MultiLingualBook : IHasMultiLingual + { + public MultiLingualBook(Guid id, decimal price) + { + Id = id; + Price = price; + } + + public Guid Id { get; } + + public decimal Price { get; set; } + + public ICollection Translations { get; set; } + } +} diff --git a/framework/test/Volo.Abp.MultiLingualObject.Tests/Volo/Abp/MultiLingualObject/TestObjects/MultiLingualBookDto.cs b/framework/test/Volo.Abp.MultiLingualObject.Tests/Volo/Abp/MultiLingualObject/TestObjects/MultiLingualBookDto.cs new file mode 100644 index 0000000000..8814f9a855 --- /dev/null +++ b/framework/test/Volo.Abp.MultiLingualObject.Tests/Volo/Abp/MultiLingualObject/TestObjects/MultiLingualBookDto.cs @@ -0,0 +1,13 @@ +using System; + +namespace Volo.Abp.MultiLingualObject.TestObjects +{ + public class MultiLingualBookDto + { + public Guid Id { get; set; } + + public string Name { get; set; } + + public decimal Price { get; set; } + } +} diff --git a/framework/test/Volo.Abp.MultiLingualObject.Tests/Volo/Abp/MultiLingualObject/TestObjects/MultiLingualBookTranslation.cs b/framework/test/Volo.Abp.MultiLingualObject.Tests/Volo/Abp/MultiLingualObject/TestObjects/MultiLingualBookTranslation.cs new file mode 100644 index 0000000000..5f35554078 --- /dev/null +++ b/framework/test/Volo.Abp.MultiLingualObject.Tests/Volo/Abp/MultiLingualObject/TestObjects/MultiLingualBookTranslation.cs @@ -0,0 +1,11 @@ +using System; + +namespace Volo.Abp.MultiLingualObject.TestObjects +{ + public class MultiLingualBookTranslation : IMultiLingualTranslation + { + public string Name { get; set; } + + public string Language { get; set; } + } +} diff --git a/nupkg/common.ps1 b/nupkg/common.ps1 index 1b10d4f632..4597ead3fb 100644 --- a/nupkg/common.ps1 +++ b/nupkg/common.ps1 @@ -146,6 +146,7 @@ $projects = ( "framework/src/Volo.Abp.Validation", "framework/src/Volo.Abp.VirtualFileSystem", "framework/src/Volo.Abp.Kafka", + "framework/src/Volo.Abp.MultiLingualObject", "framework/src/Volo.Abp.Swashbuckle", # modules/account