#!/usr/bin/env node /* * Extracts translatable strings from manifest.json files. * */ function fatal(message, code) { console.log((filename || "manifest2po") + ": " + message); process.exit(code || 1); } function usage() { console.log("usage: manifest2po [-o output] input..."); process.exit(2); } var fs, path, stdio; try { fs = require('fs'); path = require('path'); stdio = require('stdio'); } catch (ex) { fatal(ex.message, 127); /* missing looks for this */ } var opts = stdio.getopt({ directory: { key: "d", args: 1, description: "Base directory for input files", default: "." }, output: { key: "o", args: 1, description: "Output file" }, from: { key: "f", args: 1, description: "File containing list of input files", default: "" }, }); if (!opts.from && opts.args.length < 1) { usage(); } var input = opts.args; var entries = { }; /* Filename being parsed */ var filename = null; prepare(); /* Decide what input files to process */ function prepare() { if (opts.from) { fs.readFile(opts.from, { encoding: "utf-8"}, function(err, data) { if (err) fatal(err.message); input = data.split("\n").filter(function(value) { return !!value; }).concat(input); step(); }); } else { step(); } } /* Now process each file in turn */ function step() { filename = input.shift(); if (filename === undefined) { finish(); return; } if (path.basename(filename) != "manifest.json") return step(); /* Qualify the filename if necessary */ var full = filename; if (opts.directory) full = path.join(opts.directory, filename); fs.readFile(full, { encoding: "utf-8"}, function(err, data) { if (err) fatal(err.message); // There are variables which when not substituted can cause JSON.parse to fail // Dummy replace them. None variable is going to be translated anyway safe_data = data.replace(/\@.+?\@/gi, 1); process_manifest(JSON.parse(safe_data)); return step(); }); } function process_manifest(manifest) { if (manifest.menu) process_menu(manifest.menu); if (manifest.tools) process_menu(manifest.tools); } function process_keywords(keywords) { keywords.forEach(v => { v.matches.forEach(keyword => push({ msgid: keyword, locations: [ filename + ":0" ] }) ); }); } function process_docs(docs) { docs.forEach(doc => { push({ msgid: doc.label, locations: [ filename + ":0" ] }) }); } function process_menu(menu) { for (var m in menu) { if (menu[m].label) { push({ msgid: menu[m].label, locations: [ filename + ":0" ] }); } if (menu[m].keywords) process_keywords(menu[m].keywords); if (menu[m].docs) process_docs(menu[m].docs); } } /* Push an entry onto the list */ function push(entry) { var key = entry.msgid + "\0" + entry.msgid_plural + "\0" + entry.msgctxt; var prev = entries[key]; if (prev) { prev.locations = prev.locations.concat(entry.locations); } else { entries[key] = entry; } } /* Escape a string for inclusion in po file */ function escape(string) { var bs = string.split('\\').join('\\\\').split('"').join('\\"'); return bs.split("\n").map(function(line) { return '"' + line + '"'; }).join("\n"); } /* Finish by writing out the strings */ function finish() { var result = [ 'msgid ""', 'msgstr ""', '"Project-Id-Version: PACKAGE_VERSION\\n"', '"MIME-Version: 1.0\\n"', '"Content-Type: text/plain; charset=UTF-8\\n"', '"Content-Transfer-Encoding: 8bit\\n"', '"X-Generator: Cockpit manifest2po\\n"', '', ]; var msgid, entry; for (msgid in entries) { entry = entries[msgid]; result.push('#: ' + entry.locations.join(" ")); if (entry.msgctxt) result.push('msgctxt ' + escape(entry.msgctxt)); result.push('msgid ' + escape(entry.msgid)); if (entry.msgid_plural) { result.push('msgid_plural ' + escape(entry.msgid_plural)); result.push('msgstr[0] ""'); result.push('msgstr[1] ""'); } else { result.push('msgstr ""'); } result.push(''); } var data = result.join('\n'); if (!opts.output) { process.stdout.write(data); process.exit(0); } else { fs.writeFile(opts.output, data, function(err) { if (err) fatal(err.message); process.exit(0); }); } }