主题
主题与国际化系统
本文引用的源码文件
本文引用的文件
- src/shared/theme.css
- src/shared/theme.js
- scripts/theme-dark-audit.mjs
- scripts/theme-map.json
- scripts/theme-contrast-check.py
- src/shared/i18n.js
- src/shared/locales/zh.js
- src/shared/locales/en.js
- agent/src/i18n.js
- agent/src/sys-lang.js
- agent/src/locales/zh.json
- agent/src/locales/en.json
- _locales/en/messages.json
- _locales/zh_CN/messages.json
目录
引言
本文件系统性梳理 AI Helper 的主题与国际化体系,覆盖以下目标:
- 解释 CSS 变量 Token 体系、设计令牌定义、主题切换机制与动态样式更新。
- 说明亮/暗主题实现原理,包括颜色映射、对比度检查与可访问性保证。
- 描述扩展端与 Agent 端的国际化框架,包括消息文件组织、运行时语言切换与复数形式处理策略。
- 给出翻译工作流程:翻译文件维护、缺失键检测与自动同步建议。
- 提供添加新主题变体、实现多语言界面的具体实践路径。
- 说明主题审计工具使用方法,包括自动化检测、合规报告和改进建议。
- 总结 i18n 最佳实践:文案提取、上下文管理与性能优化。
- 说明浏览器原生 API 的使用,包括 Intl API 和本地化数据处理。
本项目为双进程架构:Chrome 扩展端(Manifest V3,src/)与独立本地代理端 Agent(Node.js,agent/src/server.js)。两端版本号保持同步,文档以 2.3.1 为准。
项目结构
主题与国际化相关代码分布在以下位置:
- 主题 Token 与运行时:src/shared/theme.css、src/shared/theme.js
- 主题审计与迁移脚本:scripts/theme-dark-audit.mjs、scripts/theme-contrast-check.py、scripts/theme-map.json
- 扩展端国际化:src/shared/i18n.js、src/shared/locales/
- Agent 端国际化:agent/src/i18n.js、agent/src/sys-lang.js、agent/src/locales/
- Chrome 扩展元数据消息:_locales/{en,zh_CN}/messages.json
graph TB subgraph "扩展端" TCSS["theme.css<br/>Token 定义"] TJ["theme.js<br/>主题读写与同步"] I18N["i18n.js<br/>扩展端国际化"] LOCALES_ZH["locales/zh.js"] LOCALES_EN["locales/en.js"] end subgraph "Agent 端" AI18N["i18n.js<br/>Agent 端国际化"] SYS_LANG["sys-lang.js<br/>系统语言探测"] A_LOCALES_ZH["locales/zh.json"] A_LOCALES_EN["locales/en.json"] end subgraph "脚本与门禁" AUDIT["theme-dark-audit.mjs<br/>动态对比度审计"] CONTRAST["theme-contrast-check.py<br/>静态对比度门禁"] MAP["theme-map.json<br/>色值→Token 映射"] end subgraph "Chrome 消息" M_EN["_locales/en/messages.json"] M_ZH["_locales/zh_CN/messages.json"] end TCSS --> TJ TJ --> AUDIT TJ --> CONTRAST TJ --> MAP I18N --> LOCALES_ZH I18N --> LOCALES_EN AI18N --> SYS_LANG AI18N --> A_LOCALES_ZH AI18N --> A_LOCALES_EN M_EN -.-> I18N M_ZH -.-> I18N
图表来源
- src/shared/theme.css#L1-L289
- src/shared/theme.js#L1-L90
- scripts/theme-dark-audit.mjs#L1-L257
- scripts/theme-contrast-check.py#L1-L114
- scripts/theme-map.json#L1-L424
- src/shared/i18n.js#L1-L251
- src/shared/locales/zh.js#L1-L800
- src/shared/locales/en.js#L1-L800
- agent/src/i18n.js#L1-L100
- agent/src/sys-lang.js#L1-L30
- agent/src/locales/zh.json#L1-L368
- agent/src/locales/en.json#L1-L368
- _locales/en/messages.json#L1-L12
- _locales/zh_CN/messages.json#L1-L12
章节来源
- src/shared/theme.css#L1-L289
- src/shared/theme.js#L1-L90
- src/shared/i18n.js#L1-L251
- agent/src/i18n.js#L1-L100
核心组件
- 主题 Token 层:通过 CSS 变量集中定义语义色,区分亮/暗两套值,所有界面颜色只引用 token,禁止硬编码色值。
- 主题运行时:读取用户偏好(light/dark/system),计算生效值并写入 <html data-theme>,订阅者监听变更刷新 UI。
- 主题审计与门禁:静态脚本校验 token 对比度;Playwright 探针在真实渲染页面中检测对比度不达标与“白斑”。
- 扩展端 i18n:同步 t(key, params),支持运行时语言切换、跨环境同步、DOM 属性批量填充。
- Agent 端 i18n:解析 Accept-Language,按权重匹配语言,提供 t(lang, key, params) 与 getRequestI18n(req)。
- Chrome 消息:_locales 下 messages.json 用于扩展名、描述、命令等元数据国际化。
章节来源
- src/shared/theme.css#L1-L289
- src/shared/theme.js#L1-L90
- scripts/theme-dark-audit.mjs#L1-L257
- scripts/theme-contrast-check.py#L1-L114
- src/shared/i18n.js#L1-L251
- agent/src/i18n.js#L1-L100
- _locales/en/messages.json#L1-L12
- _locales/zh_CN/messages.json#L1-L12
架构总览
主题与国际化在扩展端与 Agent 端分别运行,但共享一致的语义约定:
- 主题:Token 定义在 theme.css,运行时由 theme.js 控制 data-theme,审计脚本保障 WCAG 可达性。
- 国际化:扩展端使用 src/shared/i18n.js 与 locales/.js;Agent 端使用 agent/src/i18n.js 与 locales/.json;Chrome 元数据使用 _locales/messages.json。
sequenceDiagram participant User as "用户" participant ThemeJS as "theme.js" participant CSS as "theme.css" participant Audit as "theme-dark-audit.mjs" participant I18NExt as "i18n.js(扩展端)" participant I18NAg as "i18n.js(Agent 端)" User->>ThemeJS : 设置主题偏好 light/dark/system ThemeJS->>ThemeJS : resolve(preference) ThemeJS->>Document : 写入 data-theme Note over ThemeJS,CSS : CSS 变量随 data-theme 切换 Audit->>Document : 遍历已渲染元素测对比度/白斑 Audit-->>Audit : 输出违规清单或审计通过 User->>I18NExt : 切换语言 zh/en I18NExt->>I18NExt : setLanguage + storage.onChanged I18NExt->>DOM : applyI18n 刷新文案 I18NExt->>I18NAg : (Agent 端独立,按请求头解析语言)
图表来源
- src/shared/theme.js#L1-L90
- src/shared/theme.css#L1-L289
- scripts/theme-dark-audit.mjs#L1-L257
- src/shared/i18n.js#L1-L251
- agent/src/i18n.js#L1-L100
详细组件分析
主题 Token 体系与动态样式更新
- Token 定义集中在 :root 与 html[data-theme="dark"] 两块,语义命名如 --bg-page、--text-primary、--brand-500、--danger-soft-bg 等。
- 运行时通过 theme.js 的 preference/resolved 状态机:
- getTheme() 返回用户偏好;getResolvedTheme() 返回实际生效值。
- initTheme() 初始化 matchMedia 监听系统主题变化,注册 chrome.storage.onChanged 跨环境同步。
- setTheme(pref) 校验合法值后写入存储并触发 apply() 更新 DOM 属性。
- 防闪烁:模块顶层同步读取 localStorage 镜像 appThemeResolved,先落一次 data-theme,再异步从 chrome.storage.local 恢复正式值。
flowchart TD
Start(["调用 setTheme(pref)"]) --> Validate{"pref 是否合法?"}
Validate --> |否| Return["忽略无效值"]
Validate --> |是| Apply["apply(resolve(pref))"]
Apply --> SetAttr["documentElement.setAttribute('data-theme', resolved)"]
Apply --> SaveStorage["chrome.storage.local.set({appTheme})"]
Apply --> Notify["通知订阅者 cb(resolved)"]
Notify --> End(["完成"])
图表来源
章节来源
亮/暗主题实现原理与可访问性保证
- 颜色映射:亮/暗两套 token 值,文本、背景、品牌、状态、边框、阴影、浮层等全面覆盖;部分固定不翻转(如 --mark-bg、--paper、--code-bg/--code-fg)。
- 对比度门禁:
- 静态:scripts/theme-contrast-check.py 解析 theme.css 暗色块,对文本类阈值 4.5:1、图形类 3:1 进行 WCAG 校验。
- 动态:scripts/theme-dark-audit.mjs 用 Playwright 加载 dist 扩展,侧边栏与配置页切 dark,逐个面板展开,遍历已渲染元素实测前景/背景合成后的对比度,并检测“白斑”(近纯白大块背景)。
- 可访问性:遵循 WCAG 1.4.3 文本对比度与 1.4.11 非文本对比度;暗色下原生控件 accent-color 统一品牌强调色;markdown-body 在暗色下覆盖默认亮色库样式。
flowchart TD
LoadCSS["加载 theme.css"] --> ParseDark["解析暗色 token 集合"]
ParseDark --> Classify["分类:文本/图形/装饰性分隔"]
Classify --> CalcContrast["按 WCAG 公式计算对比度"]
CalcContrast --> Threshold{"是否低于阈值?"}
Threshold --> |是| Fail["[AA FAIL] 记录未达标项"]
Threshold --> |否| Pass["通过"]
Fail --> Report["输出报告并退出码非零"]
Pass --> Done["门禁通过"]
图表来源
章节来源
- src/shared/theme.css#L1-L289
- scripts/theme-contrast-check.py#L1-L114
- scripts/theme-dark-audit.mjs#L1-L257
主题审计工具使用方法
- 前置条件:先执行 npm run build:silent 构建 dist。
- 动态审计:node scripts/theme-dark-audit.mjs [--keep]
- 启动 Chromium 持久化上下文,加载 dist 扩展。
- 将 side_panel.html 与 options.html 切到 dark,逐个面板/Tab 展开,注入审计函数遍历元素。
- 输出“白斑”与“对比度不达标”清单;存在违规则 exit 1。
- 静态审计:python3 scripts/theme-contrast-check.py src/shared/theme.css
- 仅校验 theme.css 暗色 token 对比度,不依赖渲染。
- 色值迁移:scripts/theme-map.json 维护 direct/contextDispatch/rgbaFamilies/bands/whitelist,辅助迁移与审查。
sequenceDiagram
participant Dev as "开发者"
participant Audit as "theme-dark-audit.mjs"
participant PW as "Playwright"
participant Ext as "dist 扩展"
Dev->>Audit : node scripts/theme-dark-audit.mjs
Audit->>PW : launchPersistentContext(load-extension=dist)
PW->>Ext : 打开 side_panel.html / options.html
Audit->>Ext : setDark(appTheme='dark')
Audit->>Ext : evaluate(AUDIT_FN) 遍历元素
Ext-->>Audit : {violations, blotches}
Audit-->>Dev : 输出报告/exit 码
图表来源
章节来源
- scripts/theme-dark-audit.mjs#L1-L257
- scripts/theme-contrast-check.py#L1-L114
- scripts/theme-map.json#L1-L424
扩展端国际化框架
- 语言包:src/shared/locales/zh.js 与 en.js,按模块分组,key 点分访问(如 common.confirm)。
- 运行时:
- t(key, params):当前语言查找 → 英文 fallback → 返回 key 本身便于发现遗漏。
- setLanguage(lang, persist=true):写入 chrome.storage.local,通知订阅者刷新。
- initI18n():从 storage 读取语言偏好,未设置时根据浏览器语言自动检测并持久化;注册 storage.onChanged 跨环境同步。
- applyI18n(root):批量填充 data-i18n、data-i18n-placeholder、data-i18n-title、data-i18n-aria-label。
- 动态注册:registerTranslations(lang, dict) 允许模块按需追加文案。
classDiagram
class I18NExt {
+SUPPORTED_LANGUAGES
+DEFAULT_LANGUAGE
+t(key, params) string
+getLanguage() string
+setLanguage(lang, persist) void
+subscribe(cb) function
+registerTranslations(lang, dict) void
+initI18n() Promise~string~
+applyI18n(root) void
}
class LocalesZH {
<<module>>
}
class LocalesEN {
<<module>>
}
I18NExt --> LocalesZH : "导入"
I18NExt --> LocalesEN : "导入"
图表来源
章节来源
Agent 端国际化框架
- 语言包:agent/src/locales/zh.json 与 en.json,按 error/rag/message/auth/exec/security/trash/search/skill/mcp/server/memory 等模块组织。
- 语言探测:
- sys-lang.js:优先读取 LANGUAGE/LC_ALL/LC_MESSAGES/LANG 环境变量,其次 Intl.DateTimeFormat().resolvedOptions().locale,最终回退 'en'。
- i18n.js:DEFAULT_LANG = env.AI_HELPER_LANG > detectSystemLang();parseAcceptLanguage(req.headers['accept-language']) 按 q 权重排序,精确匹配或前缀匹配(如 zh-CN → zh)。
- 翻译函数:
- t(lang, key, params):点分 key 查找,找不到回退 DEFAULT_LANG,仍无则返回 key;params 支持 {name} 插值。
- getRequestI18n(req):封装 lang 与 t 闭包供路由使用。
flowchart TD
Req["HTTP 请求"] --> Parse["parseAcceptLanguage(header)"]
Parse --> Match{"是否匹配 zh/en?"}
Match --> |是| UseLang["使用匹配语言"]
Match --> |否| Fallback["回退 DEFAULT_LANG"]
UseLang --> T["t(lang, key, params)"]
Fallback --> T
T --> Interp["参数插值 {name}"]
Interp --> Resp["返回本地化字符串"]
图表来源
章节来源
- agent/src/i18n.js#L1-L100
- agent/src/sys-lang.js#L1-L30
- agent/src/locales/zh.json#L1-L368
- agent/src/locales/en.json#L1-L368
翻译工作流程与维护建议
- 文件组织:
- 扩展端:src/shared/locales/{zh.js,en.js},按模块分组,key 点分访问。
- Agent 端:agent/src/locales/{zh.json,en.json},JSON 扁平对象。
- Chrome 元数据:_locales/{en,zh_CN}/messages.json。
- 缺失键检测:
- 扩展端 t(key) 在未找到时返回 key 本身,便于在 UI 上直接暴露遗漏。
- Agent 端 t(lang,key,params) 同样回退 key,测试用例可断言渲染结果不含残留占位符。
- 自动同步建议:
- 基于 theme-map.json 的模式,建立 i18n-key-map.json,扫描源码中 t() 调用与 data-i18n* 属性,生成待补充键列表。
- CI 阶段比较 zh.js/en.js 与 zh.json/en.json 的 key 集,差异即告警。
- 复数形式处理:
- 当前实现未内置 ICU 复数规则;建议在模板层按 count 分支选择不同 key(如 item_one/item_other),或使用轻量 pluralizer 包装 t()。
章节来源
- src/shared/i18n.js#L86-L96
- agent/src/i18n.js#L65-L86
- src/shared/locales/zh.js#L1-L800
- src/shared/locales/en.js#L1-L800
- agent/src/locales/zh.json#L1-L368
- agent/src/locales/en.json#L1-L368
添加新主题变体的步骤
- 在 theme.css 新增主题选择器(例如 html[data-theme="custom"]),复制现有亮/暗 token 结构并调整色值。
- 在 theme.js 的 VALID 数组中添加新变体,并在 resolve() 中处理 system/custom 逻辑。
- 在 theme-map.json 的 whitelist 中登记必要例外色值(若引入新的固定色)。
- 运行 theme-contrast-check.py 与 theme-dark-audit.mjs 验证对比度与渲染合规。
章节来源
- src/shared/theme.css#L1-L289
- src/shared/theme.js#L1-L90
- scripts/theme-map.json#L1-L424
- scripts/theme-contrast-check.py#L1-L114
- scripts/theme-dark-audit.mjs#L1-L257
实现多语言界面的步骤
- 在 zh.js 与 en.js 中新增对应 key 与文案。
- 在需要国际化的 DOM 元素上添加 data-i18n、data-i18n-placeholder、data-i18n-title、data-i18n-aria-label。
- 在页面加载或语言切换后调用 applyI18n(root) 刷新文案。
- 如需动态文案,使用 registerTranslations(lang, dict) 追加模块级文案。
章节来源
- src/shared/i18n.js#L214-L238
- src/shared/i18n.js#L142-L157
- src/shared/locales/zh.js#L1-L800
- src/shared/locales/en.js#L1-L800
浏览器原生 API 的使用
- 主题:matchMedia('(prefers-color-scheme: dark)') 监听系统主题变化。
- 国际化:
- 扩展端:chrome.i18n.getUILanguage() 用于浏览器 UI 语言检测。
- Agent 端:Intl.DateTimeFormat().resolvedOptions().locale 作为系统语言回退。
- 本地化数据处理:Agent 端 parseAcceptLanguage 解析 Accept-Language 头部,按 q 权重排序并支持前缀匹配。
章节来源
- src/shared/theme.js#L54-L60
- src/shared/i18n.js#L31-L38
- agent/src/sys-lang.js#L14-L29
- agent/src/i18n.js#L32-L56
依赖关系分析
- 主题依赖:
- theme.js 依赖 chrome.storage.local 与 matchMedia。
- theme-dark-audit.mjs 依赖 Playwright 与 dist 产物。
- theme-contrast-check.py 依赖 Python 与 theme.css。
- 国际化依赖:
- 扩展端 i18n.js 依赖 locales/*.js 与 chrome.storage.local。
- Agent 端 i18n.js 依赖 locales/*.json 与 sys-lang.js。
- Chrome 消息 _locales/messages.json 由扩展框架加载。
graph LR ThemeJS["theme.js"] --> Storage["chrome.storage.local"] ThemeJS --> MQ["matchMedia"] Audit["theme-dark-audit.mjs"] --> PW["Playwright"] Contrast["theme-contrast-check.py"] --> CSS["theme.css"] I18NExt["i18n.js(扩展端)"] --> Locales["locales/*.js"] I18NAg["i18n.js(Agent 端)"] --> SysLang["sys-lang.js"] I18NAg --> ALocales["locales/*.json"] ChromeMsg["_locales/messages.json"] --> ExtMeta["扩展元数据"]
图表来源
- src/shared/theme.js#L1-L90
- scripts/theme-dark-audit.mjs#L1-L257
- scripts/theme-contrast-check.py#L1-L114
- src/shared/i18n.js#L1-L251
- agent/src/i18n.js#L1-L100
- agent/src/sys-lang.js#L1-L30
章节来源
- src/shared/theme.js#L1-L90
- scripts/theme-dark-audit.mjs#L1-L257
- scripts/theme-contrast-check.py#L1-L114
- src/shared/i18n.js#L1-L251
- agent/src/i18n.js#L1-L100
- agent/src/sys-lang.js#L1-L30
性能考虑
- 主题切换:
- 防闪烁:先用 localStorage 镜像立即打标 data-theme,避免首屏闪白。
- 订阅者仅在 resolved 真正变化时触发,避免重复刷新。
- 国际化:
- t() 采用点分查找与简单正则插值,开销低;fallback 链短(当前语言 → 英文 → key)。
- applyI18n 使用 querySelectorAll 批量操作,适合页面加载与语言切换后一次性刷新。
- 审计:
- 静态对比度检查在 CI 中快速失败;动态审计仅在必要时运行,避免频繁 Playwright 开销。
[本节为通用指导,不直接分析具体文件]
故障排查指南
- 主题问题:
- 现象:暗色下文字不可读或出现白斑。
- 排查:运行 theme-dark-audit.mjs 查看违规清单;检查 theme.css 暗色 token 是否满足 WCAG;确认 HTML data-theme 是否正确。
- 国际化问题:
- 现象:UI 显示原始 key 或英文 fallback。
- 排查:确认 zh.js/en.js 中存在对应 key;检查 data-i18n* 属性是否正确;确认 initI18n() 已调用且 storage.onChanged 正常。
- Agent 端语言错误:
- 现象:API 响应文案未按预期语言返回。
- 排查:检查 Accept-Language 头部;确认 sys-lang.js 与 i18n.js 的 DEFAULT_LANG 与 SUPPORTED_LANGS;验证 locales/*.json 中 key 是否存在。
章节来源
- scripts/theme-dark-audit.mjs#L1-L257
- scripts/theme-contrast-check.py#L1-L114
- src/shared/i18n.js#L168-L207
- agent/src/i18n.js#L18-L24
结论
本项目通过 CSS 变量 Token 体系与 theme.js 运行时实现了稳定、可审计的亮/暗主题切换;通过扩展端与 Agent 端两套 i18n 模块覆盖了 UI 与后端文案需求;配合静态与动态审计脚本确保可访问性与渲染质量。建议在后续迭代中完善复数形式处理、建立 i18n key 自动同步门禁,并持续优化主题 Token 的可维护性与扩展性。
[本节为总结,不直接分析具体文件]
附录
- 最佳实践清单:
- 主题:始终使用 token,避免硬编码色值;新增主题需过静态与动态审计。
- 国际化:文案模块化分组;使用 data-i18n* 声明式绑定;t() 调用处保留 key 以便发现遗漏。
- 性能:减少 applyI18n 调用频率;避免在高频回调中重复查询 DOM。
- 可访问性:遵循 WCAG 文本与非文本对比度;暗色下原生控件 accent-color 统一。
- 浏览器 API:合理使用 matchMedia、chrome.i18n、Intl API。
[本节为通用指导,不直接分析具体文件]