Closed lludol closed 6 years ago
I don't use handlebars unfortunately, but did you figure this out yet?
Hey did you figure this out? I'm closing because it seems like you may have. Let me know. I wasn't able to find much on Google w/handlebars.
Hi.
I was having the exact same issue, so I decided to investigate and here is everything I learned about using the i18n support from email-template with the handlebars template engine.
When you add a i18n
config key in the options of the new Email(options)
call, it will be stored in the Email
object.
When you ask email-template to render a template you give two arguments:
email-template will basically find the template location, find the template engine to use, and, before rendering the template, if you gave some i18n config, it will basically instantiate a I18N
object from the @ladjs/i18n package and forward it the configuration you gave, while also asking it to register all translation functions inside your template locals
.
So, when you give a i18n, if you write:
const html = email.render("user-registration.hbs", { user: { name: "Awesome User" } });
You can imagine it's the same as writing the following (I'm using a console.log() syntax here):
const html = email.render("user-registration.hbs", {
user: { name: "Awesome User" },
t: [Function: bound i18nTranslate],
tn: [Function: bound i18nTranslatePlural],
tl: [Function: bound i18nTranslationList],
th: [Function: bound i18nTranslationHash],
tmf: [Function: bound i18nMessageformat],
// [...] and other functions I won't write here because you get the point
});
These functions come directly from the i18n package. t
is i18n.__
function, tn
is i18n.__n
function, etc.
It looks good. Basically I would like to call t("greetings", { name: "Awesome User" })
in my template and my translation file is this:
{
// [...]
"greetings": "Hello {{ name }},"
}
In handlebars, there is no syntax to create an object literal. But when you quickly read through the handlebars documentation, you can find the following syntax in the "Helpers with hash argument":
{{ t "greetings" name=user.name }}
However, this does not do what you would expect at first. It translates to the following (again, using a console.log() syntax here):
t("greetings", {
name: "t",
hash: { name: "Awesome User" },
data: { root: [Object] },
loc: { start: [Object], end: [Object] }
});
And the result after rendering is Greetings, t
. In that case I get a t
because I used {{ name }}
in my translation file and it matches one of the keys used by handlebars when calling the function so I was even more 🤯 but usually you would have no value when using any other key.
So this syntax cannot be used that way. It is designed to call a handlebars helper function.
This is the solution I chose because it was the easiest for me. I introduced a proper i18n helper for every function added by the i18n support from email-template injecting i18n base functions inside my locals, and I rely on the fact that I always have a recipient
in my locals, which has a key locale
.
function helpers() {
return {
$t(key: string, options: any) {
return options.data.root.t(
{ phrase: key, locale: options.data.root.recipient.locale },
options.hash,
);
},
// and same for $tn, $tl, $th, etc.
};
}
and I use it this way:
// The locals that you want to send to your template. Does not matter where it comes from, I use a simple object here for the sake of the example.
const locals = { user: { name: "Awesome User" } }
const localsForTemplate = {
...helpers(),
...defaultLocals(),
...locals
};
const html = email.render("user-registration.hbs", localsForTemplate);
And the template looks like this:
{{ $t "greetings" name=user.name }}
I know there are other ways to register helpers in handlebars but I did not want to have to instantiate handlebars myself to register the helper and use a custom render function so I just went for this and it works fine.
Hopefully this will be useful to someone.
Handlebars supports sub-expressions which allows to use the return value from a helper as an argument for another helper. So I though I could introduce a helper which will return only the hash from handlebars options:
function helpers() {
return {
object(options: any) {
return options.hash;
},
};
}
And using it this way in the template:
{{ t "greetings" (object name=user.name) }}
This translates to:
t("greetings", { name: "Awesome User" }, {
name: "t",
hash: {},
data: { root: [Object] },
loc: { start: [Object], end: [Object] }
});
I thought this would work but unfortunately, the i18n package way of checking if we invoke the function with named parameters or positional parameters is the following:
var argsEndWithNamedObject = function (args) {
return (
args.length > 1 &&
args[args.length - 1] !== null &&
typeof args[args.length - 1] === 'object'
)
}
It uses the last argument instead of using the second position as a fixed position for values to substitute in the translation. In our case, the last argument is the options object given by handlebars and it matches all the conditions from the i18n package, so it does not work as expected as you get the same result as before: i18n uses this options object as substitutions instead of using what we gave as a second parameter. Unfortunate!
Incredible write-up @pylebecq! Would you like to make a PR to add this to the README? I'd gladly accept.
🙇
Here it is: https://github.com/forwardemail/email-templates/pull/414 👍
Hello,
I can't find how to send parameters to the i18n method ("t"). Here an example:
Email template config:
The result:
The only thing that works is
%s
but if there is too many parameters in the translation key it's not understandable because I have to put every parameter like this:My question is simple, how can I send multiple parameters to Handlebars translation method?
Thank you.