The last Chinese lived in source comments: notes the plugin authors wrote
about why a module exists and which bug it fixed, plus two stylesheet
comment blocks. Comments never render, but they still shipped Chinese, and
the user asked for all of it gone.
- src/dictionary.comments.json: 274 whole comments (block and line form).
- src/rewrite.ts: rewriteComments replaces a comment only on an exact
whole-comment match, so box-drawing runs and the `*` alignment that give
these notes their structure cannot be reflowed by a partial replacement.
Comment rewriting runs on the source between literals; running it over
the assembled output would also scan literal contents, where a URL like
`https://…` reads as a line comment.
- scripts/merge-comments.mjs emits the dictionary and enforces the
invariants: no CJK, delimiters preserved and balanced, line count
unchanged, box runs preserved in block comments. In a single-line `//`
banner the run is decoration and may shift with the text.
Verified on the bytes a fresh instance actually served, escapes decoded:
total Chinese 55,345 -> 111 (99.80% removed). In the browser, both visible
text and the full page HTML report 0 CJK.
The 111 characters left are inside functional regular expressions that
match Chinese *input*: /失败|错误|无法/ classifies a status message,
dshmarket's PREVIEW_WORDS match Chinese plugin descriptions, and
/^[一-龥]{2}$/ validates a two-character name. Translating those would
break plugin behaviour, so they are deliberately left alone.
62 lines
2.8 KiB
JavaScript
62 lines
2.8 KiB
JavaScript
// Builds src/dictionary.comments.json from the translated comments.
|
|
//
|
|
// Two shapes feed this and both become whole delimited comments, which is what
|
|
// the rewriter matches:
|
|
// - Comment BODIES (text between `/*` and `*/`) from the stylesheet pass,
|
|
// re-wrapped in their delimiters.
|
|
// - Whole comments FROM the code-comment pass, already delimited.
|
|
//
|
|
// Inputs: ~/.cache/c2e/cmt-authored.json {wholeComment: translatedComment}
|
|
// ~/.cache/c2e/css-authored.json {body: translatedBody}
|
|
// Output: src/dictionary.comments.json {wholeComment: translatedComment}
|
|
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
|
|
const C2E = "/home/oiupoyt/.cache/c2e";
|
|
const OUT = "/home/oiupoyt/my stuff yea/projects/chinese2english/src/dictionary.comments.json";
|
|
|
|
const HAN = /[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff]/;
|
|
const BOX = /[\u2500-\u257f\u2580-\u259f]/g;
|
|
const boxOf = (t) => (t.match(BOX) ?? []).sort().join("");
|
|
const newlines = (t) => (t.match(/\n/g) ?? []).length;
|
|
|
|
function load(name) {
|
|
const path = `${C2E}/${name}`;
|
|
return existsSync(path) ? JSON.parse(readFileSync(path, "utf8")) : {};
|
|
}
|
|
|
|
const pairs = new Map();
|
|
for (const [zh, en] of Object.entries(load("cmt-authored.json"))) pairs.set(zh, en);
|
|
for (const [zh, en] of Object.entries(load("css-authored.json"))) pairs.set(`/*${zh}*/`, `/*${en}*/`);
|
|
|
|
const clean = {};
|
|
const reasons = { cjk: 0, latin: 0, identity: 0, box: 0, lines: 0, syntax: 0 };
|
|
|
|
for (const [zh, en] of pairs) {
|
|
if (typeof en !== "string" || !en.trim()) continue;
|
|
if (HAN.test(en)) { reasons.cjk++; continue; }
|
|
if (!/[A-Za-z]/.test(en)) { reasons.latin++; continue; }
|
|
if (en === zh) { reasons.identity++; continue; }
|
|
// Replacing a comment must keep it a well-formed comment, or the emitted
|
|
// bundle stops parsing.
|
|
if (zh.includes("/*") !== en.includes("/*")) { reasons.syntax++; continue; }
|
|
if (zh.includes("*/") !== en.includes("*/")) { reasons.syntax++; continue; }
|
|
if (zh.startsWith("//") !== en.startsWith("//")) { reasons.syntax++; continue; }
|
|
// Box-drawing runs are the structure of block comments, where they align the
|
|
// `*` column. In a single-line `//` banner the run is pure decoration and its
|
|
// length shifts freely with the translated text.
|
|
const isLineComment = zh.startsWith("//");
|
|
if (!isLineComment && boxOf(zh) !== boxOf(en)) { reasons.box++; continue; }
|
|
if (newlines(zh) !== newlines(en)) { reasons.lines++; continue; }
|
|
clean[zh] = en;
|
|
}
|
|
|
|
const sorted = Object.fromEntries(
|
|
Object.entries(clean).sort((a, b) => b[0].length - a[0].length || a[0].localeCompare(b[0])),
|
|
);
|
|
writeFileSync(OUT, JSON.stringify(sorted, null, 1) + "\n");
|
|
const dropped = Object.values(reasons).reduce((a, b) => a + b, 0);
|
|
console.log(
|
|
`comments: ${Object.keys(sorted).length} entries (dropped ${dropped}` +
|
|
Object.entries(reasons).filter(([, v]) => v).map(([k, v]) => `, ${k} ${v}`).join("") +
|
|
")",
|
|
);
|