Files
ThothII/frontend/scripts/check-i18n.mjs
T
Codex d8a29bfbdd Add full shell, replaceable Omics adapter and bilingual interaction
Implement approved specification #32 and tickets #33-#37. Keep host authentication server-verified and pin session interaction language. Compile scoped base selectors for browser compatibility and retain full gutters during CSS pruning.
2026-09-13 14:26:39 +02:00

57 lines
2.4 KiB
JavaScript

import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import ts from "typescript";
const root = fileURLToPath(new URL("../src/", import.meta.url));
const files = (directory) => fs.readdirSync(directory, { withFileTypes: true }).flatMap((entry) =>
entry.isDirectory() ? files(path.join(directory, entry.name)) : [path.join(directory, entry.name)],
);
const source = (file) => ts.createSourceFile(file, fs.readFileSync(file, "utf8"), ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
const catalog = new Map();
const problems = [];
const slots = (value) => [...value.matchAll(/\{([A-Za-z][A-Za-z0-9_]*)\}/g)].map((match) => match[1]).sort().join(",");
for (const file of files(path.join(root, "i18n/locales"))) {
if (!/it.*\.ts$/.test(file)) continue;
function visit(node) {
if (ts.isPropertyAssignment(node) && ts.isStringLiteral(node.name) && ts.isStringLiteral(node.initializer)) {
const key = node.name.text;
const value = node.initializer.text;
if (slots(key) !== slots(value)) problems.push(`${path.relative(root, file)}: interpolation mismatch for ${key}`);
// Shared gettext messages may appear in multiple feature catalogues.
// Their meaning and translation must agree regardless of merge order.
if (catalog.has(key) && catalog.get(key) !== value) problems.push(`${path.relative(root, file)}: conflicting translation for ${key}`);
catalog.set(key, value);
}
ts.forEachChild(node, visit);
}
visit(source(file));
}
let references = 0;
for (const file of files(root)) {
if (!/\.tsx?$/.test(file) || /(?:\.test\.|\/test\/|\/i18n\/|prototype)/i.test(file)) continue;
const tree = source(file);
function visit(node) {
if (ts.isCallExpression(node) && /^(t|translate)$/.test(node.expression.getText(tree))) {
const arg = node.arguments[0];
if (arg && ts.isStringLiteral(arg)) {
references++;
if (!catalog.has(arg.text)) {
const line = tree.getLineAndCharacterOfPosition(arg.getStart(tree)).line + 1;
problems.push(`${path.relative(root, file)}:${line}: missing Italian message ${JSON.stringify(arg.text)}`);
}
}
}
ts.forEachChild(node, visit);
}
visit(tree);
}
console.log(`${catalog.size} Italian messages; ${references} static translation references checked.`);
if (problems.length) {
console.error(problems.join("\n"));
process.exitCode = 1;
}