[Pkg-javascript-commits] [gettext.js] 01/02: Imported Upstream version 0.5.2

Mathias Behrle mbehrle at moszumanska.debian.org
Sat Jul 23 13:35:26 UTC 2016


This is an automated email from the git hooks/post-receive script.

mbehrle pushed a commit to branch master
in repository gettext.js.

commit 56e819105e18dae8705514d41dc3d7d32134250d
Author: Mathias Behrle <mathiasb at m9s.biz>
Date:   Wed Jul 13 12:35:25 2016 +0200

    Imported Upstream version 0.5.2
---
 .gitignore                |   2 +
 .travis.yml               |   7 ++
 CHANGELOG.md              |  12 +++
 README.md                 | 161 +++++++++++++++++++++++++++++++++
 bin/po2json               |  61 +++++++++++++
 bower.json                |  25 ++++++
 dist/gettext.js           | 212 +++++++++++++++++++++++++++++++++++++++++++
 dist/gettext.min.js       |   2 +
 gulpfile.js               |  15 ++++
 lib/gettext.js            | 212 +++++++++++++++++++++++++++++++++++++++++++
 package.json              |  27 ++++++
 tests/karma.config.dev.js |  17 ++++
 tests/karma.config.js     |  18 ++++
 tests/tests.js            | 222 ++++++++++++++++++++++++++++++++++++++++++++++
 14 files changed, 993 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7d6ad5f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.DS_Store
+node_modules/*
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..fd87826
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,7 @@
+language: node_js
+
+node_js:
+  - 0.11
+
+script:
+  - npm run-script test
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..9a17427
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,12 @@
+# gettext.js changelog
+
+**[0.5.2]**
+
+    - fixed bugs for plurals. (#2)
+
+**[0.5.0]**
+
+    - fixed default plural that were not used when a key does not have a
+    translation in dictionary.
+    - fixed multiple locale & plurals
+    - updated plural regex that where too permissive
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..fbb92b8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,161 @@
+# gettext.js
+
+gettext.js is a lightweight (3k minified!) yet complete and accurate GNU
+gettext port for node and the browser. Manage your i18n translations the right
+way in your javascript projects.
+
+
+## Why another i18n javascript library?
+
+gettext.js aim is to port the famous GNU gettext and ngettext functions to
+javascript node and browser applications.
+It should be standards respectful and lightweight (no dictionary loading
+management, no extra features).
+
+The result is a < 200 lines javascript tiny lib yet implementing fully
+`gettext()` and `ngettext()` and having the lighter json translation files
+possible (embeding only translated forms, and not much headers).
+
+There are plenty good i18n libraries out there, notably
+[Jed](https://github.com/SlexAxton/Jed) and [i18n-next](http://i18next.com/),
+but either there are too complex and too heavy, or they do not embrace fully
+the gettext API and philosophy.
+
+There is also [gettext.js](https://github.com/Orange-OpenSource/gettext.js)
+which is pretty good and heavily inspired this one, but not active since 2012
+and without any tests.
+
+
+## Installation
+
+### Node
+
+Install the lib with the following command: `npm install gettext.js --save`
+
+Require it in your project:
+
+```javascript
+var i18n = require('gettext.js');
+i18n.gettext('foo');
+```
+
+### Browser
+
+Download the latest
+[archive](https://github.com/guillaumepotier/gettext.js/archive/master.zip) or
+get it through bower: `bower install gettext.js --save`
+
+```html
+<script src="/path/to/gettext.js" type="text/javascript"></script>
+<script>
+  var i18n = window.i18n(options);
+  i18n.gettext('foo');
+</script>
+```
+
+## Usage
+
+### Load your messages
+
+You can load your messages these ways:
+
+```javascript
+// i18n.setMessages(domain, locale, messages, plural_form);
+i18n.setMessages('messages', 'fr', {
+  "Welcome": "Bienvenue",
+  "There is %1 apple": [
+    "Il y a %1 pomme",
+    "Il y a %1 pommes"
+  ]
+}, 'nplurals=2; plural=n>1;');
+```
+
+```javascript
+// i18n.loadJSON(jsonData /*, domain */);
+var json = {
+  "": {
+    "locale": "fr",
+    "plural-forms": "nplurals=2; plural=n>1;"
+  }
+  "Welcome": "Bienvenue",
+  "There is %1 apple": [
+    "Il y a %1 pomme",
+    "Il y a %1 pommes"
+  ]
+};
+i18n.loadJSON(jsonData, 'messages');
+```
+
+See Required JSON format section below for more info.
+
+
+### Set the locale
+
+You could do it from your dom
+
+```html
+<html lang="fr">
+```
+
+or from javascript
+
+```javascript
+i18n.setLocate('fr');
+```
+
+
+### `gettext(msgid)`
+
+Translate a string.
+
+
+### `ngettext(msgid, msgid_plural, n)`
+
+Translate a pluralizable string
+
+
+### Variabilized strings
+
+`gettext('There are %1 in the %2`, 'apples', 'bowl'); -> "There are apples in the bowl`
+`ngettext('One %2', '%1 %2', 10, 'bananas'); -> "10 bananas"`
+
+It uses the public method `i18n.strfmt("string", var1, var2, ...)` you could
+reuse elsewhere in your project.
+
+
+## Required JSON format
+
+You'll find in `/bin` a `po2json.js` converter, based on the excellent
+[po2json](https://github.com/mikeedwards/po2json) project that will dump your
+`.po` files into the proper json format below:
+
+```json
+{
+    "": {
+        "lang": "en",
+        "plural_forms": "nplurals=2; plural=(n!=1);"
+    },
+    "simple key": "It's tranlation",
+    "another with %1 parameter": "It's %1 tranlsation",
+    "a key with plural": [
+        "a plural form",
+        "another plural form",
+        "could have up to 6 forms with some languages"
+    ],
+    "a context\u0004a contextualized key": "translation here"
+}
+```
+
+Use `bin/po2json.js input.po output.json` or
+`bin/po2json.js input.po output.json -p` for pretty format.
+
+
+## Parsers
+
+You could use [xgettext-php](https://github.com/Wisembly/xgettext-php) parser
+to parse your files. It provides helpful javascript and handlebars parsers.
+
+
+## License
+
+MIT
diff --git a/bin/po2json b/bin/po2json
new file mode 100755
index 0000000..6cc9a17
--- /dev/null
+++ b/bin/po2json
@@ -0,0 +1,61 @@
+#!/usr/bin/env node
+
+/*
+  po2json wrapper for gettext.js
+  https://github.com/mikeedwards/po2json
+
+  Dump a .po file in a json like this one:
+
+  {
+    "": {
+        "lang": "en",
+        "plural_forms": "nplurals=2; plural=(n!=1);"
+    },
+    "simple key": "It's tranlation",
+    "another with %1 parameter": "It's %1 tranlsation",
+    "a key with plural": [
+        "a plural form",
+        "another plural form",
+        "could have up to 6 forms with some languages"
+    ],
+    "a context\u0004a contextualized key": "translation here"
+  }
+
+*/
+
+var
+  fs = require('fs'),
+  po2json = require('po2json'),
+  argv = process.argv,
+  json = {},
+  pretty = '-p' === argv[4];
+
+if (argv.length < 4)
+  return console.error("Wrong parameters.\nFormat: po2json.js input.po output.json [OPTIONS]\n-p for pretty");
+
+fs.readFile(argv[2], function (err, buffer) {
+  var jsonData = po2json.parse(buffer);
+
+  for (var key in jsonData) {
+    // Special headers handling, we do not need everything
+    if ('' === key) {
+      json[''] = {
+        'language': jsonData['']['language'],
+        'plural-forms': jsonData['']['plural-forms']
+      };
+
+      continue;
+    }
+
+    // Do not dump untranslated keys, they already are in the templates!
+    if ('' !== jsonData[key][1])
+      json[key] = 2 === jsonData[key].length ? jsonData[key][1] : jsonData[key].slice(1);
+  }
+
+  fs.writeFile(argv[3], JSON.stringify(json, null, pretty ? 4 : 0), function(err) {
+    if (err)
+      console.log(err);
+    else
+      console.log('JSON ' + (pretty ? 'pretty' : 'compactly') + ' saved to ' + argv[3]);
+  });
+});
diff --git a/bower.json b/bower.json
new file mode 100644
index 0000000..4439a0a
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,25 @@
+{
+  "name": "gettext.js",
+  "ignore": [
+    "tests",
+    "lib",
+    "*.md",
+    "*.json",
+    "*.yml",
+    "gulpfile.js"
+  ],
+  "keywords": [
+    "i18n",
+    "internationalization",
+    "gettext",
+    "ngettext",
+    "po"
+  ],
+  "author": {
+    "name": "Guillaume Potier",
+    "email": "guillaume at wisembly.com",
+    "url": "http://guillaumepotier.com/"
+  },
+  "license": "MIT",
+  "main": "dist/gettext.js"
+}
diff --git a/dist/gettext.js b/dist/gettext.js
new file mode 100644
index 0000000..cd8e5cd
--- /dev/null
+++ b/dist/gettext.js
@@ -0,0 +1,212 @@
+/*! gettext.js - Guillaume Potier - MIT Licensed */
+(function (root, undef) {
+  var i18n = function (options) {
+    options = options || {};
+    this.__version = '0.5.2';
+
+    // default values that could be overriden in i18n() construct
+    var defaults = {
+      domain: 'messages',
+      locale: document.documentElement.getAttribute('lang') || 'en',
+      plural_func: function (n) { return { nplurals: 2, plural: (n!=1) ? 1 : 0 }; },
+      ctxt_delimiter: String.fromCharCode(4) // \u0004
+    };
+
+    // handy mixins taken from underscode.js
+    var _ = {
+      isObject: function (obj) {
+        var type = typeof obj;
+        return type === 'function' || type === 'object' && !!obj;
+      },
+      isArray: function (obj) {
+        return toString.call(obj) === '[object Array]';
+      }
+    };
+
+    var
+      _plural_funcs = {},
+      _locale = options.locale || defaults.locale,
+      _domain = options.domain || defaults.domain,
+      _dictionary = {},
+      _plural_forms = {},
+      _ctxt_delimiter = options.ctxt_delimiter || defaults.ctxt_delimiter;
+
+      if (options.messages) {
+        _dictionary[_domain] = {};
+        _dictionary[_domain][_locale] = options.messages;
+      }
+
+      if (options.plural_forms) {
+        _plural_forms[_locale] = options.plural_forms;
+      }
+
+      // sprintf equivalent, takes a string and some arguments to make a computed string
+      // eg: strfmt("%1 dogs are in %2", 7, "the kitchen"); => "7 dogs are in the kitchen"
+      // eg: strfmt("I like %1, bananas and %1", "apples"); => "I like apples, bananas and apples"
+      var strfmt = function (fmt) {
+        var args = arguments;
+
+        return fmt.replace(/%(\d+)/g, function (str, p1) {
+          return args[p1];
+        });
+      };
+
+      var getPluralFunc = function (plural_form) {
+        // Plural form string regexp
+        // taken from https://github.com/Orange-OpenSource/gettext.js/blob/master/lib.gettext.js
+        // plural forms list available here http://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html
+        var pf_re = new RegExp('^\\s*nplurals\\s*=\\s*[0-9]+\\s*;\\s*plural\\s*=\\s*(?:\\s|[-\\?\\|&=!<>+*/%:;n0-9_\(\)])+');
+
+        if (!pf_re.test(plural_form))
+          throw new Error(strfmt('The plural form "%1" is not valid', plural_form));
+
+        // Careful here, this is a hidden eval() equivalent..
+        // Risk should be reasonable though since we test the plural_form through regex before
+        // taken from https://github.com/Orange-OpenSource/gettext.js/blob/master/lib.gettext.js
+        // TODO: should test if https://github.com/soney/jsep present and use it if so
+        return new Function("n", 'var plural, nplurals; '+ plural_form +' return { nplurals: nplurals, plural: (plural === true ? 1 : (plural ? plural : 0)) };');
+      };
+
+      // Proper translation function that handle plurals and directives
+      // Contains juicy parts of https://github.com/Orange-OpenSource/gettext.js/blob/master/lib.gettext.js
+      var t = function (messages, n, options /* ,extra */) {
+        // Singular is very easy, just pass dictionnary message through strfmt
+        if (1 === messages.length)
+          return strfmt.apply(this, [messages[0]].concat(Array.prototype.slice.call(arguments, 3)));
+
+        var plural;
+
+        // if a plural func is given, use that one
+        if (options.plural_func) {
+          plural = options.plural_func(n);
+
+        // if plural form never interpreted before, do it now and store it
+        } else if (!_plural_funcs[_locale]) {
+          _plural_funcs[_locale] = getPluralFunc(_plural_forms[_locale]);
+          plural = _plural_funcs[_locale](n);
+
+        // we have the plural function, compute the plural result
+        } else {
+          plural = _plural_funcs[_locale](n);
+        }
+
+        // If there is a problem with plurals, fallback to singular one
+        if ('undefined' === typeof plural.plural || plural.plural > plural.nplurals || messages.length <= plural.plural)
+          plural.plural = 0;
+
+        return strfmt.apply(this, [messages[plural.plural], n].concat(Array.prototype.slice.call(arguments, 3)));
+      };
+
+    return {
+      strfmt: strfmt, // expose strfmt util
+
+      // Declare shortcuts
+      __: function () { return this.gettext.apply(this, arguments); },
+      _n: function () { return this.ngettext.apply(this, arguments); },
+      _p: function () { return this.pgettext.apply(this, arguments); },
+
+      setMessages: function (domain, locale, messages, plural_forms) {
+        if (!domain || !locale || !messages)
+          throw new Error('You must provide a domain, a locale and messages');
+
+        if ('string' !== typeof domain || 'string' !== typeof locale || !_.isObject(messages))
+          throw new Error('Invalid arguments');
+
+        if (plural_forms)
+          _plural_forms[locale] = plural_forms;
+
+        if (!_dictionary[domain])
+          _dictionary[domain] = {};
+
+        _dictionary[domain][locale] = messages;
+
+        return this;
+      },
+      loadJSON: function (jsonData, domain) {
+        if (!_.isObject(jsonData))
+          jsonData = JSON.parse(jsonData);
+
+        if (!jsonData[''] || !jsonData['']['language'] || !jsonData['']['plural-forms'])
+          throw new Error('Wrong JSON, it must have an empty key ("") with "language" and "plural-forms" information');
+
+        var headers = jsonData[''];
+        delete jsonData[''];
+
+        return this.setMessages(domain || defaults.domain, headers['language'], jsonData, headers['plural-forms']);
+      },
+      setLocale: function (locale) {
+        _locale = locale;
+        return this;
+      },
+      getLocale: function () {
+        return _locale;
+      },
+      // getter/setter for domain
+      textdomain: function (domain) {
+        if (!domain)
+          return _domain;
+        _domain = domain;
+        return this;
+      },
+      gettext: function (msgid /* , extra */) {
+        return this.dcnpgettext.apply(this, [undef, undef, msgid, undef, undef].concat(Array.prototype.slice.call(arguments, 1)));
+      },
+      ngettext: function (msgid, msgid_plural, n /* , extra */) {
+        return this.dcnpgettext.apply(this, [undef, undef, msgid, msgid_plural, n].concat(Array.prototype.slice.call(arguments, 3)));
+      },
+      pgettext: function (msgctxt, msgid /* , extra */) {
+        return this.dcnpgettext.apply(this, [undef, msgctxt, msgid, undef, undef].concat(Array.prototype.slice.call(arguments, 2)));
+      },
+      dcnpgettext: function (domain, msgctxt, msgid, msgid_plural, n /* , extra */) {
+        domain = domain || _domain;
+
+        if ('string' !== typeof msgid)
+          throw new Error(this.strfmt('Msgid "%1" is not a valid translatable string', msgid));
+
+        var
+          translation,
+          options = {},
+          key = msgctxt ? msgctxt + _ctxt_delimiter + msgid : msgid,
+          exist = _dictionary[domain] && _dictionary[domain][_locale] && _dictionary[domain][_locale][key];
+
+        // because it's not possible to define both a singular and a plural form of the same msgid,
+        // we need to check that the stored form is the same as the expected one.
+        // if not, we'll just ignore the translation and consider it as not translated.
+        if (msgid_plural) {
+          exist = exist && "string" !== typeof _dictionary[domain][_locale][key];
+        } else {
+          exist = exist && "string" === typeof _dictionary[domain][_locale][key];
+        }
+
+        if (!exist) {
+          translation = msgid;
+          options.plural_func = defaults.plural_func;
+        } else {
+          translation = _dictionary[domain][_locale][key];
+        }
+
+        // Singular form
+        if (!msgid_plural)
+          return t.apply(this, [[translation], n, options].concat(Array.prototype.slice.call(arguments, 5)));
+
+        // Plural one
+        return t.apply(this, [exist ? translation : [msgid, msgid_plural], n, options].concat(Array.prototype.slice.call(arguments, 5)));
+      }
+    };
+  };
+
+  // Handle node, commonjs
+  if (typeof exports !== 'undefined') {
+    if (typeof module !== 'undefined' && module.exports)
+      exports = module.exports = i18n;
+    exports.i18n = i18n;
+
+  // Handle AMD
+  } else if (typeof define === 'function' && define.amd) {
+    define(function() { return i18n; });
+
+  // Standard window browser
+  } else {
+    root['i18n'] = i18n;
+  }
+})(this);
diff --git a/dist/gettext.min.js b/dist/gettext.min.js
new file mode 100644
index 0000000..2acbab8
--- /dev/null
+++ b/dist/gettext.min.js
@@ -0,0 +1,2 @@
+/*! gettext.js - Guillaume Potier - MIT Licensed */
+!function(t,r){var e=function(t){t=t||{},this.__version="0.5.2";var e={domain:"messages",locale:document.documentElement.getAttribute("lang")||"en",plural_func:function(t){return{nplurals:2,plural:1!=t?1:0}},ctxt_delimiter:String.fromCharCode(4)},n={isObject:function(t){var r=typeof t;return"function"===r||"object"===r&&!!t},isArray:function(t){return"[object Array]"===toString.call(t)}},a={},l=t.locale||e.locale,u=t.domain||e.domain,o={},s={},i=t.ctxt_delimiter||e.ctxt_delimiter;t.messa [...]
diff --git a/gulpfile.js b/gulpfile.js
new file mode 100644
index 0000000..f6e4e1a
--- /dev/null
+++ b/gulpfile.js
@@ -0,0 +1,15 @@
+var gulp = require('gulp'),
+    uglify = require('gulp-uglify'),
+    rename = require('gulp-rename');
+
+gulp.task('default', function () {
+    gulp.start('scripts');
+});
+
+gulp.task('scripts', function () {
+    return gulp.src('lib/gettext.js')
+        .pipe(gulp.dest('dist'))
+        .pipe(rename({ suffix: '.min' }))
+        .pipe(uglify({ preserveComments: 'some' }))
+        .pipe(gulp.dest('dist'));
+});
diff --git a/lib/gettext.js b/lib/gettext.js
new file mode 100644
index 0000000..cd8e5cd
--- /dev/null
+++ b/lib/gettext.js
@@ -0,0 +1,212 @@
+/*! gettext.js - Guillaume Potier - MIT Licensed */
+(function (root, undef) {
+  var i18n = function (options) {
+    options = options || {};
+    this.__version = '0.5.2';
+
+    // default values that could be overriden in i18n() construct
+    var defaults = {
+      domain: 'messages',
+      locale: document.documentElement.getAttribute('lang') || 'en',
+      plural_func: function (n) { return { nplurals: 2, plural: (n!=1) ? 1 : 0 }; },
+      ctxt_delimiter: String.fromCharCode(4) // \u0004
+    };
+
+    // handy mixins taken from underscode.js
+    var _ = {
+      isObject: function (obj) {
+        var type = typeof obj;
+        return type === 'function' || type === 'object' && !!obj;
+      },
+      isArray: function (obj) {
+        return toString.call(obj) === '[object Array]';
+      }
+    };
+
+    var
+      _plural_funcs = {},
+      _locale = options.locale || defaults.locale,
+      _domain = options.domain || defaults.domain,
+      _dictionary = {},
+      _plural_forms = {},
+      _ctxt_delimiter = options.ctxt_delimiter || defaults.ctxt_delimiter;
+
+      if (options.messages) {
+        _dictionary[_domain] = {};
+        _dictionary[_domain][_locale] = options.messages;
+      }
+
+      if (options.plural_forms) {
+        _plural_forms[_locale] = options.plural_forms;
+      }
+
+      // sprintf equivalent, takes a string and some arguments to make a computed string
+      // eg: strfmt("%1 dogs are in %2", 7, "the kitchen"); => "7 dogs are in the kitchen"
+      // eg: strfmt("I like %1, bananas and %1", "apples"); => "I like apples, bananas and apples"
+      var strfmt = function (fmt) {
+        var args = arguments;
+
+        return fmt.replace(/%(\d+)/g, function (str, p1) {
+          return args[p1];
+        });
+      };
+
+      var getPluralFunc = function (plural_form) {
+        // Plural form string regexp
+        // taken from https://github.com/Orange-OpenSource/gettext.js/blob/master/lib.gettext.js
+        // plural forms list available here http://localization-guide.readthedocs.org/en/latest/l10n/pluralforms.html
+        var pf_re = new RegExp('^\\s*nplurals\\s*=\\s*[0-9]+\\s*;\\s*plural\\s*=\\s*(?:\\s|[-\\?\\|&=!<>+*/%:;n0-9_\(\)])+');
+
+        if (!pf_re.test(plural_form))
+          throw new Error(strfmt('The plural form "%1" is not valid', plural_form));
+
+        // Careful here, this is a hidden eval() equivalent..
+        // Risk should be reasonable though since we test the plural_form through regex before
+        // taken from https://github.com/Orange-OpenSource/gettext.js/blob/master/lib.gettext.js
+        // TODO: should test if https://github.com/soney/jsep present and use it if so
+        return new Function("n", 'var plural, nplurals; '+ plural_form +' return { nplurals: nplurals, plural: (plural === true ? 1 : (plural ? plural : 0)) };');
+      };
+
+      // Proper translation function that handle plurals and directives
+      // Contains juicy parts of https://github.com/Orange-OpenSource/gettext.js/blob/master/lib.gettext.js
+      var t = function (messages, n, options /* ,extra */) {
+        // Singular is very easy, just pass dictionnary message through strfmt
+        if (1 === messages.length)
+          return strfmt.apply(this, [messages[0]].concat(Array.prototype.slice.call(arguments, 3)));
+
+        var plural;
+
+        // if a plural func is given, use that one
+        if (options.plural_func) {
+          plural = options.plural_func(n);
+
+        // if plural form never interpreted before, do it now and store it
+        } else if (!_plural_funcs[_locale]) {
+          _plural_funcs[_locale] = getPluralFunc(_plural_forms[_locale]);
+          plural = _plural_funcs[_locale](n);
+
+        // we have the plural function, compute the plural result
+        } else {
+          plural = _plural_funcs[_locale](n);
+        }
+
+        // If there is a problem with plurals, fallback to singular one
+        if ('undefined' === typeof plural.plural || plural.plural > plural.nplurals || messages.length <= plural.plural)
+          plural.plural = 0;
+
+        return strfmt.apply(this, [messages[plural.plural], n].concat(Array.prototype.slice.call(arguments, 3)));
+      };
+
+    return {
+      strfmt: strfmt, // expose strfmt util
+
+      // Declare shortcuts
+      __: function () { return this.gettext.apply(this, arguments); },
+      _n: function () { return this.ngettext.apply(this, arguments); },
+      _p: function () { return this.pgettext.apply(this, arguments); },
+
+      setMessages: function (domain, locale, messages, plural_forms) {
+        if (!domain || !locale || !messages)
+          throw new Error('You must provide a domain, a locale and messages');
+
+        if ('string' !== typeof domain || 'string' !== typeof locale || !_.isObject(messages))
+          throw new Error('Invalid arguments');
+
+        if (plural_forms)
+          _plural_forms[locale] = plural_forms;
+
+        if (!_dictionary[domain])
+          _dictionary[domain] = {};
+
+        _dictionary[domain][locale] = messages;
+
+        return this;
+      },
+      loadJSON: function (jsonData, domain) {
+        if (!_.isObject(jsonData))
+          jsonData = JSON.parse(jsonData);
+
+        if (!jsonData[''] || !jsonData['']['language'] || !jsonData['']['plural-forms'])
+          throw new Error('Wrong JSON, it must have an empty key ("") with "language" and "plural-forms" information');
+
+        var headers = jsonData[''];
+        delete jsonData[''];
+
+        return this.setMessages(domain || defaults.domain, headers['language'], jsonData, headers['plural-forms']);
+      },
+      setLocale: function (locale) {
+        _locale = locale;
+        return this;
+      },
+      getLocale: function () {
+        return _locale;
+      },
+      // getter/setter for domain
+      textdomain: function (domain) {
+        if (!domain)
+          return _domain;
+        _domain = domain;
+        return this;
+      },
+      gettext: function (msgid /* , extra */) {
+        return this.dcnpgettext.apply(this, [undef, undef, msgid, undef, undef].concat(Array.prototype.slice.call(arguments, 1)));
+      },
+      ngettext: function (msgid, msgid_plural, n /* , extra */) {
+        return this.dcnpgettext.apply(this, [undef, undef, msgid, msgid_plural, n].concat(Array.prototype.slice.call(arguments, 3)));
+      },
+      pgettext: function (msgctxt, msgid /* , extra */) {
+        return this.dcnpgettext.apply(this, [undef, msgctxt, msgid, undef, undef].concat(Array.prototype.slice.call(arguments, 2)));
+      },
+      dcnpgettext: function (domain, msgctxt, msgid, msgid_plural, n /* , extra */) {
+        domain = domain || _domain;
+
+        if ('string' !== typeof msgid)
+          throw new Error(this.strfmt('Msgid "%1" is not a valid translatable string', msgid));
+
+        var
+          translation,
+          options = {},
+          key = msgctxt ? msgctxt + _ctxt_delimiter + msgid : msgid,
+          exist = _dictionary[domain] && _dictionary[domain][_locale] && _dictionary[domain][_locale][key];
+
+        // because it's not possible to define both a singular and a plural form of the same msgid,
+        // we need to check that the stored form is the same as the expected one.
+        // if not, we'll just ignore the translation and consider it as not translated.
+        if (msgid_plural) {
+          exist = exist && "string" !== typeof _dictionary[domain][_locale][key];
+        } else {
+          exist = exist && "string" === typeof _dictionary[domain][_locale][key];
+        }
+
+        if (!exist) {
+          translation = msgid;
+          options.plural_func = defaults.plural_func;
+        } else {
+          translation = _dictionary[domain][_locale][key];
+        }
+
+        // Singular form
+        if (!msgid_plural)
+          return t.apply(this, [[translation], n, options].concat(Array.prototype.slice.call(arguments, 5)));
+
+        // Plural one
+        return t.apply(this, [exist ? translation : [msgid, msgid_plural], n, options].concat(Array.prototype.slice.call(arguments, 5)));
+      }
+    };
+  };
+
+  // Handle node, commonjs
+  if (typeof exports !== 'undefined') {
+    if (typeof module !== 'undefined' && module.exports)
+      exports = module.exports = i18n;
+    exports.i18n = i18n;
+
+  // Handle AMD
+  } else if (typeof define === 'function' && define.amd) {
+    define(function() { return i18n; });
+
+  // Standard window browser
+  } else {
+    root['i18n'] = i18n;
+  }
+})(this);
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..a00f268
--- /dev/null
+++ b/package.json
@@ -0,0 +1,27 @@
+{
+    "name": "gettext.js",
+    "version": "0.5.2",
+    "scripts": {
+        "test": "node node_modules/karma/bin/karma start tests/karma.config.js",
+        "test-dev": "node node_modules/karma/bin/karma start tests/karma.config.dev.js",
+        "build": "node node_modules/gulp/bin/gulp.js"
+    },
+    "bin": {
+        "po2json-gettextjs": "bin/po2json"
+    },
+    "dependencies": {
+        "po2json": "^0.3.2"
+    },
+    "devDependencies": {
+        "karma": "*",
+        "karma-mocha": "*",
+        "karma-sinon-expect": "*",
+        "karma-phantomjs-launcher": "*",
+        "gulp": "*",
+        "gulp-uglify": "*",
+        "gulp-rename": "*"
+    },
+    "spm": {
+        "main": "lib/gettext.js"
+    }
+}
diff --git a/tests/karma.config.dev.js b/tests/karma.config.dev.js
new file mode 100644
index 0000000..f39838e
--- /dev/null
+++ b/tests/karma.config.dev.js
@@ -0,0 +1,17 @@
+module.exports = function(config) {
+  config.set({
+    basePath: '../',
+    frameworks: ['mocha', 'sinon-expect'],
+    files: [
+        'lib/gettext.js',
+        'tests/tests.js',
+    ],
+    exclude: [],
+    reporters: ['progress'], // or `dots`
+    port: 9876,
+    colors: true,
+    logLevel: config.LOG_INFO,
+    autoWatch: true,
+    browsers: ['PhantomJS']
+  });
+};
diff --git a/tests/karma.config.js b/tests/karma.config.js
new file mode 100644
index 0000000..3aa827c
--- /dev/null
+++ b/tests/karma.config.js
@@ -0,0 +1,18 @@
+module.exports = function(config) {
+  config.set({
+    basePath: '../',
+    frameworks: ['mocha', 'sinon-expect'],
+    files: [
+        'lib/gettext.js',
+        'tests/tests.js',
+    ],
+    exclude: [],
+    reporters: ['progress'], // or `dots`
+    port: 9876,
+    colors: true,
+    logLevel: config.LOG_INFO,
+    autoWatch: false,
+    browsers: ['PhantomJS'],
+    singleRun: true
+  });
+};
diff --git a/tests/tests.js b/tests/tests.js
new file mode 100644
index 0000000..f5684b5
--- /dev/null
+++ b/tests/tests.js
@@ -0,0 +1,222 @@
+(function () {
+    describe('gettext.js test suite', function () {
+        describe('General API', function () {
+            it('should be defined', function () {
+                expect(window.i18n).to.be.a('function');
+            });
+            it('should be instanciable', function () {
+                expect(window.i18n()).to.be.an('object');
+            });
+            it('should have default locale', function () {
+                var i18n = window.i18n();
+                expect(i18n.getLocale()).to.be('en');
+            });
+            it('should allow to set locale', function () {
+                var i18n = window.i18n({
+                    locale: 'fr'
+                });
+                expect(i18n.getLocale()).to.be('fr');
+            });
+            it('should allow to set messages', function () {
+                var i18n = window.i18n({
+                    locale: 'fr',
+                    messages: {
+                        "apple": "pomme"
+                    }
+                });
+                expect(i18n.getLocale()).to.be('fr');
+                expect(i18n.gettext('apple')).to.be('pomme');
+            });
+        });
+        describe('methods', function () {
+            var i18n;
+            before(function () {
+                i18n = new window.i18n();
+            });
+
+            describe('strfmt', function () {
+                it('should be a i18n method', function () {
+                    expect(i18n.strfmt).to.be.a('function');
+                });
+                it('should handle one replacement', function () {
+                    expect(i18n.strfmt('foo %1 baz', 'bar')).to.be('foo bar baz');
+                });
+                it('should handle many replacements', function () {
+                    expect(i18n.strfmt('foo %1 baz %2', 'bar', 42)).to.be('foo bar baz 42');
+                });
+                it('should handle order', function () {
+                    expect(i18n.strfmt('foo %2 baz %1', 'bar', 42)).to.be('foo 42 baz bar');
+                });
+                it('should handle repeat', function () {
+                    expect(i18n.strfmt('foo %1 baz %1', 'bar', 42)).to.be('foo bar baz bar');
+                });
+            });
+
+            describe('gettext', function () {
+                it('should handle peacefully singular untranslated keys', function () {
+                    expect(i18n.gettext('not translated')).to.be('not translated');
+                });
+                it('should handle peacefully singular untranslated keys with extra', function () {
+                    expect(i18n.gettext('not %1 translated', 'correctly')).to.be('not correctly translated');
+                    expect(i18n.gettext('not %1 %2 translated', 'fully', 'correctly')).to.be('not fully correctly translated');
+                });
+            });
+
+            describe('ngettext', function () {
+                it('should handle peacefully plural untranslated keys', function () {
+                    // english default plural rule is n !== 1
+                    expect(i18n.ngettext('%1 not translated singular', '%1 not translated plural', 0)).to.be('0 not translated plural');
+                    expect(i18n.ngettext('%1 not translated singular', '%1 not translated plural', 1)).to.be('1 not translated singular');
+                    expect(i18n.ngettext('%1 not translated singular', '%1 not translated plural', 3)).to.be('3 not translated plural');
+                });
+                it('should handle peacefully plural untranslated keys with extra', function () {
+                    expect(i18n.ngettext('%1 not %2 singular', '%1 not %2 plural', 1, 'foo')).to.be('1 not foo singular');
+                    expect(i18n.ngettext('%1 not %2 singular', '%1 not %2 plural', 3, 'foo')).to.be('3 not foo plural');
+                });
+                it('should use default english plural form for untranslated keys', function () {
+                    i18n = new window.i18n({ locale: 'fr', plural_forms: 'nplurals=2; plural=n>1;' });
+                    expect(i18n.ngettext('%1 not translated singular', '%1 not translated plural', 0)).to.be('0 not translated plural');
+                    expect(i18n.ngettext('%1 not translated singular', '%1 not translated plural', 1)).to.be('1 not translated singular');
+                    expect(i18n.ngettext('%1 not translated singular', '%1 not translated plural', 3)).to.be('3 not translated plural');
+                });
+                it('should handle correctly other language plural passed through setMessages method', function () {
+                    i18n = new window.i18n({locale: 'fr'});
+                    i18n.setMessages('messages', 'fr', {
+                        "There is %1 apple": [
+                            "Il y a %1 pomme",
+                            "Il y a %1 pommes"
+                        ]
+                    }, 'nplurals=2; plural=n>1;');
+                    expect(i18n.ngettext('There is %1 apple', 'There are %1 apples', 0)).to.be('Il y a 0 pomme');
+                    expect(i18n.ngettext('There is %1 apple', 'There are %1 apples', 1)).to.be('Il y a 1 pomme');
+                    expect(i18n.ngettext('There is %1 apple', 'There are %1 apples', 2)).to.be('Il y a 2 pommes');
+                });
+                it('should handle correctly other language plural passed through init options', function () {
+                    i18n = new window.i18n({
+                        locale: 'fr',
+                        messages: {
+                            "There is %1 apple": [
+                                "Il y a %1 pomme",
+                                "Il y a %1 pommes"
+                            ]
+                        },
+                        plural_forms: 'nplurals=2; plural=n>1;'
+                    });
+                    expect(i18n.ngettext('There is %1 apple', 'There are %1 apples', 0)).to.be('Il y a 0 pomme');
+                    expect(i18n.ngettext('There is %1 apple', 'There are %1 apples', 1)).to.be('Il y a 1 pomme');
+                    expect(i18n.ngettext('There is %1 apple', 'There are %1 apples', 2)).to.be('Il y a 2 pommes');
+                });
+                it('should ignore a plural translation when requesting the singular form', function () {
+                    i18n = new window.i18n({ locale: 'fr' });
+                    i18n.setMessages('messages', 'fr', {
+                        "apple": [
+                            "pomme",
+                            "pommes"
+                        ]
+                    }, 'nplurals=2; plural=n>1;');
+                    expect(i18n.gettext('apple')).to.be('apple');
+                    expect(i18n.ngettext('apple', 'apples', 1)).to.be('pomme');
+                    expect(i18n.ngettext('apple', 'apples', 2)).to.be('pommes');
+                });
+                it('should ignore a singular translation when requesting the plural form', function () {
+                    i18n = new window.i18n({ locale: 'fr' });
+                    i18n.setMessages('messages', 'fr', {
+                        "apple": "pomme"
+                    });
+                    expect(i18n.gettext('apple')).to.be('pomme');
+                    expect(i18n.ngettext('apple', 'apples', 1)).to.be('apple');
+                    expect(i18n.ngettext('apple', 'apples', 2)).to.be('apples');
+                });
+                it('should fail unvalid plural form', function () {
+                    i18n = new window.i18n({ locale: 'foo' });
+                    i18n.setMessages('messages', 'foo', {
+                        "There is %1 apple": [
+                            "Il y a %1 pomme",
+                            "Il y a %1 pommes"
+                        ]
+                    }, 'nplurals=2; plural=[not valid];');
+
+                    // do not throw error on default plural form if key does not have a translation
+                    expect(i18n.ngettext('foo', 'bar', 2)).to.be('bar');
+
+                    try {
+                        i18n.ngettext('There is %1 apple', 'There are %1 apples', 42);
+                    } catch (e) {
+                        expect(e.message).to.be('The plural form "nplurals=2; plural=[not valid];" is not valid');
+                    }
+                });
+                it('should handle multiple locale & pluals cohabitation', function () {
+                    i18n = new window.i18n({ locale: 'foo' });
+                    i18n.setMessages('messages', 'foo', {
+                        "singular": [
+                            "singular",
+                            "plural"
+                        ]
+                    }, 'nplurals=2; plural=n>10;');
+                    i18n.setMessages('messages', 'bar', {
+                        "singular": [
+                            "singulier",
+                            "pluriel"
+                        ]
+                    }, 'nplurals=2; plural=n>100;');
+                    expect(i18n.ngettext('singular', 'plural', 9)).to.be('singular');
+                    expect(i18n.ngettext('singular', 'plural', 11)).to.be('plural');
+
+                    i18n.setLocale('bar');
+                    expect(i18n.ngettext('singular', 'plural', 9)).to.be('singulier');
+                    expect(i18n.ngettext('singular', 'plural', 11)).to.be('singulier');
+                    expect(i18n.ngettext('singular', 'plural', 111)).to.be('pluriel');
+                });
+                it('should fallback to singular form if there is a problem with plurals', function () {
+                    // incorrect plural, more than nplurals
+                    i18n = new window.i18n({ locale: 'foo' });
+                    i18n.setMessages('messages', 'foo', {
+                        "apple": [
+                            "pomme",
+                            "pommes"
+                        ]
+                    }, 'nplurals=2; plural=3;');
+                    expect(i18n.ngettext('apple', 'apples', 1)).to.be('pomme');
+
+                    // plural is correct, but according to nplurals there should be more translations
+                    i18n = new window.i18n({ locale: 'ru' });
+                    i18n.setMessages('messages', 'ru', {
+                        "%1 apple": [
+                            "%1 яблоко",
+                            "%1 яблока"
+                            // "%1 яблок" - missed translation
+                        ]
+                    }, "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);");
+                    expect(i18n.ngettext('%1 apple', '%1 apples', 5)).to.be('5 яблоко');
+                });
+            });
+
+            describe('loadJSON', function () {
+                it('should properly load json', function () {
+                    var parsedJSON = {
+                        "": {
+                            "language": "fr",
+                            "plural-forms": "nplurals=2; plural=n>1;"
+                        },
+                        "Loading...": "Chargement...",
+                        "Save": "Sauvegarder",
+                        "There is %1 apple": [
+                            "Il y a %1 pomme",
+                            "Il y a %1 pommes"
+                        ],
+                        "Checkout\u0004Save": "Sauvegarder votre panier"
+                    },
+                    i18n = window.i18n()
+                        .loadJSON(JSON.stringify(parsedJSON))
+                        .setLocale('fr');
+                    expect(i18n.getLocale(), 'fr');
+                    expect(i18n.textdomain(), 'messages');
+                    expect(i18n.gettext('Save')).to.be('Sauvegarder');
+                    expect(i18n.gettext('Loading...')).to.be('Chargement...');
+                    expect(i18n.ngettext('There is %1 apple', 'There are %1 apples', 0)).to.be('Il y a 0 pomme');
+                    expect(i18n.ngettext('There is %1 apple', 'There are %1 apples', 2)).to.be('Il y a 2 pommes');
+                });
+            });
+        });
+    });
+}());

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/gettext.js.git



More information about the Pkg-javascript-commits mailing list