/** * `web/i18n.js` 的品牌文案渲染回归(2026-09-19,档案 134 配套)。 * * 为什么单独钉这一条:品牌标识从 DeepSeek 图形换成了**文字**(中文「能力网络」(2026-09-19 * 定名)/英文及其他语言 `CapabilityNet`)。词条漏配或 key 写错时,i18n 会**静默回退英文** * —— 页面上看不出报错,只有中文用户看到英文名。这类"静默失败"必须靠测试钉住。 * * 做法:用 `node:vm` 跑**仓库里那份真实的 `web/i18n.js`**(不是复制一遍逻辑), * 配一个够用的最小 DOM 桩,断言六种语言解析路径下的**实际渲染结果**。 * * ⚠️ 桩里必须注入 `URLSearchParams` / `URL`:`vm` 上下文只含 ECMAScript 内置、 * 不含 Web API;少了它们 `new URLSearchParams(location.search)` 会抛 ReferenceError, * 被 i18n.js 的 try/catch 吞掉 ⇒ 表现为"`?lang=` 不生效"的**假失败**(第一版踩过)。 */ import { test } from 'node:test' import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import vm from 'node:vm' const SRC = readFileSync(new URL('../web/i18n.js', import.meta.url), 'utf8') /** 造一个够 i18n.js 用的最小元素桩(只需要 getAttribute / textContent)。 */ function makeEl(attrs = {}) { return { _attrs: { ...attrs }, textContent: attrs.__text ?? '', className: '', innerHTML: '', style: {}, getAttribute(name) { return Object.prototype.hasOwnProperty.call(this._attrs, name) ? this._attrs[name] : null }, setAttribute(name, value) { this._attrs[name] = value }, appendChild() {}, addEventListener() {}, querySelectorAll() { return [] }, } } /** 在最小 DOM 桩里跑一次 i18n.js,返回品牌节点的实际渲染结果与解析出的语言。 */ function renderBrand({ search = '', cookie = '', language = 'en-US' } = {}) { const brandNode = makeEl({ 'data-i18n': 'brand.name', __text: 'CapabilityNet' }) const sandbox = { console, setTimeout: () => {}, URLSearchParams, URL, document: { readyState: 'complete', cookie, documentElement: makeEl(), querySelectorAll(selector) { return selector === '[data-i18n]' ? [brandNode] : [] }, createElement: () => makeEl(), addEventListener: () => {}, }, navigator: { language, languages: [language] }, location: { search, href: `https://ai1net.com/login.html${search}`, reload: () => {} }, window: {}, } sandbox.window = sandbox vm.createContext(sandbox) vm.runInContext(SRC, sandbox, { filename: 'web/i18n.js' }) return { brand: brandNode.textContent, lang: sandbox.window.I18N.lang } } test('品牌文案:中文显示「能力网络」,英文及其他语言显示 CapabilityNet', () => { const cases = [ ['默认(浏览器 en-US)', { language: 'en-US' }, 'CapabilityNet'], ['中文(浏览器 zh-CN)', { language: 'zh-CN' }, '能力网络'], ['中文(?lang=zh)', { search: '?lang=zh', language: 'en-US' }, '能力网络'], ['英文(?lang=en 覆盖中文浏览器)', { search: '?lang=en', language: 'zh-CN' }, 'CapabilityNet'], ['cookie dsh_lang=zh', { cookie: 'dsh_lang=zh', language: 'en-US' }, '能力网络'], ['未支持语言(ja)⇒ 回退英文', { language: 'ja-JP' }, 'CapabilityNet'], ] for (const [label, opts, expected] of cases) { const got = renderBrand(opts) assert.equal(got.brand, expected, label) } }) test('品牌文案:不会退化成裸 key、不留空', () => { for (const language of ['en-US', 'zh-CN', 'ja-JP', 'de-DE']) { const { brand } = renderBrand({ language }) assert.notEqual(brand, 'brand.name', `裸 key 泄露(${language})`) assert.ok(brand.trim() !== '', `空文案(${language})`) } })