Lightweight simple translation module with dynamic JSON storage. Supports plain vanilla Node.js apps and should work with any framework (like Express, restify and probably more) that exposes an app.use() method passing in res and req objects.
Uses common __('...') syntax in app and templates.
Stores language files in json files compatible to webtranslateit json format.
Adds new strings on-the-fly when first used in your app.
No extra parsing needed.
[![Test Coverage][codecov-image]][codecov-url]
[![NPM version][npm-image]][npm-url]
[![Known Vulnerabilities][snyk-image]][snyk-url]
[![FOSSA Status][fossa-image]][fossa-url]
npm install i18n --save
const http = require('http')
const path = require('path')
const { I18n } = require('i18n')
const i18n = new I18n({
locales: ['en', 'de'],
directory: path.join(__dirname, 'locales')
})
const app = http.createServer((req, res) => {
i18n.init(req, res)
res.end(res.__('Hello'))
})
app.listen(3000, '127.0.0.1')
This wires up a plain http server and return "Hello" or "Hallo" depending on browsers 'Accept-Language'. The first configured locale 'en' will default in case the browser doesn't include any of those locales in his request.
With 0.12.0 i18n now provides options to be used as instance or singleton.
Before 0.12.0 singleton usage was the only option. Instances give much more intuitive control and should be considered the "better practice" in complex setups.
Minimal example, just setup two locales and a project specific directory.
/**
* require I18n with capital I as constructor
*/
const { I18n } = require('i18n')
/**
* create a new instance with it's configuration
*/
const i18n = new I18n({
locales: ['en', 'de'],
directory: path.join(__dirname, 'locales')
})
Alternatively split creation and configuration, useful when split up into different modules for bootstrapping.
/**
* require I18n with capital I as constructor
*/
const { I18n } = require('i18n')
/**
* create a new instance
*/
const i18n = new I18n()
/**
* later in code configure
*/
i18n.configure({
locales: ['en', 'de'],
directory: path.join(__dirname, '/locales')
})
Same Minimal example, just setup two locales and a project specific directory.
const i18n = require('i18n')
/**
* configure shared state
*/
i18n.configure({
locales: ['en', 'de'],
directory: path.join(__dirname, '/locales')
})
Now you are ready to use a global i18n.__('Hello').
Require i18nin another file reuses same configuration and shares state:
const i18n = require('i18n')
module.exports = () => {
console.log(i18n.__('Hello'))
}
In your cli, when not registered to a specific object:
var greeting = i18n.__('Hello')
Global assumes you share a common state of localization in any time and any part of your app. This is usually fine in cli-style scripts. When serving responses to http requests you'll need to make sure that scope is NOT shared globally but attached to your request object.
In an express app, you might use i18n.init to gather language settings of your visitors and also bind your helpers to response object honoring request objects locale, ie:
// Configuration
app.configure(function () {
// [...]
// default: using 'accept-language' header to guess language settings
app.use(i18n.init)
// [...]
})
in your apps methods:
app.get('/de', function (req, res) {
var greeting = res.__('Hello')
})
in your templates (depending on your template engine)
<%= __('Hello') %>
${__('Hello')}
See tested examples inside /examples for some inspiration in node and express or browse these gists:
PLEASE NOTE: Those gist examples worked until node 0.12.x only
For serving the same static files with different language url, you could:
app.use(express.static(__dirname + '/www'))
app.use('/en', express.static(__dirname + '/www'))
app.use('/de', express.static(__dirname + '/www'))
The api is subject of incremental development. That means, it should not change nor remove any aspect of the current api but new features and options will get added that don't break compatibility backwards within a major version.
You should configure your application once to bootstrap all aspects of i18n. You should not configure i18n in each loop when used in an http based scenario. During configuration, i18n reads all known locales into memory and prepares to keep that superfast object in sync with your files in filesystem as configured
i18n.configure({
locales: ['en', 'de'],
directory: path.join(__dirname, 'locales')
})
Since 0.7.0 you may even omit the locales setting and just configure a directory. i18n will read all files within that directory and detect all given locales by their filenames.
i18n.configure({
directory: path.join(__dirname, 'locales')
});
i18n.configure({
// setup some locales - other locales default to en silently
locales: ['en', 'de'],
// fallback from Dutch to German and from any localized German (de-at, de-li etc.) to German
fallbacks: { nl: 'de', 'de-*': 'de' },
// you may alter a site wide default locale
defaultLocale: 'en',
// will return translation from defaultLocale in case current locale doesn't provide it
retryInDefaultLocale: false,
// sets a custom cookie name to parse locale settings from - defaults to NULL
cookie: 'yourcookiename',
// sets a custom header name to read the language preference from - accept-language header by default
header: 'accept-language',
// query parameter to switch locale (ie. /home?lang=ch) - defaults to NULL
queryParameter: 'lang',
// where to store json files - defaults to './locales' relative to modules directory
directory: './mylocales',
// control mode on directory creation - defaults to NULL which defaults to umask of process user. Setting has no effect on win.
directoryPermissions: '755',
// watch for changes in JSON files to reload locale on updates - defaults to false
autoReload: true,
// whether to write new locale information to disk - defaults to true
updateFiles: false,
// sync locale information across all files - defaults to false
syncFiles: false,
// what to use as the indentation unit - defaults to "\t"
indent: '\t',
// setting extension of json files - defaults to '.json' (you might want to set this to '.js' according to webtranslateit)
extension: '.json',
// setting prefix of json files name - default to none '' (in case you use different locale files naming scheme (webapp-en.json), rather then just en.json)
prefix: 'webapp-',
// enable object notation
objectNotation: false,
// setting of log level DEBUG - default to require('debug')('i18n:debug')
logDebugFn: function (msg) {
console.log('debug', msg)
},
// setting of log level WARN - default to require('debug')('i18n:warn')
logWarnFn: function (msg) {
console.log('warn', msg)
},
// setting of log level ERROR - default to require('debug')('i18n:error')
logErrorFn: function (msg) {
console.log('error', msg)
},
// used to alter the behaviour of missing keys
missingKeyFn: function (locale, value) {
return value
},
// object or [obj1, obj2] to bind the i18n api and current locale to - defaults to null
register: global,
// hash to specify different aliases for i18n's internal methods to apply on the request/response objects (method -> alias).
// note that this will *not* overwrite existing properties with the same name
api: {
__: 't', // now req.__ becomes req.t
__n: 'tn' // and req.__n can be called as req.tn
},
// When set to true, downcase locale when passed on queryParam; e.g. lang=en-US becomes en-us.
// When set to false, the queryParam value will be used as passed;
// e.g. lang=en-US remains en-US.
preserveLegacyCase: true, // defaults to true
// set the language catalog statically
// also overrides locales
staticCatalog: {
de: {
/* require('de.json') */
}
},
// use mustache with customTags (https://www.npmjs.com/package/mustache#custom-delimiters) or disable mustache entirely
mustacheConfig: {
tags: ['{{', '}}'],
disable: false
},
// Parser can be any object that responds to .parse & .stringify
parser: JSON
})
The locale itself is gathered directly from the browser by header, cookie or query parameter depending on your setup.
In case of cookie you will also need to enable cookies for your application. For express this done by adding app.use(express.cookieParser())). Now use the same cookie name when setting it in the user preferred language, like here:
res.cookie('yourcookiename', 'de', { maxAge: 900000, httpOnly: true })
After this and until the cookie expires, i18n.init() will get the value of the cookie to set that language instead of default for every page.
register optionUsed especially in a CLI-like script. You won't use any i18n.init() to guess language settings from your user, thus i18n won't bind itself to any res or req object and will work like a static module.
var anyObject = {}
i18n.configure({
locales: ['en', 'de'],
register: anyObject
})
anyObject.setLocale('de')
anyObject.__('Hallo') // --> Hallo
Cli usage is a special use case, as we won't need to maintain any transaction / concurrency aware setting of locale, so you could even choose to bind i18n to global scope of node:
i18n.configure({
locales: ['en', 'de'],
register: global
})
i18n.setLocale('de')
__('Hello') // --> Hallo
staticCatalog optionInstead of letting i18n load translations from a given directory you may pass translations as static js object right on configuration. This supports any method that returns a key:value translation object ({ Hello: 'Hallo', Cat: 'Katze' }). So you might even mix native json with js modules and parsed yaml files, like so:
// DEMO: quickly add yaml support
const yaml = require('js-yaml')
const fs = require('fs')
// configure and load translations from different locations
i18n.configure({
staticCatalog: {
de: require('../../locales/de-as-json.json'),
en: require('../../locales/en-as-module.js'),
fr: yaml.safeLoad(fs.readFileSync('../../locales/fr-as-yaml.yml', 'utf8'))
},
defaultLocale: 'de'
})
NOTE: Enabling staticCatalog disables all other fs realated options such as updateFiles, autoReload and syncFiles
parser optionInstead of parsing all file contents as JSON, you can parse them as YAML or any other format you like
const YAML = require('yaml')
i18n.configure({
extension: '.yml',
parser: YAML
})
When used as middleware in frameworks like express to setup the current environment for each loop. In contrast to configure the i18n.init() should be called within each request-response-cycle.
var app = express()
app.use(cookieParser())
app.use(i18n.init)
When i18n is used like this, the i18n.init() tries to
Express would call i18n.init(req, res, next), which is "classic" and adopted by many frameworks. Thus i18n will attach it's api to that schema:
{
req: {
locals: {},
res: {
locals: {},
}
}
}
and add it's extra attributes and methods, like so:
{
req: {
locals: {
locale: "de",
__: [function],
__n: [function],
[...]
},
res: {
locals: {
locale: "de",
__: [function],
__n: [function],
[...]
},
locale: "de",
__: [function],
__n: [function],
[...]
},
locale: "de",
__: [function],
__n: [function],
[...]
}
}
Now each local object (ie. res.locals) is setup with it's own "private" locale and methods to get the appropriate translation from the global catalog.
Translates a single phrase and adds it to locales if unknown. Returns translated parsed and substituted string.
```js // template and global (this.locale == 'de') ('Hello') // Hallo ('Hello %s', 'Marcus') // Hallo Marcus __('Hello {{name}}', { name: 'Marcus' }) // Hallo Marcus
// scoped via req object (req.locale == 'de') req.('Hello') // Hallo req.('Hello %s', 'M
$ claude mcp add i18n-node \
-- python -m otcore.mcp_server <graph>