// strings_node drives the string lookup one script carries, // fiestaPickLang and fiestaWord, in plain Node against a stub page. // Both datetime.js and select.js carry a copy (each enhancement is one // script tag), so the script's file name is the one argument: // // echo '{"registry":["en","pt-PT"],"tags":["pt-BR",""]}' | // node node/strings_node.mjs datetime.js // // check/strings_test.go is the only caller. It reads back one JSON // object: "pick", fiestaPickLang's answer for each tag on stdin plus // one for undefined under "", against a registry holding // an (empty) table per tag named; and "word", fiestaWord's answer in // five scenarios on a stub element, four in a lang="es" document whose // table says "Tabla": "both" (attribute "Atributo" and the table), // "empty" (an empty attribute and the table), "table" (no attribute) // and "neither" (no attribute, no table, so the fallback "English"); // and "notable", a lang="de" document with only that es table and no // attribute, the fallback again. The word scenarios build their own // tables, so stdin's registry is for "pick" alone. // // The page is the least DOM either script touches on load: a document // that is still loading, with a root carrying lang, and a self holding // the registry. The script's page half registers its listeners and // waits for DOMContentLoaded, which never comes; the lookup is read // off module.exports, which both scripts fill behind their guard. import { readFileSync } from "node:fs"; import vm from "node:vm"; const name = process.argv[2]; if (!name) { process.stderr.write("usage: strings_node.mjs \n"); process.exit(2); } const src = readFileSync(new URL("../../js/" + name, import.meta.url), "utf8"); const chunks = []; for await (const chunk of process.stdin) chunks.push(chunk); // Go encodes a nil slice as null, which a default would not fill. const stdin = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}"); const registry = stdin.registry || []; const tags = stdin.tags || []; // load evaluates the script in a fresh context whose document says // lang and whose self.fiestaStrings is strings, and returns what it // exported. function load(lang, strings) { const root = { lang, getAttribute: (k) => (k === "lang" ? lang : null) }; const document = { readyState: "loading", documentElement: root, addEventListener() {}, querySelectorAll: () => [], }; const self = { fiestaStrings: strings }; const module = { exports: {} }; const context = { document, self, window: self, module, exports: module.exports, setTimeout, clearTimeout }; vm.runInNewContext(src, context, { filename: name }); return module.exports; } // An element with attributes and nothing else. function element(attrs) { return { getAttribute: (k) => (Object.prototype.hasOwnProperty.call(attrs, k) ? attrs[k] : null) }; } const out = { pick: {}, word: {} }; { const tables = {}; for (const tag of registry) tables[tag] = {}; const { fiestaPickLang } = load("en", tables); if (typeof fiestaPickLang !== "function") { process.stderr.write(name + " does not export fiestaPickLang\n"); process.exit(1); } for (const tag of tags) out.pick[tag] = fiestaPickLang(tag, tables); out.pick[""] = fiestaPickLang(undefined, tables); } { const table = { es: { "date-set": "Tabla" } }; const withTable = load("es", table).fiestaWord; const without = load("es", {}).fiestaWord; if (typeof withTable !== "function") { process.stderr.write(name + " does not export fiestaWord\n"); process.exit(1); } out.word.both = withTable(element({ "data-rst-date-set": "Atributo" }), "date-set", "English"); out.word.empty = withTable(element({ "data-rst-date-set": "" }), "date-set", "English"); out.word.table = withTable(element({}), "date-set", "English"); out.word.neither = without(element({}), "date-set", "English"); out.word.notable = load("de", table).fiestaWord(element({}), "date-set", "English"); } process.stdout.write(JSON.stringify(out));