xgettext-html 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. #!/usr/bin/env node
  2. /*
  3. * xgettext-html: HTML gettext parser
  4. * Copyright (C) 2018 The noVNC Authors
  5. * Licensed under MPL 2.0 (see LICENSE.txt)
  6. */
  7. const getopt = require('node-getopt');
  8. const jsdom = require("jsdom");
  9. const fs = require("fs");
  10. const opt = getopt.create([
  11. ['o', 'output=FILE', 'write output to specified file'],
  12. ['h', 'help', 'display this help'],
  13. ]).bindHelp().parseSystem();
  14. const strings = {};
  15. function addString(str, location) {
  16. if (str.length == 0) {
  17. return;
  18. }
  19. if (strings[str] === undefined) {
  20. strings[str] = {};
  21. }
  22. strings[str][location] = null;
  23. }
  24. // See https://html.spec.whatwg.org/multipage/dom.html#attr-translate
  25. function process(elem, locator, enabled) {
  26. function isAnyOf(searchElement, items) {
  27. return items.indexOf(searchElement) !== -1;
  28. }
  29. if (elem.hasAttribute("translate")) {
  30. if (isAnyOf(elem.getAttribute("translate"), ["", "yes"])) {
  31. enabled = true;
  32. } else if (isAnyOf(elem.getAttribute("translate"), ["no"])) {
  33. enabled = false;
  34. }
  35. }
  36. if (enabled) {
  37. if (elem.hasAttribute("abbr") &&
  38. elem.tagName === "TH") {
  39. addString(elem.getAttribute("abbr"), locator(elem));
  40. }
  41. if (elem.hasAttribute("alt") &&
  42. isAnyOf(elem.tagName, ["AREA", "IMG", "INPUT"])) {
  43. addString(elem.getAttribute("alt"), locator(elem));
  44. }
  45. if (elem.hasAttribute("download") &&
  46. isAnyOf(elem.tagName, ["A", "AREA"])) {
  47. addString(elem.getAttribute("download"), locator(elem));
  48. }
  49. if (elem.hasAttribute("label") &&
  50. isAnyOf(elem.tagName, ["MENUITEM", "MENU", "OPTGROUP",
  51. "OPTION", "TRACK"])) {
  52. addString(elem.getAttribute("label"), locator(elem));
  53. }
  54. if (elem.hasAttribute("placeholder") &&
  55. isAnyOf(elem.tagName in ["INPUT", "TEXTAREA"])) {
  56. addString(elem.getAttribute("placeholder"), locator(elem));
  57. }
  58. if (elem.hasAttribute("title")) {
  59. addString(elem.getAttribute("title"), locator(elem));
  60. }
  61. if (elem.hasAttribute("value") &&
  62. elem.tagName === "INPUT" &&
  63. isAnyOf(elem.getAttribute("type"), ["reset", "button", "submit"])) {
  64. addString(elem.getAttribute("value"), locator(elem));
  65. }
  66. }
  67. for (let i = 0; i < elem.childNodes.length; i++) {
  68. let node = elem.childNodes[i];
  69. if (node.nodeType === node.ELEMENT_NODE) {
  70. process(node, locator, enabled);
  71. } else if (node.nodeType === node.TEXT_NODE && enabled) {
  72. addString(node.data.trim(), locator(node));
  73. }
  74. }
  75. }
  76. for (let i = 0; i < opt.argv.length; i++) {
  77. const fn = opt.argv[i];
  78. const file = fs.readFileSync(fn, "utf8");
  79. const dom = new jsdom.JSDOM(file, { includeNodeLocations: true });
  80. const body = dom.window.document.body;
  81. let locator = (elem) => {
  82. const offset = dom.nodeLocation(elem).startOffset;
  83. const line = file.slice(0, offset).split("\n").length;
  84. return fn + ":" + line;
  85. };
  86. process(body, locator, true);
  87. }
  88. let output = "";
  89. for (let str in strings) {
  90. output += "#:";
  91. for (location in strings[str]) {
  92. output += " " + location;
  93. }
  94. output += "\n";
  95. output += "msgid " + JSON.stringify(str) + "\n";
  96. output += "msgstr \"\"\n";
  97. output += "\n";
  98. }
  99. fs.writeFileSync(opt.options.output, output);