Skip to content

国际化与主题

回答的问题:四种语言与三档主题在「共享库」和「各站」之间怎么分工,为什么这样分, 以及那条防止文案回显 key 的自动检查在防什么。

1. 宿主契约

ui/ 里的组件不自带 theme store、不自己决定当前语言。这些归 app 所有,由每个 app 在启动时调一次 setUiHost() 注入:

ts
import { setUiHost } from '$ui';
setUiHost({ locale, theme });   // 传对象引用,不要解构快照

未注入时组件仍可渲染(英文、亮色兜底),因此孤立渲染与首帧不会崩——但正常 app 必须注入。

必须传引用而非解构快照。 库侧的 locale / theme 是逐层读穿到宿主对象的 getter,在组件的反应上下文里读 locale.code / theme.mode 才能被追踪。解构会切断 $state 反应性,表现为用户切语言或切主题时,库内的图表不重绘。

2. 字典归库,语言信号归宿主

这条分工是整个设计的核心,两边各有理由:

文案字典自带在库里ui/i18n.svelte.ts)。因为 topo.*fstat.*routing.*pflap.* 这些命名空间是组件自有语义,不是应用语义。若改成由宿主注入,每个借用 组件的 app 都得把同一批 key × 四种语言抄进自己的字典——组件搬得越多负担越重。自带则 相反:加一个组件只动一个文件。

当前语言由宿主注入。库和 app 各持一个 locale,会在用户切换语言时不同步。

通用词汇留在 app 字典里,库通过 UiHost.t 回落读取。common.updatedAtcommon.noMatch 这类词 app 自己也在到处用,两边各存一份会出现「同一个词在同屏两处 渲染,只改了一边」的自相矛盾。回落是双向的:库的 t() 查不到就找宿主,app 的 t() 查不到就找库。

3. 各站的语言覆盖并不相同

语言存储键切换入口
controlen / zh / zh-Hant / jadn42.locale顶栏
peeringen / zhnatlan.peering.locale顶栏
authen / zh / zh-Hant / jaapps/auth/src/lib/i18n.svelte.ts页内
home固定 en

首页注入的是两个常量而不是响应式实例——它是纯静态页,既没有语言切换器,也没有 theme store。宿主契约做成可选注入,正是为了容纳这种场景。

4. 完整性检查

npm run check:i18nscripts/check-i18n.mjs)在 CI 里守两条规则:

  1. 库组件用到的 key 必须在库字典里ALLOW_HOST 名单里的通用词汇除外。
  2. app 用到的 key 必须在「自己字典 ∪ 库字典」里

动态 key(t(\ns.${x}`)` 形式)只校验前缀:该前缀下有任意条目即算通过,逐个枚举 取值超出静态分析的能力范围。

这条检查防的是一类不会报错的回退:把组件搬进共享库时,它用的 key 还留在原 app 的 字典里,于是另一边整块回显 pflap.xxx。构建不报错,类型检查也不报错,只有真的打开 页面才看得见——这个 bug 咬过两次,两次都是靠截图发现的,因此固化成一条能自动跑的检查。

5. 主题的两条投递路径

设计令牌定义在 ui/styles/app.css,暗色值出现两次,这是有意的:

选择器服务谁触发方式
:root[data-theme='dark']control、peeringapp.html 的预绘制脚本读 localStorage 后盖标记
@media (prefers-color-scheme: dark) { :root:not([data-theme]) }auth系统色,无需 JS

控制台与门户由 app.html 里的一段预绘制脚本恒定盖上 data-theme(顺带设 color-scheme,让浏览器立刻把画布画成暗色,避免首屏白闪),因此 :not([data-theme]) 在那两站永不命中,媒体查询对它们零影响。

授权页从不盖标记——它是一次性流程,没有导航也没有设置入口,主题直接跟随系统——于是 只走媒体查询这一条。四种「系统色 × 存储偏好」组合都实测过。

⚠️ 两处令牌值必须逐字一致,改一处就得改两处。 纯 CSS 没有不重复的写法(媒体查询 里的选择器合并不进外面的规则)。light-dark() 能消掉这份重复,但需要把整套令牌改写 成双值形式,那是独立的一次改造。

首页不引 ui/styles/app.css:它是自包含单页,令牌内联在 index.html 里,并额外定义 了台地灰阶(--mesa-far/-mid/-near)这组只有它用的令牌。值与共享层保持同源,口径见 reference/design-tokens.md

6. 主题状态机

ThemePref 是三档 light | dark | systemThemeMode 是解析后的两档 light | darksystem 档会持续跟踪 prefers-color-scheme 的变化(注册 change 监听),不需要 刷新页面。注入给库的是解析后的 mode

图表只把 theme.mode重绘信号——颜色本身走 CSS 变量运行时解析,不在 JS 里硬编码。