Skip to content

Latest commit

 

History

History
264 lines (209 loc) · 10.6 KB

i18n.md

File metadata and controls

264 lines (209 loc) · 10.6 KB

Internationalisation

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 {{messageHelper}} Helper

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.

{{messageHelper "my-message-key"}}

Variable Substitution

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:

{{messageHelper "my-variable-key"}}

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:

{{messageHelper "my-variable-key" subRecord}}

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:

{{messageHelper keys.variable subRecord}}

fluid.handlebars.helper.messageHelper Component Options

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).

Node Usage

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).

Message Bundles

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).

fluid.handlebars.i18n.messageBundleLoader

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"
}

fluid.handlebars.i18n.inlineMessageBundlingMiddleware

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.

Browser Usage

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.