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.
57 lines
2.4 KiB
JavaScript
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;
|
|
}
|