pax_global_header00006660000000000000000000000064134347556030014524gustar00rootroot0000000000000052 comment=48ead7682b7d0ba21c4b876d60179ed317fe6daf gettext.js-0.7.0/000077500000000000000000000000001343475560300136275ustar00rootroot00000000000000gettext.js-0.7.0/.gitignore000066400000000000000000000000311343475560300156110ustar00rootroot00000000000000.DS_Store node_modules/* gettext.js-0.7.0/.travis.yml000066400000000000000000000001101343475560300157300ustar00rootroot00000000000000language: node_js node_js: - "node" script: - npm run-script test gettext.js-0.7.0/CHANGELOG.md000066400000000000000000000005061343475560300154410ustar00rootroot00000000000000# 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.md000066400000000000000000000102441343475560300151070ustar00rootroot00000000000000# 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/000077500000000000000000000000001343475560300143775ustar00rootroot00000000000000gettext.js-0.7.0/bin/po2json000077500000000000000000000030601343475560300157160ustar00rootroot00000000000000#!/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.json000066400000000000000000000006301343475560300156370ustar00rootroot00000000000000{ "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/000077500000000000000000000000001343475560300145725ustar00rootroot00000000000000gettext.js-0.7.0/dist/gettext.js000066400000000000000000000203011343475560300166100ustar00rootroot00000000000000/*! 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.js000066400000000000000000000063501343475560300174020ustar00rootroot00000000000000/*! 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.js000066400000000000000000000006321343475560300157750ustar00rootroot00000000000000var 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/000077500000000000000000000000001343475560300143755ustar00rootroot00000000000000gettext.js-0.7.0/lib/gettext.js000066400000000000000000000221441343475560300164220ustar00rootroot00000000000000/*! 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.json000066400000000000000000000011701343475560300161140ustar00rootroot00000000000000{ "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/000077500000000000000000000000001343475560300147715ustar00rootroot00000000000000gettext.js-0.7.0/tests/karma.config.dev.js000066400000000000000000000005611343475560300204450ustar00rootroot00000000000000module.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.js000066400000000000000000000006071343475560300176710ustar00rootroot00000000000000module.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.js000066400000000000000000000326271343475560300165030ustar00rootroot00000000000000(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'); }); }); }); }); }());