Blob Blame History Raw
#!/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" },
    output: { key: "o", args: 1, description: "Output file" },
    from: { key: "f", args: 1, description: "File containing list of input files" },
});

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();

    fs.readFile(filename, { encoding: "utf-8"}, function(err, data) {
        if (err)
            fatal(err.message);

        process_manifest(JSON.parse(data));

        return step();
    });
}

function process_manifest(manifest) {
    if (manifest.menu)
        process_menu(manifest.menu);
    if (manifest.tools)
        process_menu(manifest.tools);
}

function process_menu(menu) {
    for (var m in menu) {
        if (menu[m].label) {
            push({
                msgid: menu[m].label,
                locations: [ filename ]
            });
        }
    }
}

/* 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);
        });
    }
}