├── .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 | [![Build Status](https://travis-ci.org/alexanderwallin/node-gettext.svg?branch=master)](http://travis-ci.org/alexanderwallin/node-gettext) 11 | [![npm version](https://badge.fury.io/js/node-gettext.svg)](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 | [![Build Status](https://travis-ci.org/alexanderwallin/node-gettext.svg?branch=master)](http://travis-ci.org/alexanderwallin/node-gettext) 11 | [![npm version](https://badge.fury.io/js/node-gettext.svg)](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 | [![Build Status](https://secure.travis-ci.org/alexanderwallin/node-gettext.png)](http://travis-ci.org/alexanderwallin/node-gettext) 4 | [![npm version](https://badge.fury.io/js/node-gettext.svg)](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 | --------------------------------------------------------------------------------