pax_global_header00006660000000000000000000000064131672142520014515gustar00rootroot0000000000000052 comment=45d2568800d6c57be045e76dc4984b2ef3432233 y18n-4.0.0/000077500000000000000000000000001316721425200123155ustar00rootroot00000000000000y18n-4.0.0/.gitignore000066400000000000000000000000431316721425200143020ustar00rootroot00000000000000.DS_Store node_modules .nyc_output y18n-4.0.0/.travis.yml000066400000000000000000000001421316721425200144230ustar00rootroot00000000000000language: node_js sudo: false node_js: - "4" - "6" - "node" after_success: npm run coverage y18n-4.0.0/CHANGELOG.md000066400000000000000000000013111316721425200141220ustar00rootroot00000000000000# 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/LICENSE000066400000000000000000000013331316721425200133220ustar00rootroot00000000000000Copyright (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.md000066400000000000000000000054401316721425200135770ustar00rootroot00000000000000# 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] [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](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.js000066400000000000000000000122101316721425200137560ustar00rootroot00000000000000var 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.json000066400000000000000000000015711316721425200146070ustar00rootroot00000000000000{ "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/000077500000000000000000000000001316721425200132745ustar00rootroot00000000000000y18n-4.0.0/test/locales/000077500000000000000000000000001316721425200147165ustar00rootroot00000000000000y18n-4.0.0/test/locales/bad-locale.json000066400000000000000000000000211316721425200175650ustar00rootroot00000000000000{"hello": "worl} y18n-4.0.0/test/locales/en.json000066400000000000000000000005031316721425200162110ustar00rootroot00000000000000{ "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.json000066400000000000000000000002601316721425200170730ustar00rootroot00000000000000{ "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.js000066400000000000000000000246341316721425200154170ustar00rootroot00000000000000/* 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') }) })