---
description: "一层薄薄的 i18n 缝：一份按 locale 分好的字典 + 一个把 active locale 放在 React state 的 I18nProvider + 一个 t(key, vars?)。UI 字符串走 t()，日期、数字、货币一律交给 Intl.* —— locale 一变，整页跟着刷新"
---

# i18n（多语言 + 日期/数字/货币本地化）

一层薄薄的 i18n 缝：一份按 locale 分好的字典 + 一个把 active locale 放在 React state 的 `I18nProvider` + 一个 `t(key, vars?)`。UI 字符串走 `t()`，日期、数字、货币一律交给 `Intl.*` —— locale 一变，整页跟着刷新。

<iframe src="/skills/dashboard/assets/demo-i18n.html"
        style="width:100%;height:640px;border:1px solid #8884;border-radius:10px"
        loading="lazy" title="形状 demo：i18n"></iframe>

试这几下：

- 点右上角**中文 / English** —— 顶栏、卡片、表头、日期、金额一次性刷新，右下角 toast 提示
- 切到 **English** 看 promo 那一行 —— 英文字典里故意没这条 key，回落到中文并显示灰色「缺译回退 · zh」小徽标
- 日期在中文下是 `2026年7月6日 09:14`，英文下是 `Jul 6, 2026, 09:14`；货币中文是 `¥1,234.56`，英文换算成 `$172.67`
- 刷新 iframe，语言**不丢**（写在 `localStorage["demo-i18n.locale"]`）；点**恢复默认**先弹确认，确认后清空偏好回到中文

## 规矩

- **active locale 只放在 state 里**：`useState` 初值函数里 `typeof window !== "undefined"` 后再读 localStorage，模块顶层不碰 `window`（SSR 安全）。写入放到 `useEffect` 里
- **字典键 en/zh 对齐**：真实版靠 `satisfies Record<Locale, …>` + `TranslationKey` 类型让漏译在编译期就冒出来；demo 没类型，靠一个 `missing(key)` 助手把回退可视化
- **回退链固定**：`dict[key] ?? dictionaries[defaultLocale][key] ?? key`。回退目标永远是**声明期的** `defaultLocale`，不受 provider 的初始 locale 影响
- **插值只支持 `{var}`**，值 `String()` 一下；不做复数、性别、ICU MessageFormat —— 那是 i18next / vue-i18n 的活
- **日期 / 数字 / 货币走 Intl.***：数据里存 ISO 时间 + 基准货币金额，渲染时用当前 locale 的 formatter 出结果；不在字典里塞 `"¥{amount}"` 这种模板
- 真实版与 demo 的差：demo 是自造字典 + `createContext`；真实版换成 `i18next` / `vue-i18n`，多语言拆成按需加载的 JSON。持久化 demo 用 `localStorage`，真实版初始 locale 通常读 `Accept-Language` header 或 cookie（SSR 场景必须在服务端就定住，否则会 hydration 闪烁）

> 蓝本：`add-i18n.md`[^add-i18n]（open-dashboard @ `aa9815f`，MIT，Invariants 已消化进上面「规矩」）

??? abstract "demo 源码：`assets/demo-i18n.html`（自包含、未压缩）"

    ```html
    --8<-- "skills/dashboard/assets/demo-i18n.html"
    ```

[^add-i18n]: [`add-i18n.md`](https://github.com/ahpxex/open-dashboard/blob/aa9815f426e9520841c4aa15a341bfd9f866091f/.claude/skills/add-component/references/add-i18n.md)，2026-07-28 读。
