| 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); |
| } |
| } |
| export function y18n(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 |
| }; |
| } |