'use strict'; var fs = require('fs'); var util = require('util'); var path = require('path'); let shim; class Y18N { constructor(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 = Object.create(null); this.writeQueue = []; } __(...args) { if (typeof arguments[0] !== 'string') { return this._taggedLiteral(arguments[0], ...arguments); } const str = args.shift(); let 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({ directory: this.directory, locale: this.locale, cb }); } else { cb(); } return shim.format.apply(shim.format, [this.cache[this.locale][str] || str].concat(args)); } __n() { const args = Array.prototype.slice.call(arguments); const singular = args.shift(); const plural = args.shift(); const quantity = args.shift(); let cb = function () { }; // start with noop. if (typeof args[args.length - 1] === 'function') cb = args.pop(); if (!this.cache[this.locale]) this._readLocaleFile(); let str = quantity === 1 ? singular : plural; if (this.cache[this.locale][singular]) { const entry = this.cache[this.locale][singular]; str = entry[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({ directory: this.directory, locale: this.locale, cb }); } else { cb(); } // if a %d placeholder is provided, add quantity // to the arguments expanded by util.format. const values = [str]; if (~str.indexOf('%d')) values.push(quantity); return shim.format.apply(shim.format, values.concat(args)); } setLocale(locale) { this.locale = locale; } getLocale() { return this.locale; } updateLocale(obj) { if (!this.cache[this.locale]) this._readLocaleFile(); for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { this.cache[this.locale][key] = obj[key]; } } } _taggedLiteral(parts, ...args) { let str = ''; parts.forEach(function (part, i) { const arg = args[i + 1]; str += part; if (typeof arg !== 'undefined') { str += '%s'; } }); return this.__.apply(this, [str].concat([].slice.call(args, 1))); } _enqueueWrite(work) { this.writeQueue.push(work); if (this.writeQueue.length === 1) this._processWriteQueue(); } _processWriteQueue() { const _this = this; const work = this.writeQueue[0]; // destructure the enqueued work. const directory = work.directory; const locale = work.locale; const cb = work.cb; const languageFile = this._resolveLocaleFile(directory, locale); const serializedLocale = JSON.stringify(this.cache[locale], null, 2); shim.fs.writeFile(languageFile, serializedLocale, 'utf-8', function (err) { _this.writeQueue.shift(); if (_this.writeQueue.length > 0) _this._processWriteQueue(); cb(err); }); } _readLocaleFile() { let localeLookup = {}; const languageFile = this._resolveLocaleFile(this.directory, this.locale); try { // When using a bundler such as webpack, readFileSync may not be defined: if (shim.fs.readFileSync) { localeLookup = JSON.parse(shim.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; } _resolveLocaleFile(directory, locale) { let file = shim.resolve(directory, './', locale + '.json'); if (this.fallbackToLanguage && !this._fileExistsSync(file) && ~locale.lastIndexOf('_')) { // attempt fallback to language only const languageFile = shim.resolve(directory, './', locale.split('_')[0] + '.json'); if (this._fileExistsSync(languageFile)) file = languageFile; } return file; } _fileExistsSync(file) { return shim.exists(file); } } function y18n$1(opts, _shim) { shim = _shim; const y18n = new Y18N(opts); return { __: y18n.__.bind(y18n), __n: y18n.__n.bind(y18n), setLocale: y18n.setLocale.bind(y18n), getLocale: y18n.getLocale.bind(y18n), updateLocale: y18n.updateLocale.bind(y18n), locale: y18n.locale }; } var nodePlatformShim = { fs: { readFileSync: fs.readFileSync, writeFile: fs.writeFile }, format: util.format, resolve: path.resolve, exists: (file) => { try { return fs.statSync(file).isFile(); } catch (err) { return false; } } }; const y18n = (opts) => { return y18n$1(opts, nodePlatformShim); }; module.exports = y18n;