'use strict'; var winston = require('winston'); var path = require('path'); var async = require('async'); var fs = require('fs'); var mkdirp = require('mkdirp'); var rimraf = require('rimraf'); var file = require('../file'); var utils = require('../../public/src/utils'); var Plugins = require('../plugins'); var db = require('../database'); var buildLanguagesPath = path.join(__dirname, '../../build/public/language'); var coreLanguagesPath = path.join(__dirname, '../../public/language'); function getTranslationTree(callback) { async.waterfall([ // get plugin data function (next) { db.getSortedSetRange('plugins:active', 0, -1, next); }, function (plugins, next) { var pluginBasePath = path.join(__dirname, '../../node_modules'); var paths = plugins.map(function (plugin) { return path.join(pluginBasePath, plugin); }); // Filter out plugins with invalid paths async.filter(paths, file.exists, function (err, paths) { next(null, paths); }); }, function (paths, next) { async.map(paths, Plugins.loadPluginInfo, next); }, // generate list of languages and namespaces function (plugins, next) { var languages = [], namespaces = []; // pull languages and namespaces from paths function extrude(languageDir, paths) { paths.forEach(function (p) { var rel = p.split(languageDir)[1].split(/[\/\\]/).slice(1); var language = rel.shift().replace('_', '-').replace('@', '-x-'); var namespace = rel.join('/').replace(/\.json$/, ''); if (!language || !namespace) { return; } if (languages.indexOf(language) === -1) { languages.push(language); } if (namespaces.indexOf(namespace) === -1) { namespaces.push(namespace); } }); } plugins = plugins.filter(function (pluginData) { return (typeof pluginData.languages === 'string'); }); async.parallel([ // get core languages and namespaces function (nxt) { utils.walk(coreLanguagesPath, function (err, paths) { if (err) { return nxt(err); } extrude(coreLanguagesPath, paths); nxt(); }); }, // get plugin languages and namespaces function (nxt) { async.each(plugins, function (pluginData, cb) { var pathToFolder = path.join(__dirname, '../../node_modules/', pluginData.id, pluginData.languages); utils.walk(pathToFolder, function (err, paths) { if (err) { return cb(err); } extrude(pathToFolder, paths); cb(); }); }, nxt); }, ], function (err) { if (err) { return next(err); } next(null, { languages: languages, namespaces: namespaces, plugins: plugins, }); }); }, // for each language and namespace combination, // run through core and all plugins to generate // a full translation hash function (ref, next) { var languages = ref.languages; var namespaces = ref.namespaces; var plugins = ref.plugins; var tree = {}; async.eachLimit(languages, 10, function (lang, nxt) { async.eachLimit(namespaces, 10, function (ns, cb) { var translations = {}; async.series([ // core first function (n) { fs.readFile(path.join(coreLanguagesPath, lang, ns + '.json'), function (err, buffer) { if (err) { if (err.code === 'ENOENT') { return n(); } return n(err); } try { Object.assign(translations, JSON.parse(buffer.toString())); n(); } catch (err) { n(err); } }); }, function (n) { // for each plugin, fallback in this order: // 1. correct language string (en-GB) // 2. old language string (en_GB) // 3. corrected plugin defaultLang (en-US) // 4. old plugin defaultLang (en_US) async.eachLimit(plugins, 10, function (pluginData, call) { var pluginLanguages = path.join(__dirname, '../../node_modules/', pluginData.id, pluginData.languages); var defaultLang = pluginData.defaultLang || 'en-GB'; async.some([ lang, lang.replace('-', '_').replace('-x-', '@'), defaultLang.replace('_', '-').replace('@', '-x-'), defaultLang.replace('-', '_').replace('-x-', '@'), ], function (language, next) { fs.readFile(path.join(pluginLanguages, language, ns + '.json'), function (err, buffer) { if (err) { if (err.code === 'ENOENT') { return next(null, false); } return next(err); } try { Object.assign(translations, JSON.parse(buffer.toString())); next(null, true); } catch (err) { next(err); } }); }, call); }, function (err) { if (err) { return n(err); } if (Object.keys(translations).length) { tree[lang] = tree[lang] || {}; tree[lang][ns] = translations; } n(); }); }, ], cb); }, nxt); }, function (err) { next(err, tree); }); }, ], callback); } // write translation hashes from the generated tree to language files function writeLanguageFiles(tree, callback) { // iterate over languages and namespaces async.eachLimit(Object.keys(tree), 10, function (language, cb) { var namespaces = tree[language]; async.eachLimit(Object.keys(namespaces), 100, function (namespace, next) { var translations = namespaces[namespace]; var filePath = path.join(buildLanguagesPath, language, namespace + '.json'); mkdirp(path.dirname(filePath), function (err) { if (err) { return next(err); } fs.writeFile(filePath, JSON.stringify(translations), next); }); }, cb); }, callback); } exports.build = function buildLanguages(callback) { async.waterfall([ function (next) { rimraf(buildLanguagesPath, next); }, getTranslationTree, writeLanguageFiles, ], function (err) { if (err) { winston.error('[build] Language build failed: ' + err.message); throw err; } callback(); }); };