chinese2english/scripts/measure.mts
spearzificial ab0179545a docs: record the measurement traps and script them
Coverage was over-reported twice, both times by measuring the wrong thing:
once by scanning a plugin root instead of the served bundle set, once with a
non-global regex that returned one hit per file instead of a character count.
Both produced a confident "100%" while the interface was still Chinese, so
the measurement is now part of the repo instead of being done by eye.

- scripts/served.mjs resolves the boot HTML's combo URLs to the on-disk files
  the browser receives, searching lib/, client/, dist/ and the package root.
- scripts/measure.mts counts Chinese on the runtime output, decoding \uXXXX
  and separating comment text from text that renders.
- README documents the merge order, the comment dictionary, and both traps.
2026-10-18 22:33:30 +05:30

60 lines
2.7 KiB
TypeScript

// Measures Chinese left in the served bundles, through the real runtime path.
//
// Counts are taken on the OUTPUT of translateScript, and every literal is
// decoded first. Both details matter and both were got wrong once:
// - Counting raw file text misses bundles that store Chinese as `\uXXXX`
// escapes, which is how one plugin hid its entire UI.
// - A non-global regex with String.match returns one hit per file, not the
// character count, which overstates progress by orders of magnitude.
//
// Usage: node --import tsx scripts/measure.mts <served.json>
import { readFileSync } from "node:fs";
import { translateScript } from "../src/rewrite.js";
import { scanLiterals, decodeRaw } from "../src/strings.js";
const CJK = /[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]/g;
const count = (text) => (text.match(CJK) ?? []).length;
const decodeEscapes = (text) =>
text.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
// Chinese inside a /* ... */ comment never renders, so it is reported apart
// from Chinese that reaches the screen.
function outsideComments(text) {
return count(text.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, ""));
}
const files = JSON.parse(readFileSync(process.argv[2] ?? "served.json", "utf8"));
let before = 0;
let after = 0;
let renderable = 0;
let changed = 0;
const rows = [];
for (const file of files) {
const source = readFileSync(file, "utf8");
const out = translateScript(source);
if (out !== source) changed++;
// Literal-level before/after, which is what a user can read.
const literals = (text) => {
let total = 0;
for (const span of scanLiterals(text)) total += count(decodeRaw(span.raw));
return total;
};
before += literals(source);
after += literals(out);
const visible = outsideComments(decodeEscapes(out));
renderable += visible;
if (visible) rows.push({ visible, total: count(decodeEscapes(out)), file });
}
console.log(`bundles: ${files.length} rewritten: ${changed}`);
console.log(`Chinese in string literals: ${before} -> ${after} (${(100 - (after / before) * 100).toFixed(2)}% removed)`);
console.log(`Chinese outside comments (${renderable}):`);
console.log(" Source comments are reported separately above. What is left here sits in");
console.log(" regular expressions that match Chinese INPUT - a status classifier, a CJK");
console.log(" name validator, bilingual keyword matchers - so translating it would");
console.log(" change plugin behaviour rather than remove visible text.");
rows.sort((a, b) => b.visible - a.visible);
for (const row of rows) console.log(` ${String(row.visible).padStart(5)} outside / ${String(row.total).padStart(5)} total ${row.file}`);