diff --git a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateRenderingEngineBase.cs b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateRenderingEngineBase.cs index 03c026cccb..5ad6e0afc8 100644 --- a/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateRenderingEngineBase.cs +++ b/framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateRenderingEngineBase.cs @@ -41,15 +41,22 @@ public abstract class TemplateRenderingEngineBase : ITemplateRenderingEngine // The invariant culture has an empty name, which is not a valid value for a lang attribute. var cultureName = CultureInfo.CurrentUICulture.Name; - if (!globalContext.ContainsKey(CultureContextKey)) - { - globalContext[CultureContextKey] = cultureName.IsNullOrWhiteSpace() ? "en" : cultureName; - } + SetCultureContextValue(globalContext, CultureContextKey, cultureName.IsNullOrWhiteSpace() ? "en" : cultureName); + SetCultureContextValue(globalContext, TextDirectionContextKey, CultureHelper.IsRtl ? "rtl" : "ltr"); + } - if (!globalContext.ContainsKey(TextDirectionContextKey)) + protected virtual void SetCultureContextValue(Dictionary globalContext, string key, string value) + { + if (!globalContext.TryGetValue(key, out var callerValue)) { - globalContext[TextDirectionContextKey] = CultureHelper.IsRtl ? "rtl" : "ltr"; + globalContext.Add(key, value); + return; } + + // A case insensitive context can hold the value under another casing, while templates look the key + // up as it is written here, so it is re-inserted with the canonical one. + globalContext.Remove(key); + globalContext.Add(key, callerValue); } protected virtual async Task GetContentOrNullAsync(TemplateDefinition templateDefinition) diff --git a/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine_CultureContext_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine_CultureContext_Tests.cs index ccaa0aad00..063e203866 100644 --- a/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine_CultureContext_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine_CultureContext_Tests.cs @@ -80,9 +80,8 @@ public class ScribanTemplateRenderingEngine_CultureContext_Tests : AbpTextTempla [Fact] public async Task Should_Keep_The_Comparer_Of_The_Context_Of_The_Caller() { - // ABP_CULTURE already covers the key here, so nothing is added; losing the comparer on the copy - // would add a second, lowercase entry and render "ar". Scriban itself looks names up case - // sensitively, hence the empty culture in the output. + // Scriban looks the key up as the template writes it, so the value of a case insensitive context + // has to be re-inserted under the canonical casing to stay reachable. var globalContext = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["ABP_CULTURE"] = "custom" @@ -92,7 +91,7 @@ public class ScribanTemplateRenderingEngine_CultureContext_Tests : AbpTextTempla ScribanTestTemplateDefinitionProvider.CultureContext, cultureName: "ar", globalContext: globalContext - )).ShouldBe(""); + )).ShouldBe(""); } [Fact]