Browse Source

Merge pull request #25974 from abpframework/maliming/emailing-layout-culture-dir

Render the `lang` and the `dir` of the current culture in email templates
pull/25975/head
Engincan VESKE 2 months ago
committed by GitHub
parent
commit
be0c29b4d1
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      docs/en/framework/infrastructure/emailing.md
  2. 19
      docs/en/framework/infrastructure/text-templating/razor.md
  3. 19
      docs/en/framework/infrastructure/text-templating/scriban.md
  4. 2
      framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Layout.tpl
  5. 35
      framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateRenderingEngineBase.cs
  6. 13
      framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine.cs
  7. 13
      framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine.cs
  8. 1
      framework/test/Volo.Abp.Emailing.Tests/Volo.Abp.Emailing.Tests.csproj
  9. 2
      framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/AbpEmailingTestModule.cs
  10. 44
      framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Templates/StandardEmailTemplates_Tests.cs
  11. 65
      framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine_CultureContext_Tests.cs
  12. 4
      framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTestTemplateDefinitionProvider.cs
  13. 2
      framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/CultureContext.cshtml
  14. 1
      framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/RazorTestTemplates.cs
  15. 1
      framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/CultureContext.tpl
  16. 107
      framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine_CultureContext_Tests.cs
  17. 5
      framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTestTemplateDefinitionProvider.cs

6
docs/en/framework/infrastructure/emailing.md

@ -193,7 +193,7 @@ The resulting email body will be shown below:
````html ````html
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <html lang="en" dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
</head> </head>
@ -217,7 +217,7 @@ This template uses the "Abp.StandardEmailTemplates.Layout" as its layout.
````html ````html
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <html lang="{%{{{abp_culture}}}%}" dir="{%{{{abp_dir}}}%}" xmlns="http://www.w3.org/1999/xhtml">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
</head> </head>
@ -227,6 +227,8 @@ This template uses the "Abp.StandardEmailTemplates.Layout" as its layout.
</html> </html>
```` ````
`abp_culture` and `abp_dir` are provided by the rendering engine, so the document declares the language and the text direction of the culture it was rendered with. See [the text templating documentation](./text-templating/scriban.md) for the details.
The final rendered message was shown above. The final rendered message was shown above.
> These template names are contants defined in the `Volo.Abp.Emailing.Templates.StandardEmailTemplates` class. > These template names are contants defined in the `Volo.Abp.Emailing.Templates.StandardEmailTemplates` class.

19
docs/en/framework/infrastructure/text-templating/razor.md

@ -330,7 +330,7 @@ First, create a template file just like before:
````html ````html
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase @inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <html lang="@GlobalContext["abp_culture"]" dir="@GlobalContext["abp_dir"]" xmlns="http://www.w3.org/1999/xhtml">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
</head> </head>
@ -404,6 +404,21 @@ The rendering result will be:
A global object value: TEST VALUE A global object value: TEST VALUE
```` ````
### Built-In Global Context Values
The Razor and Scriban engines add the following values to the global context, so a template can declare the language and the text direction of the document it renders:
| Key | Value |
|-----|-------|
| `abp_culture` | Name of the culture the template is rendered with, `en` when it is the invariant culture. |
| `abp_dir` | `rtl` for a right-to-left culture, `ltr` otherwise. |
````html
<html lang="@GlobalContext["abp_culture"]" dir="@GlobalContext["abp_dir"]">
````
A value you pass yourself under the same key is kept. The rendering works on a copy of the dictionary you pass, so you can reuse the same instance for several renderings.
## Replacing the Existing Templates ## Replacing the Existing Templates
It is possible to replace a template defined by a module that used in your application. In this way, you can customize the templates based on your requirements without changing the module code. It is possible to replace a template defined by a module that used in your application. In this way, you can customize the templates based on your requirements without changing the module code.
@ -427,7 +442,7 @@ Do the following steps to replace the template file with your own;
````html ````html
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase @inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <html lang="@GlobalContext["abp_culture"]" dir="@GlobalContext["abp_dir"]" xmlns="http://www.w3.org/1999/xhtml">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
</head> </head>

19
docs/en/framework/infrastructure/text-templating/scriban.md

@ -302,7 +302,7 @@ First, create a template file just like before:
````xml ````xml
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <html lang="{%{{{abp_culture}}}%}" dir="{%{{{abp_dir}}}%}" xmlns="http://www.w3.org/1999/xhtml">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
</head> </head>
@ -375,6 +375,21 @@ The rendering result will be:
A global object value: TEST VALUE A global object value: TEST VALUE
```` ````
### Built-In Global Context Values
The Scriban and Razor engines add the following values to the global context, so a template can declare the language and the text direction of the document it renders:
| Key | Value |
|-----|-------|
| `abp_culture` | Name of the culture the template is rendered with, `en` when it is the invariant culture. |
| `abp_dir` | `rtl` for a right-to-left culture, `ltr` otherwise. |
````html
<html lang="{%{{{abp_culture}}}%}" dir="{%{{{abp_dir}}}%}">
````
A value you pass yourself under the same key is kept. The rendering works on a copy of the dictionary you pass, so you can reuse the same instance for several renderings.
## Replacing the Existing Templates ## Replacing the Existing Templates
It is possible to replace a template defined by a module that used in your application. In this way, you can customize the templates based on your requirements without changing the module code. It is possible to replace a template defined by a module that used in your application. In this way, you can customize the templates based on your requirements without changing the module code.
@ -397,7 +412,7 @@ Do the following steps to replace the template file with your own;
````html ````html
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <html lang="{%{{{abp_culture}}}%}" dir="{%{{{abp_dir}}}%}" xmlns="http://www.w3.org/1999/xhtml">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
</head> </head>

2
framework/src/Volo.Abp.Emailing/Volo/Abp/Emailing/Templates/Layout.tpl

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <html lang="{{abp_culture}}" dir="{{abp_dir}}" xmlns="http://www.w3.org/1999/xhtml">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
</head> </head>

35
framework/src/Volo.Abp.TextTemplating.Core/Volo/Abp/TextTemplating/TemplateRenderingEngineBase.cs

@ -1,11 +1,18 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using Volo.Abp.Localization;
namespace Volo.Abp.TextTemplating; namespace Volo.Abp.TextTemplating;
public abstract class TemplateRenderingEngineBase : ITemplateRenderingEngine public abstract class TemplateRenderingEngineBase : ITemplateRenderingEngine
{ {
public const string CultureContextKey = "abp_culture";
public const string TextDirectionContextKey = "abp_dir";
public abstract string Name { get; } public abstract string Name { get; }
public virtual bool IsSandboxed => false; public virtual bool IsSandboxed => false;
@ -26,6 +33,32 @@ public abstract class TemplateRenderingEngineBase : ITemplateRenderingEngine
public abstract Task<string> RenderAsync(string templateName, object? model = null, string? cultureName = null, Dictionary<string, object>? globalContext = null); public abstract Task<string> RenderAsync(string templateName, object? model = null, string? cultureName = null, Dictionary<string, object>? globalContext = null);
/// <summary>
/// Must be called inside the culture scope of the rendering. Values set by the caller are kept.
/// </summary>
protected virtual void SetCultureContext(Dictionary<string, object> globalContext)
{
// The invariant culture has an empty name, which is not a valid value for a lang attribute.
var cultureName = CultureInfo.CurrentUICulture.Name;
SetCultureContextValue(globalContext, CultureContextKey, cultureName.IsNullOrWhiteSpace() ? "en" : cultureName);
SetCultureContextValue(globalContext, TextDirectionContextKey, CultureHelper.IsRtl ? "rtl" : "ltr");
}
protected virtual void SetCultureContextValue(Dictionary<string, object> globalContext, string key, string value)
{
if (!globalContext.TryGetValue(key, out var callerValue))
{
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<string?> GetContentOrNullAsync(TemplateDefinition templateDefinition) protected virtual async Task<string?> GetContentOrNullAsync(TemplateDefinition templateDefinition)
{ {
return await TemplateContentProvider.GetContentOrNullAsync(templateDefinition); return await TemplateContentProvider.GetContentOrNullAsync(templateDefinition);

13
framework/src/Volo.Abp.TextTemplating.Razor/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine.cs

@ -39,13 +39,16 @@ public class RazorTemplateRenderingEngine : TemplateRenderingEngineBase, ITransi
{ {
Check.NotNullOrWhiteSpace(templateName, nameof(templateName)); Check.NotNullOrWhiteSpace(templateName, nameof(templateName));
if (globalContext == null) // The rendering writes the culture context into this dictionary, so it works on a copy: a caller
{ // reusing one instance would carry the values of a rendering into the next.
globalContext = new Dictionary<string, object>(); globalContext = globalContext == null
} ? new Dictionary<string, object>()
: new Dictionary<string, object>(globalContext, globalContext.Comparer);
if (cultureName == null) if (cultureName == null)
{ {
SetCultureContext(globalContext);
return await RenderInternalAsync( return await RenderInternalAsync(
templateName, templateName,
null, null,
@ -57,6 +60,8 @@ public class RazorTemplateRenderingEngine : TemplateRenderingEngineBase, ITransi
{ {
using (CultureHelper.Use(cultureName)) using (CultureHelper.Use(cultureName))
{ {
SetCultureContext(globalContext);
return await RenderInternalAsync( return await RenderInternalAsync(
templateName, templateName,
null, null,

13
framework/src/Volo.Abp.TextTemplating.Scriban/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine.cs

@ -33,13 +33,16 @@ public class ScribanTemplateRenderingEngine : TemplateRenderingEngineBase, ITran
{ {
Check.NotNullOrWhiteSpace(templateName, nameof(templateName)); Check.NotNullOrWhiteSpace(templateName, nameof(templateName));
if (globalContext == null) // The rendering writes the culture context and the layout content into this dictionary, so it works
{ // on a copy: a caller reusing one instance would carry the values of a rendering into the next.
globalContext = new Dictionary<string, object>(); globalContext = globalContext == null
} ? new Dictionary<string, object>()
: new Dictionary<string, object>(globalContext, globalContext.Comparer);
if (cultureName == null) if (cultureName == null)
{ {
SetCultureContext(globalContext);
return await RenderInternalAsync( return await RenderInternalAsync(
templateName, templateName,
globalContext, globalContext,
@ -50,6 +53,8 @@ public class ScribanTemplateRenderingEngine : TemplateRenderingEngineBase, ITran
{ {
using (CultureHelper.Use(cultureName)) using (CultureHelper.Use(cultureName))
{ {
SetCultureContext(globalContext);
return await RenderInternalAsync( return await RenderInternalAsync(
templateName, templateName,
globalContext, globalContext,

1
framework/test/Volo.Abp.Emailing.Tests/Volo.Abp.Emailing.Tests.csproj

@ -9,6 +9,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Volo.Abp.Emailing\Volo.Abp.Emailing.csproj" /> <ProjectReference Include="..\..\src\Volo.Abp.Emailing\Volo.Abp.Emailing.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.TextTemplating.Scriban\Volo.Abp.TextTemplating.Scriban.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" /> <ProjectReference Include="..\..\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" /> <ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="Microsoft.NET.Test.Sdk" />

2
framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/AbpEmailingTestModule.cs

@ -1,11 +1,13 @@
using Volo.Abp.Autofac; using Volo.Abp.Autofac;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.TextTemplating.Scriban;
using Volo.Abp.VirtualFileSystem; using Volo.Abp.VirtualFileSystem;
namespace Volo.Abp.Emailing; namespace Volo.Abp.Emailing;
[DependsOn( [DependsOn(
typeof(AbpEmailingModule), typeof(AbpEmailingModule),
typeof(AbpTextTemplatingScribanModule),
typeof(AbpAutofacModule), typeof(AbpAutofacModule),
typeof(AbpTestBaseModule))] typeof(AbpTestBaseModule))]
public class AbpEmailingTestModule : AbpModule public class AbpEmailingTestModule : AbpModule

44
framework/test/Volo.Abp.Emailing.Tests/Volo/Abp/Emailing/Templates/StandardEmailTemplates_Tests.cs

@ -0,0 +1,44 @@
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Modularity;
using Volo.Abp.Testing;
using Volo.Abp.TextTemplating;
using Xunit;
namespace Volo.Abp.Emailing.Templates;
public abstract class AbpEmailingTestBase<TStartupModule> : AbpIntegratedTest<TStartupModule>
where TStartupModule : IAbpModule
{
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{
options.UseAutofac();
}
}
public class StandardEmailTemplates_Tests : AbpEmailingTestBase<AbpEmailingTestModule>
{
private readonly ITemplateRenderer _templateRenderer;
public StandardEmailTemplates_Tests()
{
_templateRenderer = GetRequiredService<ITemplateRenderer>();
}
[Theory]
[InlineData("en", "<html lang=\"en\" dir=\"ltr\"")]
[InlineData("fr", "<html lang=\"fr\" dir=\"ltr\"")]
[InlineData("ar", "<html lang=\"ar\" dir=\"rtl\"")]
public async Task Should_Declare_The_Language_And_The_Direction_Of_The_Rendering_Culture(
string cultureName, string expectedHtmlTag)
{
var result = await _templateRenderer.RenderAsync(
StandardEmailTemplates.Message,
model: new { message = "This is email body..." },
cultureName: cultureName
);
result.ShouldContain(expectedHtmlTag);
result.ShouldContain("This is email body...");
}
}

65
framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTemplateRenderingEngine_CultureContext_Tests.cs

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.TextTemplating.Razor.SampleTemplates;
using Xunit;
namespace Volo.Abp.TextTemplating.Razor;
public class RazorTemplateRenderingEngine_CultureContext_Tests : AbpTextTemplatingTestBase<RazorTextTemplatingTestModule>
{
private readonly ITemplateRenderer _templateRenderer;
public RazorTemplateRenderingEngine_CultureContext_Tests()
{
_templateRenderer = GetRequiredService<ITemplateRenderer>();
}
[Theory]
[InlineData("en", "<html lang=\"en\" dir=\"ltr\">")]
[InlineData("fr", "<html lang=\"fr\" dir=\"ltr\">")]
[InlineData("ar", "<html lang=\"ar\" dir=\"rtl\">")]
public async Task Should_Render_Culture_And_Text_Direction_Of_The_Rendering_Culture(string cultureName, string expected)
{
(await _templateRenderer.RenderAsync(
RazorTestTemplates.CultureContext,
cultureName: cultureName
)).Trim().ShouldBe(expected);
}
[Fact]
public async Task Should_Not_Leak_The_Culture_Of_A_Rendering_Into_The_Next_One()
{
var globalContext = new Dictionary<string, object>();
(await _templateRenderer.RenderAsync(
RazorTestTemplates.CultureContext,
cultureName: "en",
globalContext: globalContext
)).Trim().ShouldBe("<html lang=\"en\" dir=\"ltr\">");
(await _templateRenderer.RenderAsync(
RazorTestTemplates.CultureContext,
cultureName: "ar",
globalContext: globalContext
)).Trim().ShouldBe("<html lang=\"ar\" dir=\"rtl\">");
globalContext.ShouldBeEmpty();
}
[Fact]
public async Task Should_Keep_The_Comparer_Of_The_Context_Of_The_Caller()
{
var globalContext = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
{
["ABP_CULTURE"] = "custom"
};
(await _templateRenderer.RenderAsync(
RazorTestTemplates.CultureContext,
cultureName: "ar",
globalContext: globalContext
)).Trim().ShouldBe("<html lang=\"custom\" dir=\"rtl\">");
}
}

4
framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/RazorTestTemplateDefinitionProvider.cs

@ -23,5 +23,9 @@ public class RazorTestTemplateDefinitionProvider : TemplateDefinitionProvider
.WithRazorEngine(); .WithRazorEngine();
context.Add(new TemplateDefinition(RazorTestTemplates.TestTemplate).WithVirtualFilePath("/SampleTemplates/TestTemplate.cshtml", true)); context.Add(new TemplateDefinition(RazorTestTemplates.TestTemplate).WithVirtualFilePath("/SampleTemplates/TestTemplate.cshtml", true));
context.Add(new TemplateDefinition(RazorTestTemplates.CultureContext)
.WithVirtualFilePath("/SampleTemplates/CultureContext.cshtml", true)
.WithRazorEngine());
} }
} }

2
framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/CultureContext.cshtml

@ -0,0 +1,2 @@
@inherits Volo.Abp.TextTemplating.Razor.RazorTemplatePageBase
<html lang="@GlobalContext["abp_culture"]" dir="@GlobalContext["abp_dir"]">

1
framework/test/Volo.Abp.TextTemplating.Razor.Tests/Volo/Abp/TextTemplating/Razor/SampleTemplates/RazorTestTemplates.cs

@ -3,4 +3,5 @@
public static class RazorTestTemplates public static class RazorTestTemplates
{ {
public const string TestTemplate = "TestTemplate"; public const string TestTemplate = "TestTemplate";
public const string CultureContext = "CultureContext";
} }

1
framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/SampleTemplates/CultureContext.tpl

@ -0,0 +1 @@
<html lang="{{abp_culture}}" dir="{{abp_dir}}">

107
framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTemplateRenderingEngine_CultureContext_Tests.cs

@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Localization;
using Xunit;
namespace Volo.Abp.TextTemplating.Scriban;
public class ScribanTemplateRenderingEngine_CultureContext_Tests : AbpTextTemplatingTestBase<ScribanTextTemplatingTestModule>
{
private readonly ITemplateRenderer _templateRenderer;
public ScribanTemplateRenderingEngine_CultureContext_Tests()
{
_templateRenderer = GetRequiredService<ITemplateRenderer>();
}
[Theory]
[InlineData("en", "<html lang=\"en\" dir=\"ltr\">")]
[InlineData("fr", "<html lang=\"fr\" dir=\"ltr\">")]
[InlineData("ar", "<html lang=\"ar\" dir=\"rtl\">")]
public async Task Should_Render_Culture_And_Text_Direction_Of_The_Rendering_Culture(string cultureName, string expected)
{
(await _templateRenderer.RenderAsync(
ScribanTestTemplateDefinitionProvider.CultureContext,
cultureName: cultureName
)).ShouldBe(expected);
}
[Fact]
public async Task Should_Keep_The_Values_Passed_By_The_Caller()
{
(await _templateRenderer.RenderAsync(
ScribanTestTemplateDefinitionProvider.CultureContext,
cultureName: "ar",
globalContext: new Dictionary<string, object>
{
[TemplateRenderingEngineBase.CultureContextKey] = "custom"
}
)).ShouldBe("<html lang=\"custom\" dir=\"rtl\">");
}
[Fact]
public async Task Should_Not_Leak_The_Culture_Of_A_Rendering_Into_The_Next_One()
{
var globalContext = new Dictionary<string, object>();
(await _templateRenderer.RenderAsync(
ScribanTestTemplateDefinitionProvider.CultureContext,
cultureName: "en",
globalContext: globalContext
)).ShouldBe("<html lang=\"en\" dir=\"ltr\">");
(await _templateRenderer.RenderAsync(
ScribanTestTemplateDefinitionProvider.CultureContext,
cultureName: "ar",
globalContext: globalContext
)).ShouldBe("<html lang=\"ar\" dir=\"rtl\">");
globalContext.ShouldBeEmpty();
}
[Fact]
public async Task Should_Not_Write_The_Layout_Content_Into_The_Context_Of_The_Caller()
{
var globalContext = new Dictionary<string, object>();
await _templateRenderer.RenderAsync(
TestTemplates.ForgotPasswordEmail,
model: new { link = "http://abp.io" },
cultureName: "en",
globalContext: globalContext
);
globalContext.ShouldBeEmpty();
}
[Fact]
public async Task Should_Keep_The_Comparer_Of_The_Context_Of_The_Caller()
{
// 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<string, object>(StringComparer.OrdinalIgnoreCase)
{
["ABP_CULTURE"] = "custom"
};
(await _templateRenderer.RenderAsync(
ScribanTestTemplateDefinitionProvider.CultureContext,
cultureName: "ar",
globalContext: globalContext
)).ShouldBe("<html lang=\"custom\" dir=\"rtl\">");
}
[Fact]
public async Task Should_Fall_Back_To_English_For_The_Invariant_Culture()
{
using (CultureHelper.Use(CultureInfo.InvariantCulture))
{
(await _templateRenderer.RenderAsync(
ScribanTestTemplateDefinitionProvider.CultureContext
)).ShouldBe("<html lang=\"en\" dir=\"ltr\">");
}
}
}

5
framework/test/Volo.Abp.TextTemplating.Scriban.Tests/Volo/Abp/TextTemplating/Scriban/ScribanTestTemplateDefinitionProvider.cs

@ -6,6 +6,7 @@ public class ScribanTestTemplateDefinitionProvider : TemplateDefinitionProvider
public const string ReflectionEscapeChain = "ReflectionEscapeChain"; public const string ReflectionEscapeChain = "ReflectionEscapeChain";
public const string MethodInvocationAttempt = "MethodInvocationAttempt"; public const string MethodInvocationAttempt = "MethodInvocationAttempt";
public const string NestedPropertyAccess = "NestedPropertyAccess"; public const string NestedPropertyAccess = "NestedPropertyAccess";
public const string CultureContext = "CultureContext";
public override void Define(ITemplateDefinitionContext context) public override void Define(ITemplateDefinitionContext context)
{ {
@ -40,5 +41,9 @@ public class ScribanTestTemplateDefinitionProvider : TemplateDefinitionProvider
context.Add(new TemplateDefinition(NestedPropertyAccess) context.Add(new TemplateDefinition(NestedPropertyAccess)
.WithVirtualFilePath("/SampleTemplates/NestedPropertyAccess.tpl", true) .WithVirtualFilePath("/SampleTemplates/NestedPropertyAccess.tpl", true)
.WithScribanEngine()); .WithScribanEngine());
context.Add(new TemplateDefinition(CultureContext)
.WithVirtualFilePath("/SampleTemplates/CultureContext.tpl", true)
.WithScribanEngine());
} }
} }

Loading…
Cancel
Save