diff --git a/Makefile b/Makefile index 203078e..9577545 100644 --- a/Makefile +++ b/Makefile @@ -55,13 +55,13 @@ po/$(PACKAGE_NAME).js.pot: --keyword=gettext:1,1t --keyword=gettext:1c,2,2t \ --keyword=ngettext:1,2,3t --keyword=ngettext:1c,2,3,4t \ --keyword=gettextCatalog.getString:1,3c --keyword=gettextCatalog.getPlural:2,3,4c \ - --from-code=UTF-8 $$(find src/ \( -name '*.js' -o -name '*.jsx' \)) + --from-code=UTF-8 $$(find src/ -name '*.js' -o -name '*.jsx') -po/$(PACKAGE_NAME).html.pot: $(NODE_MODULES_TEST) - po/html2po -o $@ $$(find src -name '*.html') +po/$(PACKAGE_NAME).html.pot: $(NODE_MODULES_TEST) $(COCKPIT_REPO_STAMP) + pkg/lib/html2po -o $@ $$(find src -name '*.html') -po/$(PACKAGE_NAME).manifest.pot: $(NODE_MODULES_TEST) - po/manifest2po src/manifest.json -o $@ +po/$(PACKAGE_NAME).manifest.pot: $(NODE_MODULES_TEST) $(COCKPIT_REPO_STAMP) + pkg/lib/manifest2po src/manifest.json -o $@ po/$(PACKAGE_NAME).metainfo.pot: $(APPSTREAMFILE) xgettext --default-domain=$(PACKAGE_NAME) --output=$@ $< diff --git a/package.json b/package.json index 6d80085..e099ce4 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@babel/eslint-parser": "^7.17.0", "@babel/preset-env": "^7.5.4", "@babel/preset-react": "^7.0.0", + "argparse": "^2.0.1", "babel-loader": "^8.0.6", "chrome-remote-interface": "^0.31.0", "compression-webpack-plugin": "^9.0.0", @@ -41,7 +42,6 @@ "sass": "^1.35.2", "sass-loader": "^12.1.0", "sizzle": "^2.3.3", - "stdio": "^2.1.0", "string-replace-loader": "^3.0.0", "terser-webpack-plugin": "^5.1.4", "webpack": "^5.54.0", diff --git a/po/html2po b/po/html2po deleted file mode 100755 index 8b34fa0..0000000 --- a/po/html2po +++ /dev/null @@ -1,264 +0,0 @@ -#!/usr/bin/env node - -/* - * Extracts translatable strings from HTML files in the following forms: - * - * String - * String - * String - * - * - * Supports the following Glade compatible forms: - * - * String - * String - * - * Supports the following angular-gettext compatible forms: - * - * String - * Singular - * - * 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); - }); - } -} diff --git a/po/manifest2po b/po/manifest2po deleted file mode 100755 index 46fa744..0000000 --- a/po/manifest2po +++ /dev/null @@ -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); - }); - } -}