build: use translation tools from pkg/lib

Use the html2po and manifest2po tools that we already check out from
cockpit repo pkg/lib.

These tools switched to a new argument parser library (`argparse`) in
cockpit-project/cockpit#16271, so add it to our package.json and drop
`stdio`.

Drop our old copies.

I checked the result of building `po/starter-kit.pot` before and after
the change, and aside from the timestamp, it's identical.

Cherry-picked from cockpit-podman commit bf53f801b17ba6.
This commit is contained in:
Martin Pitt 2022-06-30 07:27:35 +02:00 committed by Martin Pitt
parent e38a1c3906
commit ebe52bcdb7
4 changed files with 6 additions and 463 deletions

View file

@ -1,264 +0,0 @@
#!/usr/bin/env node
/*
* Extracts translatable strings from HTML files in the following forms:
*
* <tag translate>String</tag>
* <tag translate context="value">String</tag>
* <tag translate="...">String</tag>
* <tag translate-attr attr="String"></tag>
*
* Supports the following Glade compatible forms:
*
* <tag translatable="yes">String</tag>
* <tag translatable="yes" context="value">String</tag>
*
* Supports the following angular-gettext compatible forms:
*
* <translate>String</translate>
* <tag translate-plural="Plural">Singular</tag>
*
* Note that some of the use of the translated may not support all the strings
* depending on the code actually using these strings to translate the HTML.
*/
function fatal(message, code) {
console.log((filename || "html2po") + ": " + message);
process.exit(code || 1);
}
function usage() {
console.log("usage: html2po input output");
process.exit(2);
}
var fs, htmlparser, path, stdio;
try {
fs = require('fs');
path = require('path');
htmlparser = require('htmlparser');
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 and offset of line number */
var filename = null;
var offsets = 0;
/* The HTML parser we're using */
var handler = new htmlparser.DefaultHandler(function(error, dom) {
if (error)
fatal(error);
else
walk(dom);
});
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;
}
/* 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);
var parser = new htmlparser.Parser(handler, { includeLocation: true });
parser.parseComplete(data);
step();
});
}
/* Process an array of nodes */
function walk(children) {
if (!children)
return;
children.forEach(function(child) {
var line = (child.location || { }).line || 0;
var offset = line - 1;
/* Scripts get their text processed as HTML */
if (child.type == 'script' && child.children) {
var parser = new htmlparser.Parser(handler, { includeLocation: true });
/* Make note of how far into the outer HTML file we are */
offsets += offset;
child.children.forEach(function(node) {
parser.parseChunk(node.raw);
});
parser.done();
offsets -= offset;
/* Tags get extracted as usual */
} else if (child.type == 'tag') {
tag(child);
}
});
}
/* Process a single loaded tag */
function tag(node) {
var tasks, line, entry;
var attrs = node.attribs || { };
var nest = true;
/* Extract translate strings */
if ("translate" in attrs || "translatable" in attrs) {
tasks = (attrs["translate"] || attrs["translatable"] || "yes").split(" ");
/* Calculate the line location taking into account nested parsing */
line = (node.location || { })["line"] || 0;
line += offsets;
entry = {
msgctxt: attrs['translate-context'] || attrs['context'],
msgid_plural: attrs['translate-plural'],
locations: [ filename + ":" + line ]
};
/* For each thing listed */
tasks.forEach(function(task) {
var copy = Object.assign({}, entry);
/* The element text itself */
if (task == "yes" || task == "translate") {
copy.msgid = extract(node.children);
nest = false;
/* An attribute */
} else if (task) {
copy.msgid = attrs[task];
}
if (copy.msgid)
push(copy);
});
}
/* Walk through all the children */
if (nest)
walk(node.children);
}
/* 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;
}
}
/* Extract the given text */
function extract(children) {
if (!children)
return null;
var i, len, node, str = [];
children.forEach(function(node) {
if (node.type == 'tag' && node.children)
str.push(extract(node.children))
else if (node.type == 'text' && node.data)
str.push(node.data);
});
return str.join("");
}
/* 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 html2po\\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);
});
}
}

View file

@ -1,193 +0,0 @@
#!/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);
});
}
}