93 lines
3.2 KiB
Markdown
93 lines
3.2 KiB
Markdown
# 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
|
||
|
||
- `<html lang>`:`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` 通过
|