国际化

使用 next-intl 配置语言与路由。

模板使用 next-intl 内置双语(英文 + 中文)。语言通过 [locale] 段成为 URL 的一部分,文案为 JSON 词条文件,src/proxy.ts 负责检测与重定向。

重要文件

文件作用
src/i18n/routing.ts语言列表、默认语言、前缀策略
src/i18n/request.ts按请求加载文案词条
src/i18n/navigation.ts区分语言的 LinkredirectuseRouter
src/proxy.ts语言检测 + 重定向中间件
messages/en.jsonmessages/zh.json翻译词条

路由

src/i18n/routing.ts
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/辅助函数(useTranslationsgetTranslations)按 messages/*.json 的键取字符串。任何内部导航都应从 src/i18n/navigation.ts 导入,而非 next/link,以保留当前语言:

src/i18n/navigation.ts
export const { Link, redirect, usePathname, useRouter } =
  createNavigation(routing);

检测与重定向:src/proxy.ts

proxy.ts 先运行 next-intl 中间件以检测并加上语言前缀,再执行受保护路径 检查。其 matcher 排除了 API 路由、Next 内部路径以及带扩展名的文件:

src/proxy.ts
export const config = {
  matcher: ["/((?!api|_next|_vercel|.*\\..*).*)"],
};

API 路由被显式跳过,因此 /api/* 永远不会带语言前缀。完整请求流程见 项目结构

新增语言

添加词条文件

创建 messages/<locale>.json,键与 en.json 保持一致。

在路由中注册

将语言代码加入 src/i18n/routing.tslocalesrequest.ts 动态导入 词条,无需改动。

翻译 UI 配置

页脚等文案来源都经过 next-intl 翻译函数,因此词条补全后新键会自动生效。

文档语言(Fumadocs)

文档同样是双语的,在 src/lib/source.ts 中配置:

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.mdxslug.zh.mdx,否则缺失的语言会让导航链接 404。

相关页面

On this page