node-gettext
Advanced tools
Comparing version 2.0.0-rc.0 to 2.0.0-rc.1
@@ -12,3 +12,3 @@ 'use strict'; | ||
* @constructor | ||
* @param {Object} options A set of options | ||
* @param {Object} [options] A set of options | ||
* @param {Boolean} options.debug Whether to output debug info into the | ||
@@ -22,3 +22,3 @@ * console. | ||
this.catalogs = {}; | ||
this.locale = null; | ||
this.locale = ''; | ||
this.domain = 'messages'; | ||
@@ -71,2 +71,3 @@ | ||
* | ||
* @private | ||
* @param {String} eventName An event name | ||
@@ -126,10 +127,16 @@ * @param {any} eventData Data to pass to event listeners | ||
Gettext.prototype.setLocale = function(locale) { | ||
if (!locale) { | ||
this.warn('You called setLocale() with an empty value, which makes little sense.'); | ||
if (typeof locale !== 'string') { | ||
this.warn( | ||
'You called setLocale() with an argument of type ' + (typeof locale) + '. ' + | ||
'The locale must be a string.' | ||
); | ||
return; | ||
} | ||
if (locale.trim() === '') { | ||
this.warn('You called setLocale() with an empty value, which makes little sense.'); | ||
} | ||
if (!this.catalogs[locale]) { | ||
this.warn('You called setLocale() with "' + locale + '", but no translations for that locale has been added.'); | ||
return; | ||
} | ||
@@ -149,7 +156,14 @@ | ||
Gettext.prototype.setTextDomain = function(domain) { | ||
if (!domain) { | ||
this.warn('You called setTextDomain() with an empty `domain` value, which is not allowed.'); | ||
if (typeof domain !== 'string') { | ||
this.warn( | ||
'You called setTextDomain() with an argument of type ' + (typeof domain) + '. ' + | ||
'The domain must be a string.' | ||
); | ||
return; | ||
} | ||
if (domain.trim() === '') { | ||
this.warn('You called setTextDomain() with an empty `domain` value.'); | ||
} | ||
this.domain = domain; | ||
@@ -285,7 +299,2 @@ }; | ||
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); | ||
@@ -320,2 +329,3 @@ | ||
* | ||
* @private | ||
* @param {String} domain A gettext domain name | ||
@@ -359,3 +369,4 @@ * @param {String} msgctxt Translation context | ||
* | ||
* @param {String} locale A case-insensitive locale string | ||
* @private | ||
* @param {String} locale A case-insensitive locale string | ||
* @returns {String} A language code | ||
@@ -367,19 +378,33 @@ */ | ||
/* C-style aliases */ | ||
/** | ||
* This function will be removed in the final 2.0.0 release. | ||
* C-style alias for [setTextDomain](#gettextsettextdomaindomain) | ||
* | ||
* @deprecated | ||
* @see Gettext#setTextDomain | ||
*/ | ||
Gettext.prototype.addTextdomain = function() { | ||
Gettext.prototype.textdomain = function(domain) { | ||
if (this.debug) { | ||
console.warn('textdomain(domain) was used to set locales in node-gettext v1. ' + | ||
'Make sure you are using it for domains, and switch to setLocale(locale) if you are not.\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\n\n' + | ||
'This warning will be removed in the final 2.0.0'); | ||
} | ||
// 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.setTextDomain(domain); | ||
}; | ||
/** | ||
* C-style alias for [setLocale](#gettextsetlocalelocale) | ||
* | ||
* @see Gettext#setLocale | ||
*/ | ||
Gettext.prototype.setlocale = function(locale) { | ||
this.setLomain(locale); | ||
}; | ||
/* Deprecated functions */ | ||
/** | ||
* This function will be removed in the final 2.0.0 release. | ||
@@ -389,6 +414,6 @@ * | ||
*/ | ||
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' + | ||
Gettext.prototype.addTextdomain = function() { | ||
console.error('addTextdomain() is deprecated.\n\n' + | ||
'* To add translations, use addTranslations()\n' + | ||
'* To set the default domain, use setTextDomain() (or its alias textdomain())\n' + | ||
'\n' + | ||
@@ -395,0 +420,0 @@ 'To read more about the migration from node-gettext v1 to v2, ' + |
{ | ||
"name": "node-gettext", | ||
"description": "A JavaScript implementation of gettext, a localization framework", | ||
"version": "2.0.0-rc.0", | ||
"version": "2.0.0-rc.1", | ||
"author": "Andris Reinman", | ||
@@ -41,3 +41,3 @@ "maintainers": [ | ||
"grunt-mocha-test": "^0.12.7", | ||
"jsdoc-to-markdown": "^2.0.1", | ||
"jsdoc-to-markdown": "^3.0.0", | ||
"mocha": "^2.4.5", | ||
@@ -44,0 +44,0 @@ "sinon": "^1.17.7" |
140
README.md
@@ -13,3 +13,3 @@ | ||
**`node-gettext`** is a JavaScript implementation of [gettext](https://www.gnu.org/software/gettext/gettext.html), a localization framework. | ||
**`node-gettext`** is a JavaScript implementation of (a large subset of) [gettext](https://www.gnu.org/software/gettext/gettext.html), a localization framework originally written in C. | ||
@@ -21,6 +21,9 @@ If you just want to parse or compile mo/po files, check out [gettext-parser](https://github.com/smhg/gettext-parser). | ||
* [Features](#features) | ||
* [Differences from GNU gettext](#differences-from-gnu-gettext) | ||
* [Installation](#installation) | ||
* [Usage](#usage) | ||
* [Error events](#error-events) | ||
* [Recipes](#recipes) | ||
* [API](#api) | ||
* [Migrating from v1 to v2](#migrating-from-v1-to-v2) | ||
* [API](#api) | ||
* [License](#license) | ||
@@ -40,2 +43,14 @@ * [See also](#see-also) | ||
### Differences from GNU gettext | ||
There are two main differences between `node-gettext` and GNU's gettext: | ||
1. **There are no categories.** GNU gettext features [categories such as `LC_MESSAGES`, `LC_NUMERIC` and `LC_MONETARY`](https://www.gnu.org/software/gettext/manual/gettext.html#Locale-Environment-Variables), but since there already is a plethora of great JavaScript libraries to deal with numbers, currencies, dates etc, `node-gettext` is simply targeted towards strings/phrases. You could say it just assumes the `LC_MESSAGES` category at all times. | ||
2. **You have to read translation files from the file system yourself.** GNU gettext is a C library that reads files from the file system. This is done using `bindtextdomain(domain, localesDirPath)` and `setlocale(category, locale)`, where these four parameters combined are used to read the appropriate translations file. | ||
However, since `node-gettext` needs to work both on the server in web browsers (which usually is referred to as it being *universal* or *isomorphic* JavaScript), it is up to the developer to read translation files from disk or somehow provide it with translations as pure JavaScript objects using [`addTranslations(locale, domain, translations)`](#gettextsetlocalelocale). | ||
`bindtextdomain` will be provided as an optional feature in a future release. | ||
## Installation | ||
@@ -72,19 +87,35 @@ | ||
### Recipes | ||
## Migrating from v1 to v2 | ||
#### Load and add translations from .mo or .po files | ||
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. | ||
`node-gettext` expects all translations to be in the format specified by [`gettext-parser`](https://github.com/smhg/gettext-parser). Therefor, you should use that to parse .mo or .po files. | ||
Here is a full list of all breaking changes: | ||
Here is an example where we read a bunch of translation files from disk and add them to our `Gettext` instance: | ||
* `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)` | ||
```js | ||
import fs from 'fs' | ||
import path from 'path' | ||
import Gettext from 'node-gettext' | ||
import { po } from 'gettext-parser' | ||
// In this example, our translations are found at | ||
// path/to/locales/LOCALE/DOMAIN.po | ||
const translationsDir = 'path/to/locales' | ||
const locales = ['en', 'fi-FI', 'sv-SE'] | ||
const domain = 'messages' | ||
const gt = new Gettext() | ||
locales.forEach((locale) => { | ||
const fileName = `${domain}.po` | ||
const translationsFilePath = path.join(translationsDir, locale, filename) | ||
const translationsContent = fs.readSync(translationsFilePath) | ||
const parsedTranslations = po.parse(translationsContent) | ||
gt.addTranslations(locale, domain, parsedTranslations) | ||
}) | ||
``` | ||
## API | ||
@@ -95,5 +126,25 @@ | ||
## Gettext | ||
* [Gettext](#Gettext) | ||
* [new Gettext([options])](#new_Gettext_new) | ||
* [.on(eventName, callback)](#Gettext+on) | ||
* [.off(eventName, callback)](#Gettext+off) | ||
* [.addTranslations(locale, domain, translations)](#Gettext+addTranslations) | ||
* [.setLocale(locale)](#Gettext+setLocale) | ||
* [.setTextDomain(domain)](#Gettext+setTextDomain) | ||
* [.gettext(msgid)](#Gettext+gettext) ⇒ <code>String</code> | ||
* [.dgettext(domain, msgid)](#Gettext+dgettext) ⇒ <code>String</code> | ||
* [.ngettext(msgid, msgidPlural, count)](#Gettext+ngettext) ⇒ <code>String</code> | ||
* [.dngettext(domain, msgid, msgidPlural, count)](#Gettext+dngettext) ⇒ <code>String</code> | ||
* [.pgettext(msgctxt, msgid)](#Gettext+pgettext) ⇒ <code>String</code> | ||
* [.dpgettext(domain, msgctxt, msgid)](#Gettext+dpgettext) ⇒ <code>String</code> | ||
* [.npgettext(msgctxt, msgid, msgidPlural, count)](#Gettext+npgettext) ⇒ <code>String</code> | ||
* [.dnpgettext(domain, msgctxt, msgid, msgidPlural, count)](#Gettext+dnpgettext) ⇒ <code>String</code> | ||
* [.textdomain()](#Gettext+textdomain) | ||
* [.setlocale()](#Gettext+setlocale) | ||
* ~~[.addTextdomain()](#Gettext+addTextdomain)~~ | ||
<a name="new_Gettext_new"></a> | ||
### new Gettext(options) | ||
### new Gettext([options]) | ||
Creates and returns a new Gettext instance. | ||
@@ -104,3 +155,3 @@ | ||
- `options`: <code>Object</code> - A set of options | ||
- `[options]`: <code>Object</code> - A set of options | ||
- `.debug`: <code>Boolean</code> - Whether to output debug info into the | ||
@@ -129,12 +180,2 @@ console. | ||
<a name="Gettext+emit"></a> | ||
### gettext.emit(eventName, eventData) | ||
Emits an event to all registered event listener. | ||
**Params** | ||
- `eventName`: <code>String</code> - An event name | ||
- `eventData`: <code>any</code> - Data to pass to event listeners | ||
<a name="Gettext+addTranslations"></a> | ||
@@ -310,19 +351,14 @@ | ||
``` | ||
<a name="Gettext+getComment"></a> | ||
<a name="Gettext+textdomain"></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 }`. | ||
### gettext.textdomain() | ||
C-style alias for [setTextDomain](#gettextsettextdomaindomain) | ||
**Returns**: <code>Object</code> - Comments object or false if not found | ||
**Params** | ||
**See**: Gettext#setTextDomain | ||
<a name="Gettext+setlocale"></a> | ||
- `domain`: <code>String</code> - A gettext domain name | ||
- `msgctxt`: <code>String</code> - Translation context | ||
- `msgid`: <code>String</code> - String to be translated | ||
### gettext.setlocale() | ||
C-style alias for [setLocale](#gettextsetlocalelocale) | ||
**Example** | ||
```js | ||
const comment = gt.getComment('domainname', 'sports', 'Backs') | ||
``` | ||
**See**: Gettext#setLocale | ||
<a name="Gettext+addTextdomain"></a> | ||
@@ -335,26 +371,20 @@ | ||
<a name="Gettext+textdomain"></a> | ||
### ~~gettext.textdomain()~~ | ||
***Deprecated*** | ||
This function will be removed in the final 2.0.0 release. | ||
## Migrating from v1 to v2 | ||
<a name="Gettext.getLanguageCode"></a> | ||
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. | ||
### Gettext.getLanguageCode(locale) ⇒ <code>String</code> | ||
Returns the language code part of a locale | ||
Here is a full list of all breaking changes: | ||
**Returns**: <code>String</code> - A language code | ||
**Params** | ||
* `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)` | ||
- `locale`: <code>String</code> - A case-insensitive locale string | ||
**Example** | ||
```js | ||
Gettext.getLanguageCode('sv-SE') | ||
// -> "sv" | ||
``` | ||
## License | ||
@@ -361,0 +391,0 @@ |
@@ -45,15 +45,28 @@ 'use strict'; | ||
describe('#setLocale', function() { | ||
it('should have no default locale', function() { | ||
expect(gt.locale).to.equal(null); | ||
it('should have the empty string as default locale', function() { | ||
expect(gt.locale).to.equal(''); | ||
}); | ||
it ('should not accept a locale that has no translations', function() { | ||
it('should accept whatever string is passed as locale', function() { | ||
gt.setLocale('de-AT'); | ||
expect(gt.locale).to.equal(null); | ||
expect(gt.locale).to.equal('de-AT'); | ||
gt.setLocale('01234'); | ||
expect(gt.locale).to.equal('01234'); | ||
gt.setLocale(''); | ||
expect(gt.locale).to.equal(''); | ||
}); | ||
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'); | ||
it('should reject non-string locales', function() { | ||
gt.setLocale(null); | ||
expect(gt.locale).to.equal(''); | ||
gt.setLocale(123); | ||
expect(gt.locale).to.equal(''); | ||
gt.setLocale(false); | ||
expect(gt.locale).to.equal(''); | ||
gt.setLocale(function() {}); | ||
expect(gt.locale).to.equal(''); | ||
gt.setLocale(NaN); | ||
expect(gt.locale).to.equal(''); | ||
gt.setLocale(); | ||
expect(gt.locale).to.equal(''); | ||
}); | ||
@@ -63,14 +76,28 @@ }); | ||
describe('#setTextDomain', function() { | ||
it('defaults to "messages"', function() { | ||
it('should default to "messages"', function() { | ||
expect(gt.domain).to.equal('messages'); | ||
}); | ||
it('does not accept an empty value', function() { | ||
it('should accept and store any string as domain name', function() { | ||
gt.setTextDomain('mydomain'); | ||
expect(gt.domain).to.equal('mydomain'); | ||
gt.setTextDomain('01234'); | ||
expect(gt.domain).to.equal('01234'); | ||
gt.setTextDomain(''); | ||
expect(gt.domain).to.equal('messages'); | ||
expect(gt.domain).to.equal(''); | ||
}); | ||
it('accepts and stores a non-empty domain name', function() { | ||
gt.setTextDomain('mydomain'); | ||
expect(gt.domain).to.equal('mydomain'); | ||
it('should reject non-string domains', function() { | ||
gt.setTextDomain(null); | ||
expect(gt.domain).to.equal('messages'); | ||
gt.setTextDomain(123); | ||
expect(gt.domain).to.equal('messages'); | ||
gt.setTextDomain(false); | ||
expect(gt.domain).to.equal('messages'); | ||
gt.setTextDomain(function() {}); | ||
expect(gt.domain).to.equal('messages'); | ||
gt.setTextDomain(NaN); | ||
expect(gt.domain).to.equal('messages'); | ||
gt.setTextDomain(); | ||
expect(gt.domain).to.equal('messages'); | ||
}); | ||
@@ -167,6 +194,9 @@ }); | ||
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 msgid when no translation is found', function() { | ||
expect(gt.gettext('unknown phrase')).to.equal('unknown phrase'); | ||
expect(gt.dnpgettext('unknown domain', null, 'hello')).to.equal('hello'); | ||
expect(gt.dnpgettext('messages', 'unknown context', 'hello')).to.equal('hello'); | ||
// 'o2-1' is translated, but no locale has been set yet | ||
expect(gt.dnpgettext('messages', '', 'o2-1')).to.equal('o2-1'); | ||
}); | ||
@@ -201,3 +231,47 @@ | ||
}); | ||
it('should emit an error event when a locale that has no translations is set', function() { | ||
gt.setLocale('et-EE'); | ||
expect(errorListener.callCount).to.equal(1); | ||
}); | ||
it('should emit an error event when no locale has been set', function() { | ||
gt.addTranslations('et-EE', 'messages', jsonFile); | ||
gt.gettext('o2-1'); | ||
expect(errorListener.callCount).to.equal(1); | ||
gt.setLocale('et-EE'); | ||
gt.gettext('o2-1'); | ||
expect(errorListener.callCount).to.equal(1); | ||
}); | ||
it('should emit an error event when a translation is missing', function() { | ||
gt.addTranslations('et-EE', 'messages', jsonFile); | ||
gt.setLocale('et-EE'); | ||
gt.gettext('This message is not translated'); | ||
expect(errorListener.callCount).to.equal(1); | ||
}); | ||
it('should not emit any error events when a translation is found', function() { | ||
gt.addTranslations('et-EE', 'messages', jsonFile); | ||
gt.setLocale('et-EE'); | ||
gt.gettext('o2-1'); | ||
expect(errorListener.callCount).to.equal(0); | ||
}); | ||
}); | ||
describe('Aliases', function() { | ||
it('should forward textdomain(domain) to setTextDomain(domain)', function() { | ||
sinon.stub(gt, 'setTextDomain'); | ||
gt.textdomain('messages'); | ||
expect(gt.setTextDomain.calledWith('messages')); | ||
gt.setTextDomain.restore(); | ||
}); | ||
it('should forward setlocale(locale) to setLocale(locale)', function() { | ||
sinon.stub(gt, 'setLocale'); | ||
gt.setLocale('et-EE'); | ||
expect(gt.setLocale.calledWith('et-EE')); | ||
gt.setLocale.restore(); | ||
}); | ||
}); | ||
}); |
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
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
92408
2758
391
1