fix: keep translated comments across a rebuild, cover two updated plugins
Checking the live instance a day later showed 2,023 Chinese characters back in the served bundles, from two causes. The comment merge rebuilt its dictionary from the scratch files in ~/.cache only, so the committed translations were never an input. Once a later pass added comments, a rebuild discarded them: the count stayed at 274 while eight new translations vanished. src/dictionary.comments.json is now loaded first and the scratch files overlay it, so a rebuild can only grow the dictionary. Two plugins had also shipped new Chinese since the last sweep: dsh-builtin-browser (a new plugin, 834 characters of settings copy) and dsh-our-free-model 2.5.4 (1,082 characters, mostly sponsor copy and comments). Their strings were harvested from the live-served bytes and translated: 70 UI strings into dictionary.residual.json and 8 comments into the comment dictionary. Verified on a fresh instance, escapes decoded: total Chinese across the 79 served bundles 2,023 -> 107, and in the browser both visible text and full page HTML report 0 CJK. The 107 remaining characters are the functional regular expressions that match Chinese input, as before.
This commit is contained in:
parent
ab0179545a
commit
e1de66e75a
6 changed files with 253 additions and 13 deletions
78
lib/index.js
78
lib/index.js
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,13 +1,15 @@
|
|||
// 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.
|
||||
// The committed dictionary is the source of truth: it is loaded first, then any
|
||||
// newer translation passes overlay it. Earlier this read only the scratch files
|
||||
// in ~/.cache, so once those were cleared a rebuild silently produced a smaller
|
||||
// dictionary and translated comments reverted to Chinese.
|
||||
//
|
||||
// Overlays, when present:
|
||||
// ~/.cache/c2e/cmt-authored.json {wholeComment: translatedComment}
|
||||
// ~/.cache/c2e/css-authored.json {body: translatedBody} (delimiters added)
|
||||
// ~/.cache/c2e/c2-authored.json {wholeComment: translatedComment}
|
||||
//
|
||||
// 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";
|
||||
|
||||
|
|
@ -19,14 +21,26 @@ 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) {
|
||||
function load(name, fallback) {
|
||||
const path = `${C2E}/${name}`;
|
||||
return existsSync(path) ? JSON.parse(readFileSync(path, "utf8")) : {};
|
||||
if (!existsSync(path)) return fallback ?? {};
|
||||
return JSON.parse(readFileSync(path, "utf8"));
|
||||
}
|
||||
|
||||
const pairs = new Map();
|
||||
|
||||
// Existing committed translations survive a rebuild.
|
||||
if (existsSync(OUT)) {
|
||||
for (const [zh, en] of Object.entries(JSON.parse(readFileSync(OUT, "utf8")))) pairs.set(zh, en);
|
||||
}
|
||||
|
||||
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}*/`);
|
||||
for (const [zh, en] of Object.entries(load("c2-authored.json"))) pairs.set(zh, en);
|
||||
for (const [zh, en] of Object.entries(load("css-authored.json"))) {
|
||||
// A stylesheet body is stored without delimiters; the emitted key carries them.
|
||||
if (zh.includes("/*")) pairs.set(zh, en);
|
||||
else pairs.set(`/*${zh}*/`, `/*${en}*/`);
|
||||
}
|
||||
|
||||
const clean = {};
|
||||
const reasons = { cjk: 0, latin: 0, identity: 0, box: 0, lines: 0, syntax: 0 };
|
||||
|
|
|
|||
|
|
@ -170,12 +170,14 @@
|
|||
"/* ── ② 按钮 .ofm_btn ─────────────────────────── */": "/* ── ② Buttons .ofm_btn ─────────────────────────── */",
|
||||
"/* 数字列表针走格感:tabular-nums + pl5-tick 机械顿挫(steps) */": "/* Numeric list needle stepping: tabular-nums + pl5-tick mechanical detents (steps) */",
|
||||
"/** 上次同步账号库的时刻(0 = 还没读过;初始化那次 refresh 之前会先读一次)。 */": "/** Timestamp of the last account store sync (0 = not read yet; it is read once before the init refresh). */",
|
||||
"/** 使用用户当地的日历日期,不能用 UTC 的 toISOString() 划分“今天”。 */": "/** Use the user's local calendar date; a UTC toISOString() must not decide what \"today\" is. */",
|
||||
"/** 最近一次自动换号(后端只在**自动**切号时写;手动切不记)。null = 还没有过。 */": "/** The last automatic account rotation (the backend writes it only on an **automatic** switch; manual switches are not recorded). null = never yet. */",
|
||||
"/* ── ⑧【师傅批改①】拉丝纹理增强:金属感要真的能摸到 ─────────────── */": "/* ── 8 [Master review 1] Brushed texture upgrade: the metal feel has to be actually touchable ─────────────── */",
|
||||
"/* 挂到 KPI 大数字与统计卡数字:hover 时数字以机械顿挫走一格,模拟仪表指针跳动 */": "/* Attach to the KPI big number and the stat card numbers: on hover the digits step one notch with a mechanical jolt, mimicking a gauge needle */",
|
||||
"// because \"累计\" that resets at midnight is a lie.": "// because \"cumulative\" that resets at midnight is a lie.",
|
||||
"/* chip 选中态:JS 注入彩色背景,这里压回宿主语言——主色薄涂+主色边,去掉杂色 */": "/* chip selected state: JS injects a colored background, so push it back to the host language here -- light primary tint + primary border, no stray colors */",
|
||||
"/* 滑块用 --ease-settle 带回弹;按下时滑块变宽=被\"捏扁\",松开回弹归位 */": "/* The slider uses --ease-settle with bounce; on press it widens = gets \"squashed\", on release it springs back into place */",
|
||||
"/** 同源窗口共用 Web Lock;只有公告成功打开后才记账。存储禁用时跳过自动广告。 */": "/** Same-origin windows share a Web Lock; bookkeeping happens only once the announcement opened. Auto-ads are skipped when storage is disabled. */",
|
||||
"// 8 秒超时兜底:后端路由未注册/挂起时 fetch 不再永久挂起——此前全部 api 调用": "// 8s timeout fallback: when a backend route is unregistered/hung, fetch no longer hangs forever -- previously every api call",
|
||||
"/* ── ⑧【师傅批改③】表格竖条语言统一:所有表格行 hover 都带主色竖条 ── */": "/* ── 8 [Master review 3] Unify the table rail language: every table row hover carries the primary-color rail ── */",
|
||||
"/* 夜模式 3% → 4.5%,昼模式 1.5% → 2.8%,纹理间隔收紧让拉丝更密 */": "/* Dark mode 3% -> 4.5%, light mode 1.5% -> 2.8%; tighten the texture spacing so the brushed grain is denser */",
|
||||
|
|
@ -185,6 +187,7 @@
|
|||
"// 问题」,动作是「一眼能看到的星按钮」,数字是宿主 /picks 取回的真实 Star /": "// problem\", the action is \"a Star button you cannot miss\", the numbers are the real Star the host fetches via /picks",
|
||||
"/** 用最近一次载荷重画(折叠这类纯前端状态用它,不走重建签名、也不打 HTTP)。 */": "/** Redraw from the last payload (used for purely client-side state like collapse; no rebuild signature, no HTTP request). */",
|
||||
"// 实时积分:每个账号的剩余余额(credits.balances),卡片汇总 + 账号行": "// Live credits: each account's remaining balance (credits.balances), card total + account row",
|
||||
"// 宿主在 document 上用 Esc 关闭设置页;公告打开时先消费按键,保留设置页。": "// The host closes the settings page on document-level Esc; while the announcement is open, consume the key first so the settings page survives.",
|
||||
"// 超时兜底:3 秒拿不到 ack(后端挂起/超时/异常)→ 当作已 ack 主动放行——": "// timeout fallback: no ack in 3 seconds (backend hung/timed out/threw) -> treat as acked and let it through --",
|
||||
"/* 整行染成纯错误色在深色底上会刺眼且发暗;混一点正文色压回来,色条仍然说明性质。 */": "/* Painting a whole row in the pure error color is harsh and dark on a dark background; mix in a bit of the body text color to pull it back, and the bar still states the nature. */",
|
||||
"/* 聚焦环:仪器刻度环——细环+外晕,用 --ease-instrument 淡入 */": "/* Focus ring: an instrument dial ring -- thin ring + outer glow, faded in with --ease-instrument */",
|
||||
|
|
@ -199,6 +202,7 @@
|
|||
"/* 说明文字:限宽 64ch —— 满宽的一行小字读起来很累,且会拖长整张卡。 */": "/* Helper text: capped at 64ch -- a full-width line of small text is tiring to read and stretches the whole card. */",
|
||||
"// GitHub 授权状态由这一层持有:模型卡的锁标记与 EAC 面板必须看到同一个": "// GitHub authorization state is held by this layer: the model card's lock marker and the EAC panel must see the same",
|
||||
"// 只有拿到过明确判定(网关答过 /eac/status)且未授权时才上锁:状态未知": "// Lock only when there is an explicit verdict (the gateway answered /eac/status) and it is unauthorized: unknown state",
|
||||
"// 清除收起动画的 forwards 状态,否则同一界面跨日再打开会透明并卡住操作。": "// Clear the collapse animation's forwards state, or reopening the same view on a later day leaves it transparent and blocks interaction.",
|
||||
"// 默认档(balanced)而不是 deep:测速要回答的是“这个模型日常多快”,": "// default tier (balanced) rather than deep: a speed test answers \"how fast is this model day to day\",",
|
||||
"/** 最近一次 /usage 的响应(切换\"按账号/按模型\"时不必重新拉)。 */": "/** The most recent /usage response (no need to refetch when switching \"by account / by model\"). */",
|
||||
"/** 渲染进程是否支持 File System Access 的「另存为」。 */": "/** Whether the renderer supports File System Access \"Save As\". */",
|
||||
|
|
@ -217,10 +221,12 @@
|
|||
"/* 控件需要占满整行时(滑块 + 数值)用这个变体:文字在上、控件在下。 */": "/* Use this variant when a control must span the full row (slider + value): text on top, control below. */",
|
||||
"/** 24 根小柱:每小时调用量。高度按最大值归一,失败多的时段用红色。 */": "/** 24 small bars: calls per hour. Height normalised by the maximum; hours with many failures are red. */",
|
||||
"/** 与现有 RPC 的能力对应:余额、每日领取及独立签到状态分别判断。 */": "/** Maps to the capabilities of the existing RPCs: balance, daily claim, and standalone check-in state are judged separately. */",
|
||||
"/** 两端共用 React 组件;插件通过构建脚本内联,独立端直接导入。 */": "/** Both sides share the React components; the plugin inlines them at build time while the standalone side imports them directly. */",
|
||||
"// 这一回合的 token 去向。聊天窗属于宿主内核,插件无法在对话流里注入,": "// where this round's token went. The chat window belongs to the host kernel, the plugin cannot inject into the conversation stream,",
|
||||
"// 重新打开——永久关闭让公告/更新/升级推送在宿主重启或休眠后整体静默失效,": "// reopen it -- a permanent close leaves announcement/update/upgrade pushes silently dead across host restarts or sleep,",
|
||||
"// 闸门在服务器:网关拒绝没有用户令牌的对话,而这个面板是取得令牌的唯一入口。": "// The gate is server-side: the gateway rejects conversations with no user token, and this panel is the only way in to get one.",
|
||||
"/* 状态点呼吸灯保留,但改成 pl5- 前缀、机械顿挫更明显的两拍节奏 */": "/* Keep the breathing status dot, but move it under the pl5- prefix and give it a two-beat rhythm with a sharper mechanical step */",
|
||||
"/** 赞助内容与每日公告交互:浏览器专用,不读取账号或调用赞助商接口。 */": "/** Sponsored content and daily announcement interaction: browser-only; reads no account and calls no sponsor endpoint. */",
|
||||
"// 「最近一回合」跟着一个轻量轮询走(#26):看板其余部分仍是页面加载时的": "// \"Latest round\" rides a lightweight poll (#26): the rest of the board is still the one from page load",
|
||||
"// 免费车道转发端口的接入配方。它和上面这块网关是两条独立监听:这里的地址与": "// The connection recipe for the free lane's forwarding port. It and the gateway above are two independent listeners: the address here and",
|
||||
"// 复制粘贴:后端把授权页交给系统浏览器,前端轮询网关领取令牌。令牌本身从不": "// Copy-paste: the backend hands the authorization page to the system browser, the frontend polls the gateway for the token. The token itself never",
|
||||
|
|
@ -236,6 +242,7 @@
|
|||
"/** 把任意值吸附到最近的档位(配置里可能是旧值或手改过的值)。 */": "/** Snap any value to the nearest tier (the config may hold an old value or a hand-edited one). */",
|
||||
"// 配方(未启用时 /clients 回的是占位端口与占位 Key)。": "// recipe (when not enabled /clients returns placeholder ports and a placeholder Key).",
|
||||
"/* tab:激活项在滑块底衬之上再补一道品牌下划线,选中更明确 */": "/* tab: the active item adds a brand underline on top of the slider track, so the selection reads more clearly */",
|
||||
"// 插件挂在宿主设置弹窗中;菜单所在的这一层不是需要避让的其他弹窗。": "// The plugin mounts inside the host settings dialog; this menu layer is not another dialog to dodge.",
|
||||
"// 的宿主、非常规 base)就退回根路径原值——行为不比修复前差。": "// host, unusual base) fall back to the root-path original -- no worse than before the fix.",
|
||||
"// 会被清零,退避就永远是第一档而不是 30s 翻倍到 5 分钟。": "// gets zeroed, so the backoff would forever sit at the first tier instead of doubling from 30s to 5 minutes.",
|
||||
"// 保持可 grep;各仓库的地址与「第二个动作」在这里各写一份。": "// keep it greppable; each repo's address and \"second action\" are written once here.",
|
||||
|
|
@ -245,6 +252,7 @@
|
|||
"// 逐条展示;随账号列表与领积分动作刷新,另有一个慢轮询兜底。": "// shown row by row; refreshes with the account list and the claim-credits action, with a slow poll as backup.",
|
||||
"/** 渲染宿主返回的节流设置(含可选档位与清理策略)。 */": "/** Render the throttle settings returned by the host (including selectable tiers and the cleanup policy). */",
|
||||
"/** 短时间:今天只显示时分,其它显示月/日 时:分。 */": "/** Short time: for today show only hour:minute, otherwise show month/day hour:minute. */",
|
||||
"// 写入失败时不弹窗,避免刷新、重挂载或隐私模式下反复打扰。": "// No dialog when the write fails: it would nag on every refresh, remount or private-mode session.",
|
||||
"/** 倒计时:还剩 2 小时 13 分 / 已解除。 */": "/** Countdown: 2 hours 13 minutes left / lifted. */",
|
||||
"// 不该显示一把凭空的锁,网关不可达时本地记录仍算已授权。": "// must not show a lock out of nowhere; when the gateway is unreachable the local record still counts as authorized.",
|
||||
"/* 用户开了\"减少动态效果\"就别再让元素滑来滑去。 */": "/* If the user turned on \"reduce motion\", stop sliding elements around. */",
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Add table
Reference in a new issue