mirror of https://github.com/abpframework/abp.git
25 changed files with 464 additions and 172 deletions
@ -1,99 +1,41 @@ |
|||||
using System; |
using Volo.Abp.MultiLingualObject; |
||||
using System.Collections.Generic; |
|
||||
using System.Globalization; |
|
||||
using System.Linq; |
|
||||
using Volo.Abp.Domain.Entities; |
|
||||
using Volo.Abp.Localization; |
|
||||
using Volo.Abp.Settings; |
using Volo.Abp.Settings; |
||||
using Volo.Abp.Threading; |
|
||||
|
|
||||
namespace AutoMapper |
namespace AutoMapper |
||||
{ |
{ |
||||
public static class AbpAutoMapperMultiLingualDtoExtensions |
public static class AbpAutoMapperMultiLingualDtoExtensions |
||||
{ |
{ |
||||
public static CreateMultiLingualMapResult<TMultiLingualEntity, TTranslation, TDestination> CreateMultLingualMap< |
public static CreateMultiLingualMapResult<TSource, TTranslation, TDestination> MapMultiLingual<TSource, |
||||
TMultiLingualEntity, |
|
||||
TTranslation, TDestination>( |
TTranslation, TDestination>( |
||||
this Profile profile, |
this IMappingExpression<TSource, TDestination> mappingExpression, |
||||
ISettingProvider serviceProvider, |
Profile profile, |
||||
|
ISettingProvider provider, |
||||
bool fallbackToParentCultures = false) |
bool fallbackToParentCultures = false) |
||||
where TMultiLingualEntity : class, IMultiLingualEntity<TTranslation> |
where TDestination : class |
||||
where TTranslation : class, IEntityTranslation |
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>(), |
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(() => |
EntityMap = mappingExpression.BeforeMap((source, destination, context) => |
||||
serviceProvider.GetOrNullAsync(LocalizationSettingNames.DefaultLanguage)); |
{ |
||||
|
var translation = source.GetMultiLingualTranslation(provider, fallbackToParentCultures); |
||||
|
|
||||
translation = source.Translations.FirstOrDefault(pt => pt.Language == defaultLanguage); |
if (translation != null) |
||||
if (translation != null) |
{ |
||||
{ |
context.Mapper.Map(translation, destination); |
||||
context.Mapper.Map(translation, destination); |
} |
||||
return; |
}) |
||||
} |
|
||||
|
|
||||
translation = source.Translations.FirstOrDefault(); |
|
||||
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<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