This package leverages parts of the Infusion framework that support localisation, and uses them to provide a Handlebars helper that can replace a message key with localised/internationalised text. This page covers the basic usage, and the steps required to use this helper from Node or a browser.
The fluid.handlebars.helper.messageHelper
grade is designed to be wired into the renderer provided by
this package. See below for examples of how this works in each environment. Once this helper is available, you can
refer to it in your templates, and use it to display localised/internationalised messages. So, let's say that you have
a message bundle like the following:
{
"my-message-key": "Hello, world",
"my-variable-key": "Displaying %items.length %type(s)."
}
The following handlebars template content would display the above key.
Variable substitution is also supported. Let's say you passed the following context to the renderer:
{
"type": "cat",
"items": ["house cat", "wild cat"],
"subRecord": {
"type": "dog",
"items": ["junkyard dog", "wild dog"]
},
"message-keys": {
"static": "my-message-key",
"variable": "my-variable-key"
}
}
The following handlebars template content would make use of the full context:
The result would be Displaying 2 cat(s)
. Note that, like fluid.stringTemplate
,
you can reference deep material using an EL Path. So, %items.length
resolves to the length
property of the
items
array.
You can also optionally specify which part of the context to use for variable substitutions, as in this handlebars template content:
The result would be Displaying 2 dog(s)
. In both these examples, the message key has been a literal string (hence the
quotes). You can also use any variable in the context. So, the following handlebars template content results in the
same output as the previous example:
The component that provides the above helper provides the following configuration options:
Option | Type | Description |
---|---|---|
defaultLocale |
{String} |
The default locale to use if no translation is available for a user's preferred locale/language. |
messages |
{Object} |
A map of message keys and string templates. Generally assembled from one or more files (see below). |
By default, the {{messageHelper}}
helper is wired into fluid.handlebars.standaloneRenderer
,
which means it is available both through the Express view engine and in rendering content outside of Express (such as
generating emails based on templates).
As was done in earlier work on the preferences framework, the localisation and internationalisation support used in this package consists of "message bundles", JSON files that consist of keys and string templates, as in:
{
"my-motto": "Through hardships to the stars"
}
Let's assume this is our English-language wording, and that it is saved to a file called myPackageMessages-en.json
.
If I want to provide a Dutch translation of the same content, I could save a file myPackageMessages-nl.json
in the
same directory that contains:
{
"my-motto": "Naar de sterren door moelijkheiden"
}
(Thanks to Wikipedia for that translation.) The nl
and en
suffixes in the previous examples indicate the language used in each file. We might also choose to use a locale, i.e.
a language and country to distinguish nl_NL
(Dutch as used in The Netherlands) from nl_BE
(Flemish Dutch as used in
Belgium). It is recommended to provide not only variations, but a default for the language, for example, by using the
filename myPackageMessages-nl.json
for Netherlands Dutch and myPackageMessages-nl_BE.json
for Flemish. In this
manner, we can work with either a full locale (nl_BE
) or simply a language prefix (nl
).
As there are multiple components that need access to the same "bundle of message bundles", a common component is provided that loads all bundles from the filesystem. This component supports the following configuration options:
Option | Type | Description |
---|---|---|
defaultLocale |
{String} |
The locale to use if the user does not provide one in their request headers. Defaults to en_us . |
messageDirs |
{Array} |
An array referencing one or more directories that contain message bundle files. Paths may either be absolute or package-relative paths. |
The messageDirs
option supports an array of values. Each directory will have its contents scanned and all bundles
will be categorised by language and/or locale. Message bundles with the same name in multiple directories will be
merged. If both bundles have the same message key, the right-most file wins, as is the case with
options merging.
Let's say that we are extending someone else's work, we may want to reuse and selectively override their message bundle
content. Their package is registered as package1
using fluid.module.register
,
and has its message bundles located in %package1/src/messageBundles
. Let's say that that directory contains a file
named myModuleMessages-en.json
, with the following content:
{
"my-component-title": "My Generic Tool",
"my-component-description": "This tool does neat things."
}
If we want to extend their work and localise their wording, you might save the following content to the file
src/messageBundles/myModuleMessages.json
and register your own package as myPackage
:
{
"my-component-title": "My Rebranded Tool"
}
If our messageDirs
option is set to ["%package1/src/messageBundles","%myPackage/src/messageBundles"]
, the merged
message bundle would look something like:
{
"en": {
"my-component-title": "My Rebranded Tool",
"my-component-description": "This tool does neat things."
}
}
This is how the component keeps track of all available message bundles by language and/or locale. Files that lack a
language or locale suffix will be saved under a key that matches the value of options.defaultLocale
(see above).
This is an intermediate format that allows the bundling middleware to respond to requests in any supported language.
An individual request can include Accepts-Language
headers indicating what languages are desired. Let's say we have
the following combined set of message bundles:
{
"en_US": {
"football-title": "soccer"
},
"en_GB": {
"elevator-title": "lift"
},
"en": {
"football-title": "football",
"elevator-title": "elevator"
}
}
Message resolution fails over from most to least specific, i.e. first messages are resolved against the locale, and then
against the language, and then against the language that corresponds to options.defaultLocale
. So, a request for
a locale of en_GB
would receive a compiled message bundle like:
{
"elevator-title": "lift",
"football-title": "soccer"
}
In this example, elevator-title
is pulled from the locale, football-title
is inherited from the language. By
comparison, a request for en_US
would receive a compiled message bundle like:
{
"elevator-title": "elevator",
"football-title": "soccer"
}
In the second case, football-title
is determined by the locale, and elevator-title
is inherited from the language.
A request for a locale for which we have neither locale or language data receives the same as if we had requested
the default language. So, if we receive a request for fr_FR
, the compiled message bundled would be the same as in
the previous example. If we have some but not all messages localised, the message bundle fails over to the default
language. Let's say our combined set of message bundles looks like:
{
"fr_FR": {
"daily-greeting": "bon jour"
},
"fr": {
"daily-greeting": "bon soir"
},
"en_US": {
"daily-greeting": "good day",
"thanks-response": "thanks"
},
"en": {
"daily-greeting": "good night",
"thanks-response": "cheers"
}
}
If options.defaultLocale
is set to en_US
and we request fr_FR
, the compiled message bundle looks like:
{
"daily-greeting": "bon jour",
"thanks-response": "thanks"
}
Since both are locales, they both fail over from locale to language. This can only happen if we have locale data. If
options.defaultLocale
is set to en
and we request fr
, the compiled message bundle looks like:
{
"daily-greeting": "bon soir",
"thanks-response": "cheers"
}
To simplify the browser side usage, a piece of fluid.express
middleware is provided that starts with the standard
language headers provided by the browser request,
and follows the failover strategy outlined above. You are expected to populate this component's messageBundles
member
with individual message bundles, keyed by language or locale. In most cases you will want to use the
fluid.handlebars.i18n.messageBundleLoader
grade described above to populate this.
The base fluid.handlebars.renderer.standalone
grade is adequate if you wish to include all message
bundles and handlebars templates as part of your grade definition. See those docs for details.
For more complex setups, the fluid.handlebars.renderer.serverMessageAware
grade is more suitable. This
communicates with two pieces of middleware to retrieve its templates and message bundles.