> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vibestrap.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# 双语内容与 i18n

> Vibestrap 如何处理中英文文案、MDX 变体与按语言查找。

Vibestrap 开箱即支持中英双语。两套系统协作：`next-intl` 负责 UI 字符串（在
`messages/` 下），加上一层薄薄的命名约定负责 MDX 内容（在 `content/` 下）。
熟悉这套规则后，写两遍就行，不需要额外仪式。

## 前置条件

无。管线都接好了，你只管加文件。

## 路由如何识别语言

路由策略是 `as-needed`（见 `src/i18n/routing.ts`）：

* 英文（默认）走根路径：`/docs/quickstart`
* 中文加前缀：`/zh/docs/quickstart`
* 语言列表来自 `siteConfig.i18n.locales`（`['en', 'zh']`）

每个带 locale 前缀的页面必须在顶部调用 `setRequestLocale(locale)`——
不调的话 server component 会退回到默认语言，所有 `t(...)` 都默默用错语言。

```tsx theme={null}
const { locale } = await params;
setRequestLocale(locale);
```

## MDX 命名约定

每篇内容两个文件并排放：

```
content/blog/welcome-to-vibestrap.mdx       # 英文
content/blog/welcome-to-vibestrap.zh.mdx    # 中文
```

`.zh.mdx` 后缀是唯一的信号——没有单独的目录，没有 JSON 清单。
`src/lib/source-helpers.ts` 里的 helper 会去掉后缀算出 canonical slug。

## 按语言查找

三个 helper 覆盖所有场景：

```ts theme={null}
import { pickByLocale, findBySlug, canonicalSlugs } from '@/lib/source';

// 全部博客，按当前语言过滤（zh 缺失时自动回落到 en）
const posts = pickByLocale(blog.docs, locale);

// 按 slug 找单篇
const post = findBySlug(blog.docs, slug, locale);

// generateStaticParams 用的 slug 列表（只取 en 文件）
const slugs = canonicalSlugs(blog.docs);
```

`pickByLocale('zh')` 优先返回 `.zh.mdx`，没有就回落到英文版——
所以 `/zh/` 永远不会因为缺翻译而 404。

## UI 字符串——两份文件，同一套 key

翻译放在 `messages/en.json` 与 `messages/zh.json`。两份文件的 key 结构必须一致，
靠 `scripts/check-i18n.mjs` 做结构 diff。

```tsx theme={null}
import { useTranslations } from 'next-intl';

const t = useTranslations('Home.hero');
return <h1>{t('title')}</h1>;
```

往 `en.json` 加新 key 时，请同时在 `zh.json` 同位置加上——
哪怕中文是占位符。否则 validator 会报错。

## 新增一种语言

1. 追加到 `siteConfig.i18n.locales`（如 `['en', 'zh', 'ja']`）。
2. 创建 `messages/ja.json`，结构镜像 `en.json`。
3. 任何想翻译的 MDX，加上 `slug.ja.mdx`。
4. 如果要非 en 的回落策略，在 `pickByLocale` 里加分支。

完事——路由会自动识别新语言。

## 验证一下

```bash theme={null}
node scripts/check-i18n.mjs   # 结构 diff + 字面 t() 校验
pnpm typecheck                # 类型 OK
pnpm dev                      # 访问 /docs 和 /zh/docs
```

Validator 会报告只在一份文件里存在的 key，以及那些在当前命名空间下找不到的
`t('foo.bar')` 字面调用。

## 常见坑（5 个）

1. **忘写 `.zh.mdx`**——`/zh/` 页面悄悄回落到英文。开发时切到中文模式手动点一遍才能发现。
2. **两份文件的日期对不上**——更新英文博客时记得把 `.zh.mdx` 的 date 也改了，
   否则首页排序顺序会分叉。
3. **模板字符串 `t()` 调用**——`t(\`Foo.\${dyn}\`)\` 没法静态校验，
   validator 只能检查前缀存在。要靠实际跑页面来兜底。
4. **少了 `setRequestLocale(locale)`**——会让 locale 前缀页面用默认语言渲染，
   极易漏掉。
5. **直接用 `next/link`**——一定要从 `@/i18n/navigation` 导入 `Link` 与
   `useRouter`，否则 client 跳转会丢掉 locale 前缀。

## 官方文档

* [next-intl.dev](https://next-intl.dev/)——i18n 层的完整参考
* [next-intl App Router](https://next-intl.dev/docs/getting-started/app-router)——request config 与 routing
* [Next.js i18n 路由](https://nextjs.org/docs/app/building-your-application/routing/internationalization)——Next.js 基础
