const Plugin = require("broccoli-plugin"); const Yaml = require("js-yaml"); const fs = require("fs"); const concat = require("broccoli-concat"); const mergeTrees = require("broccoli-merge-trees"); const MessageFormat = require("messageformat"); const deepmerge = require("deepmerge"); const glob = require("glob"); const { shouldLoadPluginTestJs } = require("discourse-plugins"); let built = false; class TranslationPlugin extends Plugin { constructor(inputNodes, inputFile, options) { super(inputNodes, { ...options, persistentOutput: true, }); this.inputFile = inputFile; } replaceMF(formats, input, path = []) { if (!input) { return; } Object.keys(input).forEach((key) => { let value = input[key]; let subpath = path.concat(key); if (typeof value === "object") { this.replaceMF(formats, value, subpath); } else if (key.endsWith("_MF")) { // omit locale.js let mfPath = subpath.slice(2).join("."); formats[mfPath] = this.mf.precompile(this.mf.parse(value)); } }); } build() { // We could get fancy eventually and do this based on whether the yaml // or vendor files change but in practice we shouldn't need exact up to date // translations in admin. if (built) { return; } let parsed = {}; this.inputPaths.forEach((path) => { let file = path + "/" + this.inputFile; let yaml = fs.readFileSync(file, { encoding: "UTF-8" }); let loaded = Yaml.load(yaml, { json: true }); parsed = deepmerge(parsed, loaded); }); let extras = { en: { admin: parsed.en.admin_js.admin, wizard: parsed.en.wizard_js.wizard, }, }; delete parsed.en.admin_js; delete parsed.en.wizard_js; let formats = {}; this.mf = new MessageFormat("en"); this.replaceMF(formats, parsed); this.replaceMF(formats, extras); formats = Object.entries(formats).map(([k, v]) => `"${k}": ${v}`); let contents = ` I18n.locale = 'en'; I18n.translations = ${JSON.stringify(parsed)}; I18n.extras = ${JSON.stringify(extras)}; MessageFormat = { locale: {} }; I18n._compiledMFs = { ${formats.join(",\n")} }; `; fs.writeFileSync( `${this.outputPath}/` + this.inputFile.replace(".yml", ".js"), contents ); built = true; } } module.exports = function translatePlugin(...params) { return new TranslationPlugin(...params); }; module.exports.createI18nTree = function (discourseRoot, vendorJs) { let translations = [discourseRoot + "/config/locales"]; if (shouldLoadPluginTestJs()) { translations = translations.concat( glob .sync(discourseRoot + "/plugins/*/config/locales/client.en.yml") .map((f) => f.replace(/\/client\.en\.yml$/, "")) ); } let en = new TranslationPlugin(translations, "client.en.yml"); return concat( mergeTrees([ vendorJs, discourseRoot + "/app/assets/javascripts/locales", discourseRoot + "/lib/javascripts", en, ]), { inputFiles: [ "i18n.js", "moment.js", "moment-timezone-with-data.js", "messageformat-lookup.js", "locale/en.js", "client.en.js", ], headerFiles: [ "i18n.js", "moment.js", "moment-timezone-with-data.js", "messageformat-lookup.js", ], footerFiles: ["client.en.js", "locale/en.js"], outputFile: `assets/test-i18n.js`, } ); }; module.exports.TranslationPlugin = TranslationPlugin;