pax_global_header 0000666 0000000 0000000 00000000064 13167214252 0014515 g ustar 00root root 0000000 0000000 52 comment=45d2568800d6c57be045e76dc4984b2ef3432233
y18n-4.0.0/ 0000775 0000000 0000000 00000000000 13167214252 0012315 5 ustar 00root root 0000000 0000000 y18n-4.0.0/.gitignore 0000664 0000000 0000000 00000000043 13167214252 0014302 0 ustar 00root root 0000000 0000000 .DS_Store
node_modules
.nyc_output
y18n-4.0.0/.travis.yml 0000664 0000000 0000000 00000000142 13167214252 0014423 0 ustar 00root root 0000000 0000000 language: node_js
sudo: false
node_js:
- "4"
- "6"
- "node"
after_success: npm run coverage
y18n-4.0.0/CHANGELOG.md 0000664 0000000 0000000 00000001311 13167214252 0014122 0 ustar 00root root 0000000 0000000 # Change Log
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
# [4.0.0](https://github.com/yargs/y18n/compare/v3.2.1...v4.0.0) (2017-10-10)
### Bug Fixes
* allow support for falsy values like 0 in tagged literal ([#45](https://github.com/yargs/y18n/issues/45)) ([c926123](https://github.com/yargs/y18n/commit/c926123))
### Features
* **__:** added tagged template literal support ([#44](https://github.com/yargs/y18n/issues/44)) ([0598daf](https://github.com/yargs/y18n/commit/0598daf))
### BREAKING CHANGES
* **__:** dropping Node 0.10/Node 0.12 support
y18n-4.0.0/LICENSE 0000664 0000000 0000000 00000001333 13167214252 0013322 0 ustar 00root root 0000000 0000000 Copyright (c) 2015, Contributors
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
y18n-4.0.0/README.md 0000664 0000000 0000000 00000005440 13167214252 0013577 0 ustar 00root root 0000000 0000000 # y18n
[![Build Status][travis-image]][travis-url]
[![Coverage Status][coveralls-image]][coveralls-url]
[![NPM version][npm-image]][npm-url]
[![js-standard-style][standard-image]][standard-url]
[](https://conventionalcommits.org)
The bare-bones internationalization library used by yargs.
Inspired by [i18n](https://www.npmjs.com/package/i18n).
## Examples
_simple string translation:_
```js
var __ = require('y18n').__
console.log(__('my awesome string %s', 'foo'))
```
output:
`my awesome string foo`
_using tagged template literals_
```js
var __ = require('y18n').__
var str = 'foo'
console.log(__`my awesome string ${str}`)
```
output:
`my awesome string foo`
_pluralization support:_
```js
var __n = require('y18n').__n
console.log(__n('one fish %s', '%d fishes %s', 2, 'foo'))
```
output:
`2 fishes foo`
## JSON Language Files
The JSON language files should be stored in a `./locales` folder.
File names correspond to locales, e.g., `en.json`, `pirate.json`.
When strings are observed for the first time they will be
added to the JSON file corresponding to the current locale.
## Methods
### require('y18n')(config)
Create an instance of y18n with the config provided, options include:
* `directory`: the locale directory, default `./locales`.
* `updateFiles`: should newly observed strings be updated in file, default `true`.
* `locale`: what locale should be used.
* `fallbackToLanguage`: should fallback to a language-only file (e.g. `en.json`)
be allowed if a file matching the locale does not exist (e.g. `en_US.json`),
default `true`.
### y18n.\_\_(str, arg, arg, arg)
Print a localized string, `%s` will be replaced with `arg`s.
This function can also be used as a tag for a template literal. You can use it
like this: __`hello ${'world'}`
. This will be equivalent to
`__('hello %s', 'world')`.
### y18n.\_\_n(singularString, pluralString, count, arg, arg, arg)
Print a localized string with appropriate pluralization. If `%d` is provided
in the string, the `count` will replace this placeholder.
### y18n.setLocale(str)
Set the current locale being used.
### y18n.getLocale()
What locale is currently being used?
### y18n.updateLocale(obj)
Update the current locale with the key value pairs in `obj`.
## License
ISC
[travis-url]: https://travis-ci.org/yargs/y18n
[travis-image]: https://img.shields.io/travis/yargs/y18n.svg
[coveralls-url]: https://coveralls.io/github/yargs/y18n
[coveralls-image]: https://img.shields.io/coveralls/yargs/y18n.svg
[npm-url]: https://npmjs.org/package/y18n
[npm-image]: https://img.shields.io/npm/v/y18n.svg
[standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg
[standard-url]: https://github.com/feross/standard
y18n-4.0.0/index.js 0000664 0000000 0000000 00000012210 13167214252 0013756 0 ustar 00root root 0000000 0000000 var fs = require('fs')
var path = require('path')
var util = require('util')
function Y18N (opts) {
// configurable options.
opts = opts || {}
this.directory = opts.directory || './locales'
this.updateFiles = typeof opts.updateFiles === 'boolean' ? opts.updateFiles : true
this.locale = opts.locale || 'en'
this.fallbackToLanguage = typeof opts.fallbackToLanguage === 'boolean' ? opts.fallbackToLanguage : true
// internal stuff.
this.cache = {}
this.writeQueue = []
}
Y18N.prototype.__ = function () {
if (typeof arguments[0] !== 'string') {
return this._taggedLiteral.apply(this, arguments)
}
var args = Array.prototype.slice.call(arguments)
var str = args.shift()
var cb = function () {} // start with noop.
if (typeof args[args.length - 1] === 'function') cb = args.pop()
cb = cb || function () {} // noop.
if (!this.cache[this.locale]) this._readLocaleFile()
// we've observed a new string, update the language file.
if (!this.cache[this.locale][str] && this.updateFiles) {
this.cache[this.locale][str] = str
// include the current directory and locale,
// since these values could change before the
// write is performed.
this._enqueueWrite([this.directory, this.locale, cb])
} else {
cb()
}
return util.format.apply(util, [this.cache[this.locale][str] || str].concat(args))
}
Y18N.prototype._taggedLiteral = function (parts) {
var args = arguments
var str = ''
parts.forEach(function (part, i) {
var arg = args[i + 1]
str += part
if (typeof arg !== 'undefined') {
str += '%s'
}
})
return this.__.apply(null, [str].concat([].slice.call(arguments, 1)))
}
Y18N.prototype._enqueueWrite = function (work) {
this.writeQueue.push(work)
if (this.writeQueue.length === 1) this._processWriteQueue()
}
Y18N.prototype._processWriteQueue = function () {
var _this = this
var work = this.writeQueue[0]
// destructure the enqueued work.
var directory = work[0]
var locale = work[1]
var cb = work[2]
var languageFile = this._resolveLocaleFile(directory, locale)
var serializedLocale = JSON.stringify(this.cache[locale], null, 2)
fs.writeFile(languageFile, serializedLocale, 'utf-8', function (err) {
_this.writeQueue.shift()
if (_this.writeQueue.length > 0) _this._processWriteQueue()
cb(err)
})
}
Y18N.prototype._readLocaleFile = function () {
var localeLookup = {}
var languageFile = this._resolveLocaleFile(this.directory, this.locale)
try {
localeLookup = JSON.parse(fs.readFileSync(languageFile, 'utf-8'))
} catch (err) {
if (err instanceof SyntaxError) {
err.message = 'syntax error in ' + languageFile
}
if (err.code === 'ENOENT') localeLookup = {}
else throw err
}
this.cache[this.locale] = localeLookup
}
Y18N.prototype._resolveLocaleFile = function (directory, locale) {
var file = path.resolve(directory, './', locale + '.json')
if (this.fallbackToLanguage && !this._fileExistsSync(file) && ~locale.lastIndexOf('_')) {
// attempt fallback to language only
var languageFile = path.resolve(directory, './', locale.split('_')[0] + '.json')
if (this._fileExistsSync(languageFile)) file = languageFile
}
return file
}
// this only exists because fs.existsSync() "will be deprecated"
// see https://nodejs.org/api/fs.html#fs_fs_existssync_path
Y18N.prototype._fileExistsSync = function (file) {
try {
return fs.statSync(file).isFile()
} catch (err) {
return false
}
}
Y18N.prototype.__n = function () {
var args = Array.prototype.slice.call(arguments)
var singular = args.shift()
var plural = args.shift()
var quantity = args.shift()
var cb = function () {} // start with noop.
if (typeof args[args.length - 1] === 'function') cb = args.pop()
if (!this.cache[this.locale]) this._readLocaleFile()
var str = quantity === 1 ? singular : plural
if (this.cache[this.locale][singular]) {
str = this.cache[this.locale][singular][quantity === 1 ? 'one' : 'other']
}
// we've observed a new string, update the language file.
if (!this.cache[this.locale][singular] && this.updateFiles) {
this.cache[this.locale][singular] = {
one: singular,
other: plural
}
// include the current directory and locale,
// since these values could change before the
// write is performed.
this._enqueueWrite([this.directory, this.locale, cb])
} else {
cb()
}
// if a %d placeholder is provided, add quantity
// to the arguments expanded by util.format.
var values = [str]
if (~str.indexOf('%d')) values.push(quantity)
return util.format.apply(util, values.concat(args))
}
Y18N.prototype.setLocale = function (locale) {
this.locale = locale
}
Y18N.prototype.getLocale = function () {
return this.locale
}
Y18N.prototype.updateLocale = function (obj) {
if (!this.cache[this.locale]) this._readLocaleFile()
for (var key in obj) {
this.cache[this.locale][key] = obj[key]
}
}
module.exports = function (opts) {
var y18n = new Y18N(opts)
// bind all functions to y18n, so that
// they can be used in isolation.
for (var key in y18n) {
if (typeof y18n[key] === 'function') {
y18n[key] = y18n[key].bind(y18n)
}
}
return y18n
}
y18n-4.0.0/package.json 0000664 0000000 0000000 00000001571 13167214252 0014607 0 ustar 00root root 0000000 0000000 {
"name": "y18n",
"version": "4.0.0",
"description": "the bare-bones internationalization library used by yargs",
"main": "index.js",
"scripts": {
"pretest": "standard",
"test": "nyc mocha",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"release": "standard-version"
},
"repository": {
"type": "git",
"url": "git@github.com:yargs/y18n.git"
},
"files": [
"index.js"
],
"keywords": [
"i18n",
"internationalization",
"yargs"
],
"author": "Ben Coe ",
"license": "ISC",
"bugs": {
"url": "https://github.com/yargs/y18n/issues"
},
"homepage": "https://github.com/yargs/y18n",
"devDependencies": {
"chai": "^4.0.1",
"coveralls": "^3.0.0",
"mocha": "^4.0.1",
"nyc": "^11.0.1",
"rimraf": "^2.5.0",
"standard": "^10.0.0-beta.0",
"standard-version": "^4.2.0"
}
}
y18n-4.0.0/test/ 0000775 0000000 0000000 00000000000 13167214252 0013274 5 ustar 00root root 0000000 0000000 y18n-4.0.0/test/locales/ 0000775 0000000 0000000 00000000000 13167214252 0014716 5 ustar 00root root 0000000 0000000 y18n-4.0.0/test/locales/bad-locale.json 0000664 0000000 0000000 00000000021 13167214252 0017565 0 ustar 00root root 0000000 0000000 {"hello": "worl}
y18n-4.0.0/test/locales/en.json 0000664 0000000 0000000 00000000503 13167214252 0016211 0 ustar 00root root 0000000 0000000 {
"Hello": "Hello!",
"Hello %s %s": "Hello %s %s",
"%d cat": {
"one": "%d cat",
"other": "%d cats"
},
"%d %s cat": {
"one": "%d %s cat",
"other": "%d %s cats"
},
"There is one monkey in the %s": {
"one": "There is one monkey in the %s",
"other": "There are %d monkeys in the %s"
}
}
y18n-4.0.0/test/locales/pirate.json 0000664 0000000 0000000 00000000260 13167214252 0017073 0 ustar 00root root 0000000 0000000 {
"Hello": "Avast ye mateys!",
"Hi, %s %s!": "Yarr! Shiver me timbers, why 'tis %s %s!",
"%d cat": {
"one": "%d land catfish",
"other": "%d land catfishes"
}
}
y18n-4.0.0/test/y18n-test.js 0000664 0000000 0000000 00000024634 13167214252 0015417 0 ustar 00root root 0000000 0000000 /* global describe, it, after, beforeEach */
var expect = require('chai').expect
var fs = require('fs')
var rimraf = require('rimraf')
var y18n = require('../')
var path = require('path')
require('chai').should()
describe('y18n', function () {
describe('configure', function () {
it('allows you to override the default y18n configuration', function () {
var y = y18n({locale: 'fr'})
y.locale.should.equal('fr')
})
})
describe('_readLocaleFile', function () {
it('throws a helpful error if language file has invalid syntax', function () {
expect(function () {
var __ = y18n({
locale: 'bad-locale',
directory: path.join(__dirname, 'locales')
}).__
__('Hello')
}).to.throw(/syntax error/)
})
})
describe('__', function () {
it('can be used as a tag for template literals', function () {
var __ = y18n({
locale: 'pirate',
directory: path.join(__dirname, 'locales')
}).__
__`Hi, ${'Ben'} ${'Coe'}!`.should.equal('Yarr! Shiver me timbers, why \'tis Ben Coe!')
})
it('can be used as a tag for template literals with falsy arguments', function () {
var __ = y18n({
locale: 'pirate',
directory: path.join(__dirname, 'locales')
}).__
__`Hi, ${'Ben'} ${''}!`.should.equal('Yarr! Shiver me timbers, why \'tis Ben !')
})
it('uses replacements from the default locale if none is configured', function () {
var __ = y18n({
directory: path.join(__dirname, 'locales')
}).__
__('Hello').should.equal('Hello!')
})
it('uses replacements from the configured locale', function () {
var __ = y18n({
locale: 'pirate',
directory: path.join(__dirname, 'locales')
}).__
__('Hello').should.equal('Avast ye mateys!')
})
it('uses language file if language_territory file does not exist', function () {
var __ = y18n({
locale: 'pirate_JM',
directory: path.join(__dirname, 'locales')
}).__
__('Hello').should.equal('Avast ye mateys!')
})
it('does not fallback to language file if fallbackToLanguage is false', function () {
var __ = y18n({
locale: 'pirate_JM',
fallbackToLanguage: false,
updateFiles: false,
directory: path.join(__dirname, 'locales')
}).__
__('Hello').should.equal('Hello')
})
it('uses strings as given if no matching locale files found', function () {
var __ = y18n({
locale: 'zz_ZZ',
updateFiles: false,
directory: path.join(__dirname, 'locales')
}).__
__('Hello').should.equal('Hello')
})
it('expands arguments into %s placeholders', function () {
var __ = y18n({
directory: path.join(__dirname, 'locales')
}).__
__('Hello %s %s', 'Ben', 'Coe').should.equal('Hello Ben Coe')
})
describe('the first time observing a word', function () {
beforeEach(function (done) {
rimraf('./test/locales/fr*.json', function () {
return done()
})
})
it('returns the word immediately', function () {
var __ = y18n({
locale: 'fr',
directory: path.join(__dirname, 'locales')
}).__
__('banana').should.equal('banana')
})
it('writes new word to locale file if updateFiles is true', function (done) {
var __ = y18n({
locale: 'fr_FR',
directory: path.join(__dirname, 'locales')
}).__
__('banana', function (err) {
var locale = JSON.parse(fs.readFileSync('./test/locales/fr_FR.json', 'utf-8'))
locale.banana.should.equal('banana')
return done(err)
})
})
it('writes new word to language file if language_territory file does not exist', function (done) {
fs.writeFileSync('./test/locales/fr.json', '{"meow": "le meow"}', 'utf-8')
var __ = y18n({
locale: 'fr_FR',
directory: path.join(__dirname, 'locales')
}).__
__('meow').should.equal('le meow')
__('banana', function (err) {
var locale = JSON.parse(fs.readFileSync('./test/locales/fr.json', 'utf-8'))
locale.banana.should.equal('banana')
return done(err)
})
})
it('writes word to missing locale file, if no fallback takes place', function (done) {
fs.writeFileSync('./test/locales/fr.json', '{"meow": "le meow"}', 'utf-8')
var __ = y18n({
locale: 'fr_FR',
fallbackToLanguage: false,
directory: path.join(__dirname, 'locales')
}).__
__('banana', function (err) {
// 'banana' should be written to fr_FR.json
var locale = JSON.parse(fs.readFileSync('./test/locales/fr_FR.json', 'utf-8'))
locale.should.deep.equal({
banana: 'banana'
})
// fr.json should remain untouched
var frJson = JSON.parse(fs.readFileSync('./test/locales/fr.json', 'utf-8'))
frJson.should.deep.equal({
meow: 'le meow'
})
return done(err)
})
})
it('handles enqueuing multiple writes at the same time', function (done) {
var __ = y18n({
locale: 'fr',
directory: path.join(__dirname, 'locales')
}).__
__('apple')
__('banana', function () {
__('foo')
__('bar', function (err) {
var locale = JSON.parse(fs.readFileSync('./test/locales/fr.json', 'utf-8'))
locale.apple.should.equal('apple')
locale.banana.should.equal('banana')
locale.foo.should.equal('foo')
locale.bar.should.equal('bar')
return done(err)
})
})
})
it('does not write the locale file if updateFiles is false', function (done) {
var __ = y18n({
locale: 'fr',
updateFiles: false,
directory: path.join(__dirname, 'locales')
}).__
__('banana', function (err) {
fs.existsSync('./test/locales/fr.json').should.equal(false)
return done(err)
})
})
})
})
describe('__n', function () {
it('uses the singular form if quantity is 1', function () {
var __n = y18n({
directory: path.join(__dirname, 'locales')
}).__n
__n('%d cat', '%d cats', 1).should.equal('1 cat')
})
it('uses the plural form if quantity is greater than 1', function () {
var __n = y18n({
directory: path.join(__dirname, 'locales')
}).__n
__n('%d cat', '%d cats', 2).should.equal('2 cats')
})
it('allows additional arguments to be printed', function () {
var __n = y18n({
directory: path.join(__dirname, 'locales')
}).__n
__n('%d %s cat', '%d %s cats', 2, 'black').should.equal('2 black cats')
})
it('allows an alternative locale to be set', function () {
var __n = y18n({
locale: 'pirate',
directory: path.join(__dirname, 'locales')
}).__n
__n('%d cat', '%d cats', 1).should.equal('1 land catfish')
__n('%d cat', '%d cats', 3).should.equal('3 land catfishes')
})
// See: https://github.com/bcoe/yargs/pull/210
it('allows a quantity placeholder to be provided in the plural but not singular form', function () {
var __n = y18n({
directory: path.join(__dirname, 'locales')
}).__n
var singular = __n('There is one monkey in the %s', 'There are %d monkeys in the %s', 1, 'tree')
var plural = __n('There is one monkey in the %s', 'There are %d monkeys in the %s', 3, 'tree')
singular.should.equal('There is one monkey in the tree')
plural.should.equal('There are 3 monkeys in the tree')
})
describe('the first time observing a pluralization', function () {
beforeEach(function (done) {
rimraf('./test/locales/fr.json', function () {
return done()
})
})
it('returns the pluralization immediately', function () {
var __n = y18n({
locale: 'fr',
directory: path.join(__dirname, 'locales')
}).__n
__n('%d le cat', '%d le cats', 1).should.equal('1 le cat')
})
it('writes to the locale file if updateFiles is true', function (done) {
var __n = y18n({
locale: 'fr',
directory: path.join(__dirname, 'locales')
}).__n
__n('%d apple %s', '%d apples %s', 2, 'dude', function (err) {
var locale = JSON.parse(fs.readFileSync('./test/locales/fr.json', 'utf-8'))
locale['%d apple %s'].one.should.equal('%d apple %s')
locale['%d apple %s'].other.should.equal('%d apples %s')
return done(err)
})
})
it('does not write the locale file if updateFiles is false', function (done) {
var __n = y18n({
locale: 'fr',
updateFiles: false,
directory: path.join(__dirname, 'locales')
}).__n
__n('%d apple %s', '%d apples %s', 2, 'dude', function (err) {
fs.existsSync('./test/locales/fr.json').should.equal(false)
return done(err)
})
})
})
})
describe('setLocale', function () {
it('switches the locale', function () {
var i18n = y18n({
directory: path.join(__dirname, 'locales')
})
i18n.__('Hello').should.equal('Hello!')
i18n.setLocale('pirate')
i18n.__('Hello').should.equal('Avast ye mateys!')
})
})
describe('updateLocale', function () {
beforeEach(function (done) {
rimraf('./test/locales/fr.json', function () {
return done()
})
})
it('updates the locale with the new lookups provided', function () {
var i18n = y18n({
locale: 'fr',
directory: path.join(__dirname, 'locales')
})
i18n.updateLocale({
foo: 'le bar'
})
i18n.__('foo').should.equal('le bar')
})
it('loads the locale from disk prior to updating the map', function () {
fs.writeFileSync('./test/locales/fr.json', '{"meow": "le meow"}', 'utf-8')
var i18n = y18n({
locale: 'fr',
directory: path.join(__dirname, 'locales')
})
i18n.updateLocale({
foo: 'le bar'
})
i18n.__('meow').should.equal('le meow')
})
})
describe('getLocale', function () {
it('returns the configured locale', function () {
y18n().getLocale().should.equal('en')
})
})
after(function () {
rimraf.sync('./test/locales/fr.json')
})
})