MCPcopy
hub / github.com/mashpie/i18n-node

github.com/mashpie/i18n-node @v.0.15.3 sqlite

repository ↗ · DeepWiki ↗ · release v.0.15.3 ↗
43 symbols 101 edges 50 files 0 documented · 0%
README

i18n

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 [![Test Coverage][codecov-image]][codecov-url] [![NPM version][npm-image]][npm-url] npm [![Known Vulnerabilities][snyk-image]][snyk-url] [![FOSSA Status][fossa-image]][fossa-url]

Buy Me A Coffee

Install

npm install i18n --save

Synopsis

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.


Usage

With 0.12.0 i18n now provides options to be used as instance or singleton.

  • Instances allow to work with multiple different configurations and encapsulate resources and states.
  • Singletons allow to share configuration, state and resources across multiple requires, modules or files.

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.

As Instance

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')
})

As Singleton

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'))
}

CLI within global scope

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.

Middleware in express.js

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')}

Some examples for common setups

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

API

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.

i18n.configure()

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')
});

list of all configuration options

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.

Some words on register option

Used 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

Some words on staticCatalog option

Instead 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

Some words on parser option

Instead 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
})

i18n.init()

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

  1. guess the language of a visitor by it's browser settings, cookie or query parameter
  2. set that language in any of the "usual" objects provided by the framework

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.

i18n.__()

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

Core symbols most depended-on inside this repo

getJson
called by 38
test/i18n.writenewPhrase.js
visitLinks
called by 22
examples/testlib/visitlinks.js
logDebug
called by 10
i18n.js
testApi
called by 9
test/i18n.constructor.js
getJson
called by 7
test/i18n.noTraversal.js
render
called by 7
examples/express4-setLocale/index.js
translate
called by 6
i18n.js
getFallback
called by 5
i18n.js

Shape

Function 43

Languages

TypeScript100%

Modules by API surface

i18n.js28 symbols
test/i18n.verifyLocaleSelectionMethods.js3 symbols
test/i18n.writenewPhrase.js2 symbols
test/i18n.retryInDefaultLocaleWithSync.js2 symbols
test/i18n.makePlurals.js2 symbols
examples/express4-setLocale/index.js2 symbols
test/i18n.noTraversal.js1 symbols
test/i18n.constructor.js1 symbols
examples/testlib/visitrest.js1 symbols
examples/testlib/visitlinks.js1 symbols

Dependencies from manifests, versioned

@messageformat/core3.4.0 · 1×
async3.2.6 · 1×
cookie-parser1.4.7 · 1×
debug4.4.3 · 1×
eslint8.57.1 · 1×
eslint-config-prettier8.10.2 · 1×
eslint-config-standard17.1.0 · 1×
eslint-plugin-import2.32.0 · 1×
eslint-plugin-node11.1.0 · 1×
eslint-plugin-prettier4.2.5 · 1×
eslint-plugin-promise6.6.0 · 1×
eslint-plugin-standard5.0.0 · 1×

For agents

$ claude mcp add i18n-node \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact