From 804e7bd6fd49aa0615d954a37a63576ce224416d Mon Sep 17 00:00:00 2001 From: EMRG Evolution Date: Thu, 6 Aug 2026 23:09:24 +0800 Subject: [PATCH] =?UTF-8?q?emrg:=20GUI=20i18n=20=E2=80=94=20localize=20evo?= =?UTF-8?q?lution-summary=20strings=20+=20JS=20Chinese-leak=20regression?= =?UTF-8?q?=20test=20(rant=2021:19)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - app.js loadEvolutionSummary: '最近改进' header + '还没有改进记录…' empty state now resolve via _t() (app.recentImprovements / app.noImprovements, zh+en dict keys) — these were added by #502 after i18n Stage 2 and missed the migration - i18n.test.js: +2 tests — JS-side Chinese-leak scan (mirrors #507's index.html scan, excludes i18n.js dict/comments/functional regexes) + bilingual assertions - GUI 84 pass (82 + 2), Python 480 pass --- emrg/gui/renderer/js/app.js | 4 ++-- emrg/gui/renderer/js/i18n.js | 4 ++++ emrg/gui/test/i18n.test.js | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/emrg/gui/renderer/js/app.js b/emrg/gui/renderer/js/app.js index 0fd7997..267fb13 100644 --- a/emrg/gui/renderer/js/app.js +++ b/emrg/gui/renderer/js/app.js @@ -757,10 +757,10 @@ const App = (() => { const items = (res && res.recent) || []; recent.innerHTML = ""; if (items.length === 0) { - recent.appendChild(el("div", { class: "about-recent-item" }, "还没有改进记录,输入 /rant 驱动第一次进化吧")); + recent.appendChild(el("div", { class: "about-recent-item" }, _t("app.noImprovements"))); return; } - const header = el("div", { class: "about-recent-title" }, "最近改进"); + const header = el("div", { class: "about-recent-title" }, _t("app.recentImprovements")); recent.appendChild(header); for (const it of items) { const ts = String(it.timestamp || "").slice(0, 16).replace("T", " "); diff --git a/emrg/gui/renderer/js/i18n.js b/emrg/gui/renderer/js/i18n.js index 259efa2..be67610 100644 --- a/emrg/gui/renderer/js/i18n.js +++ b/emrg/gui/renderer/js/i18n.js @@ -225,6 +225,8 @@ const I18N = (() => { "app.workdirInvalid": "工作目录不可用,请到设置里改一下。", "app.bootFailed": "启动遇到了问题:{msg}", "app.needSession": "请先创建一个对话。", + "app.recentImprovements": "最近改进", + "app.noImprovements": "还没有改进记录,输入 /rant 驱动第一次进化吧", "app.cmdUnknown": "指令 {cmd} 暂未开放。", "app.cleared": "已清空当前对话。", "app.compacted": "已压缩当前对话历史。", @@ -485,6 +487,8 @@ const I18N = (() => { "app.workdirInvalid": "Working directory unavailable — update it in Settings.", "app.bootFailed": "Startup failed: {msg}", "app.needSession": "Start a conversation first.", + "app.recentImprovements": "Recent improvements", + "app.noImprovements": "No improvements recorded yet — type /rant to drive the first evolution", "app.cmdUnknown": "Command {cmd} is not available yet.", "app.cleared": "Current conversation cleared.", "app.compacted": "Conversation history compacted.", diff --git a/emrg/gui/test/i18n.test.js b/emrg/gui/test/i18n.test.js index bcf2b7a..46a5093 100644 --- a/emrg/gui/test/i18n.test.js +++ b/emrg/gui/test/i18n.test.js @@ -191,3 +191,38 @@ test("Stage3:新增静态键双语一致(growth-card title / about / status- assert.strictEqual(evalIn(zh, 'I18N.t("settings.recentTitle")'), "最近改进"); assert.strictEqual(evalIn(zh, 'I18N.t("sidebar.backToBottom")'), "回到底部"); }); + +// ── Stage 3b(cycle 20260806-230036):renderer JS 运行时中文字符串防漏网 ── +test("Stage3b:renderer JS 无未接入 i18n 的中文字符串(防 JS 侧回归)", () => { + // 扫描 renderer/js/*.js:含中文且非注释、非 i18n 取词、非词典、非功能正则的行 + const fs2 = require("node:fs"); + const path2 = require("node:path"); + const cjk = /[\u4e00-\u9fff]/; + const leaks = []; + for (const f of fs2.readdirSync(path.join(__dirname, "..", "renderer", "js"))) { + if (!f.endsWith(".js")) continue; + if (f === "i18n.js") continue; // 词典本身全是中文(合法) + const src = fs2.readFileSync(path.join(__dirname, "..", "renderer", "js", f), "utf8"); + for (const [i, line] of src.split("\n").entries()) { + let s = line.trim(); + if (!cjk.test(s)) continue; + // 剥离注释:行首 //、行尾 // 注释、/* */ 块 + s = s.replace(/^\/\/.*$/, "").replace(/^\*.*$/, "").replace(/ \/\/.*$/, "").replace(/\/\*.*?\*\//g, "").trim(); + if (!cjk.test(s)) continue; // 仅注释含中文 → 跳过 + if (s.includes("_t(") || s.includes("EMRG_I18N")) continue; // 已取词 + if (s.includes("i18n")) continue; // i18n 基础设施/注释 + if (s.includes("match(") || s.includes("replace(")) continue; // 功能正则(如文件路径提取) + if (s.includes("ensure_ascii")) continue; // 序列化说明 + leaks.push(`${f}:L${i + 1}: ${s.slice(0, 80)}`); + } + } + assert.strictEqual(leaks.length, 0, "renderer JS 存在未接入 i18n 的中文:\n" + leaks.join("\n")); +}); + +test("Stage3b:最近改进列表文案双语(#502 evolution_summary 侧)", () => { + const { ctx } = makeSandbox({ navigator: { language: "en-US" } }); + assert.strictEqual(evalIn(ctx, 'I18N.t("app.recentImprovements")'), "Recent improvements"); + assert.strictEqual(evalIn(ctx, 'I18N.t("app.noImprovements")'), "No improvements recorded yet — type /rant to drive the first evolution"); + const { ctx: zh } = makeSandbox({ navigator: { language: "zh-CN" } }); + assert.strictEqual(evalIn(zh, 'I18N.t("app.recentImprovements")'), "最近改进"); +});