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.
60 lines
2.7 KiB
TypeScript
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}`);
|