[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