translator-factory
Generate localized translator functions for use in multilingual applications.
This module makes no assumptions about where the translation data is stored or how it should be treated. It simply compiles, collates, and selects the translation messages. This allows your project to be more opinionated about stuff like pluralization rules or if/how to implement message templates.
Getting started
Install
$ npm install translator-factory
Example
Let's assume you have already retrieved your translation data from the file system, a database, etc.
The data is expected to be an object where each property is a valid
locale identifier like en
or en-US
. The value of each property
should be an object mapping message names to message text.
const data = en: gestures: hello: "hello" goodbye: "goodbye" es: gestures: hello: "hola" goodbye: "adios" fr: gestures: "hello": "bonjour" "goodbye": "au revoir" ;
To turn this data into a translator factory:
const createTranslatorFactory = ;const getTranslator = ;
Now, passing a locale string to getTranslator
will return a
translator function that returns formatted messages for that locale.
const spanish = ;const hello = key: "gestures.hello" ;console; // outputs: "hola"
Notice that the nested property structure in the raw data is transformed into a flat, dot-separated, key when selected by the translate function.
API
Reading translation files
const getTranslator = ;
The default export of this module is a function that takes as parameters an object mapping locale identifiers to message key/values:
const data = "en": greeting: "hello" "en-US": greeting: "howdy" "en-GB": greeting: "allo" ;
It takes a second options object with the following defaults:
const options = defaultLocale: "en" selectorPlugins: string;
The defaultLocale
option determines what set of translations should
be used when the locale is not provided or there is no corresponding
translation data. The default value for this option is en
because
it's my primary language, and I could not find a "correct" way to set
this automatically.
The selectorPlugins
option specifies an array of plugins that are
applied before the message is selected from a locale's dictionary.
A plugin is a function that accepts a locale identifier. This function
is called when the translator function is being generated, and allows
you to do initialization for the plugin. It should return a function
that accepts a message selector (e.g., { key: "whatever", ... }
). If
anything is returned from this function, it will be used in place of
the original selector and passed to the next plugin in the chain.
A simple example looks like:
const plugin = { return { console; };};
The templateCompiler
option specifies a function that accepts the
message strings and returns a compiled version of the string. The
assumption is that the string is passed through a compilation function
for a template engine.
The default template compiler is simply a function that returns the string unmodified.
See the recipes section for examples of both selectorPlugins
and
templateCompiler
.
Getting a localized translator
const translate = ;
The parameter locale
is expected to be an ISO 639-1 language
identifier like en
, es
, or de
. It can include an ISO 3166-2
region identifier, too, like en-US
or zh-HK
.
The return value is a translator function.
To get the american english translator:
const american = ;
Translating a message
const translated = ;
The translation function takes as its only parameter a message object
which is only required to have a key
property corresponding to the
message to translate.
console; // outputs: "howdy"
If using the templateCompiler
option mentioned above, you can also
pass a values
property which is an object mapping placeholder names
to values and is passed to the compiled template during message
generation. See the recipes section for more details.
Recipes
Reading translation data from the filesystem
The way that I prefer to store translation data is in a directory
where each file represents a locale. Each is a JSON file named
something like en.json
or zh-HK.json
.
Let's say you have a directory ./translations
:
$ ls translationsen.json en-US.json en-GB.json
You could create the data object like this:
const fs = ;const path = ;const glob = ; const data = {}; for const file of glob const locale = path; datalocale = JSON;
Now, data
is suitable to be passed as the first argument to
createTranslatorFactory
.
Reading translation data from a database
Maybe your translation data is maintained through a web interface and it makes more sense to store the translations in a database.
You might store it in a PostgreSQL database table that looks like:
( locale text not null unique, data jsonb not null default '{}');
You could create the data object like this:
const postgres = ; const sql = ; const rows = await sql`select locale, data from translations`; const data = {}; for const row of rows datarowlocale = rowdata;
Express middleware
Assuming you are using something like express-locale to detect and set a locale for an incoming request, the following middleware function could be used to set up translations for incoming requests:
const express = ;const createTranslatorFactory = ; const getTranslator = ; { reqtranslate = ; ;} const app = ; app;
Translator function creation is cached, so subsequent requests using the same locale will return the same function, ensuring the fastest response.
Now, during any request, messages can be localized:
app;
Pluralization
A common situation when generating translations is the need to alter the message depending on some count (cardinal) or rank (ordinal).
There are a number of ways to implement this, but you might start with a ready-made solution translator-pluralizer which is a plugin for this module. It uses the standard ECMAScript Internationalization API.
Once installed, you can use it like this:
const createTranslatorFactory = ;const createTranslatorPluralizer = ; const data = en: apples: one: "an apple" other: "a bunch of apples" ; const selectorPlugins = createTranslatorPluralizer; const getTranslator = ; const translate = ; console; // outputs: "a bunch of apples"
Using a template engine
By default, messages are returned unchanged. You can add template
support by using the templateCompiler
option.
If you'd like a fast, simple, and capable template engine that doesn't have too many bells and whistles, you might try my template-constructor. It's a template class that allows you to define the placeholder syntax. If you want something that looks like ECMAScript template literal syntax, you could do this:
const createTranslatorFactory = ;const Template = ; const template = prefix: "${" suffix: "}" ; const data = en: greeting: "Hello, ${name=friend}!" ; const templateCompiler = template; const getTranslator = ; const translate = ; console; // outputs: "Hello, friend!"console; // outputs: "Hello, Jane!"
You can use any template engine, however. Here's how you'd change the example above to use handlebars:
- const templateCompiler = (string) => template.compile(string);+ const templateCompiler = (string) => handlebars.compile(string);
Contributing
Testing
$ npm test