Files
computer-playbook/tests/unit/javascript/roles/web-app-docs/files/languages.test.js
T
kevinveenbirkenbachandClaude Opus 5 6a23926648 feat(docs): build and serve a translated site per language
Every version whose tree holds a locale/<code>/LC_MESSAGES/docs.po with at
least one translation now gets one site per such language next to the
English one, built with `-D language=<code> -D html_copy_source=0` after
the English site is published. Sphinx reads one docs.po per language
(locale_dirs, gettext_compact = "docs"); YAML pages stay untranslated
through an extension that runs before the Locale transform.

The deployed working tree is built as the unlisted version `deployed` from a
snapshot baked into the image, because the git mirror only holds what is
pushed to main. The server answers /<version>/<code>/ and
/api/languages/<version>, a language without a site is a 404 rather than the
English page, and a switcher on every page lists the built languages and
keeps the page path. min_storage grows to 13GB for the translated sites.

The 47 docs.po catalogs hold the 24869 documentation messages; German is
partially machine-translated so far. The raw-docker and primary-domain lints
skip .po files, which carry the Markdown's own commands and hosts as
translation sources.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 18:56:06 +02:00

87 lines
2.7 KiB
JavaScript

const test = require("node:test");
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const vm = require("node:vm");
const SOURCE = path.join(
__dirname,
"..", "..", "..", "..", "..", "..",
"roles", "web-app-docs", "files", "javascript", "languages.js",
);
const LANGUAGES = [
{ code: "en", native: "English" },
{ code: "de", native: "Deutsch" },
{ code: "ar", native: "العربية" },
];
function select(dataset) {
return {
dataset,
children: [],
value: "",
listeners: {},
replaceChildren(...nodes) {
this.children = nodes;
},
addEventListener(type, handler) {
this.listeners[type] = handler;
},
};
}
async function load(dataset, answer = { ok: true, body: { languages: LANGUAGES } }) {
let ready = null;
const requested = [];
const switcher = select(dataset);
const window = { location: { href: "" } };
const document = {
addEventListener: (type, handler) => {
if (type === "DOMContentLoaded") {
ready = handler;
}
},
createElement: () => ({}),
querySelectorAll: (selector) => (selector === ".docs-language-switcher" ? [switcher] : []),
};
const fetch = async (url) => {
requested.push(url);
return { ok: answer.ok, json: async () => answer.body };
};
vm.runInNewContext(fs.readFileSync(SOURCE, "utf8"), { document, window, fetch });
ready();
await new Promise((resolve) => setImmediate(resolve));
return { switcher, window, requested };
}
test("the switcher lists every built language and keeps the page", async () => {
const { switcher, requested } = await load({ version: "deployed", language: "de", page: "docs/index" });
assert.deepEqual(requested, ["/api/languages/deployed"]);
assert.deepEqual(
switcher.children.map((option) => [option.textContent, option.value, option.selected]),
[
["English", "/deployed/docs/index.html", false],
["Deutsch", "/deployed/de/docs/index.html", true],
["العربية", "/deployed/ar/docs/index.html", false],
],
);
});
test("choosing a language navigates to the same page in it", async () => {
const { switcher, window } = await load({ version: "v14.1.0", language: "en", page: "index" });
switcher.value = switcher.children[1].value;
switcher.listeners.change();
assert.equal(window.location.href, "/v14.1.0/de/index.html");
});
test("a failed request leaves the rendered placeholder in place", async () => {
const { switcher } = await load({ version: "latest", language: "en", page: "index" }, { ok: false, body: null });
assert.deepEqual(switcher.children, []);
assert.equal(switcher.listeners.change, undefined);
});