mirror of https://github.com/abpframework/abp.git
25 changed files with 464 additions and 172 deletions
@ -1,99 +1,41 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
using Volo.Abp.Domain.Entities; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.MultiLingualObject; |
|||
using Volo.Abp.Settings; |
|||
using Volo.Abp.Threading; |
|||
|
|||
namespace AutoMapper |
|||
{ |
|||
public static class AbpAutoMapperMultiLingualDtoExtensions |
|||
{ |
|||
public static CreateMultiLingualMapResult<TMultiLingualEntity, TTranslation, TDestination> CreateMultLingualMap< |
|||
TMultiLingualEntity, |
|||
public static CreateMultiLingualMapResult<TSource, TTranslation, TDestination> MapMultiLingual<TSource, |
|||
TTranslation, TDestination>( |
|||
this Profile profile, |
|||
ISettingProvider serviceProvider, |
|||
this IMappingExpression<TSource, TDestination> mappingExpression, |
|||
Profile profile, |
|||
ISettingProvider provider, |
|||
bool fallbackToParentCultures = false) |
|||
where TMultiLingualEntity : class, IMultiLingualEntity<TTranslation> |
|||
where TTranslation : class, IEntityTranslation |
|||
where TDestination : class |
|||
where TSource : IHasMultiLingual<TTranslation> |
|||
where TTranslation : class, IMultiLingualTranslation |
|||
{ |
|||
return new CreateMultiLingualMapResult<TMultiLingualEntity, TTranslation, TDestination> |
|||
return new CreateMultiLingualMapResult<TSource, TTranslation, TDestination> |
|||
{ |
|||
TranslationMap = profile.CreateMap<TTranslation, TDestination>(), |
|||
EntityMap = profile.CreateMap<TMultiLingualEntity, TDestination>().BeforeMap( |
|||
(source, destination, context) => |
|||
{ |
|||
if (source.Translations == null || !source.Translations.Any()) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var translation = |
|||
source.Translations.FirstOrDefault(pt => pt.Language == CultureInfo.CurrentUICulture.Name); |
|||
if (translation != null) |
|||
{ |
|||
context.Mapper.Map(translation, destination); |
|||
return; |
|||
} |
|||
|
|||
if (fallbackToParentCultures) |
|||
{ |
|||
translation = |
|||
GeTranslationBasedOnCulturalRecursive<TMultiLingualEntity, TTranslation>( |
|||
CultureInfo.CurrentUICulture.Parent, source.Translations, 0); |
|||
if (translation != null) |
|||
{ |
|||
context.Mapper.Map(translation, destination); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
var defaultLanguage = AsyncHelper.RunSync(() => |
|||
serviceProvider.GetOrNullAsync(LocalizationSettingNames.DefaultLanguage)); |
|||
EntityMap = mappingExpression.BeforeMap((source, destination, context) => |
|||
{ |
|||
var translation = source.GetMultiLingualTranslation(provider, fallbackToParentCultures); |
|||
|
|||
translation = source.Translations.FirstOrDefault(pt => pt.Language == defaultLanguage); |
|||
if (translation != null) |
|||
{ |
|||
context.Mapper.Map(translation, destination); |
|||
return; |
|||
} |
|||
|
|||
translation = source.Translations.FirstOrDefault(); |
|||
if (translation != null) |
|||
{ |
|||
context.Mapper.Map(translation, destination); |
|||
} |
|||
}) |
|||
if (translation != null) |
|||
{ |
|||
context.Mapper.Map(translation, destination); |
|||
} |
|||
}) |
|||
}; |
|||
} |
|||
|
|||
private const int MaxCultureFallbackDepth = 5; |
|||
|
|||
private static TTranslation GeTranslationBasedOnCulturalRecursive<TMultiLingualEntity, TTranslation>( |
|||
CultureInfo culture, ICollection<TTranslation> translations, int currentDepth) |
|||
where TTranslation : class, IEntityTranslation |
|||
{ |
|||
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<TMultiLingualEntity, TTranslation>(culture.Parent, |
|||
translations, currentDepth + 1); |
|||
} |
|||
} |
|||
|
|||
public class CreateMultiLingualMapResult<TMultiLingualEntity, TTranslation, TDestination> |
|||
public class CreateMultiLingualMapResult<TMultiLingual, TTranslation, TDestination> |
|||
{ |
|||
public IMappingExpression<TTranslation, TDestination> TranslationMap { get; set; } |
|||
|
|||
public IMappingExpression<TMultiLingualEntity, TDestination> EntityMap { get; set; } |
|||
public IMappingExpression<TMultiLingual, TDestination> EntityMap { get; set; } |
|||
} |
|||
} |
|||
|
|||
@ -1,21 +0,0 @@ |
|||
namespace Volo.Abp.Domain.Entities |
|||
{ |
|||
public interface IEntityTranslation |
|||
{ |
|||
string Language { get; set; } |
|||
} |
|||
|
|||
public interface IEntityTranslation<TEntity, TPrimaryKeyOfMultiLingualEntity> : IEntityTranslation |
|||
where TEntity : IEntity<TPrimaryKeyOfMultiLingualEntity> |
|||
{ |
|||
TEntity Core { get; set; } |
|||
|
|||
TPrimaryKeyOfMultiLingualEntity CoreId { get; set; } |
|||
} |
|||
|
|||
public interface IEntityTranslation<TEntity>: IEntityTranslation<TEntity, int> |
|||
where TEntity : IEntity<int> |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -1,10 +0,0 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.Domain.Entities |
|||
{ |
|||
public interface IMultiLingualEntity<TTranslation> |
|||
where TTranslation : class, IEntityTranslation |
|||
{ |
|||
ICollection<TTranslation> Translations { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,15 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\Volo.Abp.Localization\Volo.Abp.Localization.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,11 @@ |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.MultiLingualObject |
|||
{ |
|||
[DependsOn( |
|||
typeof(AbpLocalizationModule))] |
|||
public class AbpMultiLingualObjectModule : AbpModule |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.MultiLingualObject |
|||
{ |
|||
public interface IHasMultiLingual<TTranslation> |
|||
where TTranslation : class, IMultiLingualTranslation |
|||
{ |
|||
ICollection<TTranslation> Translations { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
namespace Volo.Abp.MultiLingualObject |
|||
{ |
|||
public interface IMultiLingualTranslation |
|||
{ |
|||
string Language { get; set; } |
|||
} |
|||
|
|||
public interface IMultiLingualTranslation<T, TPrimaryKeyOfMultiLingualObject> : IMultiLingualTranslation |
|||
where T : class |
|||
{ |
|||
T Core { get; set; } |
|||
|
|||
TPrimaryKeyOfMultiLingualObject CoreId { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,74 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Settings; |
|||
using Volo.Abp.Threading; |
|||
|
|||
namespace Volo.Abp.MultiLingualObject |
|||
{ |
|||
public static class MultiLingualObjectExtensions |
|||
{ |
|||
public static TTranslation GetMultiLingualTranslation<TTranslation>( |
|||
this IHasMultiLingual<TTranslation> hasMultiLingual, |
|||
ISettingProvider settingProvider, |
|||
bool fallbackToParentCultures = false) |
|||
where TTranslation : class, IMultiLingualTranslation |
|||
{ |
|||
if (hasMultiLingual.Translations == null || !hasMultiLingual.Translations.Any()) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
var translation = |
|||
hasMultiLingual.Translations.FirstOrDefault(pt => pt.Language == CultureInfo.CurrentUICulture.Name); |
|||
if (translation != null) |
|||
{ |
|||
return translation; |
|||
} |
|||
|
|||
if (fallbackToParentCultures) |
|||
{ |
|||
translation = |
|||
GeTranslationBasedOnCulturalRecursive( |
|||
CultureInfo.CurrentUICulture.Parent, hasMultiLingual.Translations, 0); |
|||
if (translation != null) |
|||
{ |
|||
return translation; |
|||
} |
|||
} |
|||
|
|||
var defaultLanguage = AsyncHelper.RunSync(() => |
|||
settingProvider.GetOrNullAsync(LocalizationSettingNames.DefaultLanguage)); |
|||
|
|||
translation = hasMultiLingual.Translations.FirstOrDefault(pt => pt.Language == defaultLanguage); |
|||
if (translation != null) |
|||
{ |
|||
return translation; |
|||
} |
|||
|
|||
translation = hasMultiLingual.Translations.FirstOrDefault(); |
|||
return translation; |
|||
} |
|||
|
|||
private const int MaxCultureFallbackDepth = 5; |
|||
|
|||
private static TTranslation GeTranslationBasedOnCulturalRecursive<TTranslation>( |
|||
CultureInfo culture, ICollection<TTranslation> 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); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.test.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netcoreapp3.1</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" /> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.MultiLingualObject\Volo.Abp.MultiLingualObject.csproj" /> |
|||
<ProjectReference Include="..\..\src\Volo.Abp.ObjectMapping\Volo.Abp.ObjectMapping.csproj" /> |
|||
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" /> |
|||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,25 @@ |
|||
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 |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
base.ConfigureServices(context); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,117 @@ |
|||
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.Settings; |
|||
using Volo.Abp.Testing; |
|||
using Xunit; |
|||
|
|||
namespace Volo.Abp.MultiLingualObject |
|||
{ |
|||
public class MultiLingualObject_Tests : AbpIntegratedTest<AbpMultiLingualObjectTestModule> |
|||
{ |
|||
private readonly IObjectMapper<MultiLingualBook, MultiLingualBookDto> _objectMapper; |
|||
private readonly MultiLingualBook _book; |
|||
|
|||
public MultiLingualObject_Tests() |
|||
{ |
|||
_objectMapper = ServiceProvider.GetRequiredService<IObjectMapper<MultiLingualBook, MultiLingualBookDto>>(); |
|||
|
|||
var id = Guid.NewGuid(); |
|||
_book = new MultiLingualBook(id, 100) |
|||
{ |
|||
Translations = new List<MultiLingualBookTranslation> |
|||
{ |
|||
new MultiLingualBookTranslation |
|||
{ |
|||
CoreId = id, |
|||
Language = "en", |
|||
Name = "C# in Depth" |
|||
}, |
|||
new MultiLingualBookTranslation |
|||
{ |
|||
CoreId = id, |
|||
Language = "zh-Hans", |
|||
Name = "深入理解C#" |
|||
} |
|||
} |
|||
}; |
|||
} |
|||
|
|||
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); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public class MultiLingualBookObjectMapper : IObjectMapper<MultiLingualBook, MultiLingualBookDto>, |
|||
ITransientDependency |
|||
{ |
|||
private readonly ISettingProvider _settingProvider; |
|||
|
|||
public MultiLingualBookObjectMapper(ISettingProvider settingProvider) |
|||
{ |
|||
_settingProvider = settingProvider; |
|||
} |
|||
|
|||
public MultiLingualBookDto Map(MultiLingualBook source) |
|||
{ |
|||
var translation = source.GetMultiLingualTranslation(_settingProvider, true); |
|||
|
|||
return new MultiLingualBookDto |
|||
{ |
|||
Id = source.Id, |
|||
Name = translation.Name, |
|||
Price = source.Price |
|||
}; |
|||
} |
|||
|
|||
public MultiLingualBookDto Map(MultiLingualBook source, MultiLingualBookDto destination) |
|||
{ |
|||
return default; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.MultiLingualObject.TestObjects |
|||
{ |
|||
public class MultiLingualBook : IHasMultiLingual<MultiLingualBookTranslation> |
|||
{ |
|||
public MultiLingualBook(Guid id, decimal price) |
|||
{ |
|||
Id = id; |
|||
Price = price; |
|||
} |
|||
|
|||
public Guid Id { get; } |
|||
|
|||
public decimal Price { get; set; } |
|||
|
|||
public ICollection<MultiLingualBookTranslation> Translations { get; set; } |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System; |
|||
|
|||
namespace Volo.Abp.MultiLingualObject.TestObjects |
|||
{ |
|||
public class MultiLingualBookTranslation : IMultiLingualTranslation<MultiLingualBook, Guid> |
|||
{ |
|||
public string Name { get; set; } |
|||
|
|||
public string Language { get; set; } |
|||
|
|||
public MultiLingualBook Core { get; set; } |
|||
|
|||
public Guid CoreId { get; set; } |
|||
} |
|||
} |
|||
Loading…
Reference in new issue