Browse Source

Merge pull request #14837 from Bdaya-Dev/bulk-multi-lingual-object-manager

Add Support for Bulk Translations in IMultilingualObjectManager
pull/15891/head
liangshiwei 3 years ago
committed by GitHub
parent
commit
0f53e70c6a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 41
      framework/src/Volo.Abp.MultiLingualObjects/Volo.Abp.MultiLingualObjects.csproj
  2. 24
      framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/IMultiLingualObjectManager.cs
  3. 123
      framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager.cs
  4. 27
      framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo.Abp.MultiLingualObjects.Tests.csproj
  5. 53
      framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/AbpMultiLingualObjectsTestModule.cs
  6. 135
      framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs
  7. 23
      framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectTestProfile.cs
  8. 38
      framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBook.cs
  9. 24
      framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookDto.cs
  10. 16
      framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookTranslation.cs

41
framework/src/Volo.Abp.MultiLingualObjects/Volo.Abp.MultiLingualObjects.csproj

@ -1,20 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>Volo.Abp.MultiLingualObject</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Localization\Volo.Abp.Localization.csproj" />
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>Volo.Abp.MultiLingualObject</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Localization\Volo.Abp.Localization.csproj" />
</ItemGroup>
</Project>

24
framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/IMultiLingualObjectManager.cs

@ -5,16 +5,30 @@ namespace Volo.Abp.MultiLingualObjects;
public interface IMultiLingualObjectManager
{
Task<TTranslation> GetTranslationAsync<TMultiLingual, TTranslation>(
Task<TTranslation?> GetTranslationAsync<TMultiLingual, TTranslation>(
TMultiLingual multiLingual,
string culture = null,
string? culture = null,
bool fallbackToParentCultures = true)
where TMultiLingual : IMultiLingualObject<TTranslation>
where TTranslation : class, IObjectTranslation;
Task<TTranslation> GetTranslationAsync<TTranslation>(
ICollection<TTranslation> translations,
string culture = null,
Task<TTranslation?> GetTranslationAsync<TTranslation>(
IEnumerable<TTranslation> translations,
string? culture = null,
bool fallbackToParentCultures = true)
where TTranslation : class, IObjectTranslation;
Task<List<TTranslation?>> GetBulkTranslationsAsync<TTranslation>(
IEnumerable<IEnumerable<TTranslation>> translationsCombined,
string? culture = null,
bool fallbackToParentCultures = true)
where TTranslation : class, IObjectTranslation;
Task<List<(TMultiLingual entity, TTranslation? translation)>> GetBulkTranslationsAsync<TMultiLingual, TTranslation>(
IEnumerable<TMultiLingual> multiLinguals,
string? culture = null,
bool fallbackToParentCultures = true)
where TMultiLingual : IMultiLingualObject<TTranslation>
where TTranslation : class, IObjectTranslation;
}

123
framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager.cs

@ -19,16 +19,16 @@ public class MultiLingualObjectManager : IMultiLingualObjectManager, ITransientD
{
SettingProvider = settingProvider;
}
public virtual async Task<TTranslation> GetTranslationAsync<TTranslation>(
ICollection<TTranslation> translations,
string culture,
public virtual async Task<TTranslation?> GetTranslationAsync<TTranslation>(
IEnumerable<TTranslation> translations,
string? culture,
bool fallbackToParentCultures)
where TTranslation : class, IObjectTranslation
{
culture ??= CultureInfo.CurrentUICulture.Name;
if (translations.IsNullOrEmpty())
if (translations == null || !translations.Any())
{
return null;
}
@ -65,9 +65,9 @@ public class MultiLingualObjectManager : IMultiLingualObjectManager, ITransientD
return translation;
}
public virtual Task<TTranslation> GetTranslationAsync<TMultiLingual, TTranslation>(
public virtual Task<TTranslation?> GetTranslationAsync<TMultiLingual, TTranslation>(
TMultiLingual multiLingual,
string culture = null,
string? culture = null,
bool fallbackToParentCultures = true)
where TMultiLingual : IMultiLingualObject<TTranslation>
where TTranslation : class, IObjectTranslation
@ -75,13 +75,13 @@ public class MultiLingualObjectManager : IMultiLingualObjectManager, ITransientD
return GetTranslationAsync(multiLingual.Translations, culture: culture, fallbackToParentCultures: fallbackToParentCultures);
}
protected virtual TTranslation GetTranslationBasedOnCulturalRecursive<TTranslation>(
CultureInfo culture, ICollection<TTranslation> translations, int currentDepth)
protected virtual TTranslation? GetTranslationBasedOnCulturalRecursive<TTranslation>(
CultureInfo culture, IEnumerable<TTranslation> translations, int currentDepth)
where TTranslation : class, IObjectTranslation
{
if (culture == null ||
culture.Name.IsNullOrWhiteSpace() ||
translations.IsNullOrEmpty() ||
translations == null || !translations.Any() ||
currentDepth > MaxCultureFallbackDepth)
{
return null;
@ -89,5 +89,108 @@ public class MultiLingualObjectManager : IMultiLingualObjectManager, ITransientD
var translation = translations.FirstOrDefault(pt => pt.Language.Equals(culture.Name, StringComparison.OrdinalIgnoreCase));
return translation ?? GetTranslationBasedOnCulturalRecursive(culture.Parent, translations, currentDepth + 1);
}
}
public virtual async Task<List<TTranslation?>> GetBulkTranslationsAsync<TTranslation>(IEnumerable<IEnumerable<TTranslation>> translationsCombined, string? culture, bool fallbackToParentCultures)
where TTranslation : class, IObjectTranslation
{
culture ??= CultureInfo.CurrentUICulture.Name;
if (translationsCombined == null || !translationsCombined.Any())
{
return new();
}
var someHaveNoTranslations = false;
var res = new List<TTranslation?>();
foreach (var translations in translationsCombined)
{
if (!translations.Any())
{
//if the src has no translations, don't try to find a translation
res.Add(null);
continue;
}
var translation = translations.FirstOrDefault(pt => pt.Language == culture);
if (translation != null)
{
res.Add(translation);
}
else
{
if (fallbackToParentCultures)
{
translation = GetTranslationBasedOnCulturalRecursive(
CultureInfo.CurrentUICulture.Parent,
translations,
0
);
if (translation != null)
{
res.Add(translation);
}
else
{
res.Add(null);
someHaveNoTranslations = true;
}
}
else
{
res.Add(null);
someHaveNoTranslations = true;
}
}
}
if (someHaveNoTranslations)
{
var defaultLanguage = await SettingProvider.GetOrNullAsync(LocalizationSettingNames.DefaultLanguage);
var index = 0;
foreach (var translations in translationsCombined)
{
if (!translations.Any())
{
//don't try to find a translation
}
else
{
var translation = res[index];
if (translation != null)
{
continue;
}
translation = translations.FirstOrDefault(pt => pt.Language == defaultLanguage);
if (translation != null)
{
res[index] = translation;
}
else
{
res[index] = translations.FirstOrDefault();
}
}
index++;
}
}
return res;
}
public virtual async Task<List<(TMultiLingual entity, TTranslation? translation)>> GetBulkTranslationsAsync<TMultiLingual, TTranslation>(IEnumerable<TMultiLingual> multiLinguals, string? culture, bool fallbackToParentCultures)
where TMultiLingual : IMultiLingualObject<TTranslation>
where TTranslation : class, IObjectTranslation
{
var resInitial = await GetBulkTranslationsAsync(multiLinguals.Select(x => x.Translations), culture, fallbackToParentCultures);
var index = 0;
var res = new List<(TMultiLingual entity, TTranslation? translation)>();
foreach (var item in multiLinguals)
{
var t = resInitial[index++];
res.Add((item, t));
}
return res;
}
}

27
framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo.Abp.MultiLingualObjects.Tests.csproj

@ -1,18 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.test.props" />
<Import Project="..\..\..\common.test.props" />
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace />
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace />
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.MultiLingualObjects\Volo.Abp.MultiLingualObjects.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.ObjectMapping\Volo.Abp.ObjectMapping.csproj" />
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.MultiLingualObjects\Volo.Abp.MultiLingualObjects.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.ObjectMapping\Volo.Abp.ObjectMapping.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.AutoMapper\Volo.Abp.AutoMapper.csproj" />
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
</ItemGroup>
</Project>

53
framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/AbpMultiLingualObjectsTestModule.cs

@ -1,19 +1,34 @@
using Volo.Abp.Autofac;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Settings;
namespace Volo.Abp.MultiLingualObjects;
[DependsOn(
typeof(AbpAutofacModule),
typeof(AbpLocalizationModule),
typeof(AbpSettingsModule),
typeof(AbpObjectMappingModule),
typeof(AbpMultiLingualObjectsModule),
typeof(AbpTestBaseModule)
)]
public class AbpMultiLingualObjectsTestModule : AbpModule
{
}
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Autofac;
using Volo.Abp.AutoMapper;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Settings;
namespace Volo.Abp.MultiLingualObjects;
[DependsOn(
typeof(AbpAutofacModule),
typeof(AbpLocalizationModule),
typeof(AbpSettingsModule),
typeof(AbpObjectMappingModule),
typeof(AbpMultiLingualObjectsModule),
typeof(AbpTestBaseModule),
typeof(AbpAutoMapperModule)
)]
public class AbpMultiLingualObjectsTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpSettingOptions>(options =>
{
options.DefinitionProviders.Add<LocalizationSettingProvider>();
});
context.Services.AddAutoMapperObjectMapper<AbpMultiLingualObjectsTestModule>();
Configure<AbpAutoMapperOptions>(options =>
{
options.AddMaps<AbpMultiLingualObjectsTestModule>(validate: true);
});
}
}

135
framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs

@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.AutoMapper;
using Volo.Abp.Localization;
using Volo.Abp.MultiLingualObjects.TestObjects;
using Volo.Abp.Testing;
@ -14,40 +16,63 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest<AbpMultiLingual
{
private readonly IMultiLingualObjectManager _multiLingualObjectManager;
private readonly MultiLingualBook _book;
private readonly List<MultiLingualBook> _books;
private readonly IMapperAccessor _mapperAccessor;
private readonly Dictionary<string, string> _testTranslations = new()
{
["ar"] = "C# التعمق في",
["zh-Hans"] = "深入理解C#",
["en"] = "C# in Depth"
};
public MultiLingualObjectManager_Tests()
{
_multiLingualObjectManager = ServiceProvider.GetRequiredService<IMultiLingualObjectManager>();
_multiLingualObjectManager = ServiceProvider.GetRequiredService<IMultiLingualObjectManager>();
//Single Lookup
_book = GetTestBook("en", "zh-Hans");
//Bulk lookup
_books = new List<MultiLingualBook>
{
//has no translations
GetTestBook(),
//english only
GetTestBook("en"),
//arabic only
GetTestBook("ar"),
//arabic + english
GetTestBook("en","ar"),
//arabic + english + chineese
GetTestBook("en", "ar", "zh-Hans")
};
_mapperAccessor = ServiceProvider.GetRequiredService<IMapperAccessor>();
}
MultiLingualBook GetTestBook(params string[] included)
{
var id = Guid.NewGuid();
_book = new MultiLingualBook(id, 100)
{
Translations = new List<MultiLingualBookTranslation>()
};
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);
}
//Single book
var res = new MultiLingualBook(id, 100);
foreach (var language in included)
{
res.Translations.Add(new MultiLingualBookTranslation
{
Language = language,
Name = _testTranslations[language],
});
}
return res;
}
[Fact]
public async Task GetTranslationAsync()
{
using (CultureHelper.Use("en-us"))
{
var translation = await _multiLingualObjectManager.GetTranslationAsync<MultiLingualBook, MultiLingualBookTranslation>(_book);
translation.Name.ShouldBe("C# in Depth");
translation.ShouldNotBeNull();
translation.Name.ShouldBe(_testTranslations["en"]);
}
}
@ -57,8 +82,8 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest<AbpMultiLingual
using (CultureHelper.Use("en-us"))
{
var translation = await _multiLingualObjectManager.GetTranslationAsync(_book.Translations);
translation.Name.ShouldBe("C# in Depth");
translation.ShouldNotBeNull();
translation.Name.ShouldBe(_testTranslations["en"]);
}
}
@ -68,8 +93,64 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest<AbpMultiLingual
using (CultureHelper.Use("zh-Hans"))
{
var translation = await _multiLingualObjectManager.GetTranslationAsync<MultiLingualBook, MultiLingualBookTranslation>(_book, culture: "en");
translation.ShouldNotBeNull();
translation.Name.ShouldBe(_testTranslations["en"]);
}
}
[Fact]
public async Task GetBulkTranslationsAsync()
{
using (CultureHelper.Use("en-us"))
{
var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync<MultiLingualBook, MultiLingualBookTranslation>(_books);
foreach (var (entity, translation) in translations)
{
if (entity.Translations.Any(x => x.Language == "en"))
{
translation.ShouldNotBeNull();
translation.Name.ShouldBe(_testTranslations["en"]);
}
else
{
translation.ShouldBeNull();
}
}
}
}
translation.Name.ShouldBe("C# in Depth");
[Fact]
public async Task GetBulkTranslationsFromListAsync()
{
using (CultureHelper.Use("en-us"))
{
var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync(_books.Select(x => x.Translations));
foreach (var translation in translations)
{
translation?.Name.ShouldBe(_testTranslations["en"]);
}
}
}
[Fact]
public async Task TestBulkMapping()
{
using (CultureHelper.Use("en-us"))
{
var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync<MultiLingualBook, MultiLingualBookTranslation>(_books);
var translationsDict = translations.ToDictionary(x => x.entity.Id, x => x.translation);
var mapped = _mapperAccessor.Mapper.Map<List<MultiLingualBook>, List<MultiLingualBookDto>>(_books, options =>
{
options.Items.Add(nameof(MultiLingualBookTranslation), translationsDict);
});
Assert.Equal(mapped.Count, _books.Count);
for (int i = 0; i < mapped.Count; i++)
{
var og = _books[i];
var m = mapped[i];
Assert.Equal(og.Translations.FirstOrDefault(x => x.Language == "en")?.Name, m.Name);
}
}
}
}

23
framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectTestProfile.cs

@ -0,0 +1,23 @@
namespace Volo.Abp.MultiLingualObjects;
using System;
using System.Collections.Generic;
using global::AutoMapper;
using Volo.Abp.MultiLingualObjects.TestObjects;
public class MultiLingualObjectTestProfile : Profile
{
public MultiLingualObjectTestProfile()
{
CreateMap<MultiLingualBook, MultiLingualBookDto>()
.ForMember(x => x.Name,
x => x.MapFrom((src, target, member, context) =>
{
if (context.Items.TryGetValue(nameof(MultiLingualBookTranslation), out var translationsRaw) && translationsRaw is IReadOnlyDictionary<Guid, MultiLingualBookTranslation> translations)
{
return translations.GetValueOrDefault(src.Id)?.Name;
}
return null;
}));
}
}

38
framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBook.cs

@ -1,19 +1,19 @@
using System;
using System.Collections.Generic;
namespace Volo.Abp.MultiLingualObjects.TestObjects;
public class MultiLingualBook : IMultiLingualObject<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; }
}
using System;
using System.Collections.Generic;
namespace Volo.Abp.MultiLingualObjects.TestObjects;
public class MultiLingualBook : IMultiLingualObject<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; } = new List<MultiLingualBookTranslation>();
}

24
framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookDto.cs

@ -1,12 +1,12 @@
using System;
namespace Volo.Abp.MultiLingualObjects.TestObjects;
public class MultiLingualBookDto
{
public Guid Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
using System;
namespace Volo.Abp.MultiLingualObjects.TestObjects;
public class MultiLingualBookDto
{
public Guid Id { get; set; }
public string? Name { get; set; }
public decimal Price { get; set; }
}

16
framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookTranslation.cs

@ -1,8 +1,8 @@
namespace Volo.Abp.MultiLingualObjects.TestObjects;
public class MultiLingualBookTranslation : IObjectTranslation
{
public string Name { get; set; }
public string Language { get; set; }
}
namespace Volo.Abp.MultiLingualObjects.TestObjects;
public class MultiLingualBookTranslation : IObjectTranslation
{
public string? Name { get; set; }
public required string Language { get; set; }
}

Loading…
Cancel
Save