# i18n 设计 — 方案 A(zh-CN + en · 路径前缀) 日期:2026-09-24 状态:已批准(用户选 A;追加 `/` 系统语言检测跳转) ## 目标 - 中文:`/zh-CN`、`/zh-CN/dashboard`(**中文也带前缀,跳 `/zh-CN`,不留在裸 `/`)** - 英文:`/en`、`/en/dashboard` - 访问 **`/`** 时:有 `app-lang` cookie 按 cookie 跳 `/zh-CN` 或 `/en`;否则按 `Accept-Language` → 中文 `/zh-CN`、英文 `/en` - 用户手动切换语言写入 cookie,优先于系统语言 - 文案集中在 `@app/i18n`;Astro / React 共用同一套静态 messages(无 i18next、无 Context 强绑) ## 非目标 - 不改 tRPC / DAO / DB - 不引入 i18next / astro-i18next - 第一阶段不做复数 ICU、后端按 locale 存文案 ## 包结构 ``` packages/i18n/ package.json # @app/dao 风格:纯 TS,无运行时重依赖 src/ locales.ts # LOCALES、DEFAULT_LOCALE、Locale 类型 cookie.ts # COOKIE_LANG = "app-lang" accept-language.ts # parsePreferredLocale(header) messages/ zh-CN.ts # 扁平命名空间对象(权威 key 源) en.ts # 必须 keyof zh-CN t.ts # t(locale, key, params?) 简单 {name} 插值 index.ts AGENTS.md ``` - `type MessageKey = keyof typeof zhCN` - `t` 缺 key 编译期失败(en 与 zh 同构) - 无 IO、无 env;与 seo-geo 同为纯函数包 ## 路由与 Middleware(@app/web) ``` astro.config.ts i18n: { defaultLocale: "zh-CN", locales: ["zh-CN", "en"], routing: { prefixDefaultLocale: true, redirectToDefaultLocale: false } } src/middleware.ts GET 且 path === "/" cookie app-lang=en → redirect /en cookie app-lang=zh-CN → redirect /zh-CN 无 cookie: Accept-Language 偏好 en → /en 否则(含 zh) → /zh-CN 响应 Vary: Accept-Language src/pages/ [locale]/index.astro | dashboard.astro # 单套动态前缀(locale 校验) views/home.astro | dashboard.astro # 双语共用 ``` 不再使用 `pages/zh-CN` / `pages/en` 双目录;也不依赖 Astro 文件式 i18n 双份路由。 语言切换:链接到对侧 URL + `document.cookie = app-lang=...; path=/; max-age=...` (island `LocaleSwitch` 或 header 内联均可) ## 文案分层 | 层 | 做法 | |----|------| | Astro 布局/页面 | `t(locale, "nav.home")` | | React island | props 传入 `locale`,组件内 `t(locale, ...)` 或传入已解析文案 | | tools 目录 | `category` 改为稳定 id(`text\|code\|dev\|net\|image`),显示名走 i18n | | SEO | `siteSeo` 按 locale 构造;sitemap 每 URL 填 `alternates`(`zh-CN` ↔ `en`) | ## SEO - ``:`zh-CN` / `en` - hreflang:`x-default` → `/...`;`zh-CN` → `/...`;`en` → `/en/...` - `lib/seo.ts` 提供 `getSiteSeo(locale)`,避免写死单 locale - llms.txt / sitemap 至少覆盖双语文档首页(工具页路径可后补 en 副本) ## 验收 1. `Accept-Language: en` 访问 `/` → 302 `/en` 2. `Accept-Language: zh-CN` 访问 `/` → 200 中文 3. cookie `app-lang=en` 访问 `/` → 302 `/en`(覆盖系统语言) 4. `/en` 与 `/` 文案、`lang`、导航一致切换 5. `yarn typecheck` / `yarn build` 通过