Rendering: Explicitly contextualize variation context for language fallback (closes #20350) (#20587)
Expliticly contextualize variation context for language fallback rendering
This commit is contained in:
@@ -317,7 +317,7 @@ public class PublishedValueFallback : IPublishedValueFallback
|
|||||||
}
|
}
|
||||||
|
|
||||||
var culture2 = language2.IsoCode;
|
var culture2 = language2.IsoCode;
|
||||||
T? culture2Value = getValue(culture2, segment);
|
T? culture2Value = TryGetExplicitlyContextualizedValue(getValue, culture2, segment);
|
||||||
if (culture2Value != null)
|
if (culture2Value != null)
|
||||||
{
|
{
|
||||||
value = culture2Value;
|
value = culture2Value;
|
||||||
@@ -329,25 +329,26 @@ public class PublishedValueFallback : IPublishedValueFallback
|
|||||||
}
|
}
|
||||||
|
|
||||||
private bool TryGetValueWithDefaultLanguageFallback<T>(IPublishedProperty property, string? culture, string? segment, out T? value)
|
private bool TryGetValueWithDefaultLanguageFallback<T>(IPublishedProperty property, string? culture, string? segment, out T? value)
|
||||||
{
|
=> TryGetValueWithDefaultLanguageFallback(
|
||||||
value = default;
|
(actualCulture, actualSegment)
|
||||||
|
=> property.HasValue(actualCulture, actualSegment)
|
||||||
if (culture.IsNullOrWhiteSpace())
|
? property.Value<T>(this, actualCulture, actualSegment)
|
||||||
{
|
: default,
|
||||||
return false;
|
culture,
|
||||||
}
|
segment,
|
||||||
|
out value);
|
||||||
string? defaultCulture = _localizationService?.GetDefaultLanguageIsoCode();
|
|
||||||
if (culture.InvariantEquals(defaultCulture) == false && property.HasValue(defaultCulture, segment))
|
|
||||||
{
|
|
||||||
value = property.Value<T>(this, defaultCulture, segment);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryGetValueWithDefaultLanguageFallback<T>(IPublishedElement element, string alias, string? culture, string? segment, out T? value)
|
private bool TryGetValueWithDefaultLanguageFallback<T>(IPublishedElement element, string alias, string? culture, string? segment, out T? value)
|
||||||
|
=> TryGetValueWithDefaultLanguageFallback(
|
||||||
|
(actualCulture, actualSegment)
|
||||||
|
=> element.HasValue(alias, actualCulture, actualSegment)
|
||||||
|
? element.Value<T>(this, alias, actualCulture, actualSegment)
|
||||||
|
: default,
|
||||||
|
culture,
|
||||||
|
segment,
|
||||||
|
out value);
|
||||||
|
|
||||||
|
private bool TryGetValueWithDefaultLanguageFallback<T>(TryGetValueForCultureAndSegment<T> getValue, string? culture, string? segment, out T? value)
|
||||||
{
|
{
|
||||||
value = default;
|
value = default;
|
||||||
|
|
||||||
@@ -356,14 +357,39 @@ public class PublishedValueFallback : IPublishedValueFallback
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
string? defaultCulture = _localizationService?.GetDefaultLanguageIsoCode();
|
var defaultCulture = _localizationService?.GetDefaultLanguageIsoCode();
|
||||||
if (culture.InvariantEquals(defaultCulture) == false && element.HasValue(alias, defaultCulture, segment))
|
if (defaultCulture.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
value = element.Value<T>(this, alias, defaultCulture, segment);
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
if (culture.InvariantEquals(defaultCulture))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
T? fallbackValue = TryGetExplicitlyContextualizedValue(getValue, defaultCulture, segment);
|
||||||
|
if (fallbackValue == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = fallbackValue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private T? TryGetExplicitlyContextualizedValue<T>(TryGetValueForCultureAndSegment<T> getValue, string culture, string? segment)
|
||||||
|
{
|
||||||
|
VariationContext? current = _variationContextAccessor.VariationContext;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_variationContextAccessor.VariationContext = new VariationContext(culture, segment);
|
||||||
|
return getValue(culture, segment);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_variationContextAccessor.VariationContext = current;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private delegate T? TryGetValueForCultureAndSegment<out T>(string actualCulture, string? actualSegment);
|
private delegate T? TryGetValueForCultureAndSegment<out T>(string actualCulture, string? actualSegment);
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ public class PublishedContentFallbackTests : UmbracoIntegrationTest
|
|||||||
|
|
||||||
private IApiContentBuilder ApiContentBuilder => GetRequiredService<IApiContentBuilder>();
|
private IApiContentBuilder ApiContentBuilder => GetRequiredService<IApiContentBuilder>();
|
||||||
|
|
||||||
|
private ILanguageService LanguageService => GetRequiredService<ILanguageService>();
|
||||||
|
|
||||||
protected override void CustomTestSetup(IUmbracoBuilder builder)
|
protected override void CustomTestSetup(IUmbracoBuilder builder)
|
||||||
=> builder
|
=> builder
|
||||||
.AddUmbracoHybridCache()
|
.AddUmbracoHybridCache()
|
||||||
@@ -98,6 +100,36 @@ public class PublishedContentFallbackTests : UmbracoIntegrationTest
|
|||||||
Assert.AreEqual(invariantTitle, invariantValue);
|
Assert.AreEqual(invariantTitle, invariantValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase("Danish title", true)]
|
||||||
|
[TestCase("Danish title", false)]
|
||||||
|
[TestCase(null, true)]
|
||||||
|
[TestCase(null, false)]
|
||||||
|
public async Task Property_Value_Can_Perform_Explicit_Language_Fallback(string? danishTitle, bool performFallbackToDefaultLanguage)
|
||||||
|
{
|
||||||
|
var danishLanguage = new Language("da-DK", "Danish")
|
||||||
|
{
|
||||||
|
FallbackIsoCode = "en-US"
|
||||||
|
};
|
||||||
|
await LanguageService.CreateAsync(danishLanguage, Constants.Security.SuperUserKey);
|
||||||
|
|
||||||
|
UmbracoContextFactory.EnsureUmbracoContext();
|
||||||
|
|
||||||
|
const string englishTitle = "English title";
|
||||||
|
var publishedContent = await SetupCultureVariantContentAsync(englishTitle, danishTitle);
|
||||||
|
|
||||||
|
VariationContextAccessor.VariationContext = new VariationContext(culture: "da-DK", segment: null);
|
||||||
|
var danishValue = publishedContent.Value<string>(PublishedValueFallback, "title");
|
||||||
|
Assert.AreEqual(danishTitle ?? string.Empty, danishValue);
|
||||||
|
|
||||||
|
var fallback = performFallbackToDefaultLanguage ? Fallback.ToDefaultLanguage : Fallback.ToLanguage;
|
||||||
|
var fallbackValue = publishedContent.Value<string>(PublishedValueFallback, "title", fallback: fallback);
|
||||||
|
Assert.AreEqual(danishTitle ?? englishTitle, fallbackValue);
|
||||||
|
|
||||||
|
VariationContextAccessor.VariationContext = new VariationContext(culture: "en-US", segment: null);
|
||||||
|
var englishValue = publishedContent.Value<string>(PublishedValueFallback, "title");
|
||||||
|
Assert.AreEqual(englishTitle, englishValue);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<IPublishedContent> SetupSegmentedContentAsync(string? invariantTitle, string? segmentedTitle)
|
private async Task<IPublishedContent> SetupSegmentedContentAsync(string? invariantTitle, string? segmentedTitle)
|
||||||
{
|
{
|
||||||
var contentType = new ContentTypeBuilder()
|
var contentType = new ContentTypeBuilder()
|
||||||
@@ -124,11 +156,47 @@ public class PublishedContentFallbackTests : UmbracoIntegrationTest
|
|||||||
ContentService.Save(content);
|
ContentService.Save(content);
|
||||||
ContentService.Publish(content, ["*"]);
|
ContentService.Publish(content, ["*"]);
|
||||||
|
|
||||||
|
return GetPublishedContent(content.Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IPublishedContent> SetupCultureVariantContentAsync(string englishTitle, string? danishTitle)
|
||||||
|
{
|
||||||
|
var contentType = new ContentTypeBuilder()
|
||||||
|
.WithAlias("theContentType")
|
||||||
|
.WithContentVariation(ContentVariation.Culture)
|
||||||
|
.AddPropertyType()
|
||||||
|
.WithAlias("title")
|
||||||
|
.WithName("Title")
|
||||||
|
.WithDataTypeId(Constants.DataTypes.Textbox)
|
||||||
|
.WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox)
|
||||||
|
.WithValueStorageType(ValueStorageType.Nvarchar)
|
||||||
|
.WithVariations(ContentVariation.Culture)
|
||||||
|
.Done()
|
||||||
|
.WithAllowAsRoot(true)
|
||||||
|
.Build();
|
||||||
|
await ContentTypeService.CreateAsync(contentType, Constants.Security.SuperUserKey);
|
||||||
|
|
||||||
|
var content = new ContentBuilder()
|
||||||
|
.WithContentType(contentType)
|
||||||
|
.WithCultureName("en-US", "EN")
|
||||||
|
.WithCultureName("da-DK", "DA")
|
||||||
|
.WithName("Content")
|
||||||
|
.Build();
|
||||||
|
content.SetValue("title", englishTitle, culture: "en-US");
|
||||||
|
content.SetValue("title", danishTitle, culture: "da-DK");
|
||||||
|
ContentService.Save(content);
|
||||||
|
ContentService.Publish(content, ["en-US", "da-DK"]);
|
||||||
|
|
||||||
|
return GetPublishedContent(content.Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IPublishedContent GetPublishedContent(Guid key)
|
||||||
|
{
|
||||||
ContentCacheRefresher.Refresh([new ContentCacheRefresher.JsonPayload { ChangeTypes = TreeChangeTypes.RefreshAll }]);
|
ContentCacheRefresher.Refresh([new ContentCacheRefresher.JsonPayload { ChangeTypes = TreeChangeTypes.RefreshAll }]);
|
||||||
|
|
||||||
UmbracoContextAccessor.Clear();
|
UmbracoContextAccessor.Clear();
|
||||||
var umbracoContext = UmbracoContextFactory.EnsureUmbracoContext().UmbracoContext;
|
var umbracoContext = UmbracoContextFactory.EnsureUmbracoContext().UmbracoContext;
|
||||||
var publishedContent = umbracoContext.Content.GetById(content.Key);
|
var publishedContent = umbracoContext.Content.GetById(key);
|
||||||
Assert.IsNotNull(publishedContent);
|
Assert.IsNotNull(publishedContent);
|
||||||
|
|
||||||
return publishedContent;
|
return publishedContent;
|
||||||
|
|||||||
@@ -1932,4 +1932,95 @@ internal partial class BlockListElementLevelVariationTests
|
|||||||
Assert.AreEqual(expectedPickedContent.Key, actualPickedPublishedContent.Key);
|
Assert.AreEqual(expectedPickedContent.Key, actualPickedPublishedContent.Key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase(ContentVariation.Culture, false)]
|
||||||
|
[TestCase(ContentVariation.Culture, true)]
|
||||||
|
[TestCase(ContentVariation.Nothing, false)]
|
||||||
|
[TestCase(ContentVariation.Nothing, true)]
|
||||||
|
public async Task Can_Perform_Language_Fallback(ContentVariation elementTypeVariation, bool performFallbackToDefaultLanguage)
|
||||||
|
{
|
||||||
|
var daDkLanguage = await LanguageService.GetAsync("da-DK");
|
||||||
|
Assert.IsNotNull(daDkLanguage);
|
||||||
|
daDkLanguage.FallbackIsoCode = "en-US";
|
||||||
|
var saveLanguageResult = await LanguageService.UpdateAsync(daDkLanguage, Constants.Security.SuperUserKey);
|
||||||
|
Assert.IsTrue(saveLanguageResult.Success);
|
||||||
|
|
||||||
|
daDkLanguage = await LanguageService.GetAsync("da-DK");
|
||||||
|
Assert.AreEqual("en-US", daDkLanguage?.FallbackIsoCode);
|
||||||
|
|
||||||
|
var elementType = CreateElementType(elementTypeVariation);
|
||||||
|
var blockListDataType = await CreateBlockListDataType(elementType);
|
||||||
|
var contentType = CreateContentType(ContentVariation.Culture, blockListDataType, ContentVariation.Culture);
|
||||||
|
|
||||||
|
var content = CreateContent(
|
||||||
|
contentType,
|
||||||
|
elementType,
|
||||||
|
new []
|
||||||
|
{
|
||||||
|
new BlockProperty(
|
||||||
|
new List<BlockPropertyValue>
|
||||||
|
{
|
||||||
|
new() { Alias = "invariantText", Value = "English invariantText content value" },
|
||||||
|
new() { Alias = "variantText", Value = "English variantText content value" }
|
||||||
|
},
|
||||||
|
new List<BlockPropertyValue>
|
||||||
|
{
|
||||||
|
new() { Alias = "invariantText", Value = "English invariantText settings value" },
|
||||||
|
new() { Alias = "variantText", Value = "English variantText settings value" }
|
||||||
|
},
|
||||||
|
"en-US",
|
||||||
|
null)
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
|
||||||
|
AssertPropertyValuesWithFallback("en-US",
|
||||||
|
"English invariantText content value", "English variantText content value",
|
||||||
|
"English invariantText settings value", "English variantText settings value");
|
||||||
|
|
||||||
|
AssetEmptyPropertyValues("da-DK");
|
||||||
|
|
||||||
|
AssertPropertyValuesWithFallback("da-DK",
|
||||||
|
"English invariantText content value", "English variantText content value",
|
||||||
|
"English invariantText settings value", "English variantText settings value");
|
||||||
|
|
||||||
|
void AssertPropertyValuesWithFallback(string culture,
|
||||||
|
string expectedInvariantContentValue, string expectedVariantContentValue,
|
||||||
|
string expectedInvariantSettingsValue, string expectedVariantSettingsValue)
|
||||||
|
{
|
||||||
|
SetVariationContext(culture, null);
|
||||||
|
var publishedContent = GetPublishedContent(content.Key);
|
||||||
|
|
||||||
|
var fallback = performFallbackToDefaultLanguage ? Fallback.ToDefaultLanguage : Fallback.ToLanguage;
|
||||||
|
|
||||||
|
var publishedValueFallback = GetRequiredService<IPublishedValueFallback>();
|
||||||
|
var value = publishedContent.Value<BlockListModel>(publishedValueFallback, "blocks", fallback: fallback);
|
||||||
|
Assert.IsNotNull(value);
|
||||||
|
Assert.AreEqual(1, value.Count);
|
||||||
|
|
||||||
|
var blockListItem = value.First();
|
||||||
|
Assert.AreEqual(2, blockListItem.Content.Properties.Count());
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.AreEqual(expectedInvariantContentValue, blockListItem.Content.Value<string>("invariantText"));
|
||||||
|
Assert.AreEqual(expectedVariantContentValue, blockListItem.Content.Value<string>("variantText"));
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.AreEqual(2, blockListItem.Settings.Properties.Count());
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.AreEqual(expectedInvariantSettingsValue, blockListItem.Settings.Value<string>("invariantText"));
|
||||||
|
Assert.AreEqual(expectedVariantSettingsValue, blockListItem.Settings.Value<string>("variantText"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssetEmptyPropertyValues(string culture)
|
||||||
|
{
|
||||||
|
SetVariationContext(culture, null);
|
||||||
|
var publishedContent = GetPublishedContent(content.Key);
|
||||||
|
|
||||||
|
var value = publishedContent.Value<BlockListModel>("blocks");
|
||||||
|
Assert.NotNull(value);
|
||||||
|
Assert.IsEmpty(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user