diff --git a/framework/src/Volo.Abp.MultiLingualObjects/Volo.Abp.MultiLingualObjects.csproj b/framework/src/Volo.Abp.MultiLingualObjects/Volo.Abp.MultiLingualObjects.csproj
index da83bc80f4..77da44eb47 100644
--- a/framework/src/Volo.Abp.MultiLingualObjects/Volo.Abp.MultiLingualObjects.csproj
+++ b/framework/src/Volo.Abp.MultiLingualObjects/Volo.Abp.MultiLingualObjects.csproj
@@ -1,20 +1,21 @@
-
-
-
-
-
-
- netstandard2.0
- Volo.Abp.MultiLingualObject
- $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
- false
- false
- false
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+ netstandard2.0
+ Volo.Abp.MultiLingualObject
+ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
+ false
+ false
+ false
+
+ enable
+
+
+
+
+
+
+
diff --git a/framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/IMultiLingualObjectManager.cs b/framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/IMultiLingualObjectManager.cs
index 904f3abd32..4edbd984bd 100644
--- a/framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/IMultiLingualObjectManager.cs
+++ b/framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/IMultiLingualObjectManager.cs
@@ -5,16 +5,30 @@ namespace Volo.Abp.MultiLingualObjects;
public interface IMultiLingualObjectManager
{
- Task GetTranslationAsync(
+ Task GetTranslationAsync(
TMultiLingual multiLingual,
- string culture = null,
+ string? culture = null,
bool fallbackToParentCultures = true)
where TMultiLingual : IMultiLingualObject
where TTranslation : class, IObjectTranslation;
- Task GetTranslationAsync(
- ICollection translations,
- string culture = null,
+ Task GetTranslationAsync(
+ IEnumerable translations,
+ string? culture = null,
bool fallbackToParentCultures = true)
+ where TTranslation : class, IObjectTranslation;
+
+
+ Task> GetBulkTranslationsAsync(
+ IEnumerable> translationsCombined,
+ string? culture = null,
+ bool fallbackToParentCultures = true)
+ where TTranslation : class, IObjectTranslation;
+
+ Task> GetBulkTranslationsAsync(
+ IEnumerable multiLinguals,
+ string? culture = null,
+ bool fallbackToParentCultures = true)
+ where TMultiLingual : IMultiLingualObject
where TTranslation : class, IObjectTranslation;
}
diff --git a/framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager.cs b/framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager.cs
index afb4509917..908431e997 100644
--- a/framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager.cs
+++ b/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 GetTranslationAsync(
- ICollection translations,
- string culture,
+ public virtual async Task GetTranslationAsync(
+ IEnumerable 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 GetTranslationAsync(
+ public virtual Task GetTranslationAsync(
TMultiLingual multiLingual,
- string culture = null,
+ string? culture = null,
bool fallbackToParentCultures = true)
where TMultiLingual : IMultiLingualObject
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(
- CultureInfo culture, ICollection translations, int currentDepth)
+ protected virtual TTranslation? GetTranslationBasedOnCulturalRecursive(
+ CultureInfo culture, IEnumerable 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> GetBulkTranslationsAsync(IEnumerable> 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();
+ 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> GetBulkTranslationsAsync(IEnumerable multiLinguals, string? culture, bool fallbackToParentCultures)
+ where TMultiLingual : IMultiLingualObject
+ 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;
+ }
}
diff --git a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo.Abp.MultiLingualObjects.Tests.csproj b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo.Abp.MultiLingualObjects.Tests.csproj
index a037cfbcf3..b26ea500bf 100644
--- a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo.Abp.MultiLingualObjects.Tests.csproj
+++ b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo.Abp.MultiLingualObjects.Tests.csproj
@@ -1,18 +1,21 @@
-
+
-
- net7.0
-
-
+
+ net7.0
+
+ enable
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/AbpMultiLingualObjectsTestModule.cs b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/AbpMultiLingualObjectsTestModule.cs
index f099b4fa6c..6fc3a45132 100644
--- a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/AbpMultiLingualObjectsTestModule.cs
+++ b/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(options =>
+ {
+ options.DefinitionProviders.Add();
+ });
+ context.Services.AddAutoMapperObjectMapper();
+ Configure(options =>
+ {
+ options.AddMaps(validate: true);
+ });
+ }
+}
diff --git a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs
index afe15bff99..e442d80953 100644
--- a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs
+++ b/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 _books;
+ private readonly IMapperAccessor _mapperAccessor;
+ private readonly Dictionary _testTranslations = new()
+ {
+ ["ar"] = "C# التعمق في",
+ ["zh-Hans"] = "深入理解C#",
+ ["en"] = "C# in Depth"
+ };
+
public MultiLingualObjectManager_Tests()
{
- _multiLingualObjectManager = ServiceProvider.GetRequiredService();
-
+ _multiLingualObjectManager = ServiceProvider.GetRequiredService();
+
+ //Single Lookup
+ _book = GetTestBook("en", "zh-Hans");
+ //Bulk lookup
+ _books = new List
+ {
+ //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();
+ }
+ MultiLingualBook GetTestBook(params string[] included)
+ {
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);
- }
+ //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(_book);
-
- translation.Name.ShouldBe("C# in Depth");
+ translation.ShouldNotBeNull();
+ translation.Name.ShouldBe(_testTranslations["en"]);
}
}
@@ -57,8 +82,8 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest(_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(_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(_books);
+ var translationsDict = translations.ToDictionary(x => x.entity.Id, x => x.translation);
+ var mapped = _mapperAccessor.Mapper.Map, List>(_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);
+ }
+ }
+ }
}
diff --git a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectTestProfile.cs b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectTestProfile.cs
new file mode 100644
index 0000000000..89cc9a51b7
--- /dev/null
+++ b/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()
+ .ForMember(x => x.Name,
+ x => x.MapFrom((src, target, member, context) =>
+ {
+ if (context.Items.TryGetValue(nameof(MultiLingualBookTranslation), out var translationsRaw) && translationsRaw is IReadOnlyDictionary translations)
+ {
+ return translations.GetValueOrDefault(src.Id)?.Name;
+ }
+ return null;
+ }));
+ }
+}
diff --git a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBook.cs b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBook.cs
index 92a295e0c5..3b880f48f2 100644
--- a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBook.cs
+++ b/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
-{
- public MultiLingualBook(Guid id, decimal price)
- {
- Id = id;
- Price = price;
- }
-
- public Guid Id { get; }
-
- public decimal Price { get; set; }
-
- public ICollection Translations { get; set; }
-}
+using System;
+using System.Collections.Generic;
+
+namespace Volo.Abp.MultiLingualObjects.TestObjects;
+
+public class MultiLingualBook : IMultiLingualObject
+{
+ public MultiLingualBook(Guid id, decimal price)
+ {
+ Id = id;
+ Price = price;
+ }
+
+ public Guid Id { get; }
+
+ public decimal Price { get; set; }
+
+ public ICollection Translations { get; set; } = new List();
+}
diff --git a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookDto.cs b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookDto.cs
index ea7e7e9afd..8d61449961 100644
--- a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookDto.cs
+++ b/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; }
+}
diff --git a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookTranslation.cs b/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookTranslation.cs
index 06cfc33b55..adf482fa17 100644
--- a/framework/test/Volo.Abp.MultiLingualObjects.Tests/Volo/Abp/MultiLingualObjects/TestObjects/MultiLingualBookTranslation.cs
+++ b/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; }
+}