60 lines
2.8 KiB
JavaScript
60 lines
2.8 KiB
JavaScript
#!/usr/bin/env node
|
|
/*
|
|
* Every source string a catalog needs, straight out of the calls.
|
|
*
|
|
* The English text is the key, so the catalog's keys are not a list somebody
|
|
* maintains -- they are whatever t(), tNode() and plural() are actually asked
|
|
* for. Reading them from the code means a catalog can never drift out of
|
|
* step with the app in the one direction that matters: a key that no longer
|
|
* exists is dead weight, but a call with no key is an untranslated string
|
|
* nobody noticed.
|
|
*/
|
|
/*
|
|
* The parser, not the compiler.
|
|
*
|
|
* TypeScript 7 is the native port: its package ships a `tsc` shim over a Go
|
|
* binary and nothing else, so `typescript` now exports `version` and
|
|
* `versionMajorMinor` and no compiler API at all. Every `ts.createSourceFile`
|
|
* in this directory started throwing "Cannot read properties of undefined
|
|
* (reading 'Latest')" the day the bump landed, and nothing noticed, because no
|
|
* workflow runs these.
|
|
*
|
|
* `typescript-ast` is an npm alias for the last TypeScript that carries the JS
|
|
* API (see package.json). It parses; `typescript` still type-checks and builds.
|
|
* Two entries, two jobs -- not a version someone forgot to remove.
|
|
*/
|
|
import ts from "typescript-ast";
|
|
import { readFileSync, globSync } from "node:fs";
|
|
|
|
const strings = new Set();
|
|
const plurals = new Set();
|
|
|
|
for (const file of globSync("web/src/**/*.{ts,tsx}").filter((f) => !f.includes("__tests__"))) {
|
|
const src = ts.createSourceFile(file, readFileSync(file, "utf8"), ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
|
|
const visit = (node) => {
|
|
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
|
|
const fn = node.expression.text;
|
|
const a0 = node.arguments[0];
|
|
if ((fn === "t" || fn === "translate" || fn === "tNode") && a0 && ts.isStringLiteral(a0)) strings.add(a0.text);
|
|
if (fn === "plural" && node.arguments[1] && ts.isObjectLiteralExpression(node.arguments[1])) {
|
|
const other = node.arguments[1].properties.find((p) => ts.isPropertyAssignment(p) && p.name.getText(src) === "other");
|
|
const forms = {};
|
|
for (const p of node.arguments[1].properties) {
|
|
if (ts.isPropertyAssignment(p) && ts.isStringLiteral(p.initializer)) forms[p.name.getText(src)] = p.initializer.text;
|
|
}
|
|
if (other) plurals.add(JSON.stringify(forms));
|
|
}
|
|
}
|
|
ts.forEachChild(node, visit);
|
|
};
|
|
visit(src);
|
|
}
|
|
|
|
const out = { strings: [...strings].sort(), plurals: [...plurals].map((p) => JSON.parse(p)) };
|
|
if (process.argv.includes("--json")) console.log(JSON.stringify(out, null, 2));
|
|
else {
|
|
console.log(`${out.strings.length} strings, ${out.plurals.length} plural sets`);
|
|
const short = out.strings.filter((s) => s.length <= 30).length;
|
|
console.log(` ${short} short (<=30 chars), ${out.strings.length - short} longer`);
|
|
}
|