UNPKG

ember-intl

Version:

Ember toolbox for internationalization.

194 lines (152 loc) 5.08 kB
/* jshint node: true */ 'use strict'; /** * Copyright 2015, Yahoo! Inc. * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. */ var CachingWriter = require('broccoli-caching-writer'); var stringify = require('json-stable-stringify'); var walkSync = require('walk-sync'); var mkdirp = require('mkdirp'); var extend = require('extend'); var yaml = require('js-yaml'); var chalk = require('chalk'); var glob = require('glob'); var path = require('path'); var fs = require('fs'); /** * Turns an object into a single dimensional array of strings * * propKeys({ a: true, b: { c: true }}) => ["a", "b.c"] * * NOTE" Period within a key are escaped. * ie, `propKeys({ 'a.a': true, b: { c: true }})` => `["a\.a", "b.c"]` * * @method propKeys * @param {Object} object * @return {Array} Returns array of strings * @private */ function propKeys(object) { var result = []; var escaped; for (var key in object) { escaped = key.replace(/\./g, '\\.'); if (object.hasOwnProperty(key)) { if (typeof object[key] === 'object') { result = result.concat(propKeys(object[key]).map(function (_key) { return escaped + '.' + _key; })); } else { result.push(escaped); } } } return result; } function readAsObject(filepath) { var data = fs.readFileSync(filepath); var ext = path.extname(filepath); switch (ext) { case '.json': return JSON.parse(data); case '.yaml': case '.yml': return yaml.load(data, 'utf8'); } } function TranslationReducer(inputNode, options) { options = options || {}; if (!(this instanceof TranslationReducer)) { return new TranslationReducer(inputNode, options); } if (!Array.isArray(inputNode)) { inputNode = [inputNode]; } CachingWriter.call(this, inputNode, { annotation: 'Translation Reducer' }); if (typeof options.log === 'undefined') { options.log = function() {} } this.options = options; } TranslationReducer.prototype = Object.create(CachingWriter.prototype); TranslationReducer.prototype.constructor = TranslationReducer; TranslationReducer.prototype.findMissingKeys = function(target, defaultTranslationKeys, locale) { var targetProps = propKeys(target); var log = this.options.log; defaultTranslationKeys.forEach(function (property) { if (targetProps.indexOf(property) === -1) { log(property + '\' missing from ' + locale); } }); }; TranslationReducer.prototype.readDirectory = function(inputPath) { var log = this.options.log; // sorted so that any translation path starts with `__addon__` // move to the head of the array. this ensures the application's translations // take presidence over addon translations. var sortedPaths = walkSync(inputPath).sort(function(a, b) { if (a.indexOf('__addon__') === 0) { return -1; } return 1; }); return sortedPaths.reduce(function (translations, file) { var fullPath = inputPath + '/' + file; if (fs.statSync(fullPath).isDirectory()) { return translations; } var translation = readAsObject(inputPath + '/' + file); if (!translation) { log('cannot read path "' + fullPath + '"'); return translations; } var basename = path.basename(file).split('.')[0]; var keyedTranslation = {}; keyedTranslation[basename] = translation; return extend(true, translations, keyedTranslation); }, {}); }; TranslationReducer.prototype.filename = function(key) { if (typeof this.options.filename === 'function') { return this.options.filename(key); } return key + '.json'; } TranslationReducer.prototype.wrapEntry = function(obj) { if (typeof this.options.wrapEntry === 'function') { return this.options.wrapEntry(obj); } return stringify(obj); } TranslationReducer.prototype.build = function() { var log = this.options.log; var inputPath = this.inputPaths[0]; var outputPath = this.outputPath + '/' + this.options.outputPath; var translations = this.readDirectory(inputPath); var defaultTranslationKeys, defaultTranslation, translation; mkdirp.sync(outputPath); if (this.options.baseLocale) { var defaultTranslationPath = glob.sync(inputPath + '/' + this.options.baseLocale + '\.@(json|yaml|yml)', { nosort: true, silent: true })[0]; if (!defaultTranslationPath) { log(this.options.baseLocale + '" default locale missing `translations` folder'); return; } defaultTranslation = translations[this.options.baseLocale]; defaultTranslationKeys = propKeys(defaultTranslation); } for (var key in translations) { if (translations.hasOwnProperty(key)) { translation = translations[key]; if (this.options.baseLocale) { this.findMissingKeys(translation, defaultTranslationKeys, key); } translation = extend(true, {}, defaultTranslation, translation); fs.writeFileSync(outputPath + '/' + this.filename(key), this.wrapEntry(translation), { encoding: 'utf8' }); } } }; module.exports = TranslationReducer;