From d093ac1528c399bb8777a2562cf7aa1ee403f061 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 21 Jul 2026 10:34:42 +0800 Subject: [PATCH] Fix index misalignment in MultiLingualObjectManager bulk fallback --- .../MultiLingualObjectManager.cs | 23 ++----- .../MultiLingualObjectManager_Tests.cs | 24 +++++-- .../MultiLingualObjectManager_Tests.cs | 68 +++++++++++++++++-- 3 files changed, 83 insertions(+), 32 deletions(-) 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 c41605ad0e..8625355e52 100644 --- a/framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager.cs +++ b/framework/src/Volo.Abp.MultiLingualObjects/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager.cs @@ -152,26 +152,11 @@ public class MultiLingualObjectManager : IMultiLingualObjectManager, ITransientD var index = 0; foreach (var translations in translationsCombined) { - if (!translations.Any()) - { - //don't try to find a translation - } - else + //if the src has no translations, don't try to find a translation + if (translations.Any() && res[index] == null) { - 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(); - } + res[index] = translations.FirstOrDefault(pt => pt.Language == defaultLanguage) ?? + translations.FirstOrDefault(); } index++; } diff --git a/framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs b/framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs index 9053472c48..6fdb848f49 100644 --- a/framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs +++ b/framework/test/Volo.Abp.LuckyPenny.AutoMapper.Tests/Volo/Abp/MultiLingualObjects/MultiLingualObjectManager_Tests.cs @@ -107,14 +107,20 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest(_books); foreach (var (entity, translation) in translations) { - if (entity.Translations.Any(x => x.Language == "en")) + if (!entity.Translations.Any()) + { + translation.ShouldBeNull(); + } + else if (entity.Translations.Any(x => x.Language == "en")) { translation.ShouldNotBeNull(); translation.Name.ShouldBe(_testTranslations["en"]); } else { - translation.ShouldBeNull(); + //Falls back to the default language or the first available translation + translation.ShouldNotBeNull(); + entity.Translations.ShouldContain(translation); } } } @@ -126,10 +132,12 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest x.Translations)); - foreach (var translation in translations) - { - translation?.Name.ShouldBe(_testTranslations["en"]); - } + translations.Count.ShouldBe(_books.Count); + translations[0].ShouldBeNull(); + translations[1]!.Name.ShouldBe(_testTranslations["en"]); + translations[2]!.Name.ShouldBe(_testTranslations["ar"]); + translations[3]!.Name.ShouldBe(_testTranslations["en"]); + translations[4]!.Name.ShouldBe(_testTranslations["en"]); } } @@ -149,7 +157,9 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest x.Language == "en")?.Name, m.Name); + var expectedName = og.Translations.FirstOrDefault(x => x.Language == "en")?.Name ?? + og.Translations.FirstOrDefault()?.Name; + Assert.Equal(expectedName, m.Name); } } } 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 bb527d2fcd..ef65c6a133 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 @@ -108,14 +108,20 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest(_books); foreach (var (entity, translation) in translations) { - if (entity.Translations.Any(x => x.Language == "en")) + if (!entity.Translations.Any()) + { + translation.ShouldBeNull(); + } + else if (entity.Translations.Any(x => x.Language == "en")) { translation.ShouldNotBeNull(); translation.Name.ShouldBe(_testTranslations["en"]); } else { - translation.ShouldBeNull(); + //Falls back to the default language or the first available translation + translation.ShouldNotBeNull(); + entity.Translations.ShouldContain(translation); } } } @@ -127,10 +133,58 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest x.Translations)); - foreach (var translation in translations) + translations.Count.ShouldBe(_books.Count); + translations[0].ShouldBeNull(); + translations[1]!.Name.ShouldBe(_testTranslations["en"]); + translations[2]!.Name.ShouldBe(_testTranslations["ar"]); + translations[3]!.Name.ShouldBe(_testTranslations["en"]); + translations[4]!.Name.ShouldBe(_testTranslations["en"]); + } + } + + [Fact] + public async Task GetBulkTranslationsAsync_Should_Keep_Translations_Aligned_With_Entities() + { + using (CultureHelper.Use("en-us")) + { + var books = new List { - translation?.Name.ShouldBe(_testTranslations["en"]); - } + //resolved in the first pass + GetTestBook("en"), + //has no translations + GetTestBook(), + //resolved by the fallback pass + GetTestBook("ar") + }; + + var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync(books); + + translations.Count.ShouldBe(3); + translations[0].entity.ShouldBe(books[0]); + translations[0].translation.ShouldNotBeNull(); + translations[0].translation!.Name.ShouldBe(_testTranslations["en"]); + translations[1].entity.ShouldBe(books[1]); + translations[1].translation.ShouldBeNull(); + translations[2].entity.ShouldBe(books[2]); + translations[2].translation.ShouldNotBeNull(); + translations[2].translation!.Name.ShouldBe(_testTranslations["ar"]); + } + } + + [Fact] + public async Task GetBulkTranslationsAsync_Should_Prefer_Default_Language_Over_First_Translation() + { + using (CultureHelper.Use("fr-FR")) + { + var books = new List + { + GetTestBook("ar", "en") + }; + + var translations = await _multiLingualObjectManager.GetBulkTranslationsAsync(books); + + translations[0].translation.ShouldNotBeNull(); + translations[0].translation!.Name.ShouldBe(_testTranslations["en"]); } } @@ -150,7 +204,9 @@ public class MultiLingualObjectManager_Tests : AbpIntegratedTest x.Language == "en")?.Name, m.Name); + var expectedName = og.Translations.FirstOrDefault(x => x.Language == "en")?.Name ?? + og.Translations.FirstOrDefault()?.Name; + Assert.Equal(expectedName, m.Name); } } }