node-gettext
Advanced tools
Comparing version 1.1.0 to 2.0.0-rc.0
'use strict'; | ||
var get = require('lodash.get'); | ||
var plurals = require('./plurals'); | ||
var gettextParser = require('gettext-parser'); | ||
@@ -9,79 +9,157 @@ module.exports = Gettext; | ||
/** | ||
* Gettext function | ||
* Creates and returns a new Gettext instance. | ||
* | ||
* @constructor | ||
* @param {Object} options A set of options | ||
* @param {Boolean} options.debug Whether to output debug info into the | ||
* console. | ||
* @return {Object} A Gettext instance | ||
*/ | ||
function Gettext() { | ||
this.domains = {}; | ||
this._currentDomain = false; | ||
function Gettext(options) { | ||
options = options || {}; | ||
this.catalogs = {}; | ||
this.locale = null; | ||
this.domain = 'messages'; | ||
this.listeners = []; | ||
// Set debug flag | ||
if ('debug' in options) { | ||
this.debug = options.debug === true; | ||
} | ||
else if (typeof process !== 'undefined' && process.env && process.env.NODE_ENV) { | ||
this.debug = process.env.NODE_ENV !== 'production'; | ||
} | ||
else { | ||
this.debug = false; | ||
} | ||
} | ||
/** | ||
* Adds a gettext to the domains list. If default textdomain is not set, uses it | ||
* as default | ||
* Adds an event listener. | ||
* | ||
* @param {String} domain Case insensitive language identifier (domain) | ||
* @param {Buffer} fileContents Translations file (*.mo) contents as a Buffer object | ||
* @param {String} eventName An event name | ||
* @param {Function} callback An event handler function | ||
*/ | ||
Gettext.prototype.addTextdomain = function(domain, file) { | ||
domain = this._normalizeDomain(domain); | ||
var translation; | ||
Gettext.prototype.on = function(eventName, callback) { | ||
this.listeners.push({ | ||
eventName: eventName, | ||
callback: callback | ||
}); | ||
}; | ||
if (file && file.translations) { | ||
translation = file; | ||
} | ||
else if (file && typeof file !== 'string') { | ||
translation = gettextParser.mo.parse(file, 'utf-8'); | ||
} | ||
/** | ||
* Removes an event listener. | ||
* | ||
* @param {String} eventName An event name | ||
* @param {Function} callback A previously registered event handler function | ||
*/ | ||
Gettext.prototype.off = function(eventName, callback) { | ||
this.listeners = this.listeners.filter(function(listener) { | ||
return ( | ||
listener.eventName === eventName && | ||
listener.callback === callback | ||
) === false; | ||
}); | ||
}; | ||
if (!translation) { | ||
translation = gettextParser.po.parse(file || '', 'utf-8'); | ||
/** | ||
* Emits an event to all registered event listener. | ||
* | ||
* @param {String} eventName An event name | ||
* @param {any} eventData Data to pass to event listeners | ||
*/ | ||
Gettext.prototype.emit = function(eventName, eventData) { | ||
for (var i = 0; i < this.listeners.length; i++) { | ||
var listener = this.listeners[i]; | ||
if (listener.eventName === eventName) { | ||
listener.callback(eventData); | ||
} | ||
} | ||
}; | ||
// We do not want to parse and compile stuff from unknown sources | ||
// so we only use precompiled plural definitions | ||
var pluralsInfo = plurals[this._normalizeDomain(domain, true)]; | ||
if (pluralsInfo && translation.headers) { | ||
translation.headers['plural-forms'] = pluralsInfo.pluralsText; | ||
translation.pluralsFunc = pluralsInfo.pluralsFunc; | ||
} else { | ||
// default plurals to EN rules | ||
translation.pluralsFunc = plurals.en.pluralsFunc; | ||
/** | ||
* Logs a warning to the console if debug mode is enabled. | ||
* | ||
* @ignore | ||
* @param {String} message A warning message | ||
*/ | ||
Gettext.prototype.warn = function(message) { | ||
if (this.debug) { | ||
console.warn(message); | ||
} | ||
this.domains[domain] = translation; | ||
this.emit('error', message); | ||
}; | ||
if (!this._currentDomain) { | ||
this._currentDomain = domain; | ||
/** | ||
* Stores a set of translations in the set of gettext | ||
* catalogs. | ||
* | ||
* @example | ||
* gt.addTranslations('sv-SE', 'messages', translationsObject) | ||
* | ||
* @param {String} locale A locale string | ||
* @param {String} domain A domain name | ||
* @param {Object} translations An object of gettext-parser JSON shape | ||
*/ | ||
Gettext.prototype.addTranslations = function(locale, domain, translations) { | ||
if (!this.catalogs[locale]) { | ||
this.catalogs[locale] = {}; | ||
} | ||
this.catalogs[locale][domain] = translations; | ||
}; | ||
/** | ||
* Changes the current default textdomain | ||
* Sets the locale to get translated messages for. | ||
* | ||
* @param {String} [domain] Case insensitive language identifier | ||
* @return {String} cuurent textdomain | ||
* @example | ||
* gt.setLocale('sv-SE') | ||
* | ||
* @param {String} locale A locale | ||
*/ | ||
Gettext.prototype.textdomain = function(updatedDomain) { | ||
if (!arguments.length) { | ||
return this._currentDomain; | ||
Gettext.prototype.setLocale = function(locale) { | ||
if (!locale) { | ||
this.warn('You called setLocale() with an empty value, which makes little sense.'); | ||
return; | ||
} | ||
updatedDomain = this._normalizeDomain(updatedDomain); | ||
if (this._currentDomain !== updatedDomain && this.domains.hasOwnProperty(updatedDomain)) { | ||
this._currentDomain = updatedDomain; | ||
return true; | ||
} else { | ||
return false; | ||
if (!this.catalogs[locale]) { | ||
this.warn('You called setLocale() with "' + locale + '", but no translations for that locale has been added.'); | ||
return; | ||
} | ||
this.locale = locale; | ||
}; | ||
/** | ||
* Sets the default gettext domain. | ||
* | ||
* @example | ||
* gt.setTextDomain('domainname') | ||
* | ||
* @param {String} domain A gettext domain name | ||
*/ | ||
Gettext.prototype.setTextDomain = function(domain) { | ||
if (!domain) { | ||
this.warn('You called setTextDomain() with an empty `domain` value, which is not allowed.'); | ||
return; | ||
} | ||
this.domain = domain; | ||
}; | ||
/** | ||
* Translates a string using the default textdomain | ||
* | ||
* @param {String} msgid String to be translated | ||
* @return {String} translation or the original string if no translation was found | ||
* @example | ||
* gt.gettext('Some text') | ||
* | ||
* @param {String} msgid String to be translated | ||
* @return {String} Translation or the original string if no translation was found | ||
*/ | ||
Gettext.prototype.gettext = function(msgid) { | ||
return this.dnpgettext(this._currentDomain, '', msgid); | ||
return this.dnpgettext(this.domain, '', msgid); | ||
}; | ||
@@ -92,5 +170,8 @@ | ||
* | ||
* @param {String} domain Case insensitive language identifier | ||
* @param {String} msgid String to be translated | ||
* @return {String} translation or the original string if no translation was found | ||
* @example | ||
* gt.dgettext('domainname', 'Some text') | ||
* | ||
* @param {String} domain A gettext domain name | ||
* @param {String} msgid String to be translated | ||
* @return {String} Translation or the original string if no translation was found | ||
*/ | ||
@@ -104,9 +185,12 @@ Gettext.prototype.dgettext = function(domain, msgid) { | ||
* | ||
* @param {String} msgid String to be translated | ||
* @param {String} msgidPlural If no translation was found, return this on count!=1 | ||
* @param {Number} count Number count for the plural | ||
* @return {String} translation or the original string if no translation was found | ||
* @example | ||
* gt.ngettext('One thing', 'Many things', numberOfThings) | ||
* | ||
* @param {String} msgid String to be translated when count is not plural | ||
* @param {String} msgidPlural String to be translated when count is plural | ||
* @param {Number} count Number count for the plural | ||
* @return {String} Translation or the original string if no translation was found | ||
*/ | ||
Gettext.prototype.ngettext = function(msgid, msgidPlural, count) { | ||
return this.dnpgettext(this._currentDomain, '', msgid, msgidPlural, count); | ||
return this.dnpgettext(this.domain, '', msgid, msgidPlural, count); | ||
}; | ||
@@ -117,7 +201,10 @@ | ||
* | ||
* @param {String} domain Case insensitive language identifier | ||
* @param {String} msgid String to be translated | ||
* @param {String} msgidPlural If no translation was found, return this on count!=1 | ||
* @param {Number} count Number count for the plural | ||
* @return {String} translation or the original string if no translation was found | ||
* @example | ||
* gt.dngettext('domainname', 'One thing', 'Many things', numberOfThings) | ||
* | ||
* @param {String} domain A gettext domain name | ||
* @param {String} msgid String to be translated when count is not plural | ||
* @param {String} msgidPlural String to be translated when count is plural | ||
* @param {Number} count Number count for the plural | ||
* @return {String} Translation or the original string if no translation was found | ||
*/ | ||
@@ -131,8 +218,11 @@ Gettext.prototype.dngettext = function(domain, msgid, msgidPlural, count) { | ||
* | ||
* @param {String} msgctxt Translation context | ||
* @param {String} msgid String to be translated | ||
* @return {String} translation or the original string if no translation was found | ||
* @example | ||
* gt.pgettext('sports', 'Back') | ||
* | ||
* @param {String} msgctxt Translation context | ||
* @param {String} msgid String to be translated | ||
* @return {String} Translation or the original string if no translation was found | ||
*/ | ||
Gettext.prototype.pgettext = function(msgctxt, msgid) { | ||
return this.dnpgettext(this._currentDomain, msgctxt, msgid); | ||
return this.dnpgettext(this.domain, msgctxt, msgid); | ||
}; | ||
@@ -143,6 +233,9 @@ | ||
* | ||
* @param {String} domain Case insensitive language identifier | ||
* @param {String} msgctxt Translation context | ||
* @param {String} msgid String to be translated | ||
* @return {String} translation or the original string if no translation was found | ||
* @example | ||
* gt.dpgettext('domainname', 'sports', 'Back') | ||
* | ||
* @param {String} domain A gettext domain name | ||
* @param {String} msgctxt Translation context | ||
* @param {String} msgid String to be translated | ||
* @return {String} Translation or the original string if no translation was found | ||
*/ | ||
@@ -154,12 +247,15 @@ Gettext.prototype.dpgettext = function(domain, msgctxt, msgid) { | ||
/** | ||
* Translates a plural string from a specifi context using the default textdomain | ||
* Translates a plural string from a specific context using the default textdomain | ||
* | ||
* @param {String} msgctxt Translation context | ||
* @param {String} msgid String to be translated | ||
* @param {String} msgidPlural If no translation was found, return this on count!=1 | ||
* @param {Number} count Number count for the plural | ||
* @return {String} translation or the original string if no translation was found | ||
* @example | ||
* gt.npgettext('sports', 'Back', '%d backs', numberOfBacks) | ||
* | ||
* @param {String} msgctxt Translation context | ||
* @param {String} msgid String to be translated when count is not plural | ||
* @param {String} msgidPlural String to be translated when count is plural | ||
* @param {Number} count Number count for the plural | ||
* @return {String} Translation or the original string if no translation was found | ||
*/ | ||
Gettext.prototype.npgettext = function(msgctxt, msgid, msgidPlural, count) { | ||
return this.dnpgettext(this._currentDomain, msgctxt, msgid, msgidPlural, count); | ||
return this.dnpgettext(this.domain, msgctxt, msgid, msgidPlural, count); | ||
}; | ||
@@ -170,8 +266,11 @@ | ||
* | ||
* @param {String} domain Case insensitive language identifier | ||
* @param {String} msgctxt Translation context | ||
* @param {String} msgid String to be translated | ||
* @param {String} msgidPlural If no translation was found, return this on count!=1 | ||
* @param {Number} count Number count for the plural | ||
* @return {String} translation or the original string if no translation was found | ||
* @example | ||
* gt.dnpgettext('domainname', 'sports', 'Back', '%d backs', numberOfBacks) | ||
* | ||
* @param {String} domain A gettext domain name | ||
* @param {String} msgctxt Translation context | ||
* @param {String} msgid String to be translated | ||
* @param {String} msgidPlural If no translation was found, return this on count!=1 | ||
* @param {Number} count Number count for the plural | ||
* @return {String} Translation or the original string if no translation was found | ||
*/ | ||
@@ -183,3 +282,2 @@ Gettext.prototype.dnpgettext = function(domain, msgctxt, msgid, msgidPlural, count) { | ||
domain = this._normalizeDomain(domain); | ||
msgctxt = msgctxt || ''; | ||
@@ -191,6 +289,13 @@ | ||
if (!this.locale) { | ||
this.warn('You need to set a locale using setLocale(locale) before getting translated messages.'); | ||
return defaultTranslation; | ||
} | ||
translation = this._getTranslation(domain, msgctxt, msgid); | ||
if (translation) { | ||
if (typeof count === 'number') { | ||
index = this.domains[domain].pluralsFunc(count); | ||
var pluralsFunc = plurals[Gettext.getLanguageCode(this.locale)].pluralsFunc; | ||
index = pluralsFunc(count); | ||
if (typeof index === 'boolean') { | ||
@@ -205,2 +310,6 @@ index = index ? 1 : 0; | ||
} | ||
else { | ||
this.warn('No translation was found for msgid "' + msgid + '" in msgctxt "' + msgctxt + '" and domain "' + domain + '"'); | ||
} | ||
return defaultTranslation; | ||
@@ -210,8 +319,12 @@ }; | ||
/** | ||
* Retrieves comments object for a translation | ||
* Retrieves comments object for a translation. The comments object | ||
* has the shape `{ translator, extracted, reference, flag, previous }`. | ||
* | ||
* @param {String} domain Case insensitive language identifier | ||
* @param {String} msgctxt Translation context | ||
* @param {String} msgid String to be translated | ||
* @return {Object} comments object or false if not found | ||
* @example | ||
* const comment = gt.getComment('domainname', 'sports', 'Backs') | ||
* | ||
* @param {String} domain A gettext domain name | ||
* @param {String} msgctxt Translation context | ||
* @param {String} msgid String to be translated | ||
* @return {Object} Comments object or false if not found | ||
*/ | ||
@@ -221,4 +334,2 @@ Gettext.prototype.getComment = function(domain, msgctxt, msgid) { | ||
domain = this._normalizeDomain(domain); | ||
translation = this._getTranslation(domain, msgctxt, msgid); | ||
@@ -235,41 +346,56 @@ if (translation) { | ||
* | ||
* @param {String} domain Case insensitive language identifier | ||
* @param {String} msgctxt Translation context | ||
* @param {String} msgid String to be translated | ||
* @return {Object} translation object or false if not found | ||
* @private | ||
* @param {String} domain A gettext domain name | ||
* @param {String} msgctxt Translation context | ||
* @param {String} msgid String to be translated | ||
* @return {Object} Translation object or false if not found | ||
*/ | ||
Gettext.prototype._getTranslation = function(domain, msgctxt, msgid) { | ||
var translation; | ||
msgctxt = msgctxt || ''; | ||
domain = this._normalizeDomain(domain); | ||
if (this.domains.hasOwnProperty(domain)) { | ||
if (this.domains[domain].translations && this.domains[domain].translations[msgctxt]) { | ||
if ((translation = this.domains[domain].translations[msgctxt][msgid])) { | ||
return translation; | ||
} | ||
} | ||
} | ||
return get(this.catalogs, [this.locale, domain, 'translations', msgctxt, msgid]); | ||
}; | ||
return false; | ||
/** | ||
* Returns the language code part of a locale | ||
* | ||
* @example | ||
* Gettext.getLanguageCode('sv-SE') | ||
* // -> "sv" | ||
* | ||
* @param {String} locale A case-insensitive locale string | ||
* @returns {String} A language code | ||
*/ | ||
Gettext.getLanguageCode = function(locale) { | ||
return locale.split(/[\-_]/)[0].toLowerCase(); | ||
}; | ||
/** | ||
* Normalizes textdomain value | ||
* This function will be removed in the final 2.0.0 release. | ||
* | ||
* @param {String} domain Textdomain | ||
* @param {Boolean} [isShort] If true then returns only language | ||
* @returns {String} Normalized textdomain | ||
* @deprecated | ||
*/ | ||
Gettext.prototype._normalizeDomain = function(domain, isShort) { | ||
var parts = (domain || '').toString().split('.').shift().split(/[\-_]/); | ||
var language = (parts.shift() || '').toLowerCase(); | ||
var locale = (parts.join('-') || '').toUpperCase(); | ||
Gettext.prototype.addTextdomain = function() { | ||
if (isShort) { | ||
return language; | ||
} else { | ||
return [].concat(language || []).concat(locale || []).join('_'); | ||
} | ||
// TODO(alexanderwallin): Add instructions for file i/o | ||
console.error('addTextdomain() is deprecated.\n\n' + | ||
'* To add translations, use addTranslations()\n' + | ||
'* To set the default domain, use setTextDomain()\n' + | ||
'\n' + | ||
'To read more about the migration from node-gettext v1 to v2, ' + | ||
'see https://github.com/alexanderwallin/node-gettext/#migrating-from-1x-to-2x'); | ||
}; | ||
/** | ||
* This function will be removed in the final 2.0.0 release. | ||
* | ||
* @deprecated | ||
*/ | ||
Gettext.prototype.textdomain = function() { | ||
console.error('textdomain() is deprecated.\n\n' + | ||
'* To set the current locale, use setLocale()\n' + | ||
'* To set the default domain, use setTextDomain()\n' + | ||
'\n' + | ||
'To read more about the migration from node-gettext v1 to v2, ' + | ||
'see https://github.com/alexanderwallin/node-gettext/#migrating-from-1x-to-2x'); | ||
}; |
{ | ||
"name": "node-gettext", | ||
"description": "Gettext client for Node.js to use .mo files for I18N", | ||
"version": "1.1.0", | ||
"description": "A JavaScript implementation of gettext, a localization framework", | ||
"version": "2.0.0-rc.0", | ||
"author": "Andris Reinman", | ||
"maintainers": [ | ||
{ | ||
"name": "andris", | ||
"email": "andris@node.ee" | ||
"name": "Alexander Wallin", | ||
"email": "office@alexanderwallin.com" | ||
} | ||
], | ||
"homepage": "http://github.com/andris9/node-gettext", | ||
"homepage": "http://github.com/alexanderwallin/node-gettext", | ||
"repository": { | ||
"type": "git", | ||
"url": "http://github.com/andris9/node-gettext.git" | ||
"url": "http://github.com/alexanderwallin/node-gettext.git" | ||
}, | ||
"scripts": { | ||
"test": "grunt" | ||
"test": "grunt", | ||
"docs": "jsdoc2md -f lib/gettext.js -t docs/README.template.md --partial docs/templates/*.hbs --param-list-format list > README.md" | ||
}, | ||
"main": "./lib/gettext", | ||
"main": "./lib/gettext.js", | ||
"files": [ | ||
"lib", | ||
"test" | ||
], | ||
"licenses": [ | ||
{ | ||
"type": "MIT", | ||
"url": "http://github.com/andris9/node-gettext/blob/master/LICENSE" | ||
"url": "http://github.com/alexanderwallin/node-gettext/blob/master/LICENSE" | ||
} | ||
], | ||
"dependencies": { | ||
"gettext-parser": "^1.1.2" | ||
"lodash.get": "^4.4.2" | ||
}, | ||
@@ -36,3 +41,5 @@ "devDependencies": { | ||
"grunt-mocha-test": "^0.12.7", | ||
"mocha": "^2.4.5" | ||
"jsdoc-to-markdown": "^2.0.1", | ||
"mocha": "^2.4.5", | ||
"sinon": "^1.17.7" | ||
}, | ||
@@ -45,5 +52,7 @@ "engine": { | ||
"l10n", | ||
"gettext", | ||
"mo" | ||
"internationalization", | ||
"localization", | ||
"translation", | ||
"gettext" | ||
] | ||
} |
357
README.md
@@ -1,129 +0,360 @@ | ||
# node-gettext | ||
**node-gettext** is a Node.JS module to use .MO and .PO files. | ||
<p align="center"> | ||
<img src="docs/node-gettext-logo.png" width="160" height="160" /> | ||
</p> | ||
**NB!** If you just want to parse or compile mo/po files, check out [gettext-parser](https://github.com/andris9/gettext-parser). | ||
<h1 align="center"> | ||
node-gettext | ||
</h1> | ||
## Features | ||
[![Build Status](https://travis-ci.org/alexanderwallin/node-gettext.svg?branch=master)](http://travis-ci.org/alexanderwallin/node-gettext) | ||
[![npm version](https://badge.fury.io/js/node-gettext.svg)](https://badge.fury.io/js/node-gettext) | ||
* Load binary *MO* or source *PO* files | ||
* Supports contexts and plurals | ||
**`node-gettext`** is a JavaScript implementation of [gettext](https://www.gnu.org/software/gettext/gettext.html), a localization framework. | ||
[![Build Status](https://secure.travis-ci.org/andris9/node-gettext.png)](http://travis-ci.org/andris9/node-gettext) | ||
If you just want to parse or compile mo/po files, check out [gettext-parser](https://github.com/smhg/gettext-parser). | ||
## Support node-gettext development | ||
**NOTE:** This is the README for v2 of node-gettext, which introduces many braking changes and is currently in alpha. You can find the [README for v1 here](https://github.com/alexanderwallin/node-gettext/blob/master/docs/v1/README.md). | ||
[![Donate to author](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=DB26KWR2BQX5W) | ||
* [Features](#features) | ||
* [Installation](#installation) | ||
* [Usage](#usage) | ||
* [Migrating from v1 to v2](#migrating-from-v1-to-v2) | ||
* [API](#api) | ||
* [License](#license) | ||
* [See also](#see-also) | ||
## Features | ||
* Supports domains, contexts and plurals | ||
* Supports .json, .mo and .po files with the help of [gettext-parser](https://github.com/smhg/gettext-parser) | ||
* Ships with plural forms for 136 languages | ||
* Change locale or domain on the fly | ||
* Useful error messages enabled by a `debug` option | ||
* Emits events for internal errors, such as missing translations | ||
## Installation | ||
npm install node-gettext | ||
```sh | ||
npm install --save node-gettext | ||
``` | ||
## Usage | ||
### Create a new Gettext object | ||
```js | ||
import Gettext from 'node-gettext' | ||
import swedishTranslations from './translations/sv-SE.json' | ||
var Gettext = require("node-gettext"); | ||
const gt = new Gettext() | ||
gt.addTranslations('sv-SE', 'messages', swedishTranslations) | ||
gt.setLocale('sv-SE') | ||
var gt = new Gettext(); | ||
gt.gettext('The world is a funny place') | ||
// -> "Världen är en underlig plats" | ||
``` | ||
### Add a language | ||
### Error events | ||
*addTextdomain(domain, file)* | ||
```js | ||
// Add translations etc... | ||
Language data needs to be in the Buffer format - it can be either contents of a *MO* or *PO* file. | ||
gt.on('error', error => console.log('oh nose', error)) | ||
gt.gettext('An unrecognized message') | ||
// -> 'oh nose', 'An unrecognized message' | ||
``` | ||
*addTextdomain(domain[, fileContents])* | ||
Load from a *MO* file | ||
## Migrating from v1 to v2 | ||
var fileContents = fs.readFileSync("et.mo"); | ||
gt.addTextdomain("et", fileContents); | ||
Version 1 of `node-gettext` confused domains with locales, which version 2 has corrected. `node-gettext` also no longer parses files or file paths for you, but accepts only ready-parsed JSON translation objects. | ||
or load from a *PO* file | ||
Here is a full list of all breaking changes: | ||
var fileContents = fs.readFileSync("et.po"); | ||
gt.addTextdomain("et", fileContents); | ||
* `textdomain(domain)` is now `setLocale(locale)` | ||
* `dgettext`, `dngettext`, `dpgettext` and `dnpgettext` does not treat the leading `domain` argument as a locale, but as a domain. To get a translation from a certain locale you need to call `setLocale(locale)` beforehand. | ||
* A new `setTextDomain(domain)` has been introduced | ||
* `addTextdomain(domain, file)` is now `addTranslations(locale, domain, translations)` | ||
* `addTranslations(locale, domain, translations)` **only accepts a JSON object with the [shape described in the `gettext-parser` README](https://github.com/smhg/gettext-parser#data-structure-of-parsed-mopo-files)**. To load translations from .mo or .po files, use [gettext-parser](https://github.com/smhg/gettext-parser), and it will provide you with valid JSON objects. | ||
* `_currentDomain` is now `domain` | ||
* `domains` is now `catalogs` | ||
* The instance method `__normalizeDomain(domain)` has been replaced by a static method `Gettext.getLanguageCode(locale)` | ||
Plural rules are automatically detected from the language code | ||
gt.addTextdomain("et"); | ||
gt.setTranslation("et", false, "hello!", "tere!"); | ||
## API | ||
### Check or change default language | ||
<a name="Gettext"></a> | ||
*textdomain(domain)* | ||
## Gettext | ||
<a name="new_Gettext_new"></a> | ||
gt.textdomain("et"); | ||
### new Gettext(options) | ||
Creates and returns a new Gettext instance. | ||
The function also returns the current texdomain value | ||
**Returns**: <code>Object</code> - A Gettext instance | ||
**Params** | ||
var curlang = gt.textdomain(); | ||
- `options`: <code>Object</code> - A set of options | ||
- `.debug`: <code>Boolean</code> - Whether to output debug info into the | ||
console. | ||
## Translation methods | ||
<a name="Gettext+on"></a> | ||
### Load a string from default language file | ||
### gettext.on(eventName, callback) | ||
Adds an event listener. | ||
*gettext(msgid)* | ||
**Params** | ||
var greeting = gt.gettext("Hello!"); | ||
- `eventName`: <code>String</code> - An event name | ||
- `callback`: <code>function</code> - An event handler function | ||
### Load a string from a specific language file | ||
<a name="Gettext+off"></a> | ||
*dgettext(domain, msgid)* | ||
### gettext.off(eventName, callback) | ||
Removes an event listener. | ||
var greeting = gt.dgettext("et", "Hello!"); | ||
**Params** | ||
### Load a plural string from default language file | ||
- `eventName`: <code>String</code> - An event name | ||
- `callback`: <code>function</code> - A previously registered event handler function | ||
*ngettext(msgid, msgid_plural, count)* | ||
<a name="Gettext+emit"></a> | ||
gt.ngettext("%d Comment", "%d Comments", 10); | ||
### gettext.emit(eventName, eventData) | ||
Emits an event to all registered event listener. | ||
### Load a plural string from a specific language file | ||
**Params** | ||
*dngettext(domain, msgid, msgid_plural, count)* | ||
- `eventName`: <code>String</code> - An event name | ||
- `eventData`: <code>any</code> - Data to pass to event listeners | ||
gt.dngettext("et", "%d Comment", "%d Comments", 10) | ||
<a name="Gettext+addTranslations"></a> | ||
### Load a string of a specific context | ||
### gettext.addTranslations(locale, domain, translations) | ||
Stores a set of translations in the set of gettext | ||
catalogs. | ||
*pgettext(msgctxt, msgid)* | ||
**Params** | ||
gt.pgettext("menu items", "File"); | ||
- `locale`: <code>String</code> - A locale string | ||
- `domain`: <code>String</code> - A domain name | ||
- `translations`: <code>Object</code> - An object of gettext-parser JSON shape | ||
### Load a string of a specific context from specific language file | ||
**Example** | ||
```js | ||
gt.addTranslations('sv-SE', 'messages', translationsObject) | ||
``` | ||
<a name="Gettext+setLocale"></a> | ||
*dpgettext(domain, msgctxt, msgid)* | ||
### gettext.setLocale(locale) | ||
Sets the locale to get translated messages for. | ||
gt.dpgettext("et", "menu items", "File"); | ||
**Params** | ||
### Load a plural string of a specific context | ||
- `locale`: <code>String</code> - A locale | ||
*npgettext(msgctxt, msgid, msgid_plural, count)* | ||
**Example** | ||
```js | ||
gt.setLocale('sv-SE') | ||
``` | ||
<a name="Gettext+setTextDomain"></a> | ||
gt.npgettext("menu items", "%d Recent File", "%d Recent Files", 3); | ||
### gettext.setTextDomain(domain) | ||
Sets the default gettext domain. | ||
### Load a plural string of a specific context from specific language file | ||
**Params** | ||
*dnpgettext(domain, msgctxt, msgid, msgid_plural, count)* | ||
- `domain`: <code>String</code> - A gettext domain name | ||
gt.dnpgettext("et", "menu items", "%d Recent File", "%d Recent Files", 3); | ||
**Example** | ||
```js | ||
gt.setTextDomain('domainname') | ||
``` | ||
<a name="Gettext+gettext"></a> | ||
### Get comments for a translation (if loaded from PO) | ||
### gettext.gettext(msgid) ⇒ <code>String</code> | ||
Translates a string using the default textdomain | ||
*getComment(domain, msgctxt, msgid)* | ||
**Returns**: <code>String</code> - Translation or the original string if no translation was found | ||
**Params** | ||
gt.getComment("et", "menu items", "%d Recent File"); | ||
- `msgid`: <code>String</code> - String to be translated | ||
Returns an object in the form of `{translator: "", extracted: "", reference: "", flag: "", previous: ""}` | ||
**Example** | ||
```js | ||
gt.gettext('Some text') | ||
``` | ||
<a name="Gettext+dgettext"></a> | ||
## Advanced handling | ||
### gettext.dgettext(domain, msgid) ⇒ <code>String</code> | ||
Translates a string using a specific domain | ||
If you need the translation object for a domain, for example `et_EE`, you can access it from `gt.domains.et_EE`. | ||
**Returns**: <code>String</code> - Translation or the original string if no translation was found | ||
**Params** | ||
If you want modify it and compile it to *mo* or *po*, checkout [gettext-parser](https://github.com/andris9/gettext-parser) module. | ||
- `domain`: <code>String</code> - A gettext domain name | ||
- `msgid`: <code>String</code> - String to be translated | ||
**Example** | ||
```js | ||
gt.dgettext('domainname', 'Some text') | ||
``` | ||
<a name="Gettext+ngettext"></a> | ||
### gettext.ngettext(msgid, msgidPlural, count) ⇒ <code>String</code> | ||
Translates a plural string using the default textdomain | ||
**Returns**: <code>String</code> - Translation or the original string if no translation was found | ||
**Params** | ||
- `msgid`: <code>String</code> - String to be translated when count is not plural | ||
- `msgidPlural`: <code>String</code> - String to be translated when count is plural | ||
- `count`: <code>Number</code> - Number count for the plural | ||
**Example** | ||
```js | ||
gt.ngettext('One thing', 'Many things', numberOfThings) | ||
``` | ||
<a name="Gettext+dngettext"></a> | ||
### gettext.dngettext(domain, msgid, msgidPlural, count) ⇒ <code>String</code> | ||
Translates a plural string using a specific textdomain | ||
**Returns**: <code>String</code> - Translation or the original string if no translation was found | ||
**Params** | ||
- `domain`: <code>String</code> - A gettext domain name | ||
- `msgid`: <code>String</code> - String to be translated when count is not plural | ||
- `msgidPlural`: <code>String</code> - String to be translated when count is plural | ||
- `count`: <code>Number</code> - Number count for the plural | ||
**Example** | ||
```js | ||
gt.dngettext('domainname', 'One thing', 'Many things', numberOfThings) | ||
``` | ||
<a name="Gettext+pgettext"></a> | ||
### gettext.pgettext(msgctxt, msgid) ⇒ <code>String</code> | ||
Translates a string from a specific context using the default textdomain | ||
**Returns**: <code>String</code> - Translation or the original string if no translation was found | ||
**Params** | ||
- `msgctxt`: <code>String</code> - Translation context | ||
- `msgid`: <code>String</code> - String to be translated | ||
**Example** | ||
```js | ||
gt.pgettext('sports', 'Back') | ||
``` | ||
<a name="Gettext+dpgettext"></a> | ||
### gettext.dpgettext(domain, msgctxt, msgid) ⇒ <code>String</code> | ||
Translates a string from a specific context using s specific textdomain | ||
**Returns**: <code>String</code> - Translation or the original string if no translation was found | ||
**Params** | ||
- `domain`: <code>String</code> - A gettext domain name | ||
- `msgctxt`: <code>String</code> - Translation context | ||
- `msgid`: <code>String</code> - String to be translated | ||
**Example** | ||
```js | ||
gt.dpgettext('domainname', 'sports', 'Back') | ||
``` | ||
<a name="Gettext+npgettext"></a> | ||
### gettext.npgettext(msgctxt, msgid, msgidPlural, count) ⇒ <code>String</code> | ||
Translates a plural string from a specific context using the default textdomain | ||
**Returns**: <code>String</code> - Translation or the original string if no translation was found | ||
**Params** | ||
- `msgctxt`: <code>String</code> - Translation context | ||
- `msgid`: <code>String</code> - String to be translated when count is not plural | ||
- `msgidPlural`: <code>String</code> - String to be translated when count is plural | ||
- `count`: <code>Number</code> - Number count for the plural | ||
**Example** | ||
```js | ||
gt.npgettext('sports', 'Back', '%d backs', numberOfBacks) | ||
``` | ||
<a name="Gettext+dnpgettext"></a> | ||
### gettext.dnpgettext(domain, msgctxt, msgid, msgidPlural, count) ⇒ <code>String</code> | ||
Translates a plural string from a specifi context using a specific textdomain | ||
**Returns**: <code>String</code> - Translation or the original string if no translation was found | ||
**Params** | ||
- `domain`: <code>String</code> - A gettext domain name | ||
- `msgctxt`: <code>String</code> - Translation context | ||
- `msgid`: <code>String</code> - String to be translated | ||
- `msgidPlural`: <code>String</code> - If no translation was found, return this on count!=1 | ||
- `count`: <code>Number</code> - Number count for the plural | ||
**Example** | ||
```js | ||
gt.dnpgettext('domainname', 'sports', 'Back', '%d backs', numberOfBacks) | ||
``` | ||
<a name="Gettext+getComment"></a> | ||
### gettext.getComment(domain, msgctxt, msgid) ⇒ <code>Object</code> | ||
Retrieves comments object for a translation. The comments object | ||
has the shape `{ translator, extracted, reference, flag, previous }`. | ||
**Returns**: <code>Object</code> - Comments object or false if not found | ||
**Params** | ||
- `domain`: <code>String</code> - A gettext domain name | ||
- `msgctxt`: <code>String</code> - Translation context | ||
- `msgid`: <code>String</code> - String to be translated | ||
**Example** | ||
```js | ||
const comment = gt.getComment('domainname', 'sports', 'Backs') | ||
``` | ||
<a name="Gettext+addTextdomain"></a> | ||
### ~~gettext.addTextdomain()~~ | ||
***Deprecated*** | ||
This function will be removed in the final 2.0.0 release. | ||
<a name="Gettext+textdomain"></a> | ||
### ~~gettext.textdomain()~~ | ||
***Deprecated*** | ||
This function will be removed in the final 2.0.0 release. | ||
<a name="Gettext.getLanguageCode"></a> | ||
### Gettext.getLanguageCode(locale) ⇒ <code>String</code> | ||
Returns the language code part of a locale | ||
**Returns**: <code>String</code> - A language code | ||
**Params** | ||
- `locale`: <code>String</code> - A case-insensitive locale string | ||
**Example** | ||
```js | ||
Gettext.getLanguageCode('sv-SE') | ||
// -> "sv" | ||
``` | ||
## License | ||
MIT | ||
## See also | ||
* [gettext-parser](https://github.com/smhg/gettext-parser) - Parsing and compiling gettext translations between .po/.mo files and JSON | ||
* [react-gettext-parser](https://github.com/lagetse/react-gettext-parser) - Extracting gettext translatable strings from JS(X) code | ||
* [narp](https://github.com/lagetse/narp) - Workflow CLI tool that syncs translations between your app and Transifex |
@@ -6,2 +6,3 @@ 'use strict'; | ||
var fs = require('fs'); | ||
var sinon = require('sinon'); | ||
@@ -12,69 +13,64 @@ var expect = chai.expect; | ||
describe('Gettext', function() { | ||
var gt; | ||
var jsonFile; | ||
describe('#_normalizeDomain', function() { | ||
it('should normalize domain key', function() { | ||
var gt = new Gettext(); | ||
beforeEach(function() { | ||
gt = new Gettext({ debug: false }); | ||
jsonFile = JSON.parse(fs.readFileSync(__dirname + '/fixtures/latin13.json')); | ||
}); | ||
expect(gt._normalizeDomain('ab-cd_ef.utf-8')).to.equal('ab_CD-EF'); | ||
expect(gt._normalizeDomain('ab-cd_ef', true)).to.equal('ab'); | ||
describe('#getLanguageCode', function() { | ||
it('should normalize locale string', function() { | ||
expect(Gettext.getLanguageCode('ab-cd_ef.utf-8')).to.equal('ab'); | ||
expect(Gettext.getLanguageCode('ab-cd_ef')).to.equal('ab'); | ||
}); | ||
}); | ||
describe('#addTextdomain', function() { | ||
describe('#addTranslations', function() { | ||
it('should store added translations', function() { | ||
gt.addTranslations('et-EE', 'messages', jsonFile); | ||
it('Should add from a mo file', function() { | ||
var gt = new Gettext(); | ||
var moFile = fs.readFileSync(__dirname + '/fixtures/latin13.mo'); | ||
expect(gt.catalogs['et-EE']).to.exist; | ||
expect(gt.catalogs['et-EE'].messages).to.exist; | ||
expect(gt.catalogs['et-EE'].messages.charset).to.equal('iso-8859-13'); | ||
}); | ||
gt.addTextdomain('et-EE', moFile); | ||
it('should store added translations on a custom domain', function() { | ||
gt.addTranslations('et-EE', 'mydomain', jsonFile); | ||
expect(gt.domains.et_EE).to.exist; | ||
expect(gt.domains.et_EE.charset).to.equal('iso-8859-13'); | ||
expect(gt.catalogs['et-EE'].mydomain).to.exist; | ||
expect(gt.catalogs['et-EE'].mydomain.charset).to.equal('iso-8859-13'); | ||
}); | ||
}); | ||
it('Should add from a po file', function() { | ||
var gt = new Gettext(); | ||
var poFile = fs.readFileSync(__dirname + '/fixtures/latin13.po'); | ||
describe('#setLocale', function() { | ||
it('should have no default locale', function() { | ||
expect(gt.locale).to.equal(null); | ||
}); | ||
gt.addTextdomain('et-EE', poFile); | ||
expect(gt.domains.et_EE).to.exist; | ||
expect(gt.domains.et_EE.charset).to.equal('iso-8859-13'); | ||
it ('should not accept a locale that has no translations', function() { | ||
gt.setLocale('de-AT'); | ||
expect(gt.locale).to.equal(null); | ||
}); | ||
it('Should add from a json file', function() { | ||
var gt = new Gettext(); | ||
var jsonFile = JSON.parse(fs.readFileSync(__dirname + '/fixtures/latin13.json')); | ||
gt.addTextdomain('et-EE', jsonFile); | ||
expect(gt.domains.et_EE).to.exist; | ||
expect(gt.domains.et_EE.charset).to.equal('iso-8859-13'); | ||
it('should change locale if translations exist', function() { | ||
gt.addTranslations('et-EE', 'messages', jsonFile); | ||
gt.setLocale('et-EE'); | ||
expect(gt.locale).to.equal('et-EE'); | ||
}); | ||
}); | ||
describe('#textdomain', function() { | ||
it('should set default domain', function() { | ||
var gt = new Gettext(); | ||
var moFile = fs.readFileSync(__dirname + '/fixtures/latin13.mo'); | ||
describe('#setTextDomain', function() { | ||
it('defaults to "messages"', function() { | ||
expect(gt.domain).to.equal('messages'); | ||
}); | ||
expect(gt.textdomain()).to.be.false; | ||
gt.addTextdomain('et-EE', moFile); | ||
expect(gt.textdomain()).to.equal('et_EE'); | ||
gt.addTextdomain('cd-EE', moFile); | ||
expect(gt.textdomain()).to.equal('et_EE'); | ||
it('does not accept an empty value', function() { | ||
gt.setTextDomain(''); | ||
expect(gt.domain).to.equal('messages'); | ||
}); | ||
it('should change default domain', function() { | ||
var gt = new Gettext(); | ||
var moFile = fs.readFileSync(__dirname + '/fixtures/latin13.mo'); | ||
expect(gt.textdomain()).to.be.false; | ||
gt.addTextdomain('et-EE', moFile); | ||
expect(gt.textdomain()).to.equal('et_EE'); | ||
gt.addTextdomain('cd-EE', moFile); | ||
expect(gt.textdomain()).to.equal('et_EE'); | ||
gt.textdomain('cd_EE'); | ||
expect(gt.textdomain()).to.equal('cd_EE'); | ||
it('accepts and stores a non-empty domain name', function() { | ||
gt.setTextDomain('mydomain'); | ||
expect(gt.domain).to.equal('mydomain'); | ||
}); | ||
@@ -84,33 +80,22 @@ }); | ||
describe('Resolve translations', function() { | ||
var gt; | ||
beforeEach(function() { | ||
gt = new Gettext(); | ||
var poFile = fs.readFileSync(__dirname + '/fixtures/latin13.po'); | ||
gt.addTextdomain('et-EE', poFile); | ||
gt.addTranslations('et-EE', 'messages', jsonFile); | ||
gt.setLocale('et-EE'); | ||
}); | ||
describe('#dnpgettext', function() { | ||
it('should return default singular', function() { | ||
expect(gt.dnpgettext('et_EE', '', '0 matches', 'multiple matches', 1)).to.equal('0 matches'); | ||
}); | ||
it('should return default plural', function() { | ||
expect(gt.dnpgettext('et_EE', '', '0 matches', 'multiple matches', 100)).to.equal('multiple matches'); | ||
}); | ||
it('should return singular match from default context', function() { | ||
expect(gt.dnpgettext('et_EE', '', 'o2-1', 'o2-2', 1)).to.equal('t2-1'); | ||
expect(gt.dnpgettext('messages', '', 'o2-1', 'o2-2', 1)).to.equal('t2-1'); | ||
}); | ||
it('should return plural match from default context', function() { | ||
expect(gt.dnpgettext('et_EE', '', 'o2-1', 'o2-2', 2)).to.equal('t2-2'); | ||
expect(gt.dnpgettext('messages', '', 'o2-1', 'o2-2', 2)).to.equal('t2-2'); | ||
}); | ||
it('should return singular match from selected context', function() { | ||
expect(gt.dnpgettext('et_EE', 'c2', 'co2-1', 'co2-2', 1)).to.equal('ct2-1'); | ||
expect(gt.dnpgettext('messages', 'c2', 'co2-1', 'co2-2', 1)).to.equal('ct2-1'); | ||
}); | ||
it('should return plural match from selected context', function() { | ||
expect(gt.dnpgettext('et_EE', 'c2', 'co2-1', 'co2-2', 2)).to.equal('ct2-2'); | ||
expect(gt.dnpgettext('messages', 'c2', 'co2-1', 'co2-2', 2)).to.equal('ct2-2'); | ||
}); | ||
@@ -131,3 +116,3 @@ | ||
it('should return singular from default context', function() { | ||
expect(gt.dgettext('et-ee', 'o2-1')).to.equal('t2-1'); | ||
expect(gt.dgettext('messages', 'o2-1')).to.equal('t2-1'); | ||
}); | ||
@@ -144,3 +129,3 @@ }); | ||
it('should return plural from default context', function() { | ||
expect(gt.dngettext('et-ee', 'o2-1', 'o2-2', 2)).to.equal('t2-2'); | ||
expect(gt.dngettext('messages', 'o2-1', 'o2-2', 2)).to.equal('t2-2'); | ||
}); | ||
@@ -157,3 +142,3 @@ }); | ||
it('should return singular from selected context', function() { | ||
expect(gt.dpgettext('et-ee', 'c2', 'co2-1')).to.equal('ct2-1'); | ||
expect(gt.dpgettext('messages', 'c2', 'co2-1')).to.equal('ct2-1'); | ||
}); | ||
@@ -170,3 +155,3 @@ }); | ||
it('should return comments object', function() { | ||
expect(gt.getComment('et-ee', '', 'test')).to.deep.equal({ | ||
expect(gt.getComment('messages', '', 'test')).to.deep.equal({ | ||
translator: 'Normal comment line 1\nNormal comment line 2', | ||
@@ -181,2 +166,42 @@ extracted: 'Editors note line 1\nEditors note line 2', | ||
}); | ||
}); | ||
describe('Unresolvable transaltions', function() { | ||
beforeEach(function() { | ||
gt.addTranslations('et-EE', 'messages', jsonFile); | ||
}); | ||
it('should pass msgid until a locale is set', function() { | ||
expect(gt.gettext('o2-1')).to.equal('o2-1'); | ||
gt.setLocale('et-EE'); | ||
expect(gt.gettext('o2-1')).to.equal('t2-1'); | ||
}); | ||
it('should pass unresolved singular message when count is 1', function() { | ||
expect(gt.dnpgettext('messages', '', '0 matches', 'multiple matches', 1)).to.equal('0 matches'); | ||
}); | ||
it('should pass unresolved plural message when count > 1', function() { | ||
expect(gt.dnpgettext('messages', '', '0 matches', 'multiple matches', 100)).to.equal('multiple matches'); | ||
}); | ||
}); | ||
describe('Events', function() { | ||
var errorListener; | ||
beforeEach(function() { | ||
errorListener = sinon.spy(); | ||
gt.on('error', errorListener); | ||
}); | ||
it('should notify a registered listener of error events', function() { | ||
gt.emit('error', 'Something went wrong'); | ||
expect(errorListener.callCount).to.equal(1); | ||
}); | ||
it('should deregister a previously registered event listener', function() { | ||
gt.off('error', errorListener); | ||
gt.emit('error', 'Something went wrong'); | ||
expect(errorListener.callCount).to.equal(0); | ||
}); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
85914
2670
361
8
10
1
4
2
+ Addedlodash.get@^4.4.2
+ Addedlodash.get@4.4.2(transitive)
- Removedgettext-parser@^1.1.2
- Removedencoding@0.1.13(transitive)
- Removedgettext-parser@1.4.0(transitive)
- Removediconv-lite@0.6.3(transitive)
- Removedsafe-buffer@5.2.1(transitive)
- Removedsafer-buffer@2.1.2(transitive)