├── .editorconfig
├── .gitignore
├── .jshintrc
├── .travis.yml
├── CHANGELOG.md
├── Gruntfile.js
├── LICENSE
├── README.md
├── docs
├── README.template.md
├── node-gettext-logo.png
├── templates
│ ├── params-list.hbs
│ └── scope.hbs
└── v1
│ └── README.md
├── lib
├── gettext.js
└── plurals.js
├── package-lock.json
├── package.json
└── test
├── fixtures
├── latin13.json
├── latin13.mo
└── latin13.po
└── gettext-test.js
/.editorconfig:
--------------------------------------------------------------------------------
1 |
2 | # editorconfig.org
3 | root = true
4 |
5 | [*]
6 | indent_style = space
7 | indent_size = 4
8 | end_of_line = lf
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 |
13 | [*.md]
14 | trim_trailing_whitespace = true
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | lib-cov
2 | *.seed
3 | *.log
4 | *.csv
5 | *.dat
6 | *.out
7 | *.pid
8 | *.gz
9 |
10 | pids
11 | logs
12 | results
13 |
14 | npm-debug.log
15 | node_modules
16 | .DS_Store
17 |
18 | events/*.js
19 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "indent": 4,
3 | "node": true,
4 | "globalstrict": true,
5 | "evil": true,
6 | "unused": true,
7 | "undef": true,
8 | "newcap": true,
9 | "esnext": true,
10 | "curly": true,
11 | "eqeqeq": true,
12 | "expr": true,
13 |
14 | "predef": [
15 | "describe",
16 | "it",
17 | "beforeEach"
18 | ]
19 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 12
4 | - 14
5 | - 16
6 |
7 | notifications:
8 | email:
9 | recipients:
10 | - office@alexanderwallin.com
11 | on_success: change
12 | on_failure: change
13 | webhooks:
14 | urls:
15 | - https://webhooks.gitter.im/e/0ed18fd9b3e529b3c2cc
16 | on_success: change # options: [always|never|change] default: always
17 | on_failure: always # options: [always|never|change] default: always
18 | on_start: false # default: false
19 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## v1.1.0
4 |
5 | * Adds support for adding a textdomain from an already parsed json object
6 |
7 | ## v1.0.0 2015-01-21
8 |
9 | * Bumped version to 1.0.0 to be compatible with semver
10 | * Removed built in parser/compiler in favor of gettext-parser module
11 | * Moved tests from nodeunit to mocha
12 |
13 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function(grunt) {
4 |
5 | // Project configuration.
6 | grunt.initConfig({
7 | jshint: {
8 | all: ['lib/*.js', 'test/*.js', 'index.js'],
9 | options: {
10 | jshintrc: '.jshintrc'
11 | }
12 | },
13 |
14 | mochaTest: {
15 | all: {
16 | options: {
17 | reporter: 'spec'
18 | },
19 | src: ['test/*-test.js']
20 | }
21 | }
22 | });
23 |
24 | // Load the plugin(s)
25 | grunt.loadNpmTasks('grunt-contrib-jshint');
26 | grunt.loadNpmTasks('grunt-mocha-test');
27 |
28 | // Tasks
29 | grunt.registerTask('default', ['jshint', 'mochaTest']);
30 | };
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011-2012 Andris Reinman
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
11 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
12 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
13 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
14 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
15 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
16 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | node-gettext
8 |
9 |
10 | [](http://travis-ci.org/alexanderwallin/node-gettext)
11 | [](https://badge.fury.io/js/node-gettext)
12 |
13 | **`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.
14 |
15 | If you just want to parse or compile mo/po files, for use with this library or elsewhere, check out [gettext-parser](https://github.com/smhg/gettext-parser).
16 |
17 | **NOTE:** This is the README for v2 of node-gettext, which introduces several braking changes. You can find the [README for v1 here](https://github.com/alexanderwallin/node-gettext/blob/master/docs/v1/README.md).
18 |
19 | * [Features](#features)
20 | * [Differences from GNU gettext](#differences-from-gnu-gettext)
21 | * [Installation](#installation)
22 | * [Usage](#usage)
23 | * [Error events](#error-events)
24 | * [Recipes](#recipes)
25 | * [API](#api)
26 | * [Migrating from v1 to v2](#migrating-from-v1-to-v2)
27 | * [License](#license)
28 | * [See also](#see-also)
29 |
30 |
31 | ## Features
32 |
33 | * Supports domains, contexts and plurals
34 | * Supports .json, .mo and .po files with the help of [gettext-parser](https://github.com/smhg/gettext-parser)
35 | * Ships with plural forms for 136 languages
36 | * Change locale or domain on the fly
37 | * Useful error messages enabled by a `debug` option
38 | * Emits events for internal errors, such as missing translations
39 |
40 |
41 | ### Differences from GNU gettext
42 |
43 | There are two main differences between `node-gettext` and GNU's gettext:
44 |
45 | 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.
46 | 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.
47 |
48 | 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).
49 |
50 | `bindtextdomain` will be provided as an optional feature in a future release.
51 |
52 |
53 | ## Installation
54 |
55 | ```sh
56 | npm install --save node-gettext
57 | ```
58 |
59 |
60 | ## Usage
61 |
62 | ```js
63 | import Gettext from 'node-gettext'
64 | import swedishTranslations from './translations/sv-SE.json'
65 |
66 | const gt = new Gettext()
67 | gt.addTranslations('sv-SE', 'messages', swedishTranslations)
68 | gt.setLocale('sv-SE')
69 |
70 | gt.gettext('The world is a funny place')
71 | // -> "Världen är en underlig plats"
72 | ```
73 |
74 | ### Error events
75 |
76 | ```js
77 | // Add translations etc...
78 |
79 | gt.on('error', error => console.log('oh nose', error))
80 | gt.gettext('An unrecognized message')
81 | // -> 'oh nose', 'An unrecognized message'
82 | ```
83 |
84 | ### Recipes
85 |
86 | #### Load and add translations from .mo or .po files
87 |
88 | `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.
89 |
90 | Here is an example where we read a bunch of translation files from disk and add them to our `Gettext` instance:
91 |
92 | ```js
93 | import fs from 'fs'
94 | import path from 'path'
95 | import Gettext from 'node-gettext'
96 | import { po } from 'gettext-parser'
97 |
98 | // In this example, our translations are found at
99 | // path/to/locales/LOCALE/DOMAIN.po
100 | const translationsDir = 'path/to/locales'
101 | const locales = ['en', 'fi-FI', 'sv-SE']
102 | const domain = 'messages'
103 |
104 | const gt = new Gettext()
105 |
106 | locales.forEach((locale) => {
107 | const fileName = `${domain}.po`
108 | const translationsFilePath = path.join(translationsDir, locale, fileName)
109 | const translationsContent = fs.readFileSync(translationsFilePath)
110 |
111 | const parsedTranslations = po.parse(translationsContent)
112 | gt.addTranslations(locale, domain, parsedTranslations)
113 | })
114 | ```
115 |
116 |
117 | ## API
118 |
119 |
120 |
121 | ## Gettext
122 |
123 | * [Gettext](#Gettext)
124 | * [new Gettext([options])](#new_Gettext_new)
125 | * [.on(eventName, callback)](#Gettext+on)
126 | * [.off(eventName, callback)](#Gettext+off)
127 | * [.addTranslations(locale, domain, translations)](#Gettext+addTranslations)
128 | * [.setLocale(locale)](#Gettext+setLocale)
129 | * [.setTextDomain(domain)](#Gettext+setTextDomain)
130 | * [.gettext(msgid)](#Gettext+gettext) ⇒ String
131 | * [.dgettext(domain, msgid)](#Gettext+dgettext) ⇒ String
132 | * [.ngettext(msgid, msgidPlural, count)](#Gettext+ngettext) ⇒ String
133 | * [.dngettext(domain, msgid, msgidPlural, count)](#Gettext+dngettext) ⇒ String
134 | * [.pgettext(msgctxt, msgid)](#Gettext+pgettext) ⇒ String
135 | * [.dpgettext(domain, msgctxt, msgid)](#Gettext+dpgettext) ⇒ String
136 | * [.npgettext(msgctxt, msgid, msgidPlural, count)](#Gettext+npgettext) ⇒ String
137 | * [.dnpgettext(domain, msgctxt, msgid, msgidPlural, count)](#Gettext+dnpgettext) ⇒ String
138 | * [.textdomain()](#Gettext+textdomain)
139 | * [.setlocale()](#Gettext+setlocale)
140 | * ~~[.addTextdomain()](#Gettext+addTextdomain)~~
141 |
142 |
143 |
144 | ### new Gettext([options])
145 | Creates and returns a new Gettext instance.
146 |
147 | **Returns**: Object
- A Gettext instance
148 | **Params**
149 |
150 | - `[options]`: Object
- A set of options
151 | - `.sourceLocale`: String
- The locale that the source code and its texts are written in. Translations for this locale is not necessary.
152 | - `.debug`: Boolean
- Whether to output debug info into the
153 | console.
154 |
155 |
156 |
157 | ### gettext.on(eventName, callback)
158 | Adds an event listener.
159 |
160 | **Params**
161 |
162 | - `eventName`: String
- An event name
163 | - `callback`: function
- An event handler function
164 |
165 |
166 |
167 | ### gettext.off(eventName, callback)
168 | Removes an event listener.
169 |
170 | **Params**
171 |
172 | - `eventName`: String
- An event name
173 | - `callback`: function
- A previously registered event handler function
174 |
175 |
176 |
177 | ### gettext.addTranslations(locale, domain, translations)
178 | Stores a set of translations in the set of gettext
179 | catalogs.
180 |
181 | **Params**
182 |
183 | - `locale`: String
- A locale string
184 | - `domain`: String
- A domain name
185 | - `translations`: Object
- An object of gettext-parser JSON shape
186 |
187 | **Example**
188 | ```js
189 | gt.addTranslations('sv-SE', 'messages', translationsObject)
190 | ```
191 |
192 |
193 | ### gettext.setLocale(locale)
194 | Sets the locale to get translated messages for.
195 |
196 | **Params**
197 |
198 | - `locale`: String
- A locale
199 |
200 | **Example**
201 | ```js
202 | gt.setLocale('sv-SE')
203 | ```
204 |
205 |
206 | ### gettext.setTextDomain(domain)
207 | Sets the default gettext domain.
208 |
209 | **Params**
210 |
211 | - `domain`: String
- A gettext domain name
212 |
213 | **Example**
214 | ```js
215 | gt.setTextDomain('domainname')
216 | ```
217 |
218 |
219 | ### gettext.gettext(msgid) ⇒ String
220 | Translates a string using the default textdomain
221 |
222 | **Returns**: String
- Translation or the original string if no translation was found
223 | **Params**
224 |
225 | - `msgid`: String
- String to be translated
226 |
227 | **Example**
228 | ```js
229 | gt.gettext('Some text')
230 | ```
231 |
232 |
233 | ### gettext.dgettext(domain, msgid) ⇒ String
234 | Translates a string using a specific domain
235 |
236 | **Returns**: String
- Translation or the original string if no translation was found
237 | **Params**
238 |
239 | - `domain`: String
- A gettext domain name
240 | - `msgid`: String
- String to be translated
241 |
242 | **Example**
243 | ```js
244 | gt.dgettext('domainname', 'Some text')
245 | ```
246 |
247 |
248 | ### gettext.ngettext(msgid, msgidPlural, count) ⇒ String
249 | Translates a plural string using the default textdomain
250 |
251 | **Returns**: String
- Translation or the original string if no translation was found
252 | **Params**
253 |
254 | - `msgid`: String
- String to be translated when count is not plural
255 | - `msgidPlural`: String
- String to be translated when count is plural
256 | - `count`: Number
- Number count for the plural
257 |
258 | **Example**
259 | ```js
260 | gt.ngettext('One thing', 'Many things', numberOfThings)
261 | ```
262 |
263 |
264 | ### gettext.dngettext(domain, msgid, msgidPlural, count) ⇒ String
265 | Translates a plural string using a specific textdomain
266 |
267 | **Returns**: String
- Translation or the original string if no translation was found
268 | **Params**
269 |
270 | - `domain`: String
- A gettext domain name
271 | - `msgid`: String
- String to be translated when count is not plural
272 | - `msgidPlural`: String
- String to be translated when count is plural
273 | - `count`: Number
- Number count for the plural
274 |
275 | **Example**
276 | ```js
277 | gt.dngettext('domainname', 'One thing', 'Many things', numberOfThings)
278 | ```
279 |
280 |
281 | ### gettext.pgettext(msgctxt, msgid) ⇒ String
282 | Translates a string from a specific context using the default textdomain
283 |
284 | **Returns**: String
- Translation or the original string if no translation was found
285 | **Params**
286 |
287 | - `msgctxt`: String
- Translation context
288 | - `msgid`: String
- String to be translated
289 |
290 | **Example**
291 | ```js
292 | gt.pgettext('sports', 'Back')
293 | ```
294 |
295 |
296 | ### gettext.dpgettext(domain, msgctxt, msgid) ⇒ String
297 | Translates a string from a specific context using s specific textdomain
298 |
299 | **Returns**: String
- Translation or the original string if no translation was found
300 | **Params**
301 |
302 | - `domain`: String
- A gettext domain name
303 | - `msgctxt`: String
- Translation context
304 | - `msgid`: String
- String to be translated
305 |
306 | **Example**
307 | ```js
308 | gt.dpgettext('domainname', 'sports', 'Back')
309 | ```
310 |
311 |
312 | ### gettext.npgettext(msgctxt, msgid, msgidPlural, count) ⇒ String
313 | Translates a plural string from a specific context using the default textdomain
314 |
315 | **Returns**: String
- Translation or the original string if no translation was found
316 | **Params**
317 |
318 | - `msgctxt`: String
- Translation context
319 | - `msgid`: String
- String to be translated when count is not plural
320 | - `msgidPlural`: String
- String to be translated when count is plural
321 | - `count`: Number
- Number count for the plural
322 |
323 | **Example**
324 | ```js
325 | gt.npgettext('sports', 'Back', '%d backs', numberOfBacks)
326 | ```
327 |
328 |
329 | ### gettext.dnpgettext(domain, msgctxt, msgid, msgidPlural, count) ⇒ String
330 | Translates a plural string from a specifi context using a specific textdomain
331 |
332 | **Returns**: String
- Translation or the original string if no translation was found
333 | **Params**
334 |
335 | - `domain`: String
- A gettext domain name
336 | - `msgctxt`: String
- Translation context
337 | - `msgid`: String
- String to be translated
338 | - `msgidPlural`: String
- If no translation was found, return this on count!=1
339 | - `count`: Number
- Number count for the plural
340 |
341 | **Example**
342 | ```js
343 | gt.dnpgettext('domainname', 'sports', 'Back', '%d backs', numberOfBacks)
344 | ```
345 |
346 |
347 | ### gettext.textdomain()
348 | C-style alias for [setTextDomain](#gettextsettextdomaindomain)
349 |
350 | **See**: Gettext#setTextDomain
351 |
352 |
353 | ### gettext.setlocale()
354 | C-style alias for [setLocale](#gettextsetlocalelocale)
355 |
356 | **See**: Gettext#setLocale
357 |
358 |
359 | ### ~~gettext.addTextdomain()~~
360 | ***Deprecated***
361 |
362 | This function will be removed in the final 2.0.0 release.
363 |
364 |
365 |
366 | ## Migrating from v1 to v2
367 |
368 | 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.
369 |
370 | Here is a full list of all breaking changes:
371 |
372 | * `textdomain(domain)` is now `setLocale(locale)`
373 | * `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.
374 | * A new `setTextDomain(domain)` has been introduced
375 | * `addTextdomain(domain, file)` is now `addTranslations(locale, domain, translations)`
376 | * `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.
377 | * `_currentDomain` is now `domain`
378 | * `domains` is now `catalogs`
379 | * The instance method `__normalizeDomain(domain)` has been replaced by a static method `Gettext.getLanguageCode(locale)`
380 |
381 |
382 | ## License
383 |
384 | MIT
385 |
386 |
387 | ## See also
388 |
389 | * [gettext-parser](https://github.com/smhg/gettext-parser) - Parsing and compiling gettext translations between .po/.mo files and JSON
390 | * [lioness](https://github.com/alexanderwallin/lioness) – Gettext library for React
391 | * [react-gettext-parser](https://github.com/laget-se/react-gettext-parser) - Extracting gettext translatable strings from JS(X) code
392 | * [narp](https://github.com/laget-se/narp) - Workflow CLI tool that syncs translations between your app and Transifex
393 |
--------------------------------------------------------------------------------
/docs/README.template.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | node-gettext
8 |
9 |
10 | [](http://travis-ci.org/alexanderwallin/node-gettext)
11 | [](https://badge.fury.io/js/node-gettext)
12 |
13 | **`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.
14 |
15 | If you just want to parse or compile mo/po files, for use with this library or elsewhere, check out [gettext-parser](https://github.com/smhg/gettext-parser).
16 |
17 | **NOTE:** This is the README for v2 of node-gettext, which introduces several braking changes. You can find the [README for v1 here](https://github.com/alexanderwallin/node-gettext/blob/master/docs/v1/README.md).
18 |
19 | * [Features](#features)
20 | * [Differences from GNU gettext](#differences-from-gnu-gettext)
21 | * [Installation](#installation)
22 | * [Usage](#usage)
23 | * [Error events](#error-events)
24 | * [Recipes](#recipes)
25 | * [API](#api)
26 | * [Migrating from v1 to v2](#migrating-from-v1-to-v2)
27 | * [License](#license)
28 | * [See also](#see-also)
29 |
30 |
31 | ## Features
32 |
33 | * Supports domains, contexts and plurals
34 | * Supports .json, .mo and .po files with the help of [gettext-parser](https://github.com/smhg/gettext-parser)
35 | * Ships with plural forms for 136 languages
36 | * Change locale or domain on the fly
37 | * Useful error messages enabled by a `debug` option
38 | * Emits events for internal errors, such as missing translations
39 |
40 |
41 | ### Differences from GNU gettext
42 |
43 | There are two main differences between `node-gettext` and GNU's gettext:
44 |
45 | 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.
46 | 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.
47 |
48 | 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).
49 |
50 | `bindtextdomain` will be provided as an optional feature in a future release.
51 |
52 |
53 | ## Installation
54 |
55 | ```sh
56 | npm install --save node-gettext
57 | ```
58 |
59 |
60 | ## Usage
61 |
62 | ```js
63 | import Gettext from 'node-gettext'
64 | import swedishTranslations from './translations/sv-SE.json'
65 |
66 | const gt = new Gettext()
67 | gt.addTranslations('sv-SE', 'messages', swedishTranslations)
68 | gt.setLocale('sv-SE')
69 |
70 | gt.gettext('The world is a funny place')
71 | // -> "Världen är en underlig plats"
72 | ```
73 |
74 | ### Error events
75 |
76 | ```js
77 | // Add translations etc...
78 |
79 | gt.on('error', error => console.log('oh nose', error))
80 | gt.gettext('An unrecognized message')
81 | // -> 'oh nose', 'An unrecognized message'
82 | ```
83 |
84 | ### Recipes
85 |
86 | #### Load and add translations from .mo or .po files
87 |
88 | `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.
89 |
90 | Here is an example where we read a bunch of translation files from disk and add them to our `Gettext` instance:
91 |
92 | ```js
93 | import fs from 'fs'
94 | import path from 'path'
95 | import Gettext from 'node-gettext'
96 | import { po } from 'gettext-parser'
97 |
98 | // In this example, our translations are found at
99 | // path/to/locales/LOCALE/DOMAIN.po
100 | const translationsDir = 'path/to/locales'
101 | const locales = ['en', 'fi-FI', 'sv-SE']
102 | const domain = 'messages'
103 |
104 | const gt = new Gettext()
105 |
106 | locales.forEach((locale) => {
107 | const fileName = `${domain}.po`
108 | const translationsFilePath = path.join(translationsDir, locale, filename)
109 | const translationsContent = fs.readSync(translationsFilePath)
110 |
111 | const parsedTranslations = po.parse(translationsContent)
112 | gt.addTranslations(locale, domain, parsedTranslations)
113 | })
114 | ```
115 |
116 |
117 | ## API
118 |
119 | {{>main}}
120 |
121 |
122 | ## Migrating from v1 to v2
123 |
124 | 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.
125 |
126 | Here is a full list of all breaking changes:
127 |
128 | * `textdomain(domain)` is now `setLocale(locale)`
129 | * `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.
130 | * A new `setTextDomain(domain)` has been introduced
131 | * `addTextdomain(domain, file)` is now `addTranslations(locale, domain, translations)`
132 | * `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.
133 | * `_currentDomain` is now `domain`
134 | * `domains` is now `catalogs`
135 | * The instance method `__normalizeDomain(domain)` has been replaced by a static method `Gettext.getLanguageCode(locale)`
136 |
137 |
138 | ## License
139 |
140 | MIT
141 |
142 |
143 | ## See also
144 |
145 | * [gettext-parser](https://github.com/smhg/gettext-parser) - Parsing and compiling gettext translations between .po/.mo files and JSON
146 | * [lioness](https://github.com/alexanderwallin/lioness) – Gettext library for React
147 | * [react-gettext-parser](https://github.com/lagetse/react-gettext-parser) - Extracting gettext translatable strings from JS(X) code
148 | * [narp](https://github.com/lagetse/narp) - Workflow CLI tool that syncs translations between your app and Transifex
149 |
--------------------------------------------------------------------------------
/docs/node-gettext-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexanderwallin/node-gettext/65d9670f691c2eeca40dce129c95bcf8b613d344/docs/node-gettext-logo.png
--------------------------------------------------------------------------------
/docs/templates/params-list.hbs:
--------------------------------------------------------------------------------
1 | {{#if params}}
2 | {{#params}}**Params**
3 |
4 | {{#each this~}}
5 | {{indent}}- `{{name}}`:{{#if type}} {{>linked-type-list types=type.names delimiter=" | " }}{{/if}}{{#unless (equal defaultvalue undefined)}} {{>defaultvalue equals=true ~}}{{/unless}}{{#if description}} - {{{inlineLinks description}}}{{/if}}
6 | {{/each}}
7 |
8 | {{/params~}}
9 | {{/if}}
10 |
--------------------------------------------------------------------------------
/docs/templates/scope.hbs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexanderwallin/node-gettext/65d9670f691c2eeca40dce129c95bcf8b613d344/docs/templates/scope.hbs
--------------------------------------------------------------------------------
/docs/v1/README.md:
--------------------------------------------------------------------------------
1 | # node-gettext
2 |
3 | [](http://travis-ci.org/alexanderwallin/node-gettext)
4 | [](https://badge.fury.io/js/node-gettext)
5 |
6 | **node-gettext** is a Node.JS module to use .MO and .PO files.
7 |
8 | **NB!** If you just want to parse or compile mo/po files, check out [gettext-parser](https://github.com/andris9/gettext-parser).
9 |
10 | ## Features
11 |
12 | * Load binary *MO* or source *PO* files
13 | * Supports contexts and plurals
14 |
15 | ## Installation
16 |
17 | ```sh
18 | npm install node-gettext
19 | ```
20 |
21 | ## Usage
22 |
23 | ### Create a new Gettext object
24 |
25 | ```js
26 | var Gettext = require("node-gettext");
27 |
28 | var gt = new Gettext();
29 | ```
30 |
31 | ### Add a language
32 |
33 | *addTextdomain(domain, file)*
34 |
35 | Language data needs to be in the Buffer format - it can be either contents of a *MO* or *PO* file.
36 |
37 | *addTextdomain(domain[, fileContents])*
38 |
39 | Load from a *MO* file
40 |
41 | ```js
42 | var fileContents = fs.readFileSync("et.mo");
43 | gt.addTextdomain("et", fileContents);
44 | ```
45 |
46 | or load from a *PO* file
47 |
48 | ```js
49 | var fileContents = fs.readFileSync("et.po");
50 | gt.addTextdomain("et", fileContents);
51 | ```
52 |
53 | Plural rules are automatically detected from the language code
54 |
55 | ```js
56 | gt.addTextdomain("et");
57 | gt.setTranslation("et", false, "hello!", "tere!");
58 | ```
59 |
60 | ### Check or change default language
61 |
62 | *textdomain(domain)*
63 |
64 | ```js
65 | gt.textdomain("et");
66 | ```
67 |
68 | The function also returns the current texdomain value
69 |
70 | ```js
71 | var curlang = gt.textdomain();
72 | ```
73 |
74 | ## Translation methods
75 |
76 | ### Load a string from default language file
77 |
78 | *gettext(msgid)*
79 |
80 | ```js
81 | var greeting = gt.gettext("Hello!");
82 | ```
83 |
84 | ### Load a string from a specific language file
85 |
86 | *dgettext(domain, msgid)*
87 |
88 | ```js
89 | var greeting = gt.dgettext("et", "Hello!");
90 | ```
91 |
92 | ### Load a plural string from default language file
93 |
94 | *ngettext(msgid, msgid_plural, count)*
95 |
96 | ```js
97 | gt.ngettext("%d Comment", "%d Comments", 10);
98 | ```
99 |
100 | ### Load a plural string from a specific language file
101 |
102 | *dngettext(domain, msgid, msgid_plural, count)*
103 |
104 | ```js
105 | gt.dngettext("et", "%d Comment", "%d Comments", 10);
106 | ```
107 |
108 | ### Load a string of a specific context
109 |
110 | *pgettext(msgctxt, msgid)*
111 |
112 | ```js
113 | gt.pgettext("menu items", "File");
114 | ```
115 |
116 | ### Load a string of a specific context from specific language file
117 |
118 | *dpgettext(domain, msgctxt, msgid)*
119 |
120 | ```js
121 | gt.dpgettext("et", "menu items", "File");
122 | ```
123 |
124 | ### Load a plural string of a specific context
125 |
126 | *npgettext(msgctxt, msgid, msgid_plural, count)*
127 |
128 | ```js
129 | gt.npgettext("menu items", "%d Recent File", "%d Recent Files", 3);
130 | ```
131 |
132 | ### Load a plural string of a specific context from specific language file
133 |
134 | *dnpgettext(domain, msgctxt, msgid, msgid_plural, count)*
135 |
136 | ```js
137 | gt.dnpgettext("et", "menu items", "%d Recent File", "%d Recent Files", 3);
138 | ```
139 |
140 | ### Get comments for a translation (if loaded from PO)
141 |
142 | *getComment(domain, msgctxt, msgid)*
143 |
144 | ```js
145 | gt.getComment("et", "menu items", "%d Recent File");
146 | ```
147 |
148 | Returns an object in the form of `{translator: "", extracted: "", reference: "", flag: "", previous: ""}`
149 |
150 | ## Advanced handling
151 |
152 | If you need the translation object for a domain, for example `et_EE`, you can access it from `gt.domains.et_EE`.
153 |
154 | If you want modify it and compile it to *mo* or *po*, checkout [gettext-parser](https://github.com/andris9/gettext-parser) module.
155 |
156 | ## License
157 |
158 | MIT
159 |
--------------------------------------------------------------------------------
/lib/gettext.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var get = require('lodash.get');
4 | var plurals = require('./plurals');
5 |
6 | module.exports = Gettext;
7 |
8 | /**
9 | * Creates and returns a new Gettext instance.
10 | *
11 | * @constructor
12 | * @param {Object} [options] A set of options
13 | * @param {String} options.sourceLocale The locale that the source code and its
14 | * texts are written in. Translations for
15 | * this locale is not necessary.
16 | * @param {Boolean} options.debug Whether to output debug info into the
17 | * console.
18 | * @return {Object} A Gettext instance
19 | */
20 | function Gettext(options) {
21 | options = options || {};
22 |
23 | this.catalogs = {};
24 | this.locale = '';
25 | this.domain = 'messages';
26 |
27 | this.listeners = [];
28 |
29 | // Set source locale
30 | this.sourceLocale = '';
31 | if (options.sourceLocale) {
32 | if (typeof options.sourceLocale === 'string') {
33 | this.sourceLocale = options.sourceLocale;
34 | }
35 | else {
36 | this.warn('The `sourceLocale` option should be a string');
37 | }
38 | }
39 |
40 | // Set debug flag
41 | this.debug = 'debug' in options && options.debug === true;
42 | }
43 |
44 | /**
45 | * Adds an event listener.
46 | *
47 | * @param {String} eventName An event name
48 | * @param {Function} callback An event handler function
49 | */
50 | Gettext.prototype.on = function(eventName, callback) {
51 | this.listeners.push({
52 | eventName: eventName,
53 | callback: callback
54 | });
55 | };
56 |
57 | /**
58 | * Removes an event listener.
59 | *
60 | * @param {String} eventName An event name
61 | * @param {Function} callback A previously registered event handler function
62 | */
63 | Gettext.prototype.off = function(eventName, callback) {
64 | this.listeners = this.listeners.filter(function(listener) {
65 | return (
66 | listener.eventName === eventName &&
67 | listener.callback === callback
68 | ) === false;
69 | });
70 | };
71 |
72 | /**
73 | * Emits an event to all registered event listener.
74 | *
75 | * @private
76 | * @param {String} eventName An event name
77 | * @param {any} eventData Data to pass to event listeners
78 | */
79 | Gettext.prototype.emit = function(eventName, eventData) {
80 | for (var i = 0; i < this.listeners.length; i++) {
81 | var listener = this.listeners[i];
82 | if (listener.eventName === eventName) {
83 | listener.callback(eventData);
84 | }
85 | }
86 | };
87 |
88 | /**
89 | * Logs a warning to the console if debug mode is enabled.
90 | *
91 | * @ignore
92 | * @param {String} message A warning message
93 | */
94 | Gettext.prototype.warn = function(message) {
95 | if (this.debug) {
96 | console.warn(message);
97 | }
98 |
99 | this.emit('error', new Error(message));
100 | };
101 |
102 | /**
103 | * Stores a set of translations in the set of gettext
104 | * catalogs.
105 | *
106 | * @example
107 | * gt.addTranslations('sv-SE', 'messages', translationsObject)
108 | *
109 | * @param {String} locale A locale string
110 | * @param {String} domain A domain name
111 | * @param {Object} translations An object of gettext-parser JSON shape
112 | */
113 | Gettext.prototype.addTranslations = function(locale, domain, translations) {
114 | if (!this.catalogs[locale]) {
115 | this.catalogs[locale] = {};
116 | }
117 |
118 | this.catalogs[locale][domain] = translations;
119 | };
120 |
121 | /**
122 | * Sets the locale to get translated messages for.
123 | *
124 | * @example
125 | * gt.setLocale('sv-SE')
126 | *
127 | * @param {String} locale A locale
128 | */
129 | Gettext.prototype.setLocale = function(locale) {
130 | if (typeof locale !== 'string') {
131 | this.warn(
132 | 'You called setLocale() with an argument of type ' + (typeof locale) + '. ' +
133 | 'The locale must be a string.'
134 | );
135 | return;
136 | }
137 |
138 | if (locale.trim() === '') {
139 | this.warn('You called setLocale() with an empty value, which makes little sense.');
140 | }
141 |
142 | if (locale !== this.sourceLocale && !this.catalogs[locale]) {
143 | this.warn('You called setLocale() with "' + locale + '", but no translations for that locale has been added.');
144 | }
145 |
146 | this.locale = locale;
147 | };
148 |
149 | /**
150 | * Sets the default gettext domain.
151 | *
152 | * @example
153 | * gt.setTextDomain('domainname')
154 | *
155 | * @param {String} domain A gettext domain name
156 | */
157 | Gettext.prototype.setTextDomain = function(domain) {
158 | if (typeof domain !== 'string') {
159 | this.warn(
160 | 'You called setTextDomain() with an argument of type ' + (typeof domain) + '. ' +
161 | 'The domain must be a string.'
162 | );
163 | return;
164 | }
165 |
166 | if (domain.trim() === '') {
167 | this.warn('You called setTextDomain() with an empty `domain` value.');
168 | }
169 |
170 | this.domain = domain;
171 | };
172 |
173 | /**
174 | * Translates a string using the default textdomain
175 | *
176 | * @example
177 | * gt.gettext('Some text')
178 | *
179 | * @param {String} msgid String to be translated
180 | * @return {String} Translation or the original string if no translation was found
181 | */
182 | Gettext.prototype.gettext = function(msgid) {
183 | return this.dnpgettext(this.domain, '', msgid);
184 | };
185 |
186 | /**
187 | * Translates a string using a specific domain
188 | *
189 | * @example
190 | * gt.dgettext('domainname', 'Some text')
191 | *
192 | * @param {String} domain A gettext domain name
193 | * @param {String} msgid String to be translated
194 | * @return {String} Translation or the original string if no translation was found
195 | */
196 | Gettext.prototype.dgettext = function(domain, msgid) {
197 | return this.dnpgettext(domain, '', msgid);
198 | };
199 |
200 | /**
201 | * Translates a plural string using the default textdomain
202 | *
203 | * @example
204 | * gt.ngettext('One thing', 'Many things', numberOfThings)
205 | *
206 | * @param {String} msgid String to be translated when count is not plural
207 | * @param {String} msgidPlural String to be translated when count is plural
208 | * @param {Number} count Number count for the plural
209 | * @return {String} Translation or the original string if no translation was found
210 | */
211 | Gettext.prototype.ngettext = function(msgid, msgidPlural, count) {
212 | return this.dnpgettext(this.domain, '', msgid, msgidPlural, count);
213 | };
214 |
215 | /**
216 | * Translates a plural string using a specific textdomain
217 | *
218 | * @example
219 | * gt.dngettext('domainname', 'One thing', 'Many things', numberOfThings)
220 | *
221 | * @param {String} domain A gettext domain name
222 | * @param {String} msgid String to be translated when count is not plural
223 | * @param {String} msgidPlural String to be translated when count is plural
224 | * @param {Number} count Number count for the plural
225 | * @return {String} Translation or the original string if no translation was found
226 | */
227 | Gettext.prototype.dngettext = function(domain, msgid, msgidPlural, count) {
228 | return this.dnpgettext(domain, '', msgid, msgidPlural, count);
229 | };
230 |
231 | /**
232 | * Translates a string from a specific context using the default textdomain
233 | *
234 | * @example
235 | * gt.pgettext('sports', 'Back')
236 | *
237 | * @param {String} msgctxt Translation context
238 | * @param {String} msgid String to be translated
239 | * @return {String} Translation or the original string if no translation was found
240 | */
241 | Gettext.prototype.pgettext = function(msgctxt, msgid) {
242 | return this.dnpgettext(this.domain, msgctxt, msgid);
243 | };
244 |
245 | /**
246 | * Translates a string from a specific context using s specific textdomain
247 | *
248 | * @example
249 | * gt.dpgettext('domainname', 'sports', 'Back')
250 | *
251 | * @param {String} domain A gettext domain name
252 | * @param {String} msgctxt Translation context
253 | * @param {String} msgid String to be translated
254 | * @return {String} Translation or the original string if no translation was found
255 | */
256 | Gettext.prototype.dpgettext = function(domain, msgctxt, msgid) {
257 | return this.dnpgettext(domain, msgctxt, msgid);
258 | };
259 |
260 | /**
261 | * Translates a plural string from a specific context using the default textdomain
262 | *
263 | * @example
264 | * gt.npgettext('sports', 'Back', '%d backs', numberOfBacks)
265 | *
266 | * @param {String} msgctxt Translation context
267 | * @param {String} msgid String to be translated when count is not plural
268 | * @param {String} msgidPlural String to be translated when count is plural
269 | * @param {Number} count Number count for the plural
270 | * @return {String} Translation or the original string if no translation was found
271 | */
272 | Gettext.prototype.npgettext = function(msgctxt, msgid, msgidPlural, count) {
273 | return this.dnpgettext(this.domain, msgctxt, msgid, msgidPlural, count);
274 | };
275 |
276 | /**
277 | * Translates a plural string from a specifi context using a specific textdomain
278 | *
279 | * @example
280 | * gt.dnpgettext('domainname', 'sports', 'Back', '%d backs', numberOfBacks)
281 | *
282 | * @param {String} domain A gettext domain name
283 | * @param {String} msgctxt Translation context
284 | * @param {String} msgid String to be translated
285 | * @param {String} msgidPlural If no translation was found, return this on count!=1
286 | * @param {Number} count Number count for the plural
287 | * @return {String} Translation or the original string if no translation was found
288 | */
289 | Gettext.prototype.dnpgettext = function(domain, msgctxt, msgid, msgidPlural, count) {
290 | var defaultTranslation = msgid;
291 | var translation;
292 | var index;
293 |
294 | msgctxt = msgctxt || '';
295 |
296 | if (!isNaN(count) && count !== 1) {
297 | defaultTranslation = msgidPlural || msgid;
298 | }
299 |
300 | translation = this._getTranslation(domain, msgctxt, msgid);
301 |
302 | if (translation) {
303 | if (typeof count === 'number') {
304 | var pluralsFunc = plurals[Gettext.getLanguageCode(this.locale)].pluralsFunc;
305 | index = pluralsFunc(count);
306 | if (typeof index === 'boolean') {
307 | index = index ? 1 : 0;
308 | }
309 | } else {
310 | index = 0;
311 | }
312 |
313 | return translation.msgstr[index] || defaultTranslation;
314 | }
315 | else if (!this.sourceLocale || this.locale !== this.sourceLocale) {
316 | this.warn('No translation was found for msgid "' + msgid + '" in msgctxt "' + msgctxt + '" and domain "' + domain + '"');
317 | }
318 |
319 | return defaultTranslation;
320 | };
321 |
322 | /**
323 | * Retrieves comments object for a translation. The comments object
324 | * has the shape `{ translator, extracted, reference, flag, previous }`.
325 | *
326 | * @example
327 | * const comment = gt.getComment('domainname', 'sports', 'Backs')
328 | *
329 | * @private
330 | * @param {String} domain A gettext domain name
331 | * @param {String} msgctxt Translation context
332 | * @param {String} msgid String to be translated
333 | * @return {Object} Comments object or false if not found
334 | */
335 | Gettext.prototype.getComment = function(domain, msgctxt, msgid) {
336 | var translation;
337 |
338 | translation = this._getTranslation(domain, msgctxt, msgid);
339 | if (translation) {
340 | return translation.comments || {};
341 | }
342 |
343 | return {};
344 | };
345 |
346 | /**
347 | * Retrieves translation object from the domain and context
348 | *
349 | * @private
350 | * @param {String} domain A gettext domain name
351 | * @param {String} msgctxt Translation context
352 | * @param {String} msgid String to be translated
353 | * @return {Object} Translation object or false if not found
354 | */
355 | Gettext.prototype._getTranslation = function(domain, msgctxt, msgid) {
356 | msgctxt = msgctxt || '';
357 |
358 | return get(this.catalogs, [this.locale, domain, 'translations', msgctxt, msgid]);
359 | };
360 |
361 | /**
362 | * Returns the language code part of a locale
363 | *
364 | * @example
365 | * Gettext.getLanguageCode('sv-SE')
366 | * // -> "sv"
367 | *
368 | * @private
369 | * @param {String} locale A case-insensitive locale string
370 | * @returns {String} A language code
371 | */
372 | Gettext.getLanguageCode = function(locale) {
373 | return locale.split(/[\-_]/)[0].toLowerCase();
374 | };
375 |
376 | /* C-style aliases */
377 |
378 | /**
379 | * C-style alias for [setTextDomain](#gettextsettextdomaindomain)
380 | *
381 | * @see Gettext#setTextDomain
382 | */
383 | Gettext.prototype.textdomain = function(domain) {
384 | if (this.debug) {
385 | console.warn('textdomain(domain) was used to set locales in node-gettext v1. ' +
386 | 'Make sure you are using it for domains, and switch to setLocale(locale) if you are not.\n\n ' +
387 | 'To read more about the migration from node-gettext v1 to v2, ' +
388 | 'see https://github.com/alexanderwallin/node-gettext/#migrating-from-1x-to-2x\n\n' +
389 | 'This warning will be removed in the final 2.0.0');
390 | }
391 |
392 | this.setTextDomain(domain);
393 | };
394 |
395 | /**
396 | * C-style alias for [setLocale](#gettextsetlocalelocale)
397 | *
398 | * @see Gettext#setLocale
399 | */
400 | Gettext.prototype.setlocale = function(locale) {
401 | this.setLocale(locale);
402 | };
403 |
404 | /* Deprecated functions */
405 |
406 | /**
407 | * This function will be removed in the final 2.0.0 release.
408 | *
409 | * @deprecated
410 | */
411 | Gettext.prototype.addTextdomain = function() {
412 | console.error('addTextdomain() is deprecated.\n\n' +
413 | '* To add translations, use addTranslations()\n' +
414 | '* To set the default domain, use setTextDomain() (or its alias textdomain())\n' +
415 | '\n' +
416 | 'To read more about the migration from node-gettext v1 to v2, ' +
417 | 'see https://github.com/alexanderwallin/node-gettext/#migrating-from-1x-to-2x');
418 | };
419 |
--------------------------------------------------------------------------------
/lib/plurals.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | ach: {
5 | name: 'Acholi',
6 | examples: [{
7 | plural: 0,
8 | sample: 1
9 | }, {
10 | plural: 1,
11 | sample: 2
12 | }],
13 | nplurals: 2,
14 | pluralsText: 'nplurals = 2; plural = (n > 1)',
15 | pluralsFunc: function(n) {
16 | return (n > 1);
17 | }
18 | },
19 | af: {
20 | name: 'Afrikaans',
21 | examples: [{
22 | plural: 0,
23 | sample: 1
24 | }, {
25 | plural: 1,
26 | sample: 2
27 | }],
28 | nplurals: 2,
29 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
30 | pluralsFunc: function(n) {
31 | return (n !== 1);
32 | }
33 | },
34 | ak: {
35 | name: 'Akan',
36 | examples: [{
37 | plural: 0,
38 | sample: 1
39 | }, {
40 | plural: 1,
41 | sample: 2
42 | }],
43 | nplurals: 2,
44 | pluralsText: 'nplurals = 2; plural = (n > 1)',
45 | pluralsFunc: function(n) {
46 | return (n > 1);
47 | }
48 | },
49 | am: {
50 | name: 'Amharic',
51 | examples: [{
52 | plural: 0,
53 | sample: 1
54 | }, {
55 | plural: 1,
56 | sample: 2
57 | }],
58 | nplurals: 2,
59 | pluralsText: 'nplurals = 2; plural = (n > 1)',
60 | pluralsFunc: function(n) {
61 | return (n > 1);
62 | }
63 | },
64 | an: {
65 | name: 'Aragonese',
66 | examples: [{
67 | plural: 0,
68 | sample: 1
69 | }, {
70 | plural: 1,
71 | sample: 2
72 | }],
73 | nplurals: 2,
74 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
75 | pluralsFunc: function(n) {
76 | return (n !== 1);
77 | }
78 | },
79 | ar: {
80 | name: 'Arabic',
81 | examples: [{
82 | plural: 0,
83 | sample: 0
84 | }, {
85 | plural: 1,
86 | sample: 1
87 | }, {
88 | plural: 2,
89 | sample: 2
90 | }, {
91 | plural: 3,
92 | sample: 3
93 | }, {
94 | plural: 4,
95 | sample: 11
96 | }, {
97 | plural: 5,
98 | sample: 100
99 | }],
100 | nplurals: 6,
101 | pluralsText: 'nplurals = 6; plural = (n === 0 ? 0 : n === 1 ? 1 : n === 2 ? 2 : n % 100 >= 3 && n % 100 <= 10 ? 3 : n % 100 >= 11 ? 4 : 5)',
102 | pluralsFunc: function(n) {
103 | return (n === 0 ? 0 : n === 1 ? 1 : n === 2 ? 2 : n % 100 >= 3 && n % 100 <= 10 ? 3 : n % 100 >= 11 ? 4 : 5);
104 | }
105 | },
106 | arn: {
107 | name: 'Mapudungun',
108 | examples: [{
109 | plural: 0,
110 | sample: 1
111 | }, {
112 | plural: 1,
113 | sample: 2
114 | }],
115 | nplurals: 2,
116 | pluralsText: 'nplurals = 2; plural = (n > 1)',
117 | pluralsFunc: function(n) {
118 | return (n > 1);
119 | }
120 | },
121 | ast: {
122 | name: 'Asturian',
123 | examples: [{
124 | plural: 0,
125 | sample: 1
126 | }, {
127 | plural: 1,
128 | sample: 2
129 | }],
130 | nplurals: 2,
131 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
132 | pluralsFunc: function(n) {
133 | return (n !== 1);
134 | }
135 | },
136 | ay: {
137 | name: 'Aymará',
138 | examples: [{
139 | plural: 0,
140 | sample: 1
141 | }],
142 | nplurals: 1,
143 | pluralsText: 'nplurals = 1; plural = 0',
144 | pluralsFunc: function() {
145 | return 0;
146 | }
147 | },
148 | az: {
149 | name: 'Azerbaijani',
150 | examples: [{
151 | plural: 0,
152 | sample: 1
153 | }, {
154 | plural: 1,
155 | sample: 2
156 | }],
157 | nplurals: 2,
158 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
159 | pluralsFunc: function(n) {
160 | return (n !== 1);
161 | }
162 | },
163 | be: {
164 | name: 'Belarusian',
165 | examples: [{
166 | plural: 0,
167 | sample: 1
168 | }, {
169 | plural: 1,
170 | sample: 2
171 | }, {
172 | plural: 2,
173 | sample: 5
174 | }],
175 | nplurals: 3,
176 | pluralsText: 'nplurals = 3; plural = (n % 10 === 1 && n % 100 !== 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2)',
177 | pluralsFunc: function(n) {
178 | return (n % 10 === 1 && n % 100 !== 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
179 | }
180 | },
181 | bg: {
182 | name: 'Bulgarian',
183 | examples: [{
184 | plural: 0,
185 | sample: 1
186 | }, {
187 | plural: 1,
188 | sample: 2
189 | }],
190 | nplurals: 2,
191 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
192 | pluralsFunc: function(n) {
193 | return (n !== 1);
194 | }
195 | },
196 | bn: {
197 | name: 'Bengali',
198 | examples: [{
199 | plural: 0,
200 | sample: 1
201 | }, {
202 | plural: 1,
203 | sample: 2
204 | }],
205 | nplurals: 2,
206 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
207 | pluralsFunc: function(n) {
208 | return (n !== 1);
209 | }
210 | },
211 | bo: {
212 | name: 'Tibetan',
213 | examples: [{
214 | plural: 0,
215 | sample: 1
216 | }],
217 | nplurals: 1,
218 | pluralsText: 'nplurals = 1; plural = 0',
219 | pluralsFunc: function() {
220 | return 0;
221 | }
222 | },
223 | br: {
224 | name: 'Breton',
225 | examples: [{
226 | plural: 0,
227 | sample: 1
228 | }, {
229 | plural: 1,
230 | sample: 2
231 | }],
232 | nplurals: 2,
233 | pluralsText: 'nplurals = 2; plural = (n > 1)',
234 | pluralsFunc: function(n) {
235 | return (n > 1);
236 | }
237 | },
238 | brx: {
239 | name: 'Bodo',
240 | examples: [{
241 | plural: 0,
242 | sample: 1
243 | }, {
244 | plural: 1,
245 | sample: 2
246 | }],
247 | nplurals: 2,
248 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
249 | pluralsFunc: function(n) {
250 | return (n !== 1);
251 | }
252 | },
253 | bs: {
254 | name: 'Bosnian',
255 | examples: [{
256 | plural: 0,
257 | sample: 1
258 | }, {
259 | plural: 1,
260 | sample: 2
261 | }, {
262 | plural: 2,
263 | sample: 5
264 | }],
265 | nplurals: 3,
266 | pluralsText: 'nplurals = 3; plural = (n % 10 === 1 && n % 100 !== 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2)',
267 | pluralsFunc: function(n) {
268 | return (n % 10 === 1 && n % 100 !== 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
269 | }
270 | },
271 | ca: {
272 | name: 'Catalan',
273 | examples: [{
274 | plural: 0,
275 | sample: 1
276 | }, {
277 | plural: 1,
278 | sample: 2
279 | }],
280 | nplurals: 2,
281 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
282 | pluralsFunc: function(n) {
283 | return (n !== 1);
284 | }
285 | },
286 | cgg: {
287 | name: 'Chiga',
288 | examples: [{
289 | plural: 0,
290 | sample: 1
291 | }],
292 | nplurals: 1,
293 | pluralsText: 'nplurals = 1; plural = 0',
294 | pluralsFunc: function() {
295 | return 0;
296 | }
297 | },
298 | cs: {
299 | name: 'Czech',
300 | examples: [{
301 | plural: 0,
302 | sample: 1
303 | }, {
304 | plural: 1,
305 | sample: 2
306 | }, {
307 | plural: 2,
308 | sample: 5
309 | }],
310 | nplurals: 3,
311 | pluralsText: 'nplurals = 3; plural = (n === 1 ? 0 : (n >= 2 && n <= 4) ? 1 : 2)',
312 | pluralsFunc: function(n) {
313 | return (n === 1 ? 0 : (n >= 2 && n <= 4) ? 1 : 2);
314 | }
315 | },
316 | csb: {
317 | name: 'Kashubian',
318 | examples: [{
319 | plural: 0,
320 | sample: 1
321 | }, {
322 | plural: 1,
323 | sample: 2
324 | }, {
325 | plural: 2,
326 | sample: 5
327 | }],
328 | nplurals: 3,
329 | pluralsText: 'nplurals = 3; plural = (n === 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2)',
330 | pluralsFunc: function(n) {
331 | return (n === 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
332 | }
333 | },
334 | cy: {
335 | name: 'Welsh',
336 | examples: [{
337 | plural: 0,
338 | sample: 1
339 | }, {
340 | plural: 1,
341 | sample: 2
342 | }, {
343 | plural: 2,
344 | sample: 3
345 | }, {
346 | plural: 3,
347 | sample: 8
348 | }],
349 | nplurals: 4,
350 | pluralsText: 'nplurals = 4; plural = (n === 1 ? 0 : n === 2 ? 1 : (n !== 8 && n !== 11) ? 2 : 3)',
351 | pluralsFunc: function(n) {
352 | return (n === 1 ? 0 : n === 2 ? 1 : (n !== 8 && n !== 11) ? 2 : 3);
353 | }
354 | },
355 | da: {
356 | name: 'Danish',
357 | examples: [{
358 | plural: 0,
359 | sample: 1
360 | }, {
361 | plural: 1,
362 | sample: 2
363 | }],
364 | nplurals: 2,
365 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
366 | pluralsFunc: function(n) {
367 | return (n !== 1);
368 | }
369 | },
370 | de: {
371 | name: 'German',
372 | examples: [{
373 | plural: 0,
374 | sample: 1
375 | }, {
376 | plural: 1,
377 | sample: 2
378 | }],
379 | nplurals: 2,
380 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
381 | pluralsFunc: function(n) {
382 | return (n !== 1);
383 | }
384 | },
385 | doi: {
386 | name: 'Dogri',
387 | examples: [{
388 | plural: 0,
389 | sample: 1
390 | }, {
391 | plural: 1,
392 | sample: 2
393 | }],
394 | nplurals: 2,
395 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
396 | pluralsFunc: function(n) {
397 | return (n !== 1);
398 | }
399 | },
400 | dz: {
401 | name: 'Dzongkha',
402 | examples: [{
403 | plural: 0,
404 | sample: 1
405 | }],
406 | nplurals: 1,
407 | pluralsText: 'nplurals = 1; plural = 0',
408 | pluralsFunc: function() {
409 | return 0;
410 | }
411 | },
412 | el: {
413 | name: 'Greek',
414 | examples: [{
415 | plural: 0,
416 | sample: 1
417 | }, {
418 | plural: 1,
419 | sample: 2
420 | }],
421 | nplurals: 2,
422 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
423 | pluralsFunc: function(n) {
424 | return (n !== 1);
425 | }
426 | },
427 | en: {
428 | name: 'English',
429 | examples: [{
430 | plural: 0,
431 | sample: 1
432 | }, {
433 | plural: 1,
434 | sample: 2
435 | }],
436 | nplurals: 2,
437 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
438 | pluralsFunc: function(n) {
439 | return (n !== 1);
440 | }
441 | },
442 | eo: {
443 | name: 'Esperanto',
444 | examples: [{
445 | plural: 0,
446 | sample: 1
447 | }, {
448 | plural: 1,
449 | sample: 2
450 | }],
451 | nplurals: 2,
452 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
453 | pluralsFunc: function(n) {
454 | return (n !== 1);
455 | }
456 | },
457 | es: {
458 | name: 'Spanish',
459 | examples: [{
460 | plural: 0,
461 | sample: 1
462 | }, {
463 | plural: 1,
464 | sample: 2
465 | }],
466 | nplurals: 2,
467 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
468 | pluralsFunc: function(n) {
469 | return (n !== 1);
470 | }
471 | },
472 | et: {
473 | name: 'Estonian',
474 | examples: [{
475 | plural: 0,
476 | sample: 1
477 | }, {
478 | plural: 1,
479 | sample: 2
480 | }],
481 | nplurals: 2,
482 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
483 | pluralsFunc: function(n) {
484 | return (n !== 1);
485 | }
486 | },
487 | eu: {
488 | name: 'Basque',
489 | examples: [{
490 | plural: 0,
491 | sample: 1
492 | }, {
493 | plural: 1,
494 | sample: 2
495 | }],
496 | nplurals: 2,
497 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
498 | pluralsFunc: function(n) {
499 | return (n !== 1);
500 | }
501 | },
502 | fa: {
503 | name: 'Persian',
504 | examples: [{
505 | plural: 0,
506 | sample: 1
507 | }],
508 | nplurals: 1,
509 | pluralsText: 'nplurals = 1; plural = 0',
510 | pluralsFunc: function() {
511 | return 0;
512 | }
513 | },
514 | ff: {
515 | name: 'Fulah',
516 | examples: [{
517 | plural: 0,
518 | sample: 1
519 | }, {
520 | plural: 1,
521 | sample: 2
522 | }],
523 | nplurals: 2,
524 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
525 | pluralsFunc: function(n) {
526 | return (n !== 1);
527 | }
528 | },
529 | fi: {
530 | name: 'Finnish',
531 | examples: [{
532 | plural: 0,
533 | sample: 1
534 | }, {
535 | plural: 1,
536 | sample: 2
537 | }],
538 | nplurals: 2,
539 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
540 | pluralsFunc: function(n) {
541 | return (n !== 1);
542 | }
543 | },
544 | fil: {
545 | name: 'Filipino',
546 | examples: [{
547 | plural: 0,
548 | sample: 1
549 | }, {
550 | plural: 1,
551 | sample: 2
552 | }],
553 | nplurals: 2,
554 | pluralsText: 'nplurals = 2; plural = (n > 1)',
555 | pluralsFunc: function(n) {
556 | return (n > 1);
557 | }
558 | },
559 | fo: {
560 | name: 'Faroese',
561 | examples: [{
562 | plural: 0,
563 | sample: 1
564 | }, {
565 | plural: 1,
566 | sample: 2
567 | }],
568 | nplurals: 2,
569 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
570 | pluralsFunc: function(n) {
571 | return (n !== 1);
572 | }
573 | },
574 | fr: {
575 | name: 'French',
576 | examples: [{
577 | plural: 0,
578 | sample: 1
579 | }, {
580 | plural: 1,
581 | sample: 2
582 | }],
583 | nplurals: 2,
584 | pluralsText: 'nplurals = 2; plural = (n > 1)',
585 | pluralsFunc: function(n) {
586 | return (n > 1);
587 | }
588 | },
589 | fur: {
590 | name: 'Friulian',
591 | examples: [{
592 | plural: 0,
593 | sample: 1
594 | }, {
595 | plural: 1,
596 | sample: 2
597 | }],
598 | nplurals: 2,
599 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
600 | pluralsFunc: function(n) {
601 | return (n !== 1);
602 | }
603 | },
604 | fy: {
605 | name: 'Frisian',
606 | examples: [{
607 | plural: 0,
608 | sample: 1
609 | }, {
610 | plural: 1,
611 | sample: 2
612 | }],
613 | nplurals: 2,
614 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
615 | pluralsFunc: function(n) {
616 | return (n !== 1);
617 | }
618 | },
619 | ga: {
620 | name: 'Irish',
621 | examples: [{
622 | plural: 0,
623 | sample: 1
624 | }, {
625 | plural: 1,
626 | sample: 2
627 | }, {
628 | plural: 2,
629 | sample: 3
630 | }, {
631 | plural: 3,
632 | sample: 7
633 | }, {
634 | plural: 4,
635 | sample: 11
636 | }],
637 | nplurals: 5,
638 | pluralsText: 'nplurals = 5; plural = (n === 1 ? 0 : n === 2 ? 1 : n < 7 ? 2 : n < 11 ? 3 : 4)',
639 | pluralsFunc: function(n) {
640 | return (n === 1 ? 0 : n === 2 ? 1 : n < 7 ? 2 : n < 11 ? 3 : 4);
641 | }
642 | },
643 | gd: {
644 | name: 'Scottish Gaelic',
645 | examples: [{
646 | plural: 0,
647 | sample: 1
648 | }, {
649 | plural: 1,
650 | sample: 2
651 | }, {
652 | plural: 2,
653 | sample: 3
654 | }, {
655 | plural: 3,
656 | sample: 20
657 | }],
658 | nplurals: 4,
659 | pluralsText: 'nplurals = 4; plural = ((n === 1 || n === 11) ? 0 : (n === 2 || n === 12) ? 1 : (n > 2 && n < 20) ? 2 : 3)',
660 | pluralsFunc: function(n) {
661 | return ((n === 1 || n === 11) ? 0 : (n === 2 || n === 12) ? 1 : (n > 2 && n < 20) ? 2 : 3);
662 | }
663 | },
664 | gl: {
665 | name: 'Galician',
666 | examples: [{
667 | plural: 0,
668 | sample: 1
669 | }, {
670 | plural: 1,
671 | sample: 2
672 | }],
673 | nplurals: 2,
674 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
675 | pluralsFunc: function(n) {
676 | return (n !== 1);
677 | }
678 | },
679 | gu: {
680 | name: 'Gujarati',
681 | examples: [{
682 | plural: 0,
683 | sample: 1
684 | }, {
685 | plural: 1,
686 | sample: 2
687 | }],
688 | nplurals: 2,
689 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
690 | pluralsFunc: function(n) {
691 | return (n !== 1);
692 | }
693 | },
694 | gun: {
695 | name: 'Gun',
696 | examples: [{
697 | plural: 0,
698 | sample: 1
699 | }, {
700 | plural: 1,
701 | sample: 2
702 | }],
703 | nplurals: 2,
704 | pluralsText: 'nplurals = 2; plural = (n > 1)',
705 | pluralsFunc: function(n) {
706 | return (n > 1);
707 | }
708 | },
709 | ha: {
710 | name: 'Hausa',
711 | examples: [{
712 | plural: 0,
713 | sample: 1
714 | }, {
715 | plural: 1,
716 | sample: 2
717 | }],
718 | nplurals: 2,
719 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
720 | pluralsFunc: function(n) {
721 | return (n !== 1);
722 | }
723 | },
724 | he: {
725 | name: 'Hebrew',
726 | examples: [{
727 | plural: 0,
728 | sample: 1
729 | }, {
730 | plural: 1,
731 | sample: 2
732 | }],
733 | nplurals: 2,
734 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
735 | pluralsFunc: function(n) {
736 | return (n !== 1);
737 | }
738 | },
739 | hi: {
740 | name: 'Hindi',
741 | examples: [{
742 | plural: 0,
743 | sample: 1
744 | }, {
745 | plural: 1,
746 | sample: 2
747 | }],
748 | nplurals: 2,
749 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
750 | pluralsFunc: function(n) {
751 | return (n !== 1);
752 | }
753 | },
754 | hne: {
755 | name: 'Chhattisgarhi',
756 | examples: [{
757 | plural: 0,
758 | sample: 1
759 | }, {
760 | plural: 1,
761 | sample: 2
762 | }],
763 | nplurals: 2,
764 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
765 | pluralsFunc: function(n) {
766 | return (n !== 1);
767 | }
768 | },
769 | hr: {
770 | name: 'Croatian',
771 | examples: [{
772 | plural: 0,
773 | sample: 1
774 | }, {
775 | plural: 1,
776 | sample: 2
777 | }, {
778 | plural: 2,
779 | sample: 5
780 | }],
781 | nplurals: 3,
782 | pluralsText: 'nplurals = 3; plural = (n % 10 === 1 && n % 100 !== 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2)',
783 | pluralsFunc: function(n) {
784 | return (n % 10 === 1 && n % 100 !== 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
785 | }
786 | },
787 | hu: {
788 | name: 'Hungarian',
789 | examples: [{
790 | plural: 0,
791 | sample: 1
792 | }, {
793 | plural: 1,
794 | sample: 2
795 | }],
796 | nplurals: 2,
797 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
798 | pluralsFunc: function(n) {
799 | return (n !== 1);
800 | }
801 | },
802 | hy: {
803 | name: 'Armenian',
804 | examples: [{
805 | plural: 0,
806 | sample: 1
807 | }, {
808 | plural: 1,
809 | sample: 2
810 | }],
811 | nplurals: 2,
812 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
813 | pluralsFunc: function(n) {
814 | return (n !== 1);
815 | }
816 | },
817 | id: {
818 | name: 'Indonesian',
819 | examples: [{
820 | plural: 0,
821 | sample: 1
822 | }],
823 | nplurals: 1,
824 | pluralsText: 'nplurals = 1; plural = 0',
825 | pluralsFunc: function() {
826 | return 0;
827 | }
828 | },
829 | is: {
830 | name: 'Icelandic',
831 | examples: [{
832 | plural: 0,
833 | sample: 1
834 | }, {
835 | plural: 1,
836 | sample: 2
837 | }],
838 | nplurals: 2,
839 | pluralsText: 'nplurals = 2; plural = (n % 10 !== 1 || n % 100 === 11)',
840 | pluralsFunc: function(n) {
841 | return (n % 10 !== 1 || n % 100 === 11);
842 | }
843 | },
844 | it: {
845 | name: 'Italian',
846 | examples: [{
847 | plural: 0,
848 | sample: 1
849 | }, {
850 | plural: 1,
851 | sample: 2
852 | }],
853 | nplurals: 2,
854 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
855 | pluralsFunc: function(n) {
856 | return (n !== 1);
857 | }
858 | },
859 | ja: {
860 | name: 'Japanese',
861 | examples: [{
862 | plural: 0,
863 | sample: 1
864 | }],
865 | nplurals: 1,
866 | pluralsText: 'nplurals = 1; plural = 0',
867 | pluralsFunc: function() {
868 | return 0;
869 | }
870 | },
871 | jbo: {
872 | name: 'Lojban',
873 | examples: [{
874 | plural: 0,
875 | sample: 1
876 | }],
877 | nplurals: 1,
878 | pluralsText: 'nplurals = 1; plural = 0',
879 | pluralsFunc: function() {
880 | return 0;
881 | }
882 | },
883 | jv: {
884 | name: 'Javanese',
885 | examples: [{
886 | plural: 0,
887 | sample: 0
888 | }, {
889 | plural: 1,
890 | sample: 1
891 | }],
892 | nplurals: 2,
893 | pluralsText: 'nplurals = 2; plural = (n !== 0)',
894 | pluralsFunc: function(n) {
895 | return (n !== 0);
896 | }
897 | },
898 | ka: {
899 | name: 'Georgian',
900 | examples: [{
901 | plural: 0,
902 | sample: 1
903 | }],
904 | nplurals: 1,
905 | pluralsText: 'nplurals = 1; plural = 0',
906 | pluralsFunc: function() {
907 | return 0;
908 | }
909 | },
910 | kk: {
911 | name: 'Kazakh',
912 | examples: [{
913 | plural: 0,
914 | sample: 1
915 | }],
916 | nplurals: 1,
917 | pluralsText: 'nplurals = 1; plural = 0',
918 | pluralsFunc: function() {
919 | return 0;
920 | }
921 | },
922 | km: {
923 | name: 'Khmer',
924 | examples: [{
925 | plural: 0,
926 | sample: 1
927 | }],
928 | nplurals: 1,
929 | pluralsText: 'nplurals = 1; plural = 0',
930 | pluralsFunc: function() {
931 | return 0;
932 | }
933 | },
934 | kn: {
935 | name: 'Kannada',
936 | examples: [{
937 | plural: 0,
938 | sample: 1
939 | }, {
940 | plural: 1,
941 | sample: 2
942 | }],
943 | nplurals: 2,
944 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
945 | pluralsFunc: function(n) {
946 | return (n !== 1);
947 | }
948 | },
949 | ko: {
950 | name: 'Korean',
951 | examples: [{
952 | plural: 0,
953 | sample: 1
954 | }],
955 | nplurals: 1,
956 | pluralsText: 'nplurals = 1; plural = 0',
957 | pluralsFunc: function() {
958 | return 0;
959 | }
960 | },
961 | ku: {
962 | name: 'Kurdish',
963 | examples: [{
964 | plural: 0,
965 | sample: 1
966 | }, {
967 | plural: 1,
968 | sample: 2
969 | }],
970 | nplurals: 2,
971 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
972 | pluralsFunc: function(n) {
973 | return (n !== 1);
974 | }
975 | },
976 | kw: {
977 | name: 'Cornish',
978 | examples: [{
979 | plural: 0,
980 | sample: 1
981 | }, {
982 | plural: 1,
983 | sample: 2
984 | }, {
985 | plural: 2,
986 | sample: 3
987 | }, {
988 | plural: 3,
989 | sample: 4
990 | }],
991 | nplurals: 4,
992 | pluralsText: 'nplurals = 4; plural = (n === 1 ? 0 : n === 2 ? 1 : n === 3 ? 2 : 3)',
993 | pluralsFunc: function(n) {
994 | return (n === 1 ? 0 : n === 2 ? 1 : n === 3 ? 2 : 3);
995 | }
996 | },
997 | ky: {
998 | name: 'Kyrgyz',
999 | examples: [{
1000 | plural: 0,
1001 | sample: 1
1002 | }],
1003 | nplurals: 1,
1004 | pluralsText: 'nplurals = 1; plural = 0',
1005 | pluralsFunc: function() {
1006 | return 0;
1007 | }
1008 | },
1009 | lb: {
1010 | name: 'Letzeburgesch',
1011 | examples: [{
1012 | plural: 0,
1013 | sample: 1
1014 | }, {
1015 | plural: 1,
1016 | sample: 2
1017 | }],
1018 | nplurals: 2,
1019 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1020 | pluralsFunc: function(n) {
1021 | return (n !== 1);
1022 | }
1023 | },
1024 | ln: {
1025 | name: 'Lingala',
1026 | examples: [{
1027 | plural: 0,
1028 | sample: 1
1029 | }, {
1030 | plural: 1,
1031 | sample: 2
1032 | }],
1033 | nplurals: 2,
1034 | pluralsText: 'nplurals = 2; plural = (n > 1)',
1035 | pluralsFunc: function(n) {
1036 | return (n > 1);
1037 | }
1038 | },
1039 | lo: {
1040 | name: 'Lao',
1041 | examples: [{
1042 | plural: 0,
1043 | sample: 1
1044 | }],
1045 | nplurals: 1,
1046 | pluralsText: 'nplurals = 1; plural = 0',
1047 | pluralsFunc: function() {
1048 | return 0;
1049 | }
1050 | },
1051 | lt: {
1052 | name: 'Lithuanian',
1053 | examples: [{
1054 | plural: 0,
1055 | sample: 1
1056 | }, {
1057 | plural: 1,
1058 | sample: 2
1059 | }, {
1060 | plural: 2,
1061 | sample: 10
1062 | }],
1063 | nplurals: 3,
1064 | pluralsText: 'nplurals = 3; plural = (n % 10 === 1 && n % 100 !== 11 ? 0 : n % 10 >= 2 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2)',
1065 | pluralsFunc: function(n) {
1066 | return (n % 10 === 1 && n % 100 !== 11 ? 0 : n % 10 >= 2 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
1067 | }
1068 | },
1069 | lv: {
1070 | name: 'Latvian',
1071 | examples: [{
1072 | plural: 2,
1073 | sample: 0
1074 | }, {
1075 | plural: 0,
1076 | sample: 1
1077 | }, {
1078 | plural: 1,
1079 | sample: 2
1080 | }],
1081 | nplurals: 3,
1082 | pluralsText: 'nplurals = 3; plural = (n % 10 === 1 && n % 100 !== 11 ? 0 : n !== 0 ? 1 : 2)',
1083 | pluralsFunc: function(n) {
1084 | return (n % 10 === 1 && n % 100 !== 11 ? 0 : n !== 0 ? 1 : 2);
1085 | }
1086 | },
1087 | mai: {
1088 | name: 'Maithili',
1089 | examples: [{
1090 | plural: 0,
1091 | sample: 1
1092 | }, {
1093 | plural: 1,
1094 | sample: 2
1095 | }],
1096 | nplurals: 2,
1097 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1098 | pluralsFunc: function(n) {
1099 | return (n !== 1);
1100 | }
1101 | },
1102 | mfe: {
1103 | name: 'Mauritian Creole',
1104 | examples: [{
1105 | plural: 0,
1106 | sample: 1
1107 | }, {
1108 | plural: 1,
1109 | sample: 2
1110 | }],
1111 | nplurals: 2,
1112 | pluralsText: 'nplurals = 2; plural = (n > 1)',
1113 | pluralsFunc: function(n) {
1114 | return (n > 1);
1115 | }
1116 | },
1117 | mg: {
1118 | name: 'Malagasy',
1119 | examples: [{
1120 | plural: 0,
1121 | sample: 1
1122 | }, {
1123 | plural: 1,
1124 | sample: 2
1125 | }],
1126 | nplurals: 2,
1127 | pluralsText: 'nplurals = 2; plural = (n > 1)',
1128 | pluralsFunc: function(n) {
1129 | return (n > 1);
1130 | }
1131 | },
1132 | mi: {
1133 | name: 'Maori',
1134 | examples: [{
1135 | plural: 0,
1136 | sample: 1
1137 | }, {
1138 | plural: 1,
1139 | sample: 2
1140 | }],
1141 | nplurals: 2,
1142 | pluralsText: 'nplurals = 2; plural = (n > 1)',
1143 | pluralsFunc: function(n) {
1144 | return (n > 1);
1145 | }
1146 | },
1147 | mk: {
1148 | name: 'Macedonian',
1149 | examples: [{
1150 | plural: 0,
1151 | sample: 1
1152 | }, {
1153 | plural: 1,
1154 | sample: 2
1155 | }],
1156 | nplurals: 2,
1157 | pluralsText: 'nplurals = 2; plural = (n === 1 || n % 10 === 1 ? 0 : 1)',
1158 | pluralsFunc: function(n) {
1159 | return (n === 1 || n % 10 === 1 ? 0 : 1);
1160 | }
1161 | },
1162 | ml: {
1163 | name: 'Malayalam',
1164 | examples: [{
1165 | plural: 0,
1166 | sample: 1
1167 | }, {
1168 | plural: 1,
1169 | sample: 2
1170 | }],
1171 | nplurals: 2,
1172 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1173 | pluralsFunc: function(n) {
1174 | return (n !== 1);
1175 | }
1176 | },
1177 | mn: {
1178 | name: 'Mongolian',
1179 | examples: [{
1180 | plural: 0,
1181 | sample: 1
1182 | }, {
1183 | plural: 1,
1184 | sample: 2
1185 | }],
1186 | nplurals: 2,
1187 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1188 | pluralsFunc: function(n) {
1189 | return (n !== 1);
1190 | }
1191 | },
1192 | mni: {
1193 | name: 'Manipuri',
1194 | examples: [{
1195 | plural: 0,
1196 | sample: 1
1197 | }, {
1198 | plural: 1,
1199 | sample: 2
1200 | }],
1201 | nplurals: 2,
1202 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1203 | pluralsFunc: function(n) {
1204 | return (n !== 1);
1205 | }
1206 | },
1207 | mnk: {
1208 | name: 'Mandinka',
1209 | examples: [{
1210 | plural: 0,
1211 | sample: 0
1212 | }, {
1213 | plural: 1,
1214 | sample: 1
1215 | }, {
1216 | plural: 2,
1217 | sample: 2
1218 | }],
1219 | nplurals: 3,
1220 | pluralsText: 'nplurals = 3; plural = (n === 0 ? 0 : n === 1 ? 1 : 2)',
1221 | pluralsFunc: function(n) {
1222 | return (n === 0 ? 0 : n === 1 ? 1 : 2);
1223 | }
1224 | },
1225 | mr: {
1226 | name: 'Marathi',
1227 | examples: [{
1228 | plural: 0,
1229 | sample: 1
1230 | }, {
1231 | plural: 1,
1232 | sample: 2
1233 | }],
1234 | nplurals: 2,
1235 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1236 | pluralsFunc: function(n) {
1237 | return (n !== 1);
1238 | }
1239 | },
1240 | ms: {
1241 | name: 'Malay',
1242 | examples: [{
1243 | plural: 0,
1244 | sample: 1
1245 | }],
1246 | nplurals: 1,
1247 | pluralsText: 'nplurals = 1; plural = 0',
1248 | pluralsFunc: function() {
1249 | return 0;
1250 | }
1251 | },
1252 | mt: {
1253 | name: 'Maltese',
1254 | examples: [{
1255 | plural: 0,
1256 | sample: 1
1257 | }, {
1258 | plural: 1,
1259 | sample: 2
1260 | }, {
1261 | plural: 2,
1262 | sample: 11
1263 | }, {
1264 | plural: 3,
1265 | sample: 20
1266 | }],
1267 | nplurals: 4,
1268 | pluralsText: 'nplurals = 4; plural = (n === 1 ? 0 : n === 0 || ( n % 100 > 1 && n % 100 < 11) ? 1 : (n % 100 > 10 && n % 100 < 20 ) ? 2 : 3)',
1269 | pluralsFunc: function(n) {
1270 | return (n === 1 ? 0 : n === 0 || (n % 100 > 1 && n % 100 < 11) ? 1 : (n % 100 > 10 && n % 100 < 20) ? 2 : 3);
1271 | }
1272 | },
1273 | my: {
1274 | name: 'Burmese',
1275 | examples: [{
1276 | plural: 0,
1277 | sample: 1
1278 | }],
1279 | nplurals: 1,
1280 | pluralsText: 'nplurals = 1; plural = 0',
1281 | pluralsFunc: function() {
1282 | return 0;
1283 | }
1284 | },
1285 | nah: {
1286 | name: 'Nahuatl',
1287 | examples: [{
1288 | plural: 0,
1289 | sample: 1
1290 | }, {
1291 | plural: 1,
1292 | sample: 2
1293 | }],
1294 | nplurals: 2,
1295 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1296 | pluralsFunc: function(n) {
1297 | return (n !== 1);
1298 | }
1299 | },
1300 | nap: {
1301 | name: 'Neapolitan',
1302 | examples: [{
1303 | plural: 0,
1304 | sample: 1
1305 | }, {
1306 | plural: 1,
1307 | sample: 2
1308 | }],
1309 | nplurals: 2,
1310 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1311 | pluralsFunc: function(n) {
1312 | return (n !== 1);
1313 | }
1314 | },
1315 | nb: {
1316 | name: 'Norwegian Bokmal',
1317 | examples: [{
1318 | plural: 0,
1319 | sample: 1
1320 | }, {
1321 | plural: 1,
1322 | sample: 2
1323 | }],
1324 | nplurals: 2,
1325 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1326 | pluralsFunc: function(n) {
1327 | return (n !== 1);
1328 | }
1329 | },
1330 | ne: {
1331 | name: 'Nepali',
1332 | examples: [{
1333 | plural: 0,
1334 | sample: 1
1335 | }, {
1336 | plural: 1,
1337 | sample: 2
1338 | }],
1339 | nplurals: 2,
1340 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1341 | pluralsFunc: function(n) {
1342 | return (n !== 1);
1343 | }
1344 | },
1345 | nl: {
1346 | name: 'Dutch',
1347 | examples: [{
1348 | plural: 0,
1349 | sample: 1
1350 | }, {
1351 | plural: 1,
1352 | sample: 2
1353 | }],
1354 | nplurals: 2,
1355 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1356 | pluralsFunc: function(n) {
1357 | return (n !== 1);
1358 | }
1359 | },
1360 | nn: {
1361 | name: 'Norwegian Nynorsk',
1362 | examples: [{
1363 | plural: 0,
1364 | sample: 1
1365 | }, {
1366 | plural: 1,
1367 | sample: 2
1368 | }],
1369 | nplurals: 2,
1370 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1371 | pluralsFunc: function(n) {
1372 | return (n !== 1);
1373 | }
1374 | },
1375 | no: {
1376 | name: 'Norwegian',
1377 | examples: [{
1378 | plural: 0,
1379 | sample: 1
1380 | }, {
1381 | plural: 1,
1382 | sample: 2
1383 | }],
1384 | nplurals: 2,
1385 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1386 | pluralsFunc: function(n) {
1387 | return (n !== 1);
1388 | }
1389 | },
1390 | nso: {
1391 | name: 'Northern Sotho',
1392 | examples: [{
1393 | plural: 0,
1394 | sample: 1
1395 | }, {
1396 | plural: 1,
1397 | sample: 2
1398 | }],
1399 | nplurals: 2,
1400 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1401 | pluralsFunc: function(n) {
1402 | return (n !== 1);
1403 | }
1404 | },
1405 | oc: {
1406 | name: 'Occitan',
1407 | examples: [{
1408 | plural: 0,
1409 | sample: 1
1410 | }, {
1411 | plural: 1,
1412 | sample: 2
1413 | }],
1414 | nplurals: 2,
1415 | pluralsText: 'nplurals = 2; plural = (n > 1)',
1416 | pluralsFunc: function(n) {
1417 | return (n > 1);
1418 | }
1419 | },
1420 | or: {
1421 | name: 'Oriya',
1422 | examples: [{
1423 | plural: 0,
1424 | sample: 1
1425 | }, {
1426 | plural: 1,
1427 | sample: 2
1428 | }],
1429 | nplurals: 2,
1430 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1431 | pluralsFunc: function(n) {
1432 | return (n !== 1);
1433 | }
1434 | },
1435 | pa: {
1436 | name: 'Punjabi',
1437 | examples: [{
1438 | plural: 0,
1439 | sample: 1
1440 | }, {
1441 | plural: 1,
1442 | sample: 2
1443 | }],
1444 | nplurals: 2,
1445 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1446 | pluralsFunc: function(n) {
1447 | return (n !== 1);
1448 | }
1449 | },
1450 | pap: {
1451 | name: 'Papiamento',
1452 | examples: [{
1453 | plural: 0,
1454 | sample: 1
1455 | }, {
1456 | plural: 1,
1457 | sample: 2
1458 | }],
1459 | nplurals: 2,
1460 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1461 | pluralsFunc: function(n) {
1462 | return (n !== 1);
1463 | }
1464 | },
1465 | pl: {
1466 | name: 'Polish',
1467 | examples: [{
1468 | plural: 0,
1469 | sample: 1
1470 | }, {
1471 | plural: 1,
1472 | sample: 2
1473 | }, {
1474 | plural: 2,
1475 | sample: 5
1476 | }],
1477 | nplurals: 3,
1478 | pluralsText: 'nplurals = 3; plural = (n === 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2)',
1479 | pluralsFunc: function(n) {
1480 | return (n === 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
1481 | }
1482 | },
1483 | pms: {
1484 | name: 'Piemontese',
1485 | examples: [{
1486 | plural: 0,
1487 | sample: 1
1488 | }, {
1489 | plural: 1,
1490 | sample: 2
1491 | }],
1492 | nplurals: 2,
1493 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1494 | pluralsFunc: function(n) {
1495 | return (n !== 1);
1496 | }
1497 | },
1498 | ps: {
1499 | name: 'Pashto',
1500 | examples: [{
1501 | plural: 0,
1502 | sample: 1
1503 | }, {
1504 | plural: 1,
1505 | sample: 2
1506 | }],
1507 | nplurals: 2,
1508 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1509 | pluralsFunc: function(n) {
1510 | return (n !== 1);
1511 | }
1512 | },
1513 | pt: {
1514 | name: 'Portuguese',
1515 | examples: [{
1516 | plural: 0,
1517 | sample: 1
1518 | }, {
1519 | plural: 1,
1520 | sample: 2
1521 | }],
1522 | nplurals: 2,
1523 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1524 | pluralsFunc: function(n) {
1525 | return (n !== 1);
1526 | }
1527 | },
1528 | rm: {
1529 | name: 'Romansh',
1530 | examples: [{
1531 | plural: 0,
1532 | sample: 1
1533 | }, {
1534 | plural: 1,
1535 | sample: 2
1536 | }],
1537 | nplurals: 2,
1538 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1539 | pluralsFunc: function(n) {
1540 | return (n !== 1);
1541 | }
1542 | },
1543 | ro: {
1544 | name: 'Romanian',
1545 | examples: [{
1546 | plural: 0,
1547 | sample: 1
1548 | }, {
1549 | plural: 1,
1550 | sample: 2
1551 | }, {
1552 | plural: 2,
1553 | sample: 20
1554 | }],
1555 | nplurals: 3,
1556 | pluralsText: 'nplurals = 3; plural = (n === 1 ? 0 : (n === 0 || (n % 100 > 0 && n % 100 < 20)) ? 1 : 2)',
1557 | pluralsFunc: function(n) {
1558 | return (n === 1 ? 0 : (n === 0 || (n % 100 > 0 && n % 100 < 20)) ? 1 : 2);
1559 | }
1560 | },
1561 | ru: {
1562 | name: 'Russian',
1563 | examples: [{
1564 | plural: 0,
1565 | sample: 1
1566 | }, {
1567 | plural: 1,
1568 | sample: 2
1569 | }, {
1570 | plural: 2,
1571 | sample: 5
1572 | }],
1573 | nplurals: 3,
1574 | pluralsText: 'nplurals = 3; plural = (n % 10 === 1 && n % 100 !== 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2)',
1575 | pluralsFunc: function(n) {
1576 | return (n % 10 === 1 && n % 100 !== 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
1577 | }
1578 | },
1579 | rw: {
1580 | name: 'Kinyarwanda',
1581 | examples: [{
1582 | plural: 0,
1583 | sample: 1
1584 | }, {
1585 | plural: 1,
1586 | sample: 2
1587 | }],
1588 | nplurals: 2,
1589 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1590 | pluralsFunc: function(n) {
1591 | return (n !== 1);
1592 | }
1593 | },
1594 | sah: {
1595 | name: 'Yakut',
1596 | examples: [{
1597 | plural: 0,
1598 | sample: 1
1599 | }],
1600 | nplurals: 1,
1601 | pluralsText: 'nplurals = 1; plural = 0',
1602 | pluralsFunc: function() {
1603 | return 0;
1604 | }
1605 | },
1606 | sat: {
1607 | name: 'Santali',
1608 | examples: [{
1609 | plural: 0,
1610 | sample: 1
1611 | }, {
1612 | plural: 1,
1613 | sample: 2
1614 | }],
1615 | nplurals: 2,
1616 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1617 | pluralsFunc: function(n) {
1618 | return (n !== 1);
1619 | }
1620 | },
1621 | sco: {
1622 | name: 'Scots',
1623 | examples: [{
1624 | plural: 0,
1625 | sample: 1
1626 | }, {
1627 | plural: 1,
1628 | sample: 2
1629 | }],
1630 | nplurals: 2,
1631 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1632 | pluralsFunc: function(n) {
1633 | return (n !== 1);
1634 | }
1635 | },
1636 | sd: {
1637 | name: 'Sindhi',
1638 | examples: [{
1639 | plural: 0,
1640 | sample: 1
1641 | }, {
1642 | plural: 1,
1643 | sample: 2
1644 | }],
1645 | nplurals: 2,
1646 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1647 | pluralsFunc: function(n) {
1648 | return (n !== 1);
1649 | }
1650 | },
1651 | se: {
1652 | name: 'Northern Sami',
1653 | examples: [{
1654 | plural: 0,
1655 | sample: 1
1656 | }, {
1657 | plural: 1,
1658 | sample: 2
1659 | }],
1660 | nplurals: 2,
1661 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1662 | pluralsFunc: function(n) {
1663 | return (n !== 1);
1664 | }
1665 | },
1666 | si: {
1667 | name: 'Sinhala',
1668 | examples: [{
1669 | plural: 0,
1670 | sample: 1
1671 | }, {
1672 | plural: 1,
1673 | sample: 2
1674 | }],
1675 | nplurals: 2,
1676 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1677 | pluralsFunc: function(n) {
1678 | return (n !== 1);
1679 | }
1680 | },
1681 | sk: {
1682 | name: 'Slovak',
1683 | examples: [{
1684 | plural: 0,
1685 | sample: 1
1686 | }, {
1687 | plural: 1,
1688 | sample: 2
1689 | }, {
1690 | plural: 2,
1691 | sample: 5
1692 | }],
1693 | nplurals: 3,
1694 | pluralsText: 'nplurals = 3; plural = (n === 1 ? 0 : (n >= 2 && n <= 4) ? 1 : 2)',
1695 | pluralsFunc: function(n) {
1696 | return (n === 1 ? 0 : (n >= 2 && n <= 4) ? 1 : 2);
1697 | }
1698 | },
1699 | sl: {
1700 | name: 'Slovenian',
1701 | examples: [{
1702 | plural: 0,
1703 | sample: 1
1704 | }, {
1705 | plural: 1,
1706 | sample: 2
1707 | }, {
1708 | plural: 2,
1709 | sample: 3
1710 | }, {
1711 | plural: 3,
1712 | sample: 5
1713 | }],
1714 | nplurals: 4,
1715 | pluralsText: 'nplurals = 4; plural = (n % 100 === 1 ? 0 : n % 100 === 2 ? 1 : n % 100 === 3 || n % 100 === 4 ? 2 : 3)',
1716 | pluralsFunc: function(n) {
1717 | return (n % 100 === 1 ? 0 : n % 100 === 2 ? 1 : n % 100 === 3 || n % 100 === 4 ? 2 : 3);
1718 | }
1719 | },
1720 | so: {
1721 | name: 'Somali',
1722 | examples: [{
1723 | plural: 0,
1724 | sample: 1
1725 | }, {
1726 | plural: 1,
1727 | sample: 2
1728 | }],
1729 | nplurals: 2,
1730 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1731 | pluralsFunc: function(n) {
1732 | return (n !== 1);
1733 | }
1734 | },
1735 | son: {
1736 | name: 'Songhay',
1737 | examples: [{
1738 | plural: 0,
1739 | sample: 1
1740 | }, {
1741 | plural: 1,
1742 | sample: 2
1743 | }],
1744 | nplurals: 2,
1745 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1746 | pluralsFunc: function(n) {
1747 | return (n !== 1);
1748 | }
1749 | },
1750 | sq: {
1751 | name: 'Albanian',
1752 | examples: [{
1753 | plural: 0,
1754 | sample: 1
1755 | }, {
1756 | plural: 1,
1757 | sample: 2
1758 | }],
1759 | nplurals: 2,
1760 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1761 | pluralsFunc: function(n) {
1762 | return (n !== 1);
1763 | }
1764 | },
1765 | sr: {
1766 | name: 'Serbian',
1767 | examples: [{
1768 | plural: 0,
1769 | sample: 1
1770 | }, {
1771 | plural: 1,
1772 | sample: 2
1773 | }, {
1774 | plural: 2,
1775 | sample: 5
1776 | }],
1777 | nplurals: 3,
1778 | pluralsText: 'nplurals = 3; plural = (n % 10 === 1 && n % 100 !== 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2)',
1779 | pluralsFunc: function(n) {
1780 | return (n % 10 === 1 && n % 100 !== 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
1781 | }
1782 | },
1783 | su: {
1784 | name: 'Sundanese',
1785 | examples: [{
1786 | plural: 0,
1787 | sample: 1
1788 | }],
1789 | nplurals: 1,
1790 | pluralsText: 'nplurals = 1; plural = 0',
1791 | pluralsFunc: function() {
1792 | return 0;
1793 | }
1794 | },
1795 | sv: {
1796 | name: 'Swedish',
1797 | examples: [{
1798 | plural: 0,
1799 | sample: 1
1800 | }, {
1801 | plural: 1,
1802 | sample: 2
1803 | }],
1804 | nplurals: 2,
1805 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1806 | pluralsFunc: function(n) {
1807 | return (n !== 1);
1808 | }
1809 | },
1810 | sw: {
1811 | name: 'Swahili',
1812 | examples: [{
1813 | plural: 0,
1814 | sample: 1
1815 | }, {
1816 | plural: 1,
1817 | sample: 2
1818 | }],
1819 | nplurals: 2,
1820 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1821 | pluralsFunc: function(n) {
1822 | return (n !== 1);
1823 | }
1824 | },
1825 | ta: {
1826 | name: 'Tamil',
1827 | examples: [{
1828 | plural: 0,
1829 | sample: 1
1830 | }, {
1831 | plural: 1,
1832 | sample: 2
1833 | }],
1834 | nplurals: 2,
1835 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1836 | pluralsFunc: function(n) {
1837 | return (n !== 1);
1838 | }
1839 | },
1840 | te: {
1841 | name: 'Telugu',
1842 | examples: [{
1843 | plural: 0,
1844 | sample: 1
1845 | }, {
1846 | plural: 1,
1847 | sample: 2
1848 | }],
1849 | nplurals: 2,
1850 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1851 | pluralsFunc: function(n) {
1852 | return (n !== 1);
1853 | }
1854 | },
1855 | tg: {
1856 | name: 'Tajik',
1857 | examples: [{
1858 | plural: 0,
1859 | sample: 1
1860 | }, {
1861 | plural: 1,
1862 | sample: 2
1863 | }],
1864 | nplurals: 2,
1865 | pluralsText: 'nplurals = 2; plural = (n > 1)',
1866 | pluralsFunc: function(n) {
1867 | return (n > 1);
1868 | }
1869 | },
1870 | th: {
1871 | name: 'Thai',
1872 | examples: [{
1873 | plural: 0,
1874 | sample: 1
1875 | }],
1876 | nplurals: 1,
1877 | pluralsText: 'nplurals = 1; plural = 0',
1878 | pluralsFunc: function() {
1879 | return 0;
1880 | }
1881 | },
1882 | ti: {
1883 | name: 'Tigrinya',
1884 | examples: [{
1885 | plural: 0,
1886 | sample: 1
1887 | }, {
1888 | plural: 1,
1889 | sample: 2
1890 | }],
1891 | nplurals: 2,
1892 | pluralsText: 'nplurals = 2; plural = (n > 1)',
1893 | pluralsFunc: function(n) {
1894 | return (n > 1);
1895 | }
1896 | },
1897 | tk: {
1898 | name: 'Turkmen',
1899 | examples: [{
1900 | plural: 0,
1901 | sample: 1
1902 | }, {
1903 | plural: 1,
1904 | sample: 2
1905 | }],
1906 | nplurals: 2,
1907 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1908 | pluralsFunc: function(n) {
1909 | return (n !== 1);
1910 | }
1911 | },
1912 | tr: {
1913 | name: 'Turkish',
1914 | examples: [{
1915 | plural: 0,
1916 | sample: 1
1917 | }, {
1918 | plural: 1,
1919 | sample: 2
1920 | }],
1921 | nplurals: 2,
1922 | pluralsText: 'nplurals = 2; plural = (n > 1)',
1923 | pluralsFunc: function(n) {
1924 | return (n > 1);
1925 | }
1926 | },
1927 | tt: {
1928 | name: 'Tatar',
1929 | examples: [{
1930 | plural: 0,
1931 | sample: 1
1932 | }],
1933 | nplurals: 1,
1934 | pluralsText: 'nplurals = 1; plural = 0',
1935 | pluralsFunc: function() {
1936 | return 0;
1937 | }
1938 | },
1939 | ug: {
1940 | name: 'Uyghur',
1941 | examples: [{
1942 | plural: 0,
1943 | sample: 1
1944 | }],
1945 | nplurals: 1,
1946 | pluralsText: 'nplurals = 1; plural = 0',
1947 | pluralsFunc: function() {
1948 | return 0;
1949 | }
1950 | },
1951 | uk: {
1952 | name: 'Ukrainian',
1953 | examples: [{
1954 | plural: 0,
1955 | sample: 1
1956 | }, {
1957 | plural: 1,
1958 | sample: 2
1959 | }, {
1960 | plural: 2,
1961 | sample: 5
1962 | }],
1963 | nplurals: 3,
1964 | pluralsText: 'nplurals = 3; plural = (n % 10 === 1 && n % 100 !== 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2)',
1965 | pluralsFunc: function(n) {
1966 | return (n % 10 === 1 && n % 100 !== 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2);
1967 | }
1968 | },
1969 | ur: {
1970 | name: 'Urdu',
1971 | examples: [{
1972 | plural: 0,
1973 | sample: 1
1974 | }, {
1975 | plural: 1,
1976 | sample: 2
1977 | }],
1978 | nplurals: 2,
1979 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
1980 | pluralsFunc: function(n) {
1981 | return (n !== 1);
1982 | }
1983 | },
1984 | uz: {
1985 | name: 'Uzbek',
1986 | examples: [{
1987 | plural: 0,
1988 | sample: 1
1989 | }, {
1990 | plural: 1,
1991 | sample: 2
1992 | }],
1993 | nplurals: 2,
1994 | pluralsText: 'nplurals = 2; plural = (n > 1)',
1995 | pluralsFunc: function(n) {
1996 | return (n > 1);
1997 | }
1998 | },
1999 | vi: {
2000 | name: 'Vietnamese',
2001 | examples: [{
2002 | plural: 0,
2003 | sample: 1
2004 | }],
2005 | nplurals: 1,
2006 | pluralsText: 'nplurals = 1; plural = 0',
2007 | pluralsFunc: function() {
2008 | return 0;
2009 | }
2010 | },
2011 | wa: {
2012 | name: 'Walloon',
2013 | examples: [{
2014 | plural: 0,
2015 | sample: 1
2016 | }, {
2017 | plural: 1,
2018 | sample: 2
2019 | }],
2020 | nplurals: 2,
2021 | pluralsText: 'nplurals = 2; plural = (n > 1)',
2022 | pluralsFunc: function(n) {
2023 | return (n > 1);
2024 | }
2025 | },
2026 | wo: {
2027 | name: 'Wolof',
2028 | examples: [{
2029 | plural: 0,
2030 | sample: 1
2031 | }],
2032 | nplurals: 1,
2033 | pluralsText: 'nplurals = 1; plural = 0',
2034 | pluralsFunc: function() {
2035 | return 0;
2036 | }
2037 | },
2038 | yo: {
2039 | name: 'Yoruba',
2040 | examples: [{
2041 | plural: 0,
2042 | sample: 1
2043 | }, {
2044 | plural: 1,
2045 | sample: 2
2046 | }],
2047 | nplurals: 2,
2048 | pluralsText: 'nplurals = 2; plural = (n !== 1)',
2049 | pluralsFunc: function(n) {
2050 | return (n !== 1);
2051 | }
2052 | },
2053 | zh: {
2054 | name: 'Chinese',
2055 | examples: [{
2056 | plural: 0,
2057 | sample: 1
2058 | }],
2059 | nplurals: 1,
2060 | pluralsText: 'nplurals = 1; plural = 0',
2061 | pluralsFunc: function() {
2062 | return 0;
2063 | }
2064 | }
2065 | };
2066 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-gettext",
3 | "description": "A JavaScript implementation of gettext, a localization framework",
4 | "version": "3.0.0",
5 | "author": "Andris Reinman",
6 | "maintainers": [
7 | {
8 | "name": "Alexander Wallin",
9 | "email": "office@alexanderwallin.com"
10 | }
11 | ],
12 | "homepage": "http://github.com/alexanderwallin/node-gettext",
13 | "repository": {
14 | "type": "git",
15 | "url": "http://github.com/alexanderwallin/node-gettext.git"
16 | },
17 | "scripts": {
18 | "test": "grunt",
19 | "docs": "jsdoc2md -f lib/gettext.js -t docs/README.template.md --partial docs/templates/*.hbs --param-list-format list > README.md"
20 | },
21 | "main": "./lib/gettext.js",
22 | "files": [
23 | "lib",
24 | "test"
25 | ],
26 | "licenses": [
27 | {
28 | "type": "MIT",
29 | "url": "http://github.com/alexanderwallin/node-gettext/blob/master/LICENSE"
30 | }
31 | ],
32 | "dependencies": {
33 | "lodash.get": "^4.4.2"
34 | },
35 | "devDependencies": {
36 | "chai": "^4.2.0",
37 | "grunt": "^1.0.1",
38 | "grunt-cli": "^1.2.0",
39 | "grunt-contrib-jshint": "^1.0.0",
40 | "grunt-mocha-test": "^0.12.7",
41 | "jsdoc-to-markdown": "^5.0.3",
42 | "mocha": "^7.1.1",
43 | "sinon": "^9.0.1"
44 | },
45 | "engine": {
46 | "node": ">=10"
47 | },
48 | "keywords": [
49 | "i18n",
50 | "l10n",
51 | "internationalization",
52 | "localization",
53 | "translation",
54 | "gettext"
55 | ]
56 | }
57 |
--------------------------------------------------------------------------------
/test/fixtures/latin13.json:
--------------------------------------------------------------------------------
1 | {
2 | "charset": "iso-8859-13",
3 | "headers": {
4 | "project-id-version": "gettext-parser",
5 | "report-msgid-bugs-to": "andris@node.ee",
6 | "pot-creation-date": "2012-05-18 14:28:00+03:00",
7 | "po-revision-date": "2012-05-18 14:44+0300",
8 | "last-translator": "Andris Reinman ",
9 | "language-team": "gettext-parser ",
10 | "mime-version": "1.0",
11 | "content-type": "text/plain; charset=iso-8859-13",
12 | "content-transfer-encoding": "8bit",
13 | "language": "",
14 | "plural-forms": "nplurals=2; plural=(n!=1);",
15 | "x-poedit-language": "Estonian",
16 | "x-poedit-country": "ESTONIA",
17 | "x-poedit-sourcecharset": "iso-8859-13"
18 | },
19 | "translations": {
20 | "": {
21 | "": {
22 | "msgid": "",
23 | "comments": {
24 | "translator": "gettext-parser test file.\nCopyright (C) 2012 Andris Reinman\nThis file is distributed under the same license as the gettext-parser package.\nANDRIS REINMAN , 2012.\n"
25 | },
26 | "msgstr": [
27 | "Project-Id-Version: gettext-parser\nReport-Msgid-Bugs-To: andris@node.ee\nPOT-Creation-Date: 2012-05-18 14:28:00+03:00\nPO-Revision-Date: 2012-05-18 14:44+0300\nLast-Translator: Andris Reinman \nLanguage-Team: gettext-parser \nMIME-Version: 1.0\nContent-Type: text/plain; charset=iso-8859-13\nContent-Transfer-Encoding: 8bit\nLanguage: \nPlural-Forms: nplurals=2; plural=(n!=1);\nX-Poedit-Language: Estonian\nX-Poedit-Country: ESTONIA\nX-Poedit-Sourcecharset: iso-8859-13\n"
28 | ]
29 | },
30 | "o1": {
31 | "msgid": "o1",
32 | "comments": {
33 | "translator": "Normal string"
34 | },
35 | "msgstr": [
36 | "t1"
37 | ]
38 | },
39 | "o2-1": {
40 | "msgid": "o2-1",
41 | "comments": {
42 | "translator": "Plural string"
43 | },
44 | "msgid_plural": "o2-2",
45 | "msgstr": [
46 | "t2-1",
47 | "t2-2"
48 | ]
49 | },
50 | "o3-õäöü": {
51 | "msgid": "o3-õäöü",
52 | "comments": {
53 | "translator": "Normal string with special chars"
54 | },
55 | "msgstr": [
56 | "t3-þð"
57 | ]
58 | },
59 | "test": {
60 | "msgid": "test",
61 | "comments": {
62 | "translator": "Normal comment line 1\nNormal comment line 2",
63 | "extracted": "Editors note line 1\nEditors note line 2",
64 | "reference": "/absolute/path:13\n/absolute/path:14",
65 | "flag": "line 1\nline 2",
66 | "previous": "line 3\nline 4"
67 | },
68 | "msgstr": [
69 | "test"
70 | ]
71 | }
72 | },
73 | "c1": {
74 | "co1": {
75 | "msgid": "co1",
76 | "msgctxt": "c1",
77 | "comments": {
78 | "translator": "Normal string in a context"
79 | },
80 | "msgstr": [
81 | "ct1"
82 | ]
83 | }
84 | },
85 | "c2": {
86 | "co2-1": {
87 | "msgid": "co2-1",
88 | "msgctxt": "c2",
89 | "comments": {
90 | "translator": "Plural string in a context"
91 | },
92 | "msgid_plural": "co2-2",
93 | "msgstr": [
94 | "ct2-1",
95 | "ct2-2"
96 | ]
97 | }
98 | }
99 | }
100 | }
--------------------------------------------------------------------------------
/test/fixtures/latin13.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexanderwallin/node-gettext/65d9670f691c2eeca40dce129c95bcf8b613d344/test/fixtures/latin13.mo
--------------------------------------------------------------------------------
/test/fixtures/latin13.po:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alexanderwallin/node-gettext/65d9670f691c2eeca40dce129c95bcf8b613d344/test/fixtures/latin13.po
--------------------------------------------------------------------------------
/test/gettext-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var chai = require('chai');
4 | var Gettext = require('../lib/gettext');
5 | var fs = require('fs');
6 | var sinon = require('sinon');
7 |
8 | var expect = chai.expect;
9 | chai.config.includeStack = true;
10 |
11 | describe('Gettext', function() {
12 | var gt;
13 | var jsonFile;
14 |
15 | beforeEach(function() {
16 | gt = new Gettext({ debug: false });
17 | jsonFile = JSON.parse(fs.readFileSync(__dirname + '/fixtures/latin13.json'));
18 | });
19 |
20 | describe('#constructor', function() {
21 | var gtc;
22 |
23 | beforeEach(function() {
24 | gtc = null;
25 | });
26 |
27 | describe('#sourceLocale option', function() {
28 | it('should accept any string as a locale', function() {
29 | gtc = new Gettext({ sourceLocale: 'en-US' });
30 | expect(gtc.sourceLocale).to.equal('en-US');
31 | gtc = new Gettext({ sourceLocale: '01234' });
32 | expect(gtc.sourceLocale).to.equal('01234');
33 | });
34 |
35 | it('should default to en empty string', function() {
36 | expect((new Gettext()).sourceLocale).to.equal('');
37 | });
38 |
39 | it('should reject non-string values', function() {
40 | gtc = new Gettext({ sourceLocale: null });
41 | expect(gtc.sourceLocale).to.equal('');
42 | gtc = new Gettext({ sourceLocale: 123 });
43 | expect(gtc.sourceLocale).to.equal('');
44 | gtc = new Gettext({ sourceLocale: false });
45 | expect(gtc.sourceLocale).to.equal('');
46 | gtc = new Gettext({ sourceLocale: {} });
47 | expect(gtc.sourceLocale).to.equal('');
48 | gtc = new Gettext({ sourceLocale: function() {} });
49 | expect(gtc.sourceLocale).to.equal('');
50 | });
51 | });
52 | });
53 |
54 | describe('#getLanguageCode', function() {
55 | it('should normalize locale string', function() {
56 | expect(Gettext.getLanguageCode('ab-cd_ef.utf-8')).to.equal('ab');
57 | expect(Gettext.getLanguageCode('ab-cd_ef')).to.equal('ab');
58 | });
59 | });
60 |
61 | describe('#addTranslations', function() {
62 | it('should store added translations', function() {
63 | gt.addTranslations('et-EE', 'messages', jsonFile);
64 |
65 | expect(gt.catalogs['et-EE']).to.exist;
66 | expect(gt.catalogs['et-EE'].messages).to.exist;
67 | expect(gt.catalogs['et-EE'].messages.charset).to.equal('iso-8859-13');
68 | });
69 |
70 | it('should store added translations on a custom domain', function() {
71 | gt.addTranslations('et-EE', 'mydomain', jsonFile);
72 |
73 | expect(gt.catalogs['et-EE'].mydomain).to.exist;
74 | expect(gt.catalogs['et-EE'].mydomain.charset).to.equal('iso-8859-13');
75 | });
76 | });
77 |
78 | describe('#setLocale', function() {
79 | it('should have the empty string as default locale', function() {
80 | expect(gt.locale).to.equal('');
81 | });
82 |
83 | it('should accept whatever string is passed as locale', function() {
84 | gt.setLocale('de-AT');
85 | expect(gt.locale).to.equal('de-AT');
86 | gt.setLocale('01234');
87 | expect(gt.locale).to.equal('01234');
88 | gt.setLocale('');
89 | expect(gt.locale).to.equal('');
90 | });
91 |
92 | it('should reject non-string locales', function() {
93 | gt.setLocale(null);
94 | expect(gt.locale).to.equal('');
95 | gt.setLocale(123);
96 | expect(gt.locale).to.equal('');
97 | gt.setLocale(false);
98 | expect(gt.locale).to.equal('');
99 | gt.setLocale(function() {});
100 | expect(gt.locale).to.equal('');
101 | gt.setLocale(NaN);
102 | expect(gt.locale).to.equal('');
103 | gt.setLocale();
104 | expect(gt.locale).to.equal('');
105 | });
106 | });
107 |
108 | describe('#setTextDomain', function() {
109 | it('should default to "messages"', function() {
110 | expect(gt.domain).to.equal('messages');
111 | });
112 |
113 | it('should accept and store any string as domain name', function() {
114 | gt.setTextDomain('mydomain');
115 | expect(gt.domain).to.equal('mydomain');
116 | gt.setTextDomain('01234');
117 | expect(gt.domain).to.equal('01234');
118 | gt.setTextDomain('');
119 | expect(gt.domain).to.equal('');
120 | });
121 |
122 | it('should reject non-string domains', function() {
123 | gt.setTextDomain(null);
124 | expect(gt.domain).to.equal('messages');
125 | gt.setTextDomain(123);
126 | expect(gt.domain).to.equal('messages');
127 | gt.setTextDomain(false);
128 | expect(gt.domain).to.equal('messages');
129 | gt.setTextDomain(function() {});
130 | expect(gt.domain).to.equal('messages');
131 | gt.setTextDomain(NaN);
132 | expect(gt.domain).to.equal('messages');
133 | gt.setTextDomain();
134 | expect(gt.domain).to.equal('messages');
135 | });
136 | });
137 |
138 | describe('Resolve translations', function() {
139 | beforeEach(function() {
140 | gt.addTranslations('et-EE', 'messages', jsonFile);
141 | gt.setLocale('et-EE');
142 | });
143 |
144 | describe('#dnpgettext', function() {
145 | it('should return singular match from default context', function() {
146 | expect(gt.dnpgettext('messages', '', 'o2-1', 'o2-2', 1)).to.equal('t2-1');
147 | });
148 |
149 | it('should return plural match from default context', function() {
150 | expect(gt.dnpgettext('messages', '', 'o2-1', 'o2-2', 2)).to.equal('t2-2');
151 | });
152 |
153 | it('should return singular match from selected context', function() {
154 | expect(gt.dnpgettext('messages', 'c2', 'co2-1', 'co2-2', 1)).to.equal('ct2-1');
155 | });
156 |
157 | it('should return plural match from selected context', function() {
158 | expect(gt.dnpgettext('messages', 'c2', 'co2-1', 'co2-2', 2)).to.equal('ct2-2');
159 | });
160 |
161 | it('should return singular match for non existing domain', function() {
162 | expect(gt.dnpgettext('cccc', '', 'o2-1', 'o2-2', 1)).to.equal('o2-1');
163 | });
164 | });
165 |
166 | describe('#gettext', function() {
167 | it('should return singular from default context', function() {
168 | expect(gt.gettext('o2-1')).to.equal('t2-1');
169 | });
170 | });
171 |
172 | describe('#dgettext', function() {
173 | it('should return singular from default context', function() {
174 | expect(gt.dgettext('messages', 'o2-1')).to.equal('t2-1');
175 | });
176 | });
177 |
178 | describe('#ngettext', function() {
179 | it('should return plural from default context', function() {
180 | expect(gt.ngettext('o2-1', 'o2-2', 2)).to.equal('t2-2');
181 | });
182 | });
183 |
184 | describe('#dngettext', function() {
185 | it('should return plural from default context', function() {
186 | expect(gt.dngettext('messages', 'o2-1', 'o2-2', 2)).to.equal('t2-2');
187 | });
188 | });
189 |
190 | describe('#pgettext', function() {
191 | it('should return singular from selected context', function() {
192 | expect(gt.pgettext('c2', 'co2-1')).to.equal('ct2-1');
193 | });
194 | });
195 |
196 | describe('#dpgettext', function() {
197 | it('should return singular from selected context', function() {
198 | expect(gt.dpgettext('messages', 'c2', 'co2-1')).to.equal('ct2-1');
199 | });
200 | });
201 |
202 | describe('#npgettext', function() {
203 | it('should return plural from selected context', function() {
204 | expect(gt.npgettext('c2', 'co2-1', 'co2-2', 2)).to.equal('ct2-2');
205 | });
206 | });
207 |
208 | describe('#getComment', function() {
209 | it('should return comments object', function() {
210 | expect(gt.getComment('messages', '', 'test')).to.deep.equal({
211 | translator: 'Normal comment line 1\nNormal comment line 2',
212 | extracted: 'Editors note line 1\nEditors note line 2',
213 | reference: '/absolute/path:13\n/absolute/path:14',
214 | flag: 'line 1\nline 2',
215 | previous: 'line 3\nline 4'
216 | });
217 | });
218 | });
219 | });
220 |
221 | describe('Unresolvable transaltions', function() {
222 | beforeEach(function() {
223 | gt.addTranslations('et-EE', 'messages', jsonFile);
224 | });
225 |
226 | it('should pass msgid when no translation is found', function() {
227 | expect(gt.gettext('unknown phrase')).to.equal('unknown phrase');
228 | expect(gt.dnpgettext('unknown domain', null, 'hello')).to.equal('hello');
229 | expect(gt.dnpgettext('messages', 'unknown context', 'hello')).to.equal('hello');
230 |
231 | // 'o2-1' is translated, but no locale has been set yet
232 | expect(gt.dnpgettext('messages', '', 'o2-1')).to.equal('o2-1');
233 | });
234 |
235 | it('should pass unresolved singular message when count is 1', function() {
236 | expect(gt.dnpgettext('messages', '', '0 matches', 'multiple matches', 1)).to.equal('0 matches');
237 | });
238 |
239 | it('should pass unresolved plural message when count > 1', function() {
240 | expect(gt.dnpgettext('messages', '', '0 matches', 'multiple matches', 100)).to.equal('multiple matches');
241 | });
242 | });
243 |
244 | describe('Events', function() {
245 | var errorListener;
246 |
247 | beforeEach(function() {
248 | errorListener = sinon.spy();
249 | gt.on('error', errorListener);
250 | });
251 |
252 | it('should notify a registered listener of error events', function() {
253 | gt.emit('error', 'Something went wrong');
254 | expect(errorListener.callCount).to.equal(1);
255 | });
256 |
257 | it('should deregister a previously registered event listener', function() {
258 | gt.off('error', errorListener);
259 | gt.emit('error', 'Something went wrong');
260 | expect(errorListener.callCount).to.equal(0);
261 | });
262 |
263 | it('should emit an error event when a locale that has no translations is set', function() {
264 | gt.setLocale('et-EE');
265 | expect(errorListener.callCount).to.equal(1);
266 | });
267 |
268 | it('should emit an error event when no locale has been set', function() {
269 | gt.addTranslations('et-EE', 'messages', jsonFile);
270 | gt.gettext('o2-1');
271 | expect(errorListener.callCount).to.equal(1);
272 | gt.setLocale('et-EE');
273 | gt.gettext('o2-1');
274 | expect(errorListener.callCount).to.equal(1);
275 | });
276 |
277 | it('should emit an error event when a translation is missing', function() {
278 | gt.addTranslations('et-EE', 'messages', jsonFile);
279 | gt.setLocale('et-EE');
280 | gt.gettext('This message is not translated');
281 | expect(errorListener.callCount).to.equal(1);
282 | });
283 |
284 | it('should not emit any error events when a translation is found', function() {
285 | gt.addTranslations('et-EE', 'messages', jsonFile);
286 | gt.setLocale('et-EE');
287 | gt.gettext('o2-1');
288 | expect(errorListener.callCount).to.equal(0);
289 | });
290 |
291 | it('should not emit any error events when the current locale is the default locale', function() {
292 | var gtd = new Gettext({ sourceLocale: 'en-US' });
293 | var errorListenersourceLocale = sinon.spy();
294 | gtd.on('error', errorListenersourceLocale);
295 | gtd.setLocale('en-US');
296 | gtd.gettext('This message is not translated');
297 | expect(errorListenersourceLocale.callCount).to.equal(0);
298 | });
299 | });
300 |
301 | describe('Aliases', function() {
302 | it('should forward textdomain(domain) to setTextDomain(domain)', function() {
303 | sinon.stub(gt, 'setTextDomain');
304 | gt.textdomain('messages');
305 | expect(gt.setTextDomain.calledWith('messages'));
306 | gt.setTextDomain.restore();
307 | });
308 |
309 | it('should forward setlocale(locale) to setLocale(locale)', function() {
310 | sinon.stub(gt, 'setLocale');
311 | gt.setLocale('et-EE');
312 | expect(gt.setLocale.calledWith('et-EE'));
313 | gt.setLocale.restore();
314 | });
315 | });
316 | });
317 |
--------------------------------------------------------------------------------