The HTML source will include line breaks and indentation that is only for source formatting, and will not be displayed.
120 lines
3.6 KiB
JavaScript
Executable File
120 lines
3.6 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
/*
|
|
* xgettext-html: HTML gettext parser
|
|
* Copyright (C) 2018 The noVNC Authors
|
|
* Licensed under MPL 2.0 (see LICENSE.txt)
|
|
*/
|
|
|
|
const getopt = require('node-getopt');
|
|
const jsdom = require("jsdom");
|
|
const fs = require("fs");
|
|
|
|
const opt = getopt.create([
|
|
['o', 'output=FILE', 'write output to specified file'],
|
|
['h', 'help', 'display this help'],
|
|
]).bindHelp().parseSystem();
|
|
|
|
const strings = {};
|
|
|
|
function addString(str, location) {
|
|
// We assume surrounding whitespace, and whitespace around line
|
|
// breaks, is just for source formatting
|
|
str = str.split("\n").map(s => s.trim()).join(" ").trim();
|
|
|
|
if (str.length == 0) {
|
|
return;
|
|
}
|
|
|
|
if (strings[str] === undefined) {
|
|
strings[str] = {};
|
|
}
|
|
strings[str][location] = null;
|
|
}
|
|
|
|
// See https://html.spec.whatwg.org/multipage/dom.html#attr-translate
|
|
function process(elem, locator, enabled) {
|
|
function isAnyOf(searchElement, items) {
|
|
return items.indexOf(searchElement) !== -1;
|
|
}
|
|
|
|
if (elem.hasAttribute("translate")) {
|
|
if (isAnyOf(elem.getAttribute("translate"), ["", "yes"])) {
|
|
enabled = true;
|
|
} else if (isAnyOf(elem.getAttribute("translate"), ["no"])) {
|
|
enabled = false;
|
|
}
|
|
}
|
|
|
|
if (enabled) {
|
|
if (elem.hasAttribute("abbr") &&
|
|
elem.tagName === "TH") {
|
|
addString(elem.getAttribute("abbr"), locator(elem));
|
|
}
|
|
if (elem.hasAttribute("alt") &&
|
|
isAnyOf(elem.tagName, ["AREA", "IMG", "INPUT"])) {
|
|
addString(elem.getAttribute("alt"), locator(elem));
|
|
}
|
|
if (elem.hasAttribute("download") &&
|
|
isAnyOf(elem.tagName, ["A", "AREA"])) {
|
|
addString(elem.getAttribute("download"), locator(elem));
|
|
}
|
|
if (elem.hasAttribute("label") &&
|
|
isAnyOf(elem.tagName, ["MENUITEM", "MENU", "OPTGROUP",
|
|
"OPTION", "TRACK"])) {
|
|
addString(elem.getAttribute("label"), locator(elem));
|
|
}
|
|
if (elem.hasAttribute("placeholder") &&
|
|
isAnyOf(elem.tagName in ["INPUT", "TEXTAREA"])) {
|
|
addString(elem.getAttribute("placeholder"), locator(elem));
|
|
}
|
|
if (elem.hasAttribute("title")) {
|
|
addString(elem.getAttribute("title"), locator(elem));
|
|
}
|
|
if (elem.hasAttribute("value") &&
|
|
elem.tagName === "INPUT" &&
|
|
isAnyOf(elem.getAttribute("type"), ["reset", "button", "submit"])) {
|
|
addString(elem.getAttribute("value"), locator(elem));
|
|
}
|
|
}
|
|
|
|
for (let i = 0; i < elem.childNodes.length; i++) {
|
|
let node = elem.childNodes[i];
|
|
if (node.nodeType === node.ELEMENT_NODE) {
|
|
process(node, locator, enabled);
|
|
} else if (node.nodeType === node.TEXT_NODE && enabled) {
|
|
addString(node.data, locator(node));
|
|
}
|
|
}
|
|
}
|
|
|
|
for (let i = 0; i < opt.argv.length; i++) {
|
|
const fn = opt.argv[i];
|
|
const file = fs.readFileSync(fn, "utf8");
|
|
const dom = new jsdom.JSDOM(file, { includeNodeLocations: true });
|
|
const body = dom.window.document.body;
|
|
|
|
let locator = (elem) => {
|
|
const offset = dom.nodeLocation(elem).startOffset;
|
|
const line = file.slice(0, offset).split("\n").length;
|
|
return fn + ":" + line;
|
|
};
|
|
|
|
process(body, locator, true);
|
|
}
|
|
|
|
let output = "";
|
|
|
|
for (let str in strings) {
|
|
output += "#:";
|
|
for (location in strings[str]) {
|
|
output += " " + location;
|
|
}
|
|
output += "\n";
|
|
|
|
output += "msgid " + JSON.stringify(str) + "\n";
|
|
output += "msgstr \"\"\n";
|
|
output += "\n";
|
|
}
|
|
|
|
fs.writeFileSync(opt.options.output, output);
|