{"_id":"jus-i18n","_rev":"8-92eb4265a9edc619022b49a4058e0195","name":"jus-i18n","description":"Real I18N implementation, with a true support for plural forms, and many storage engines. Works best with Express or Jus Framework","dist-tags":{"latest":"0.1.2"},"versions":{"0.1.1":{"author":{"name":"Nicolas Chambrier","email":"naholyr@gmail.com","url":"http://naholyr.fr"},"name":"jus-i18n","description":"Real I18N implementation, with a true support for plural forms, and many storage engines. Works best with Express or Jus Framework","version":"0.1.1","homepage":"https://github.com/naholyr/node-i18n","repository":{"type":"git","url":"git://github.com/naholyr/node-i18n.git"},"main":"index.js","scripts":{"test":"node test"},"engines":{"node":"*"},"dependencies":{"gettext":"*"},"devDependencies":{},"_id":"jus-i18n@0.1.1","_engineSupported":true,"_npmVersion":"1.0.1","_nodeVersion":"v0.5.0-pre","_defaultsLoaded":true,"dist":{"shasum":"53c67cd62fe9df0059dd7efe890c0fd4f380e429","tarball":"https://registry.npmjs.org/jus-i18n/-/jus-i18n-0.1.1.tgz","integrity":"sha512-Dv9u9y0BLSyUAiuBk8OdQI2jJwkJihiVRzX20n6isHZzbn831nhaobPLmIJ7UL9IYBvKyubuRACui8ih4yjEyg==","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIBkhTtoK40G+uMD4fMHMuut65bwCaDwc30PLcdWcYSz1AiAqqDQUNtrZWjalaHlvcRzVSAplLaVE5/eBXulvVl0doA=="}]}},"0.1.2":{"author":{"name":"Nicolas Chambrier","email":"naholyr@gmail.com","url":"http://naholyr.fr"},"name":"jus-i18n","description":"Real I18N implementation, with a true support for plural forms, and many storage engines. Works best with Express or Jus Framework","version":"0.1.2","homepage":"https://github.com/naholyr/node-i18n","repository":{"type":"git","url":"git://github.com/naholyr/node-i18n.git"},"main":"index.js","scripts":{"test":"node test"},"engines":{"node":"*"},"dependencies":{"gettext":"*"},"devDependencies":{},"_npmUser":{"name":"naholyr","email":"naholyr@gmail.com"},"_id":"jus-i18n@0.1.2","optionalDependencies":{},"_engineSupported":true,"_npmVersion":"1.1.21","_nodeVersion":"v0.6.19-pre","_defaultsLoaded":true,"dist":{"shasum":"02f8891a711b0a327d80d2af68487a717dd7f3bc","tarball":"https://registry.npmjs.org/jus-i18n/-/jus-i18n-0.1.2.tgz","integrity":"sha512-cAv/p1nxJAq2yTlYYTtBvb3/tAOH7Ycn3mYS52Qaa4Y1RH1Wwtb0+qbByZBGi3QlCPSdbGBGZFc/RXZBC6VZVg==","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDKHvcprYiKd5nnST7sG2BWgEMYbK3jxhSFWQ5TaOoYGAIgVPWG5wcztDUdDpxzfmP1UqfmlERAxl6GC1/umZBGXsk="}]},"readme":"[![Build Status](https://secure.travis-ci.org/naholyr/node-jus-i18n.png)](http://travis-ci.org/naholyr/node-jus-i18n)\n\nUsage\n=====\n\nDefault usage\n-------------\n\n```javascript\n// Load module:\nvar i18n = require('/path/to/i18n');\n// Optional: set default locale\ni18n.defaultLocale = 'fr';\n// Optional: add prefix and suffix around untranslated strings (default = '[T]' and '[/T]'\ni18n.debug();\n// Mandatory: load translation data\ni18n.load(\n  catalogue, // Catalogue to load, undefined if you want to load default catalogue\n  locales,   // array of locales to load, undefined if want to load all available locales\n  function(errors, loadedLocales, store) { // Callback\n    // errors = hash of exceptions thrown by each erroneous locale, or undefined if no error\n    //          any global error will be stored in errors.ALL\n    // loadedLocales = array of successfully loaded locales\n    // store = store module\n  }\n);\n// Go translate :)\nconsole.log(i18n.translate('Chicken')); // \"Poulet\"\nconsole.log(i18n.translate('Chicken', 'it')); // \"Pollo\"\nconsole.log(i18n.translate('Chicken {name}', {name: \"KFC\"})); // \"Poulet KFC\"\nconsole.log(i18n.translate('Chicken {name}', {name: \"KFC\"}, 'it')); // \"Pollo KFC\"\n```\n\nIntegration with Express.js\n---------------------------\n\n```javascript\n// ... app initialized ...\n// Load module:\nvar i18n = require('/path/to/i18n');\n// Optional: configure default locale, debug mode, etc.\n// Then configure application:\napp.configure(function() {\n  i18n.enableForApp(app, { // options (all are optional, you can pass {} or undefined\n    \"locale\": \"en\",          // default locale\n    \"catalogue\": \"messages\", // catalogue to load\n    \"locales\": undefined,    // locales to load\n  }, function(err) { // called when i18n has loaded messages\n    ...\n  });\n});\n// Your \"req\" object is augmented:\nreq.i18n.translate(...)\nreq.locales() // returns the list of user's accept-language, ordered by preference\nreq.locale() // returns current user's chosen locale, stored in session if available\n// Your templates gain new helpers:\n...<%= _('Hello, {name}', {name: userName}) %>...\n...<%= plural('You have {n} messages', nbMessages) %>...\n```\n\nStore your messages\n===================\n\nThere are multiple message stores currently supported, details for which are provided below.\n\nStore: module\n-------------\n\nA whole catalogue in a single file:\n\n```javascript\n// Module name: \"./i18n-data/%catalogue%\"\n// ./i18n-data/messages.js\nmodule.exports = {\n  \"fr\": { \"Chicken\": \"Poulet\", \"Chicken {name}\": \"Poulet {name}\" },\n  \"it\": { \"Chicken\": \"Pollo\", \"Chicken {name}\": \"Pollo {name}\" },\n};\n// will be loaded with i18n.load('messages');\n```\n\nOr split by locale:\n\n```javascript\n// Module name: \"./i18n-data/%catalogue%/%locale%\"\n// ./i18n-data/messages/fr.js\nmodule.exports = { \"Chicken\": \"Poulet\", \"Chicken %name%\": \"Poulet %name%\" };\n// ./i18n-data/messages/it.js\nmodule.exports = { \"Chicken\": \"Pollo\", \"Chicken %name%\": \"Pollo %name%\" };\n// will be loaded with i18n.load('messages', ['fr', 'it']);\n```\n\nNote that you can customize the path to i18n-data modules:\n\n```javascript\ni18n.i18nDataModuleName.__default__ = process.cwd() + \"/i18n-data\";\n```\n\nThis method is the most flexible, faster than other file storage, and therefore ideal to embed your translations in your application.\nNevertheless, there is an important drawback using module storage: not the same file will be loaded, depending on the way you call \"i18n.load(...)\".\nThe best way to store your messages using module storage, to keep full compatibility with any \"load(...)\" parameters, is to declare your catalogue, that will include all locales:\n\n```javascript\n// ./i18n-data/messages/index.js\nmodule.exports = {\n  \"fr\": require('./fr'),\n  \"it\": require('./it')\n};\n// ./i18n-data/messages/fr.js\nmodule.exports = { \"Chicken\": \"Poulet\", \"Chicken %name%\": \"Poulet %name%\" };\n// ./i18n-data/messages/it.js\nmodule.exports = { \"Chicken\": \"Pollo\", \"Chicken %name%\": \"Pollo %name%\" };\ni18n.load('messages') and i18n.load('messages', ['fr', 'it']) will both work\n```\n\nIn this storage engine, methods to list available locales will not respond.\n\n### Formats, contexts and plural forms\n\nDefault format:\n\n    \"sentence\": \"Translated sentence\",\n\nContext embedded in the message:\n\n    \"sentence\": \"Translated sentence for default context or no context\",\n    \"context1:sentence\": \"Translation for context1\",\n    \"context2:sentence\": \"Translation for context2\",\n    ...\n\nUsing callback:\n\n    \"sentence\": function(context) {\n      switch (context) {\n        ...\n        default:\n          return \"Default translation\";\n      }\n    }\n\nUsing hash:\n\n    \"sentence\": {\n      \"\": \"Default translation\",\n      \"context1\": \"Translation for context1\",\n      \"context2\": \"Translation for context2\",\n      ...\n    }\n\nOf course, a \"translation\" can be a simple string as any representation of a plural form (string, array, hash).\n\nMixing plural forms and contexts can be confusing, the best practice is not to use numbers as context. \n\nStore: file\n-----------\n\nFilename: i18n-data/messages.{lang}[.txt]\n\nYou can specify several folders, translations will then be merged, in the same order the folders have been declared (last one overrides previous).\n\nFormat:\n\n    sentence = translation\n\n* Surrounding quotes (single or double) can be used, they'll be stripped out.\n\n    sentence = \"translation\"\n    sentence = 'translation'\n\n* Escape using backslash.\n\n    sentence = \"my \\\"translation\\\"\"\n\n* Same rules apply to keys, if you need to use a \"=\" in a key, quote it or escape the sign\n\n    \"my \\= sentence\" = \"my = translation\"\n\n### Plural forms\n\nDefault format:\n\n    You have {n} messages = [0]Vous n'avez aucun message|[1]Vous avez un message|[2-+Inf]Vous avez {n} messages\n\nMultiline format (use a single pipe, then one plural form per line, indented by one or more spaces/tabs):\n\n    You have {n} messages = |\n    \t[0]Vous n'avez aucun message\n    \t[1]Vous avez un message\n    \t[2-+Inf]Vous avez {n} messages\n\n### Contexts\n\nDefault format:\n\n    Hello, {name} = Bonjour, {name}\n    female:Hello, {name} = Bonjour, mademoiselle {name}\n    male:Hello, {name} = Bonjour, monsieur {name}\n\nMultiline format (define default translation, then one translation per context, using colon):\n\n    Hello, {name} = Bonjour, {name}\n    \tfemale: Bonjour, mademoiselle {name}\n    \tmale: Bonjour, monsieur {name}\n\nIf you need to use a single pipe as default translation, this will trigger plural forms (and then a syntax error) unless you quote it or escape it. \n\nStore: gettext\n--------------\n\nThe hierarchy is fully similar with the file storage, except that files are expected to end with \".po\".\n\nThis store uses Javascript implementation of Gettext, and interprets directly \".po\" files. No need to compile.\n\nThe PO format won't be described here, use it as expected :) Some notes though:\n\n* Generic plural forms handler implemented by jus-i18n is not used here, we directly use gettext's one.\n* Plural forms API in jus-i18n expects only one parameter \"msg\". If your `msgid` and `msgid_plural` values are not the same in your PO file, then you'll simply have to provide an array `[msgid, msgid_plural]`.\n* Contexts are supported by gettext, but jus-i18n context handling will still be used. This is expected to change.\n\nStore: db\n---------\n\nSoon available (redis, mongodb, mysql...).\n\nPlural forms\n============\n\nTODO documentation.\n\nThis is the most important feature to come. Still in development though.\n\nPlanned API (WiP)\n-----------------\n\nThe base function will expect only the \"plural form\", and the associated number:\n\n    plural(msg, number)\n\nA \"plural form\" could be a simple string, or a complex structure.\nHere are some valid formats we could imagine:\n\n    // Simple string\n    \"[0]No message|[1]One message|[2,+Inf)%count% messages\"\n\n    // Complex structure\n    [\n      [ function(count){return count == 0;}, \"No message\" ],\n      [ function(count){return count == 1;}, \"One message\" ],\n      [ function(count){return count >= 2;}, \"%count% messages\" ],\n    ]\n\nSupporting both type of structures could ease support for complex rules like polish plurals (where we need to use euclidian divides). Eval() is an option too...\n\nAbout translating message passer to `plural()`, following behavior has to be discussed:\n\n> \"plural()\" is able to automatically translate the message, BUT ONLY IF YOU EXPECT IT TO:\n> \n> * plural(msg, number) → will not translate msg\n> * plural(msg, number, params, locale, catalogue) → will translate msg\n> \n> If you want \"plural()\" to translate the message, but using default locale and no replacement, then call `plural(msg, number, {})`\n> \n> Other option: always translate.\n\nExample in a template:\n\n    <%= plural(\"You have %n% messages\", 3, {}) %>\n    // _(\"You have %n% messages\") returns \"[0]No message|[1]One message|[2,+Inf)%n% messages\"\n    // plural(\"[0]No message|[1]One message|[2,+Inf)%n% messages\", 3) returns \"3 messages\"\n\nContextual translations\n=======================\n\nYou may sometimes need to translate a sentence differently depending on a unpredictible context. Usual case is the gender (male/female).\nThis is handled using a special parameter named \"context\", and a special translation \"context:message\".\n\nFor example, supposing you want to say \"hello, {name}\" differently depending on civility (\"mr\", \"mrs\", \"miss\"), you will provide these translations in the store:\n\n    {\n      \"hello, {name}\": \"hello, {name}\",                  // default translation, no context\n      \"mr:hello, {name}\": \"hello, Mister {name}\",        // translation for civility \"mr\"\n      \"mrs:hello, {name}\": \"hello, Mrs. {name}\",         // translation for civility \"mrs\"\n      \"miss:hello, {name}\": \"hello, Miss {name}\"         // translation for civility \"miss\"\n    }\n\nYou will then be able to translate \"hello, {name}\" differently depending on provided context:\n\n    i18n.translate(\"hello, {name}\", {name: \"Jones\", context: \"mr\"});  // hello, Mister Jones\n    i18n.translate(\"hello, {name}\", {name: \"Jones\", context: \"mrs\"}); // hello, Mrs. Jones\n\nConfiguration\n=============\n\n* Customize the session key to store user's locale:\n  \n      i18n.localeSessKey = 'locale';\n\n* Customize the messages store:\n  \n      // Embedded store\n      i18n.setStore('module', options, function(err, i18n) {\n        ...\n      });\n      // You custom store module\n      i18n.setStore(require('/path/to/my/store'), options, function(err, i18n) {\n        ...\n      });\n  \n  Beware you must call \"i18n.load(...)\" again if you had already loaded another store.\n  You can use only one store at a time.\n\n* Customize default locale:\n  \n      i18n.defaultLocale = 'en';\n\n* Default catalogue to load and search translations from:\n  \n      i18n.defaultCatalogue = 'messages';\n\n* Change format of replaced parameters in your messages:\n  \n      i18n.replaceFormat = '{...}';\n      // i18n.replaceFormat = ':...';\n      // and i18n.translate('hello, :name', {name: 'John'}) will work as expected\n\n* In plural forms, the parameter 'n' is replaced by the number, you can change this name:\n  \n      i18n.defaultPluralReplace = 'n';\n\nWrite your own store\n--------------------\n\nYou must write a module that will expose at least two self-explanatory methods:\n\n* load(catalogue, locales, i18n, callback)\n  * catalogue and i18n will always be provided by i18n module.\n  * if no locale is provided, you're expected to enable all available locales.\n  * callback expects following parameters: (errors, loadedLocales, this)\n* get(key, locale, catalogue, i18n)\n  * all parameters will always be provided by i18n module.\n  * if no translation is found, you MUST return undefined.\n  * this function HAS TO BE synchronous.\n* locales(prefix, callback)\n  * callback expects following parameters: (err, array of locales starting with prefix, this)\n* catalogues(callback)\n  * callback expects following parameters: (err, array of available catalogues, this)\n* configure(options, callback)\n  * configure the store, options depend on your store\n  * callback expects following parameters: (err, this)\n\nThe i18n module is passed to these methods whenever you would need it in your store.\n\nStupid example (will always translate \"js\", and only this one, into \"rox\"):\n\n    var data = {};\n    exports.load = function(catalogue, locale, i18n, callback) {\n      catalogue = catalogue || i18n.defaultCatalogue;\n      locale = locale || i18n.defaultLocale;\n      if (!data[catalogue]) {\n        data[catalogue] = {locale: {\"js\": \"rox\"}};\n      } else {\n        if (!data[catalogue][locale]) {\n          data[catalogue][locale] = {\"js\": \"rox\"};\n        } else {\n          data[catalogue][locale][\"js\"] = \"rox\";\n        }\n      }\n      // Make it asynchronous\n      setTimeout(function() { callback(undefined, i18n, exports); }, 1);\n    }\n    exports.get = function(key, locale, catalogue) {\n      return data[catalogue][locale][key];\n    }\n    exports.configure = function(options, callback) {\n      // This time, it's synchronous, your implementation, your choice\n      callback(undefined, i18n, this);\n    }\n\nTODO\n====\n\n* Fix the data loading when we specify the list of loaded locales.\n* Provide more stores (at least Redis).\n* Better documentation.\n* If provided a \"file\" store: Ability to merge data from more than one folder.\n* Ability to use more than one store at same time.\n* Better support for locales \"lang_COUNTRY\" (loads messages for locales \"lang\" and \"lang-country\").\n* All these things I didn't think about yet.\n\n---\n\n* Done: <del>Plural forms, including ranges and expressions recognition</del>.\n* Done: <del>Context like gender (original idea from dialect)</del>.\n","maintainers":[{"name":"naholyr","email":"naholyr@gmail.com"}]}},"maintainers":[{"name":"naholyr","email":"naholyr@gmail.com"}],"time":{"modified":"2022-06-19T07:23:49.804Z","created":"2011-05-16T20:58:58.381Z","0.1.1":"2011-05-16T20:58:59.812Z","0.1.2":"2012-06-18T09:13:56.321Z"},"author":{"name":"Nicolas Chambrier","email":"naholyr@gmail.com","url":"http://naholyr.fr"},"repository":{"type":"git","url":"git://github.com/naholyr/node-i18n.git"},"users":{"naholyr":true}}