I am trying to send emails from grails and the mail template should be multilingual.
I found that we can render GSP as a string or even in the grails mail plugin we can render the GSP.
In the GSP now I read the static messages from messages.properties assuming that I would define for each languages and my emails would go multi lingual.
Now here is the problem that I am facing
In the template the language is always set to en_US. I am using the below API to get the string of the template. I am not using mail plugin directly as I need to store the send message as string into the database as well
def contents = groovyPageRenderer.render(template:"/layouts/emailparse", model:[mailObj: mailObj])
I also read on other post on forum about setting the language using lang parameter but still the language is set to en_US only.
Would the above method call support specifying language?
Is there an option to use velocity template to do this kind of multilingual mails?
If you're sending the mail from within a request handling thread (e.g. from a controller action) then it should pick up the right locale from the request automatically. If you're sending from a background thread then it won't know what locale to use because there's no "current request" context.
If you have another way to know the correct language to use (e.g. if you store each user's preferred language in the database) then you could reset the LocaleContextHolder
def savedContext = LocaleContextHolder.getLocaleContext()
LocaleContextHolder.setLocale(correctLocaleForThisUser)
try {
def contents = groovyPageRenderer.render(template:"/layouts/emailparse", model:[mailObj: mailObj])
// etc. etc.
} finally {
LocaleContextHolder.setLocaleContext(savedContext)
}
Exactly how you determine the correctLocaleForThisUser depends on your application. You could store each user's preferred language as a property of the User domain object in the database, or if you're using something like the executor plugin's runAsync from a controller action then you could save the request locale while you have access to it and then re-use that in the async task:
// SomeController.groovy
def sendEmail() {
// get locale from the thread-local request and save it in a local variable
// that the runAsync closure can see
Locale localeFromRequest = LocaleContextHolder.getLocale()
runAsync {
def savedContext = LocaleContextHolder.getLocaleContext()
// inject the locale extracted from the request
LocaleContextHolder.setLocale(localeFromRequest)
try {
def contents = groovyPageRenderer.render(template:"/layouts/emailparse", model:[mailObj: mailObj])
// etc. etc.
} finally {
LocaleContextHolder.setLocaleContext(savedContext)
}
}
}
Can you work around this by creating a model containing a list with the correct translations?
For example:
def messages = [:]
messages['hello.world'] = messageSource.getMessage(
"hello.world",
null,
new Locale("nb")
)
def template = groovyPageRenderer.render(
template: '/mail/email',
model:[messages:messages]
)
And then in the view you just write:
<html>
<head>
<title>${messages['hello.world']}</title>
</head>
<body>
</body>
</html>
Related
I'm new with ASP.NET Core and I am trying to make emails, based on templates.
I want to include tags, which are then replaced with specific data from the controller.
I found a engine called razorengine that was exectly what I was looking for, but it is not compatible with .netcoreapp1.1. Are there other alternatives that I could use?
I am using Mailkit btw.
UPDATE:
I found a alternative called DotLiquid, Thanks anyways!
You may also use RazorLight which allows the same using the built-in Razor engine and runs with .Net Core.
You don't need to include an extra library for that.
Just create an html file that lies in your project scope and mark your placeholders e.g. via {{ ... }}
For example create a file wwwroot/emails/welcome.html:
Dear {{user}},
<p>Have fun with this page.</p>
<p>Please click here to activate your account.</p>
Best wishes
Now in your controller action:
public async Task<IActionResult> SendSomeEmailTo(userId)
{
string body;
using (var file = System.IO.File.OpenText("wwwroot/emails/welcome.html"))
{
body = file.ReadToEnd();
}
var user = this.userManager.findById(userId);
// replace your placeholders here
body = body
.Replace("{{user}}", user.getFullName())
.Replace("{{link}}", "www.foo.bar");
// use the email body in your email service of your choice
await sender.SendEmailAsync(user.Email, "Welcome", body);
}
My problem is that i want to preview data on index view without saving data in database. For this purpose, I want to send the list of objects in params section of redirect. And on receiving action want to use that list of objects.But when i include as below
def preview(){
//some code
redirect action: "index", params:[planId:params.planId, beamsInfoList: beamsInfoList]
}
I want something like below to happen.
def index() {
//some code
try{
planInfo.beamInfo = (params.beamsInfoList==null)?planInfo.beamInfo:params.beamsInfoList //beamInfo is also list
//some code
Object[] obj = GRMUtils.calculateTotalBeamsPower(planInfo.beamInfo)
totalPlanPower = (Float)obj[0];
beamPowerMap= (Map<Integer, String>)obj[1];
AmMapUtility utility=new AmMapUtility()
output = utility.generateAMmapFromBeams(planInfo.beamInfo, GRMConstants.POWER_MAP_PAGE);
if(null==output){
flash.error = message(code: 'beammap.noinfoerror.message')
}
}catch(Exception e){
log.error "Excepton occured while loading Power Map", e
}
respond beams, model:[centerLong:output.getCenterLongitude(),centerLat:output.getCenterLatitude(),amMapImageProperty:output.getMapImages(),
amMapLinesProperty:output.getMapLines(), planId:params.planId, planInfo:planInfo, powersControlCarrier: powersControlCarrier, powersTrafficCarrier:powersTrafficCarrier,satPower: planInfo.satellite.satelliteMaxPower, totalPlanPower: totalPlanPower, gatewayPower: planInfo.gateway.gatewayAggregateEIRP,fesOutputPowerLimit:fesOutputPowerLimit, beamPowerMap: beamPowerMap,powerRangeColorMap:output.getReuseColorMap()]
}
It does not redirect to index method and not showing any errors. Both actions are in same controller. I have used flash, but its not helping either as value reflected on second request. I have tried session too, but i am getting error
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
on some DB fetch. I am stuck. I am new to grails and groovy. Please help.
Edit: i have found my list is large, that is why its not redirecting. Please help me with another alternative like how can i use request attribute if it is possible?
I think i have solved the problem. Its working now. All i need to do is to use the request setattribute as
request.beams = beams
And no need to pass the list in params of redirect, which i was earlier used to do. Instead of using redirect, i used forward as below:
request.beams = beams
forward action: "index", params:[planId:params.planId]
The error I get when I try to send an email is:
NoViewsFoundException
You must provide a view for this email. Views should be named
~/Views/Email/VerificationEmail.html.vbhtml.txt.cshtml or
~/Views/Email/VerificationEmail.html.vbhtml.html.cshtml (or aspx for
WebFormsViewEngine) depending on the format you wish to render.
Error on line:
Return Email("~/Views/Email/VerificationEmail.html.vbhtml", model)
Can emails not be sent in .vbhtml, must they be sent in .cshtml? How can this work for VB?
Here is my code controller:
Imports ActionMailer.Net.Mvc
Public Class EmailController
Inherits MailerBase
Public Function VerificationEmail(ByVal model As RegisterModel) As EmailResult
[To].Add(model.Email)
From = "me#my.org"
Subject = "Thanks for registering with us!"
Return Email("~/Views/Email/VerificationEmail.html.vbhtml", model)
End Function
End Class
Here is my view:
#modelType MyBlog.RegisterModel
#Code
Layout = Nothing
End code
Welcome to My Cool Site, #Model.UserName
We need you to verify your email. Click this nifty link to get verified!
#Html.ActionLink("Verify", "Account", New With {.code = Model.Email})
Thanks!
After reading a couple of issues and answer, it could get it to work with this:
public override string ViewPath {
get { return AppDomain.CurrentDomain.BaseDirectory + #"\EmailTemplates\"; }
}
Of course you can have vbhtml email templates you just need to be careful with the naming (the .cshtmls exception message are hardcoded so don't be confused on it)
Your view is named correctly as VerificationEmail.html.vbhtml you just need remove all the prefixes from the view name in the Email call:
Return Email("VerificationEmail", model)
Because ActionMailer will be automatically add the prefixes and select the correct template for you.
Note that currently you cannot use relative viewnames like which start with ~ e.g. "~/Views/..." (I don't know wether this is a bug or feature).
So you need put your mail template to the regular view folders e.g.
/Views/{MailControllerName}/
/View/Shared/
Had the same issue as Chad Richardson. To solve the issue which happens when trying to send email from other area just add this code to Application_Start method:
var razorEngine = ViewEngines.Engines.OfType<RazorViewEngine>().First();
razorEngine.ViewLocationFormats = razorEngine.ViewLocationFormats.Concat(new string[]
{
"~/Areas/.../{0}.cshtml"
}).ToArray();
Want to fetch a value from message.properties file in grails in a job , how can I do that ??
My Job:
def execute() {
// execute task
List<String> emails = NayaxUser.findAllByEmailSent(false)*.username
emails.each {emailAddress->
mailService.sendMail {
//todo: FETCH FROM MESSAGE.PROPERTIES
to emailAddress
from FETCH FROM MESSAGE.PROPERTIES
subject FETCH FROM MESSAGE.PROPERTIES
html body.toString()
}
}
}
You can use:
g.message(code: 'my.message.code')
//or
g.message(code: 'my.message.code', args: [arg1, arg2])
You can inject the messageSource to retrieve the message:
class MyJob {
def messageSource
def execute() {
...
def message = messageSource.getMessage('message.code', ...)
...
}
}
Here's the documentation for getMessage(); you need to provide a couple more method arguments, namely args (an Object[]) and a Locale.
You can get a reference to the messageSource bean from anywhere using:
import org.codehaus.groovy.grails.commons.ApplicationHolder
import org.springframework.context.MessageSource
MessageSource messageSource = ApplicationHolder.application.mainContext.getBean('messageSource')
You can then get the messages themselves using the methods of the MessageSource interface.
Just as an addition to above answers, there could be following places where you need to implement internationalisation or fetch the message from message bundles.
views
controllers
services
Filters
Utility files(e.g. in util package or generalised exception message handling)
Special files e.g. in Shiro security rest realms
Below are the elaborate usage scenarios:
Views:- we have taglib available with message tag. Use this on views.
controllers :- message method is by default available here and locale conversion is automatically handled. See enter link description here
service: we may call taglibs inside services as below:
def myCustomTaglib = grailsApplication.mainContext.getBean('com.custom.MyCustomTagLib');
Or inject messageSource bean as
def messageSource
4.Filters / utility / Special files:- For these you may create something like below and then use it throughout.
String i18nMessage(def input,String defaultMessage) {
String[] languageCode = RequestContextHolder.currentRequestAttributes().request.getHeader("Accept-Language").split("-")
Locale locale = languageCode.length == 2 ? new Locale(languageCode[0], languageCode[1]) : new Locale(languageCode[0])
String message = defaultMessage
try {
message = messageSource.getMessage(input.code,input?.args?.toArray(),locale)
}catch (NoSuchMessageException nsme ){
log.info("No such error message--> ${nsme.getMessage()}")
}
return message
}
Also, if you get exception below:
java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
Then, you might need to add request listener to your web.xml
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
Note: web.xml is not available by default, you need to generate it from template.
These are the most common places you might need message bundle conversions.
Solution at point 4 would work in almost all cases. If you notice locale has been handled here manually which we could pass after fetching from requestHeader or request params optionally.
Hope it helps.
Is there a way to call a taglib closure from inside the grails console? I want to be able to get at the message tag within the grails console and I can not figure this out...
You can get the configured taglib, but most expect to be running in the context of a web request. To get around that you can bind a mock request:
import grails.util.GrailsWebUtil
GrailsWebUtil.bindMockWebRequest ctx
def g = ctx.getBean('org.codehaus.groovy.grails.plugins.web.taglib.ValidationTagLib')
String message = g.message(code: 'default.button.delete.confirm.message')
You can also get messages for other languages by setting the locale of the request, e.g.
import grails.util.GrailsWebUtil
def webRequest = GrailsWebUtil.bindMockWebRequest(ctx)
webRequest.currentRequest.addPreferredLocale(Locale.GERMANY)
def g = ctx.getBean('org.codehaus.groovy.grails.plugins.web.taglib.ValidationTagLib')
String message = g.message(code: 'default.button.delete.confirm.message')
Using #Burt console plugin this is even easier as we don't have to mock the web request...
import org.codehaus.groovy.grails.plugins.web.taglib.ValidationTagLib
// Getting the class name to reduce horizontal
// scrolling in StackOverflow
def g = ctx.getBean(ValidationTagLib.class.getName())
g.message(code: 'default.button.delete.confirm.message');
You can get a list of all the tagLibs in your application by running this code in the console...
// prints a bean name per line.
ctx.getBeanNamesForType(Object).findAll {
it =~ /.*TagLib$/
} .sort() {println it}
// add false to prevent console printing the map out
false