pax_global_header 0000666 0000000 0000000 00000000064 13434755603 0014524 g ustar 00root root 0000000 0000000 52 comment=48ead7682b7d0ba21c4b876d60179ed317fe6daf
gettext.js-0.7.0/ 0000775 0000000 0000000 00000000000 13434755603 0013627 5 ustar 00root root 0000000 0000000 gettext.js-0.7.0/.gitignore 0000664 0000000 0000000 00000000031 13434755603 0015611 0 ustar 00root root 0000000 0000000 .DS_Store
node_modules/*
gettext.js-0.7.0/.travis.yml 0000664 0000000 0000000 00000000110 13434755603 0015730 0 ustar 00root root 0000000 0000000 language: node_js
node_js:
- "node"
script:
- npm run-script test
gettext.js-0.7.0/CHANGELOG.md 0000664 0000000 0000000 00000000506 13434755603 0015441 0 ustar 00root root 0000000 0000000 # gettext.js changelog
- add support for language with subtags
**[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
gettext.js-0.7.0/README.md 0000664 0000000 0000000 00000010244 13434755603 0015107 0 ustar 00root root 0000000 0000000 # 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
### Debian (9, stretch)
apt-get install libjs-gettext.js
### 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
```
## 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 = {
"": {
"language": "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(json, 'messages');
```
See Required JSON format section below for more info.
### Set the locale
You could do it from your dom
```html
```
or from javascript
```javascript
i18n.setLocale('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.
#### Literal percent sign (%)
When you need to have literal percent sign followed by a number (common in Hebrew or Turkish) you can escape it using another percent sign, for example:
`gettext('My credit card has an interest rate of %%%1', 20);` -> "My credit card has an interest rate of %20"
or without variables
`gettext('My credit card has an interest rate of %%20');` -> "My credit card has an interest rate of %20"
## 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
gettext.js-0.7.0/bin/ 0000775 0000000 0000000 00000000000 13434755603 0014377 5 ustar 00root root 0000000 0000000 gettext.js-0.7.0/bin/po2json 0000775 0000000 0000000 00000003060 13434755603 0015716 0 ustar 00root root 0000000 0000000 #!/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]);
});
});
gettext.js-0.7.0/bower.json 0000664 0000000 0000000 00000000630 13434755603 0015637 0 ustar 00root root 0000000 0000000 {
"name": "gettext.js",
"ignore": [
"tests",
"lib",
"*.md",
"*.json",
"*.yml",
"gulpfile.js"
],
"keywords": [
"i18n",
"internationalization",
"gettext",
"ngettext",
"po"
],
"author": {
"name": "Guillaume Potier",
"email": "guillaume@wisembly.com",
"url": "http://guillaumepotier.com/"
},
"license": "MIT",
"main": "dist/gettext.js"
}
gettext.js-0.7.0/dist/ 0000775 0000000 0000000 00000000000 13434755603 0014572 5 ustar 00root root 0000000 0000000 gettext.js-0.7.0/dist/gettext.js 0000664 0000000 0000000 00000020301 13434755603 0016610 0 ustar 00root root 0000000 0000000 /*! gettext.js - Guillaume Potier - MIT Licensed */
(function (root, undef) {
var i18n = function (options) {
options = options || {};
this.__version = '0.5.3';
// 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);
gettext.js-0.7.0/dist/gettext.min.js 0000664 0000000 0000000 00000006350 13434755603 0017402 0 ustar 00root root 0000000 0000000 /*! gettext.js - Guillaume Potier - MIT Licensed */
!function(t,r){var e=function(t){t=t||{},this.__version="0.5.3";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.messages&&(o[u]={},o[u][l]=t.messages),t.plural_forms&&(s[l]=t.plural_forms);var p=function(t){var r=arguments;return t.replace(/%(\d+)/g,function(t,e){return r[e]})},c=function(t){var r=new RegExp("^\\s*nplurals\\s*=\\s*[0-9]+\\s*;\\s*plural\\s*=\\s*(?:\\s|[-\\?\\|&=!<>+*/%:;n0-9_()])+");if(!r.test(t))throw new Error(p('The plural form "%1" is not valid',t));return new Function("n","var plural, nplurals; "+t+" return { nplurals: nplurals, plural: (plural === true ? 1 : (plural ? plural : 0)) };")},f=function(t,r,e){if(1===t.length)return p.apply(this,[t[0]].concat(Array.prototype.slice.call(arguments,3)));var n;return e.plural_func?n=e.plural_func(r):a[l]?n=a[l](r):(a[l]=c(s[l]),n=a[l](r)),("undefined"==typeof n.plural||n.plural>n.nplurals||t.length<=n.plural)&&(n.plural=0),p.apply(this,[t[n.plural],r].concat(Array.prototype.slice.call(arguments,3)))};return{strfmt:p,__: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(t,r,e,a){if(!t||!r||!e)throw new Error("You must provide a domain, a locale and messages");if("string"!=typeof t||"string"!=typeof r||!n.isObject(e))throw new Error("Invalid arguments");return a&&(s[r]=a),o[t]||(o[t]={}),o[t][r]=e,this},loadJSON:function(t,r){if(n.isObject(t)||(t=JSON.parse(t)),!t[""]||!t[""].language||!t[""]["plural-forms"])throw new Error('Wrong JSON, it must have an empty key ("") with "language" and "plural-forms" information');var a=t[""];return delete t[""],this.setMessages(r||e.domain,a.language,t,a["plural-forms"])},setLocale:function(t){return l=t,this},getLocale:function(){return l},textdomain:function(t){return t?(u=t,this):u},gettext:function(t){return this.dcnpgettext.apply(this,[r,r,t,r,r].concat(Array.prototype.slice.call(arguments,1)))},ngettext:function(t,e,n){return this.dcnpgettext.apply(this,[r,r,t,e,n].concat(Array.prototype.slice.call(arguments,3)))},pgettext:function(t,e){return this.dcnpgettext.apply(this,[r,t,e,r,r].concat(Array.prototype.slice.call(arguments,2)))},dcnpgettext:function(t,r,n,a,s){if(t=t||u,"string"!=typeof n)throw new Error(this.strfmt('Msgid "%1" is not a valid translatable string',n));var p,c={},g=r?r+i+n:n,m=o[t]&&o[t][l]&&o[t][l][g];return m=a?m&&"string"!=typeof o[t][l][g]:m&&"string"==typeof o[t][l][g],m?p=o[t][l][g]:(p=n,c.plural_func=e.plural_func),a?f.apply(this,[m?p:[n,a],s,c].concat(Array.prototype.slice.call(arguments,5))):f.apply(this,[[p],s,c].concat(Array.prototype.slice.call(arguments,5)))}}};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=e),exports.i18n=e):"function"==typeof define&&define.amd?define(function(){return e}):t.i18n=e}(this); gettext.js-0.7.0/gulpfile.js 0000664 0000000 0000000 00000000632 13434755603 0015775 0 ustar 00root root 0000000 0000000 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({output: { comments: 'some' }}))
.pipe(gulp.dest('dist'));
});
gettext.js-0.7.0/lib/ 0000775 0000000 0000000 00000000000 13434755603 0014375 5 ustar 00root root 0000000 0000000 gettext.js-0.7.0/lib/gettext.js 0000664 0000000 0000000 00000022144 13434755603 0016422 0 ustar 00root root 0000000 0000000 /*! gettext.js - Guillaume Potier - MIT Licensed */
(function (root, undef) {
var i18n = function (options) {
options = options || {};
this.__version = '0.5.3';
// default values that could be overriden in i18n() construct
var defaults = {
domain: 'messages',
locale: (typeof document !== 'undefined' ? document.documentElement.getAttribute('lang') : false) || '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
// put space after double % to prevent placeholder replacement of such matches
.replace(/%%/g, '%% ')
// replace placeholders
.replace(/%(\d+)/g, function (str, p1) {
return args[p1];
})
// replace double % and space with single %
.replace(/%% /g, '%')
};
var expand_locale = function(locale) {
var locales = [locale],
i = locale.lastIndexOf('-');
while (i > 0) {
locale = locale.slice(0, i);
locales.push(locale);
i = locale.lastIndexOf('-');
}
return locales;
};
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
expand_locale: expand_locale, // expose expand_locale 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,
locale;
var locales = expand_locale(_locale);
for (var i in locales) {
locale = locales[i];
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) {
break;
}
}
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);
gettext.js-0.7.0/package.json 0000664 0000000 0000000 00000001170 13434755603 0016114 0 ustar 00root root 0000000 0000000 {
"name": "gettext.js",
"version": "0.6.0",
"scripts": {
"test": "karma start tests/karma.config.js",
"test-dev": "karma start tests/karma.config.dev.js",
"build": "gulp"
},
"bin": {
"po2json-gettextjs": "bin/po2json"
},
"dependencies": {
"po2json": "^0.4.0"
},
"devDependencies": {
"gulp": "^3.9.1",
"gulp-rename": "^1.2.2",
"gulp-uglify": "^3.0.0",
"karma": "^3.0.0",
"karma-mocha": "^1.3.0",
"karma-phantomjs-launcher": "^1.0.4",
"karma-sinon-expect": "^0.1.4",
"mocha": "^4.0.1"
},
"spm": {
"main": "lib/gettext.js"
},
"main": "lib/gettext.js"
}
gettext.js-0.7.0/tests/ 0000775 0000000 0000000 00000000000 13434755603 0014771 5 ustar 00root root 0000000 0000000 gettext.js-0.7.0/tests/karma.config.dev.js 0000664 0000000 0000000 00000000561 13434755603 0020445 0 ustar 00root root 0000000 0000000 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']
});
};
gettext.js-0.7.0/tests/karma.config.js 0000664 0000000 0000000 00000000607 13434755603 0017671 0 ustar 00root root 0000000 0000000 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
});
};
gettext.js-0.7.0/tests/tests.js 0000664 0000000 0000000 00000032627 13434755603 0016503 0 ustar 00root root 0000000 0000000 (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');
});
it('should handle literal percent (%) signs', function () {
expect(i18n.strfmt('foo 1%% bar')).to.be('foo 1% bar');
expect(i18n.strfmt('foo %1%% bar', 10)).to.be('foo 10% bar');
expect(i18n.strfmt('foo %%1 bar')).to.be('foo %1 bar');
expect(i18n.strfmt('foo %%%1 bar', 10)).to.be('foo %10 bar');
});
});
describe('expand_locale', function() {
it('should be a i18n method', function() {
expect(i18n.expand_locale).to.be.a('function');
});
it('should handle simple locale', function() {
expect(i18n.expand_locale('fr')).to.eql(['fr']);
});
it('should handle complex locale', function() {
expect(i18n.expand_locale('de-CH-1996')).to.eql(['de-CH-1996', 'de-CH', 'de']);
});
});
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');
});
it('should fallback to father language', function() {
i18n = new window.i18n();
i18n.setMessages('messages', 'fr', {
"Mop": "Serpillière",
});
i18n.setMessages('messages', 'fr-BE', {
"Mop": "Torchon",
});
i18n.setLocale('fr-BE');
expect(i18n.gettext("Mop")).to.be("Torchon");
i18n.setLocale('fr');
expect(i18n.gettext("Mop")).to.be("Serpillière");
i18n.setLocale('fr-FR');
expect(i18n.gettext("Mop")).to.be("Serpillière");
});
});
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');
});
});
});
});
}());