diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs index 8f663e084e..d4d9cfb1c0 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Localization/CultureHelper.cs @@ -10,7 +10,12 @@ namespace Volo.Abp.Localization { Check.NotNull(culture, nameof(culture)); - return Use(new CultureInfo(culture), uiCulture == null ? null : new CultureInfo(uiCulture)); + return Use( + new CultureInfo(culture), + uiCulture == null + ? null + : new CultureInfo(uiCulture) + ); } public static IDisposable Use([NotNull] CultureInfo culture, CultureInfo uiCulture = null) diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs index ea4ba03e27..8e113829ea 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateDefinition.cs @@ -5,8 +5,6 @@ namespace Volo.Abp.TextTemplating { public class TemplateDefinition { - public const string DefaultLayoutPlaceHolder = "_"; - [NotNull] public string Name { get; } @@ -27,7 +25,7 @@ namespace Volo.Abp.TextTemplating [NotNull] string name, [CanBeNull] Type localizationResource = null, bool isLayout = false, - string layout = DefaultLayoutPlaceHolder, + string layout = null, string defaultCultureName = null) { Name = Check.NotNullOrWhiteSpace(name, nameof(name)); diff --git a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs index c3e3f2b70d..4ff2beb9ba 100644 --- a/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs +++ b/framework/src/Volo.Abp.TextTemplating/Volo/Abp/TextTemplating/TemplateRenderer.cs @@ -1,9 +1,13 @@ -using System.Text.RegularExpressions; +using System; +using System.Collections.Generic; +using System.Globalization; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.Localization; using Scriban; +using Scriban.Runtime; using Volo.Abp.DependencyInjection; +using Volo.Abp.Localization; namespace Volo.Abp.TextTemplating { @@ -14,8 +18,8 @@ namespace Volo.Abp.TextTemplating private readonly IStringLocalizerFactory _stringLocalizerFactory; public TemplateRenderer( - ITemplateContentProvider templateContentProvider, - ITemplateDefinitionManager templateDefinitionManager, + ITemplateContentProvider templateContentProvider, + ITemplateDefinitionManager templateDefinitionManager, IStringLocalizerFactory stringLocalizerFactory) { _templateContentProvider = templateContentProvider; @@ -30,43 +34,111 @@ namespace Volo.Abp.TextTemplating { Check.NotNullOrWhiteSpace(templateName, nameof(templateName)); - var templateDefinition = _templateDefinitionManager.Get(templateName); - - var content = await _templateContentProvider.GetContentOrNullAsync( - templateDefinition, - cultureName - ); + if (cultureName == null) + { + cultureName = CultureInfo.CurrentUICulture.Name; + } - if (templateDefinition.LocalizationResource != null) + using (CultureHelper.Use(cultureName)) { - var localizer = _stringLocalizerFactory.Create(templateDefinition.LocalizationResource); - content = Localize(localizer, content); + return await RenderInternalAsync( + templateName, + new Dictionary(), + model + ); } + } - var renderedContent = await Template.Parse(content).RenderAsync(model); + private async Task RenderInternalAsync( + string templateName, + Dictionary globalContext, + object model = null) + { + var templateDefinition = _templateDefinitionManager.Get(templateName); + + var renderedContent = await RenderSingleTemplateAsync( + templateDefinition, + globalContext, + model + ); if (templateDefinition.Layout != null) { - renderedContent = await RenderAsync( + globalContext["content"] = renderedContent; + renderedContent = await RenderInternalAsync( templateDefinition.Layout, - new - { - content = renderedContent - }, - cultureName: cultureName + globalContext ); } return renderedContent; } - public string Localize(IStringLocalizer localizer, string text) + private async Task RenderSingleTemplateAsync( + TemplateDefinition templateDefinition, + Dictionary globalContext, + object model = null) + { + var rawTemplateContent = await _templateContentProvider + .GetContentOrNullAsync( + templateDefinition + ); + + return await RenderTemplateContentWithScribanAsync( + templateDefinition, + rawTemplateContent, + globalContext, + model + ); + } + + protected virtual async Task RenderTemplateContentWithScribanAsync( + TemplateDefinition templateDefinition, + string templateContent, + Dictionary globalContext, + object model = null) + { + var context = CreateScribanTemplateContext( + templateDefinition, + globalContext, + model + ); + + return await Template + .Parse(templateContent) + .RenderAsync(context); + } + + protected virtual TemplateContext CreateScribanTemplateContext( + TemplateDefinition templateDefinition, + Dictionary globalContext, + object model = null) { - return new Regex("\\{\\{#L:.+?\\}\\}") - .Replace( - text, - match => localizer[match.Value.Substring(5, match.Length - 7)] + var context = new TemplateContext(); + + var scriptObject = new ScriptObject(); + + scriptObject.Import(globalContext); + + if (model != null) + { + scriptObject["model"] = model; + } + + if (templateDefinition.LocalizationResource != null) + { + var localizer = _stringLocalizerFactory.Create(templateDefinition.LocalizationResource); + scriptObject.Import( + "l", + new Func( + name => localizer[name] + ) ); + } + + context.PushGlobal(scriptObject); + + return context; } } } \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj b/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj index 916495b116..0d230552e8 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo.Abp.TextTemplating.Tests.csproj @@ -8,11 +8,14 @@ + + + diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs index 0457e0136d..8e8b69e116 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/AbpTextTemplatingTestModule.cs @@ -1,5 +1,7 @@ using Volo.Abp.Autofac; +using Volo.Abp.Localization; using Volo.Abp.Modularity; +using Volo.Abp.TextTemplating.Localization; using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.TextTemplating @@ -7,7 +9,8 @@ namespace Volo.Abp.TextTemplating [DependsOn( typeof(AbpTextTemplatingModule), typeof(AbpTestBaseModule), - typeof(AbpAutofacModule) + typeof(AbpAutofacModule), + typeof(AbpLocalizationModule) )] public class AbpTextTemplatingTestModule : AbpModule { @@ -17,6 +20,13 @@ namespace Volo.Abp.TextTemplating { options.FileSets.AddEmbedded("Volo.Abp.TextTemplating"); }); + + Configure(options => + { + options.Resources + .Add("en") + .AddVirtualJson("/Localization"); + }); } } } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/TestLocalizationSource.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/TestLocalizationSource.cs new file mode 100644 index 0000000000..a4e53e3861 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/TestLocalizationSource.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.TextTemplating.Localization +{ + public class TestLocalizationSource + { + } +} diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/en.json b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/en.json new file mode 100644 index 0000000000..a0dff7e930 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/en.json @@ -0,0 +1,6 @@ +{ + "culture": "en", + "texts": { + "HelloText": "Hello" + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/tr.json b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/tr.json new file mode 100644 index 0000000000..d09f02cd8f --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/Localization/tr.json @@ -0,0 +1,6 @@ +{ + "culture": "tr", + "texts": { + "HelloText": "Merhaba" + } +} \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl index 674b734c8a..7d844c6f7c 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/ForgotPasswordEmail.tpl @@ -1 +1 @@ -Please click to the following link to get an email to reset your password! \ No newline at end of file +{{l "HelloText"}}. Please click to the following link to get an email to reset your password! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/TestTemplateLayout1.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/TestTemplateLayout1.tpl new file mode 100644 index 0000000000..a780e210b0 --- /dev/null +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/TestTemplateLayout1.tpl @@ -0,0 +1 @@ +*BEGIN*{{content}}*END* \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl index 1088362628..1746eed52b 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/en.tpl @@ -1 +1 @@ -Welcome {{name}} to the abp.io! \ No newline at end of file +Welcome {{model.name}} to the abp.io! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl index b457611f1e..581016bc4d 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/SampleTemplates/WelcomeEmail/tr.tpl @@ -1 +1 @@ -Merhaba {{name}}, abp.io'ya hoşgeldiniz! \ No newline at end of file +Merhaba {{model.name}}, abp.io'ya hoşgeldiniz! \ No newline at end of file diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs index 9e76d22ab8..14b3df3486 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TemplateRenderer_Tests.cs @@ -14,16 +14,6 @@ namespace Volo.Abp.TextTemplating _templateRenderer = GetRequiredService(); } - [Fact] - public async Task Should_Get_Rendered_Non_Localized_Template_Content() - { - var content = await _templateRenderer.RenderAsync( - TestTemplates.ForgotPasswordEmail - ); - - content.ShouldBe("Please click to the following link to get an email to reset your password!"); - } - [Fact] public async Task Should_Get_Rendered_Localized_Template_Content_With_Different_Cultures() { @@ -86,6 +76,20 @@ namespace Volo.Abp.TextTemplating )).ShouldBe("Welcome John to the abp.io!"); } + [Fact] + public async Task Should_Get_Rendered_Inline_Localized_Template() + { + (await _templateRenderer.RenderAsync( + TestTemplates.ForgotPasswordEmail, + cultureName: "en" + )).ShouldBe("*BEGIN*Hello. Please click to the following link to get an email to reset your password!*END*"); + + (await _templateRenderer.RenderAsync( + TestTemplates.ForgotPasswordEmail, + cultureName: "tr" + )).ShouldBe("*BEGIN*Merhaba. Please click to the following link to get an email to reset your password!*END*"); + } + private class WelcomeEmailModel { public string Name { get; set; } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs index 7a1ff4121b..8fe7c98e38 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/TestTemplateDefinitionProvider.cs @@ -1,4 +1,6 @@ -namespace Volo.Abp.TextTemplating +using Volo.Abp.TextTemplating.Localization; + +namespace Volo.Abp.TextTemplating { public class TestTemplateDefinitionProvider : TemplateDefinitionProvider { @@ -13,13 +15,18 @@ context.Add( new TemplateDefinition( - TestTemplates.ForgotPasswordEmail + TestTemplates.ForgotPasswordEmail, + localizationResource: typeof(TestLocalizationSource), + layout: TestTemplates.TestTemplateLayout1 ).AddVirtualFiles("/SampleTemplates/ForgotPasswordEmail.tpl") ); - context.Add(new TemplateDefinition( - TestTemplates.TestTemplateLayout1 - )); + context.Add( + new TemplateDefinition( + TestTemplates.TestTemplateLayout1, + isLayout: true + ).AddVirtualFiles("/SampleTemplates/TestTemplateLayout1.tpl") + ); } } } diff --git a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs index 1c636d7813..2c104b632d 100644 --- a/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs +++ b/framework/test/Volo.Abp.TextTemplating.Tests/Volo/Abp/TextTemplating/VirtualFiles/VirtualFileTemplateContributor_Tests.cs @@ -21,11 +21,11 @@ namespace Volo.Abp.TextTemplating.VirtualFiles contributor .GetOrNull("en") - .ShouldBe("Welcome {{name}} to the abp.io!"); + .ShouldBe("Welcome {{model.name}} to the abp.io!"); contributor .GetOrNull("tr") - .ShouldBe("Merhaba {{name}}, abp.io'ya hoşgeldiniz!"); + .ShouldBe("Merhaba {{model.name}}, abp.io'ya hoşgeldiniz!"); } [Fact] @@ -44,7 +44,7 @@ namespace Volo.Abp.TextTemplating.VirtualFiles contributor .GetOrNull() - .ShouldBe("Please click to the following link to get an email to reset your password!"); + .ShouldBe("{{l \"HelloText\"}}. Please click to the following link to get an email to reset your password!"); } } }