国际化
使用 next-intl 配置语言与路由。
模板使用 next-intl 内置双语(英文 + 中文)。语言通过 [locale] 段成为
URL 的一部分,文案为 JSON 词条文件,src/proxy.ts 负责检测与重定向。
重要文件
| 文件 | 作用 |
|---|---|
src/i18n/routing.ts | 语言列表、默认语言、前缀策略 |
src/i18n/request.ts | 按请求加载文案词条 |
src/i18n/navigation.ts | 区分语言的 Link、redirect、useRouter |
src/proxy.ts | 语言检测 + 重定向中间件 |
messages/en.json、messages/zh.json | 翻译词条 |
路由
export const routing = defineRouting({
locales: ["en", "zh"],
defaultLocale: "en",
localePrefix: "as-needed",
});localePrefix: "as-needed" 表示默认语言不带前缀,其他语言带语言代码:
| 语言 | URL |
|---|---|
| 英文(默认) | /、/about、/docs/next |
| 中文 | /zh、/zh/about、/zh/docs/next |
request.ts 读取解析出的语言,对未知值回退到默认语言,并导入对应的
messages/{locale}.json。
使用翻译与导航
在组件中,用 next-intl 的 hook/辅助函数(useTranslations、
getTranslations)按 messages/*.json 的键取字符串。任何内部导航都应从
src/i18n/navigation.ts 导入,而非 next/link,以保留当前语言:
export const { Link, redirect, usePathname, useRouter } =
createNavigation(routing);检测与重定向:src/proxy.ts
proxy.ts 先运行 next-intl 中间件以检测并加上语言前缀,再执行受保护路径
检查。其 matcher 排除了 API 路由、Next 内部路径以及带扩展名的文件:
export const config = {
matcher: ["/((?!api|_next|_vercel|.*\\..*).*)"],
};API 路由被显式跳过,因此 /api/* 永远不会带语言前缀。完整请求流程见
项目结构。
新增语言
添加词条文件
创建 messages/<locale>.json,键与 en.json 保持一致。
在路由中注册
将语言代码加入 src/i18n/routing.ts 的 locales。request.ts 动态导入
词条,无需改动。
翻译 UI 配置
页脚等文案来源都经过 next-intl 翻译函数,因此词条补全后新键会自动生效。
文档语言(Fumadocs)
文档同样是双语的,在 src/lib/source.ts 中配置:
export const source = loader({
baseUrl: "/docs",
source: docs.toFumadocsSource(),
i18n: { defaultLanguage: "en", languages: ["en", "zh"] },
});每篇文档以文件名成对出现:slug.mdx(英文)与 slug.zh.mdx(中文)——
章节首页则是 index.mdx / index.zh.mdx。Fumadocs 会根据请求语言解析出
正确文件。
请保持成对同步:文档 meta.json 中列出的 slug 必须同时存在
slug.mdx 和 slug.zh.mdx,否则缺失的语言会让导航链接 404。
