pax_global_header00006660000000000000000000000064131711500700014505gustar00rootroot0000000000000052 comment=30b2f3a414831e20f7bef2d545985920113b7cd5 gettext.js-0.5.4/000077500000000000000000000000001317115007000136125ustar00rootroot00000000000000gettext.js-0.5.4/.gitignore000066400000000000000000000000311317115007000155740ustar00rootroot00000000000000.DS_Store node_modules/* gettext.js-0.5.4/.travis.yml000066400000000000000000000001061317115007000157200ustar00rootroot00000000000000language: node_js node_js: - 0.11 script: - npm run-script test gettext.js-0.5.4/CHANGELOG.md000066400000000000000000000004311317115007000154210ustar00rootroot00000000000000# 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 gettext.js-0.5.4/README.md000066400000000000000000000073731317115007000151030ustar00rootroot00000000000000# 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 = { "": { "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 ``` 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. ## 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.5.4/bin/000077500000000000000000000000001317115007000143625ustar00rootroot00000000000000gettext.js-0.5.4/bin/po2json000077500000000000000000000030601317115007000157010ustar00rootroot00000000000000#!/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.5.4/bower.json000066400000000000000000000006301317115007000156220ustar00rootroot00000000000000{ "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.5.4/dist/000077500000000000000000000000001317115007000145555ustar00rootroot00000000000000gettext.js-0.5.4/dist/gettext.js000066400000000000000000000203011317115007000165730ustar00rootroot00000000000000/*! 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.5.4/dist/gettext.min.js000066400000000000000000000063501317115007000173650ustar00rootroot00000000000000/*! 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.5.4/gulpfile.js000066400000000000000000000006301317115007000157560ustar00rootroot00000000000000var 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')); }); gettext.js-0.5.4/lib/000077500000000000000000000000001317115007000143605ustar00rootroot00000000000000gettext.js-0.5.4/lib/gettext.js000066400000000000000000000203261317115007000164050ustar00rootroot00000000000000/*! 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 ? 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.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.5.4/package.json000066400000000000000000000013421317115007000161000ustar00rootroot00000000000000{ "name": "gettext.js", "version": "0.5.3", "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" }, "main": "lib/gettext.js" } gettext.js-0.5.4/tests/000077500000000000000000000000001317115007000147545ustar00rootroot00000000000000gettext.js-0.5.4/tests/karma.config.dev.js000066400000000000000000000005611317115007000204300ustar00rootroot00000000000000module.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.5.4/tests/karma.config.js000066400000000000000000000006071317115007000176540ustar00rootroot00000000000000module.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.5.4/tests/tests.js000066400000000000000000000273671317115007000164730ustar00rootroot00000000000000(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'); }); }); }); }); }());