{"id":13454589,"url":"https://github.com/mashpie/i18n-node","last_synced_at":"2025-05-13T15:08:25.543Z","repository":{"id":509173,"uuid":"1527521","full_name":"mashpie/i18n-node","owner":"mashpie","description":"Lightweight simple translation module for node.js / express.js with dynamic json storage. Uses common __('...') syntax in app and templates.","archived":false,"fork":false,"pushed_at":"2025-02-24T10:39:59.000Z","size":1998,"stargazers_count":3083,"open_issues_count":78,"forks_count":419,"subscribers_count":36,"default_branch":"master","last_synced_at":"2025-05-06T14:56:47.663Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mashpie.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"mashpie","tidelift":"npm/i18n"}},"created_at":"2011-03-25T22:17:37.000Z","updated_at":"2025-05-01T11:55:41.000Z","dependencies_parsed_at":"2023-07-05T15:01:11.220Z","dependency_job_id":"0970c4a4-801a-4395-8e6c-715d85c29ed2","html_url":"https://github.com/mashpie/i18n-node","commit_stats":{"total_commits":480,"total_committers":76,"mean_commits":6.315789473684211,"dds":"0.38749999999999996","last_synced_commit":"ba496b4d40a16149437f25d19e209c6078be4f84"},"previous_names":[],"tags_count":34,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mashpie%2Fi18n-node","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mashpie%2Fi18n-node/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mashpie%2Fi18n-node/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mashpie%2Fi18n-node/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mashpie","download_url":"https://codeload.github.com/mashpie/i18n-node/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253969231,"owners_count":21992262,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T08:00:55.634Z","updated_at":"2025-05-13T15:08:25.508Z","avatar_url":"https://github.com/mashpie.png","language":"JavaScript","readme":"# i18n\n\nLightweight 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.\nUses common __('...') syntax in app and templates.\nStores language files in json files compatible to [webtranslateit](http://webtranslateit.com/) json format.\nAdds new strings on-the-fly when first used in your app.\nNo extra parsing needed.\n\n![Test](https://github.com/mashpie/i18n-node/actions/workflows/node.js.yml/badge.svg)\n[![Test Coverage][coveralls-image]][coveralls-url]\n[![NPM version][npm-image]][npm-url]\n![npm](https://img.shields.io/npm/dw/i18n)\n[![Known Vulnerabilities][snyk-image]][snyk-url]\n[![FOSSA Status][fossa-image]][fossa-url]\n\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://www.buymeacoffee.com/mashpie\" target=\"_blank\"\u003e\u003cimg src=\"https://cdn.buymeacoffee.com/buttons/default-orange.png\" alt=\"Buy Me A Coffee\" style=\"height: 51px !important;width: 217px !important; border-radius: 0.5rem !important;\" width=\"217\" height=\"51\"\u003e\u003c/a\u003e\u003cbr\u003e\n\u003c/p\u003e\n\n## Install\n```sh\nnpm install i18n --save\n```\n\n## Synopsis\n\n```js\nconst http = require('http')\nconst path = require('path')\nconst { I18n } = require('i18n')\n\nconst i18n = new I18n({\n  locales: ['en', 'de'],\n  directory: path.join(__dirname, 'locales')\n})\n\nconst app = http.createServer((req, res) =\u003e {\n  i18n.init(req, res)\n  res.end(res.__('Hello'))\n})\n\napp.listen(3000, '127.0.0.1')\n```\nThis 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.\n\n---\n\n## Usage\n\nWith __0.12.0__ `i18n` now provides options to be used as instance or singleton.\n\n- __Instances__ allow to work with multiple different configurations and encapsulate resources and states.\n- __Singletons__ allow to share configuration, state and resources across multiple requires, modules or files.\n\nBefore __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.\n\n### As Instance\n\nMinimal example, just setup two locales and a project specific directory.\n\n```js\n/**\n * require I18n with capital I as constructor\n */\nconst { I18n } = require('i18n')\n\n/**\n * create a new instance with it's configuration\n */\nconst i18n = new I18n({\n  locales: ['en', 'de'],\n  directory: path.join(__dirname, 'locales')\n})\n```\n\nAlternatively split creation and configuration, useful when split up into different modules for bootstrapping.\n\n```js\n/**\n * require I18n with capital I as constructor\n */\nconst { I18n } = require('i18n')\n\n/**\n * create a new instance\n */\nconst i18n = new I18n()\n\n/**\n * later in code configure\n */\ni18n.configure({\n  locales: ['en', 'de'],\n  directory: path.join(__dirname, '/locales')\n})\n```\n\n\n### As Singleton\n\nSame Minimal example, just setup two locales and a project specific directory.\n\n```js\nconst i18n = require('i18n')\n\n/**\n * configure shared state\n */\ni18n.configure({\n  locales: ['en', 'de'],\n  directory: path.join(__dirname, '/locales')\n})\n```\n\nNow you are ready to use a global `i18n.__('Hello')`.\n\nRequire `i18n`in another file reuses same configuration and shares state:\n\n```js\nconst i18n = require('i18n')\n\nmodule.exports = () =\u003e {\n  console.log(i18n.__('Hello'))\n}\n```\n\n### CLI within global scope\n\nIn your cli, when not registered to a specific object:\n\n```js\nvar greeting = i18n.__('Hello')\n```\n\n\n\u003e **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.\n\n### Middleware in express.js\n\nIn 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:\n\n```js\n// Configuration\napp.configure(function () {\n  // [...]\n\n  // default: using 'accept-language' header to guess language settings\n  app.use(i18n.init)\n\n  // [...]\n})\n```\n\nin your apps methods:\n\n```js\napp.get('/de', function (req, res) {\n  var greeting = res.__('Hello')\n})\n```\n\n\nin your templates (depending on your template engine)\n\n```ejs\n\u003c%= __('Hello') %\u003e\n\n${__('Hello')}\n```\n\n### Some examples for common setups\n\nSee [tested examples](https://github.com/mashpie/i18n-node/tree/master/examples) inside `/examples` for some inspiration in node and express or browse these gists:\n\n\u003e PLEASE NOTE: Those gist examples worked until node 0.12.x only\n\n* [plain Node.js + HTTP](https://gist.github.com/mashpie/5188567)\n* [plain Node.js + restify](https://gist.github.com/mashpie/5694251)\n* [Express 3 + cookie](https://gist.github.com/mashpie/5124626)\n* [Express 3 + hbs 2 (+ cookie)](https://gist.github.com/mashpie/5246334)\n* [Express 3 + Mustache (+ cookie)](https://gist.github.com/mashpie/5247373)\n* [Express 4 + cookie](https://gist.github.com/mashpie/08e5a0ee764f7b6b1355)\n\nFor serving the same static files with different language url, you could:\n\n```js\napp.use(express.static(__dirname + '/www'))\napp.use('/en', express.static(__dirname + '/www'))\napp.use('/de', express.static(__dirname + '/www'))\n```\n---\n\n## API\n\nThe 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.\n\n### i18n.configure()\n\nYou 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\n\n```js\ni18n.configure({\n  locales: ['en', 'de'],\n  directory: path.join(__dirname, 'locales')\n})\n```\n\n**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.\n\n```js\ni18n.configure({\n  directory: path.join(__dirname, 'locales')\n});\n```\n\n#### list of all configuration options\n```js\ni18n.configure({\n  // setup some locales - other locales default to en silently\n  locales: ['en', 'de'],\n\n  // fallback from Dutch to German and from any localized German (de-at, de-li etc.) to German\n  fallbacks: { nl: 'de', 'de-*': 'de' },\n\n  // you may alter a site wide default locale\n  defaultLocale: 'en',\n\n  // will return translation from defaultLocale in case current locale doesn't provide it\n  retryInDefaultLocale: false,\n\n  // sets a custom cookie name to parse locale settings from - defaults to NULL\n  cookie: 'yourcookiename',\n\n  // sets a custom header name to read the language preference from - accept-language header by default\n  header: 'accept-language',\n\n  // query parameter to switch locale (ie. /home?lang=ch) - defaults to NULL\n  queryParameter: 'lang',\n\n  // where to store json files - defaults to './locales' relative to modules directory\n  directory: './mylocales',\n\n  // control mode on directory creation - defaults to NULL which defaults to umask of process user. Setting has no effect on win.\n  directoryPermissions: '755',\n\n  // watch for changes in JSON files to reload locale on updates - defaults to false\n  autoReload: true,\n\n  // whether to write new locale information to disk - defaults to true\n  updateFiles: false,\n\n  // sync locale information across all files - defaults to false\n  syncFiles: false,\n\n  // what to use as the indentation unit - defaults to \"\\t\"\n  indent: '\\t',\n\n  // setting extension of json files - defaults to '.json' (you might want to set this to '.js' according to webtranslateit)\n  extension: '.json',\n\n  // 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)\n  prefix: 'webapp-',\n\n  // enable object notation\n  objectNotation: false,\n\n  // setting of log level DEBUG - default to require('debug')('i18n:debug')\n  logDebugFn: function (msg) {\n    console.log('debug', msg)\n  },\n\n  // setting of log level WARN - default to require('debug')('i18n:warn')\n  logWarnFn: function (msg) {\n    console.log('warn', msg)\n  },\n\n  // setting of log level ERROR - default to require('debug')('i18n:error')\n  logErrorFn: function (msg) {\n    console.log('error', msg)\n  },\n\n  // used to alter the behaviour of missing keys\n  missingKeyFn: function (locale, value) {\n    return value\n  },\n\n  // object or [obj1, obj2] to bind the i18n api and current locale to - defaults to null\n  register: global,\n\n  // hash to specify different aliases for i18n's internal methods to apply on the request/response objects (method -\u003e alias).\n  // note that this will *not* overwrite existing properties with the same name\n  api: {\n    __: 't', // now req.__ becomes req.t\n    __n: 'tn' // and req.__n can be called as req.tn\n  },\n\n  // When set to true, downcase locale when passed on queryParam; e.g. lang=en-US becomes en-us.\n  // When set to false, the queryParam value will be used as passed;\n  // e.g. lang=en-US remains en-US.\n  preserveLegacyCase: true, // defaults to true\n\n  // set the language catalog statically\n  // also overrides locales\n  staticCatalog: {\n    de: {\n      /* require('de.json') */\n    }\n  },\n\n  // use mustache with customTags (https://www.npmjs.com/package/mustache#custom-delimiters) or disable mustache entirely\n  mustacheConfig: {\n    tags: ['{{', '}}'],\n    disable: false\n  },\n\n  // Parser can be any object that responds to .parse \u0026 .stringify\n  parser: JSON\n})\n```\n\nThe locale itself is gathered directly from the browser by header, cookie or query parameter depending on your setup.\n\nIn 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:\n\n```js\nres.cookie('yourcookiename', 'de', { maxAge: 900000, httpOnly: true })\n```\n\nAfter 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.\n\n#### Some words on `register` option\n\nUsed 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.\n\n```js\nvar anyObject = {}\n\ni18n.configure({\n  locales: ['en', 'de'],\n  register: anyObject\n})\n\nanyObject.setLocale('de')\nanyObject.__('Hallo') // --\u003e Hallo\n```\n\nCli 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:\n\n```js\ni18n.configure({\n  locales: ['en', 'de'],\n  register: global\n})\n\ni18n.setLocale('de')\n__('Hello') // --\u003e Hallo\n```\n\n#### Some words on `staticCatalog` option\n\nInstead 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:\n\n```js\n// DEMO: quickly add yaml support\nconst yaml = require('js-yaml')\nconst fs = require('fs')\n\n// configure and load translations from different locations\ni18n.configure({\n  staticCatalog: {\n    de: require('../../locales/de-as-json.json'),\n    en: require('../../locales/en-as-module.js'),\n    fr: yaml.safeLoad(fs.readFileSync('../../locales/fr-as-yaml.yml', 'utf8'))\n  },\n  defaultLocale: 'de'\n})\n```\n\n**NOTE:** Enabling `staticCatalog` disables all other fs realated options such as `updateFiles`,  `autoReload` and `syncFiles`\n\n#### Some words on `parser` option\n\nInstead of parsing all file contents as JSON, you can parse them as YAML or any other format you like\n```js\nconst YAML = require('yaml')\n\ni18n.configure({\n  extension: '.yml',\n  parser: YAML\n})\n```\n\n### i18n.init()\n\nWhen 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.\n\n```js\nvar app = express()\napp.use(cookieParser())\napp.use(i18n.init)\n```\n\nWhen i18n is used like this, the `i18n.init()` tries to\n\n1. guess the language of a visitor by it's browser settings, cookie or query parameter\n2. set that language in any of the \"usual\" objects provided by the framework\n\nExpress 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:\n\n```js\n{\n  req: {\n    locals: {},\n    res: {\n      locals: {},\n    }\n  }\n}\n```\n\nand add it's extra attributes and methods, like so:\n\n```js\n{\n  req: {\n    locals: {\n      locale: \"de\",\n      __: [function],\n      __n: [function],\n      [...]\n    },\n    res: {\n      locals: {\n        locale: \"de\",\n        __: [function],\n        __n: [function],\n        [...]\n      },\n      locale: \"de\",\n      __: [function],\n      __n: [function],\n      [...]\n    },\n    locale: \"de\",\n    __: [function],\n    __n: [function],\n    [...]\n  }\n}\n```\n\nNow 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.\n\n### i18n.__()\n\nTranslates a single phrase and adds it to locales if unknown. Returns translated parsed and substituted string.\n\n```js\n// template and global (this.locale == 'de')\n__('Hello') // Hallo\n__('Hello %s', 'Marcus') // Hallo Marcus\n__('Hello {{name}}', { name: 'Marcus' }) // Hallo Marcus\n\n// scoped via req object (req.locale == 'de')\nreq.__('Hello') // Hallo\nreq.__('Hello %s', 'Marcus') // Hallo Marcus\nreq.__('Hello {{name}}', { name: 'Marcus' }) // Hallo Marcus\n\n// scoped via res object (res.locale == 'de')\nres.__('Hello') // Hallo\nres.__('Hello %s', 'Marcus') // Hallo Marcus\nres.__('Hello {{name}}', { name: 'Marcus' }) // Hallo Marcus\n\n// passing specific locale\n__({ phrase: 'Hello', locale: 'fr' }) // Salut\n__({ phrase: 'Hello %s', locale: 'fr' }, 'Marcus') // Salut Marcus\n__({ phrase: 'Hello {{name}}', locale: 'fr' }, { name: 'Marcus' }) // Salut Marcus\n```\n\n### i18n.__n()\n\nPlurals translation of a single phrase. Singular and plural forms will get added to locales if unknown. Returns translated parsed and substituted string based on last `count` parameter.\n\n```js\n// short syntax is best suited for reading\n// --\u003e writes '%s cat' to both `one` and `other` plurals\n__n('%s cat', 1) // --\u003e 1 Katze\n__n('%s cat', 3) // --\u003e 3 Katzen\n\n// long syntax works fine in combination with `updateFiles`\n// --\u003e writes '%s cat' to `one` and '%s cats' to `other` plurals\n// \"one\" (singular) \u0026 \"other\" (plural) just covers the basic Germanic Rule#1 correctly.\n__n('%s cat', '%s cats', 1) // 1 Katze\n__n('%s cat', '%s cats', 3) // 3 Katzen\n\n// scoped via req object (req.locale == 'de')\nreq.__n('%s cat', 1) // 1 Katze\nreq.__n('%s cat', 3) // 3 Katzen\n\n// scoped via res object (res.locale == 'de')\nres.__n('%s cat', 1) // 1 Katze\nres.__n('%s cat', 3) // 3 Katzen\n\n// passing specific locale\n__n({ singular: '%s cat', plural: '%s cats', locale: 'fr' }, 1) // 1 chat\n__n({ singular: '%s cat', plural: '%s cats', locale: 'fr' }, 3) // 3 chats\n\n// the all in one object signature\n__n({ singular: '%s cat', plural: '%s cats', locale: 'fr', count: 1 }) // 1 chat\n__n({ singular: '%s cat', plural: '%s cats', locale: 'fr', count: 3 }) // 3 chats\n```\n\nWhen used in short form like `__n(phrase, count)` the following will get added to your json files:\n\n```js\n__n('%s dog', 1)\n```\n\n```json\n{\n  \"%s dog\": {\n    \"one\": \"%s dog\",\n    \"other\": \"%s dog\"\n  }\n}\n```\n\nWhen used in long form like `__n(singular, plural, count)` you benefit form passing defaults to both forms:\n\n```js\n__n('%s kitty', '%s kittens', 0)\n```\n\n```json\n{\n  \"%s kitty\": {\n    \"one\": \"%s kitty\",\n    \"other\": \"%s kittens\"\n  }\n}\n```\n\nYou might now add extra forms to certain json files to support the complete set of plural forms, like for example in russian:\n\n```json\n{\n  \"%s cat\": {\n    \"one\": \"%d кошка\",\n    \"few\": \"%d кошки\",\n    \"many\": \"%d кошек\",\n    \"other\": \"%d кошка\",\n  }\n}\n```\n\nand let `__n()` select the correct form for you:\n\n```js\n__n('%s cat', 0) // --\u003e 0 кошек\n__n('%s cat', 1) // --\u003e 1 кошка\n__n('%s cat', 2) // --\u003e 2 кошки\n__n('%s cat', 5) // --\u003e 5 кошек\n__n('%s cat', 6) // --\u003e 6 кошек\n__n('%s cat', 21) // --\u003e 21 кошка\n```\n\n\u003e __Note__ i18n.__n() will add a blueprint (\"one, other\" or \"one, few, other\" for example) for each locale to your json on updateFiles in a future version.\n\n### i18n.__mf()\n\nSupports the advanced MessageFormat as provided by excellent [messageformat module](https://www.npmjs.com/package/@messageformat/core). You should definetly head over to [messageformat.github.io](http://messageformat.github.io/messageformat/api/core/) for a guide to MessageFormat. i18n takes care of `new MessageFormat('en').compile(msg);` with the current `msg` loaded from it's json files and cache that complied fn in memory. So in short you might use it similar to `__()` plus extra object to accomplish MessageFormat's formatting. Ok, some examples:\n\n```js\n// assume res is set to german\nres.setLocale('de')\n\n// start simple\nres.__mf('Hello') // --\u003e Hallo\n\n// can replace too\nres.__mf('Hello {name}', { name: 'Marcus' }) // --\u003e Hallo Marcus\n\n// and combines with sprintf\nres.__mf('Hello {name}, how was your %s?', 'test', { name: 'Marcus' }) // --\u003e Hallo Marcus, wie war dein test?\n\n// now check out a plural rule\nres.__mf('{N, selectordinal, one{# cat} two{# cats} few{# cats} other{# cats}}', {\n  N: 1\n})\n\n// results for \"1\" in   (all use \"one\")\n// en --\u003e 1 cat\n// de --\u003e 1 Katze\n// fr --\u003e 1 chat\n// ru --\u003e 1 кошка       ru uses \"__one__\" when ending on \"1\"\n\n// results for \"0\" in   (most use \"others\")\n// en --\u003e 0 cats\n// de --\u003e 0 Katzen\n// fr --\u003e 0 chat        fr uses \"__one__\" for zero\n// ru --\u003e 0 кошек       ru uses \"__many__\"\n\n// results for \"2\" in   (most use \"others\")\n// en --\u003e 2 cat\n// de --\u003e 2 Katze\n// fr --\u003e 2 chat\n// ru --\u003e 2 кошки       ru uses \"__few__\" when ending on \"1\"\n\n// results for \"5\" in   (most use \"others\")\n// en --\u003e 5 cat\n// de --\u003e 5 Katze\n// fr --\u003e 5 chat\n// ru --\u003e 5 кошек       ru uses \"__many__\"\n\n// results for \"21\" in  (most use \"others\")\n// en --\u003e 21 cat\n// de --\u003e 21 Katze\n// fr --\u003e 21 chat\n// ru --\u003e 21 кошка       ru uses \"__one__\" when ending on \"1\"\n```\n\nTake a look at [Mozilla](https://developer.mozilla.org/en-US/docs/Mozilla/Localization/Localization_and_Plurals) to quickly get an idea of what pluralization has to deal with. With `__mf()` you get a very powerful tool, but you need to handle it correctly.\n\nBut MessageFormat can handle more! You get ability to process:\n\n* Simple Variable Replacement (similar to mustache placeholders)\n* SelectFormat (ie. switch msg based on gender)\n* [PluralFormat](http://messageformat.github.io/messageformat/guide/#pluralformat) (see above and [ranges](#ranged-interval-support))\n\nCombinations of those give superpower, but should get tested well (contribute your use case, please!) on integration.\n\n### i18n.__l()\n\nReturns a list of translations for a given phrase in each language.\n\n```js\ni18n.__l('Hello') // --\u003e [ 'Hallo', 'Hello' ]\n```\n\nThis will be usefull when setting up localized routes for example (kudos to @xpepermint, #150):\n\n```js\n// this will match routes\n// EN --\u003e /:locale/products/:id?\n// ES --\u003e /:locale/productos/:id?\napp.get(__l('/:locale/products/:id?'), function (req, res) {\n  // guess what you might use req.params.locale for?\n})\n```\n\n\u003e i18n.__ln() to get plurals will come up in another release...\n\n### i18n.__h()\n\nReturns a hashed list of translations for a given phrase in each language.\n\n```js\ni18n.__h('Hello') // --\u003e [ { de: 'Hallo' }, { en: 'Hello' } ]\n```\n\n\u003e i18n.__hn() to get plurals will come up in another release...\n\n### i18n.setLocale()\n\nSetting the current locale (ie.: `en`) globally or in current scope.\n\n```js\nsetLocale('de')\nsetLocale(req, 'de')\nreq.setLocale('de')\n```\n\nUse setLocale to change any initial locale that was set in `i18n.init()`. You get more control on how when and which objects get setup with a given locale. Locale values are inherited within the given schema like in `i18n.init()` Let's see some examples:\n\n```js\ni18n.setLocale(req, 'ar') // --\u003e req: مرحبا res: مرحبا res.locals: مرحبا\ni18n.setLocale(res, 'ar') // --\u003e req: Hallo res: مرحبا res.locals: مرحبا\ni18n.setLocale(res.locals, 'ar') // --\u003e req: Hallo res: Hallo res.locals: مرحبا\n```\n\nYou'll get even more control when passing an array of objects:\n\n```js\ni18n.setLocale([req, res.locals], req.params.lang) // --\u003e req: مرحبا res: Hallo res.locals: مرحبا\n```\n\nor disable inheritance by passing true as third parameter:\n\n```js\ni18n.setLocale(res, 'ar', true) // --\u003e req: Hallo res: مرحبا res.locals: Hallo\n```\n\n### i18n.getLocale()\n\nGetting the current locale (ie.: `en`) from current scope or globally.\n\n```js\ngetLocale() // --\u003e de\ngetLocale(req) // --\u003e de\nreq.getLocale() // --\u003e de\n```\n\n### i18n.getLocales()\n\nReturns a list with all configured locales.\n\n```js\ni18n.getLocales() // --\u003e ['en', 'de', 'en-GB']\n```\n\n### i18n.getCatalog()\n\nReturns a whole catalog optionally based on current scope and locale.\n\n```js\ngetCatalog() // returns catalog for all locales\ngetCatalog('de') // returns just for 'de'\n\ngetCatalog(req) // returns catalog for all locales\ngetCatalog(req, 'de') // returns just for 'de'\n\nreq.getCatalog() // returns catalog for all locales\nreq.getCatalog('de') // returns just for 'de'\n```\n\n## Attaching helpers for template engines\n\nIn general i18n has to be attached to the response object to let it's public api get accessible in your templates and methods. As of **0.4.0** i18n tries to do so internally via `i18n.init`, as if you were doing it in `app.configure` on your own:\n\n```js\napp.use(function (req, res, next) {\n  // express helper for natively supported engines\n  res.locals.__ = res.__ = function () {\n    return i18n.__.apply(req, arguments)\n  }\n\n  // [...]\n\n  next()\n})\n```\n\nDifferent engines need different implementations, so yours might miss or not work with the current default helpers. This one showing an example for mustache in express:\n\n```js\n// register helper as a locals function wrapped as mustache expects\napp.use(function (req, res, next) {\n  // mustache helper\n  res.locals.__ = function () {\n    return function (text, render) {\n      return i18n.__.apply(req, arguments)\n    }\n  }\n\n  // [...]\n\n  next()\n})\n```\n\nYou could still setup your own implementation. Please refer to Examples below, post an issue or contribute your setup.\n\n## Output formats\n\nAs inspired by gettext there is currently support for sprintf-style expressions. You can also use mustache syntax for named parameters.\n\n### sprintf support\n\n```js\nvar greeting = __('Hello %s, how are you today?', 'Marcus')\n```\n\nthis puts *Hello Marcus, how are you today?*. You might add endless arguments and even nest it.\n\n```js\nvar greeting = __('Hello %s, how are you today? How was your %s.', 'Marcus', __('weekend'))\n```\n\nwhich puts *Hello Marcus, how are you today? How was your weekend.*\n\nYou might need to have repeated references to the same argument, which can be done with sprintf.\n\n```js\nvar example = __('%1$s, %1$s, %1$s', 'repeat')\n```\n\nwhich puts\n\n```\nrepeat, repeat, repeat\n```\n\nIn some cases the argument order will need to be switched for different locales.  The arguments can be strings, floats, numbers, etc.\n\n```js\nvar example = __('%2$d then %1$s then %3$.2f', 'First', 2, 333.333)\n```\n\nwhich puts\n\n```\n2 then First then 333.33\n```\n\n### mustache support\n\nYou may also use [mustache](http://mustache.github.io/) syntax for your message strings. To pass named parameters to your message, just provide an object as the last parameter. You can still pass unnamed parameters by adding additional arguments.\n\n\n```js\nvar greeting = __('Hello {{name}}, how are you today?', { name: 'Marcus' })\n```\n\nthis puts *Hello Marcus, how are you today?*. You might also combine it with sprintf arguments...\n\n```js\nvar greeting = __('Hello {{name}}, how was your %s.', __('weekend'), { name: 'Marcus' })\n```\n\nand even nest it...\n\n```js\nvar greeting = __( __('Hello {{name}}, how was your %s?', { name: 'Marcus' }), __('weekend') )\n```\n\nwhich both put *Hello Marcus, how was your weekend.*\n\n#### how about markup?\n\nIncluding markup in translation and/or variables is considered to be bad practice, as it leads to side effects (translators need to understand it, might break it, inject malformed markup or worse). But well, mustache supports unescaped markup out-of-the-box (*Quote from https://mustache.github.io/mustache.5.html*):\n\n\u003e All variables are HTML escaped by default. If you want to return unescaped HTML, use the triple mustache: {{{name}}}.\n\nSo this will work\n\n```js\nvar greeting = __('Hello {{{name}}}, how are you today?', { name: '\u003cu\u003eMarcus\u003c/u\u003e' })\n```\n\nas expected:\n\n```html\nHello \u003cu\u003eMarcus\u003c/u\u003e, how are you today\n```\n\n### basic plural support\n\ntwo different plural forms are supported as response to `count`:\n\n```js\nvar singular = __n('%s cat', '%s cats', 1)\nvar plural = __n('%s cat', '%s cats', 3)\n```\n\nthis puts **1 cat** or **3 cats**\nand again these could get nested:\n\n```js\nvar singular = __n('There is one monkey in the %%s', 'There are %d monkeys in the %%s', 1, 'tree')\nvar plural = __n('There is one monkey in the %%s', 'There are %d monkeys in the %%s', 3, 'tree')\n```\nputting *There is one monkey in the tree* or *There are 3 monkeys in the tree*. Passing all 3 parameters would write a `one` and `other` to your json. For reading you might just use 2 parameters, too:\n\n```js\n__n('%s cat', 1) // --\u003e 1 Katze\n__n('%s cat', 3) // --\u003e 3 Katzen\n```\n\n### ranged interval support\n\nuse mathematical intervals to declare any own plural rules based on [ISO 31-11](https://en.wikipedia.org/wiki/Interval_(mathematics)#Notations_for_intervals) notation. Let's assume the following json snippet:\n\n```json\n\"dogs\": {\n    \"one\": \"one dog\",\n    \"other\": \"[0] no dog|[2,5] some dogs|[6,11] many dogs|[12,36] dozens of dogs|a horde of %s dogs|[100,] too many dogs\"\n}\n```\n\nthis will result in\n\n```js\n__n('dogs', 0) // --\u003e no dog\n__n('dogs', 1) // --\u003e one dog\n__n('dogs', 2) // --\u003e some dogs\n__n('dogs', 10) // --\u003e many dogs\n__n('dogs', 25) // --\u003e dozens of dogs\n__n('dogs', 42) // --\u003e a horde of 42 dogs\n__n('dogs', 199) // --\u003e too many dogs\n```\n\nThe rules are parsed in sequenced order, so the first match will skip any extra rules. Example:\n\n```json\n{\n    \"dogs\":\"[0]no dog|[1]one dog|[,10[ less than ten dogs|[,20[ less than 20 dogs|too many dogs\"\n}\n```\n\nresults in\n\n```js\n// interval matched by number\n__n('dogs', 0) // --\u003e no dog\n__n('dogs', 1) // --\u003e one dog\n__n('dogs', 2) // --\u003e less than ten dogs\n__n('dogs', 9) // --\u003e less than ten dogs\n__n('dogs', 10) // --\u003e less than 20 dogs\n__n('dogs', 19) // --\u003e less than 20 dogs\n__n('dogs', 20) // --\u003e too many dogs\n__n('dogs', 199) // --\u003e too many dogs\n\n// no interval returned, but found a catchall\n__('dogs') // --\u003e too many dogs\n```\n\nSee [en.json example](https://github.com/mashpie/i18n-node/blob/master/locales/en.json) inside `/locales` for some inspiration on use cases. Each phrase might get decorated further with mustache and sprintf expressions:\n\n```json\n{\n    \"example\":\"[0] %s is zero rule for {{me}}|[2,5] %s is between two and five for {{me}}|and a catchall rule for {{me}} to get my number %s\"\n}\n```\n\nwill put (as taken from tests):\n\n```js\n// will always use a found catchall\n__('example', {me: 'marcus'}) // --\u003e and a catchall rule for marcus to get my number %s\n__('example', ['one'], {me: 'marcus'}) // --\u003e and a catchall rule for marcus to get my number one\n\n// will search a matching interval or fallback to catchall\n__n('example', 1, {me: 'marcus'}) // --\u003e and a catchall rule for marcus to get my number 1\n__n('example', 2, {me: 'marcus'}) // --\u003e 2 is between two and five for marcus\n__n('example', 5, {me: 'marcus'}) // --\u003e 5 is between two and five for marcus\n__n('example', 3, {me: 'marcus'}) // --\u003e 3 is between two and five for marcus\n__n('example', 6, {me: 'marcus'}) // --\u003e and a catchall rule for marcus to get my number 6\n```\n\u003e __Notice__: the \"example\" object in your json doesn't use any \"one\", \"other\" subnodes although you _could_ use and even combine them. Currently \"one\" referres to the value of exactly 1 while \"other\" referres to every other value (think of 0, -10, null, false)\n\n\n### variable support\n\nyou might even use dynamic variables as they get interpreted on the fly. Better make sure no user input finds it's way to that point as they all get added to the `en.js` file if not yet existing.\n\n```js\nvar greetings = ['Hi', 'Hello', 'Howdy']\nfor (var i = 0; i \u003c greetings.length; i++) {\n  console.log(__(greetings[i]))\n}\n```\n\nwhich puts\n\n```\nHi\nHello\nHowdy\n```\n\n## Object notation\n\nIn addition to the traditional, linear translation lists, i18n also supports hierarchical translation catalogs.\n\nTo enable this feature, be sure to set `objectNotation` to `true` in your `configure()` call. **Note**: If you can't or don't want to use `.` as a delimiter, set `objectNotation` to any other delimiter you like.\n\nInstead of calling `__(\"Hello\")` you might call `__(\"greeting.formal\")` to retrieve a formal greeting from a translation document like this one:\n\n```json\n{\n  \"greeting\": {\n    \"formal\": \"Hello\",\n    \"informal\": \"Hi\",\n    \"placeholder\": {\n      \"formal\": \"Hello %s\",\n      \"informal\": \"Hi %s\"\n    }\n  }\n}\n```\n\nIn the document, the translation terms, which include placeholders, are nested inside the \"greeting\" translation. They can be accessed and used in the same way, like so `__('greeting.placeholder.informal', 'Marcus')`.\n\n### Pluralization\n\nObject notation also supports pluralization. When making use of it, the \"one\" and \"other\" entries are used implicitly for an object in the translation document. For example, consider the following document:\n\n```json\n{\n  \"pets\": {\n    \"cat\": {\n      \"one\": \"Katze\",\n      \"other\": \"Katzen\"\n    }\n  }\n}\n```\n\nWhen accessing these, you would use `__n(\"pets.cat\", \"pets.cat\", 3)` to tell i18n to use both the singular and plural form of the \"cat\" entry. Naturally, you could also access these members explicitly with `__(\"pets.cat.one\")` and `__(\"pets.cat.other\")`.\n\n### Defaults\n\nWhen starting a project from scratch, your translation documents will probably be empty. i18n takes care of filling your translation documents for you. Whenever you use an unknown object, it is added to the translation documents.\n\nBy default, when using object notation, the provided string literal will be inserted and returned as the default string. As an example, this is what the \"greeting\" object shown earlier would look like by default:\n\n```json\n{\n  \"greeting\": {\n    \"formal\": \"greeting.formal\",\n    \"informal\": \"greeting.informal\"\n  }\n}\n```\n\nIn case you would prefer to have a default string automatically inserted and returned, you can provide that default string by appending it to your object literal, delimited by a `:`. For example:\n\n```js\n__('greeting.formal:Hello')\n__('greeting.placeholder.informal:Hi %s')\n```\n\n## Storage\n\n\u003e Will get modular support for different storage engines, currently just json files are stored in filesystem.\n\n### json file\n\nthe above will automatically generate a `en.json` by default inside `./locales/` which looks like\n\n```json\n{\n  \"Hello\": \"Hello\",\n  \"Hello %s, how are you today?\": \"Hello %s, how are you today?\",\n  \"weekend\": \"weekend\",\n  \"Hello %s, how are you today? How was your %s.\": \"Hello %s, how are you today? How was your %s.\",\n  \"Hi\": \"Hi\",\n  \"Howdy\": \"Howdy\",\n  \"%s cat\": {\n    \"one\": \"%s cat\",\n    \"other\": \"%s cats\"\n  },\n  \"There is one monkey in the %%s\": {\n    \"one\": \"There is one monkey in the %%s\",\n    \"other\": \"There are %d monkeys in the %%s\"\n  },\n  \"tree\": \"tree\",\n  \"%s dog\": {\n    \"one\": \"one dog\",\n    \"other\": \"[0] no dog|[2,5] some dogs|[6,11] many dogs|[12,36] dozens of dogs|a horde of %s dogs\"\n  }\n}\n```\n\nthat file can be edited or just uploaded to [webtranslateit](http://docs.webtranslateit.com/file_formats/) for any kind of collaborative translation workflow:\n\n```json\n{\n  \"Hello\": \"Hallo\",\n  \"Hello %s, how are you today?\": \"Hallo %s, wie geht es dir heute?\",\n  \"weekend\": \"Wochenende\",\n  \"Hello %s, how are you today? How was your %s.\": \"Hallo %s, wie geht es dir heute? Wie war dein %s.\",\n  \"Hi\": \"Hi\",\n  \"Howdy\": \"Hallöchen\",\n  \"%s cat\": {\n    \"one\": \"%s Katze\",\n    \"other\": \"%s Katzen\"\n  },\n  \"There is one monkey in the %%s\": {\n    \"one\": \"Im %%s sitzt ein Affe\",\n    \"other\": \"Im %%s sitzen %d Affen\"\n  },\n  \"tree\": \"Baum\",\n  \"%s dog\": {\n    \"one\": \"Ein Hund\",\n    \"other\": \"[0] Kein Hund|[2,5] Ein paar Hunde|[6,11] Viele Hunde|[12,36] Dutzende Hunde|Ein Rudel von %s Hunden\"\n  }\n}\n```\n\n## Logging \u0026 Debugging\n\nLogging any kind of output is moved to [debug](https://github.com/visionmedia/debug) module. To let i18n output anything run your app with `DEBUG` env set like so:\n\n```sh\n$ DEBUG=i18n:* node app.js\n```\n\ni18n exposes three log-levels:\n\n* i18n:debug\n* i18n:warn\n* i18n:error\n\nif you only want to get errors and warnings reported start your node server like so:\n\n```sh\n$ DEBUG=i18n:warn,i18n:error node app.js\n```\n\nCombine those settings with you existing application if any of you other modules or libs also uses __debug__\n\n### Using custom logger\n\nYou can configure i18n to use a custom logger. For example attach some simple `console`-logging:\n\n```js\ni18n.configure({\n  // setting of log level DEBUG - default to require('debug')('i18n:debug')\n  logDebugFn: function (msg) {\n    console.log('debug', msg)\n  },\n\n  // setting of log level WARN - default to require('debug')('i18n:warn')\n  logWarnFn: function (msg) {\n    console.log('warn', msg)\n  },\n\n  // setting of log level ERROR - default to require('debug')('i18n:error')\n  logErrorFn: function (msg) {\n    console.log('error', msg)\n  }\n})\n```\n\n## Test\n```sh\nnpm test\n```\n\n## Roadmap\n\n* add a storage adapter (to support .yaml, .js, memory)\n* improved fallbacks\n* refactor dot notation (ie. configurable delimiters)\n* refactor to ES6\n* refactor to standard + prettier\n* move docs + examples to github pages\n\n## Changelog\n\nFor current release notes see [GitHub Release Notes](https://github.com/mashpie/i18n-node/releases). Changes until 0.8.3 are filed as [Changelog Summary](https://github.com/mashpie/i18n-node/blob/master/CHANGELOG.md).\n\n[npm-image]: https://badge.fury.io/js/i18n.svg\n[npm-url]: https://www.npmjs.com/package/i18n\n\n[coveralls-image]: https://coveralls.io/repos/github/mashpie/i18n-node/badge.svg?branch=master\n[coveralls-url]: https://coveralls.io/github/mashpie/i18n-node?branch=master\n\n[snyk-image]: https://snyk.io/test/npm/i18n/badge.svg\n[snyk-url]: https://snyk.io/test/npm/i18n\n\n[fossa-image]: https://app.fossa.com/api/projects/git%2Bgithub.com%2Fmashpie%2Fi18n-node.svg?type=shield\n[fossa-url]: https://app.fossa.com/projects/git%2Bgithub.com%2Fmashpie%2Fi18n-node?ref=badge_shield\n","funding_links":["https://github.com/sponsors/mashpie","https://tidelift.com/funding/github/npm/i18n","https://www.buymeacoffee.com/mashpie"],"categories":["JavaScript","Packages","Repository","包","目录","Text"],"sub_categories":["Text","Text/String","文本","文本处理","Unsorted"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmashpie%2Fi18n-node","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmashpie%2Fi18n-node","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmashpie%2Fi18n-node/lists"}