国际化机制
AutoRouter 的管理面板基于 next-intl 实现 zh-CN 与 en 双语切换。所有面向用户的页面、组件文案、错误提示都通过翻译键引用 src/messages/ 下的 JSON 资源,URL 中始终携带 locale 前缀,切换语言走 Cookie + URL 改写两条通道。
文档站本身(即正在阅读的 VitePress 站点)有独立的 i18n 体系,与应用层 next-intl 完全解耦。本文档先讲应用层,再交代两者的边界。
依赖与版本
package.json:52:
"next-intl": "^4.9.2"配置文件分层
src/i18n/ 下四个文件按职责拆分:
| 文件 | 职责 |
|---|---|
config.ts | locale 常量(locales、defaultLocale、cookie 名)和 Locale 类型 |
routing.ts | next-intl 路由配置(前缀策略、cookie 参数) |
request.ts | 服务端 getRequestConfig,按 locale 动态 import 翻译文件 |
navigation.ts | 导出 i18n-aware 的 Link / useRouter / usePathname 等导航工具 |
config.ts
export const locales = ["zh-CN", "en"] as const;
export const defaultLocale: Locale = "zh-CN";
export const localeCookieName = "NEXT_LOCALE";
export const localeCookieMaxAge = 60 * 60 * 24 * 365;
export const localeNames: Record<Locale, string> = {
"zh-CN": "简体中文",
en: "English",
};支持两种语言,默认中文,Cookie 名沿用 Next.js 约定的 NEXT_LOCALE,有效期一年。
routing.ts
export const routing = defineRouting({
locales,
defaultLocale,
localePrefix: "always",
localeCookie: {
name: localeCookieName,
maxAge: localeCookieMaxAge,
sameSite: "lax",
},
});localePrefix: "always" 表示所有路径都强制带 locale 段,即 /zh-CN/dashboard 而非 /dashboard。默认 locale 不享受省略特权。这种策略下 URL 总是显式表达语言,对外分享链接和后端日志的归类都更直接。
sameSite: "lax" 允许顶级导航跨站携带 Cookie,符合「用户从外站点开链接进来」的常见场景。
request.ts
export default getRequestConfig(async ({ requestLocale }) => {
let locale = await requestLocale;
if (!locale || !routing.locales.includes(locale as Locale)) {
locale = routing.defaultLocale;
}
return {
locale,
messages: (await import(`../messages/${locale}.json`)).default,
};
});服务端入口。requestLocale 由 next-intl 从当前请求的 URL 段解析;非法或缺失时回退到 defaultLocale,避免抛错。翻译资源走动态 import(),被 Next.js 打包时按语言切分 chunk。
navigation.ts
export const { Link, redirect, usePathname, useRouter, getPathname } = createNavigation(routing);封装出 i18n-aware 的导航 API。组件里用这套替代 next/link / next/navigation,跳转时自动维护 locale 前缀。
路由层
src/app/
├── layout.tsx # 根 layout:HTML 结构、字体、不引入 next-intl
└── [locale]/
├── layout.tsx # locale-aware layout:注入 NextIntlClientProvider
├── page.tsx # 首页(locale 根)
├── (auth)/
│ └── login/page.tsx
└── (dashboard)/
├── layout.tsx
├── dashboard/
├── keys/
├── logs/
├── settings/
├── system/
└── upstreams/根 vs locale 分工
src/app/layout.tsx 只负责 HTML 骨架、字体变量、<html> 标签的 lang 属性,不引入 next-intl。所有 i18n 上下文集中在 src/app/[locale]/layout.tsx:
export function generateStaticParams() {
return routing.locales.map((locale) => ({ locale }));
}
export default async function LocaleLayout({ children, params }: LocaleLayoutProps) {
const { locale } = await params;
if (!routing.locales.includes(locale as Locale)) {
notFound();
}
setRequestLocale(locale);
const messages = await getMessages();
return (
<ThemeProvider>
<NextIntlClientProvider messages={messages}>
<QueryProvider>
<TooltipProvider>
<AuthProvider>
<div className="min-h-dvh bg-background text-foreground">{children}</div>
<Toaster />
</AuthProvider>
</TooltipProvider>
</QueryProvider>
</NextIntlClientProvider>
</ThemeProvider>
);
}关键点:
generateStaticParams遍历routing.locales,让 Next.js 为每种语言预生成静态参数- locale 不在白名单时调
notFound(),触发 404 页面而非静默回退 setRequestLocale(locale)启用静态渲染优化,让下层 server component 在打包阶段就能确定 localegetMessages()间接调到request.ts的getRequestConfig,把完整翻译资源批量注入给NextIntlClientProvider,下层任何 client component 都能直接读取(auth)和(dashboard)是路由分组(route group),不出现在 URL 中,仅在文件系统层组织代码
中间件
src/proxy.ts 是 Next.js 的 middleware 入口(文件名不是惯用的 middleware.ts,但 Next.js 同时支持 proxy.ts):
import createMiddleware from "next-intl/middleware";
import { routing } from "./i18n/routing";
export default createMiddleware(routing);
export const config = {
matcher: ["/((?!_next|api|.*\\..*).*)"],
};matcher 排除三类路径:
| 模式 | 含义 |
|---|---|
_next | Next.js 内部资源 |
api | 所有 API 路由 |
.*\\..* | 任何包含 . 的路径(静态文件,如 favicon.ico、*.png) |
其余请求统一交给 next-intl 中间件处理:补 locale 前缀、读写 NEXT_LOCALE Cookie、按 Accept-Language 头协商默认语言。
API 路由完全不走 i18n 中间件,对外不暴露语言概念——API 返回的错误码统一英文,由前端按当前 locale 翻译展示。
翻译文件组织
src/messages/ 下两个 JSON:
| 文件 | 行数 |
|---|---|
zh-CN.json | 1551 |
en.json | 1546 |
按功能 / 页面分 19 个顶层 namespace(按 en.json 出现顺序):
common · nav · repository · auth · dashboard · keys · logs · upstreams ·
circuitBreaker · errors · language · theme · system · billing ·
backgroundSync · trafficRecording · upstreamFailureRules · compensation · cliproxy两份文件顶层 namespace 完全对齐,子树结构基本一致(5 行差异来自 zh-CN 部分键值有额外的注释字符串)。
namespace 命名约定
namespace 以「页面或功能模块」而非「组件」为粒度。例如 keys 容纳 API Key 管理整个页面的全部文案,upstreams 容纳上游管理页面,circuitBreaker 容纳熔断器子页面,跨页面共用的字段统一放 common。新增页面时同步在两份 JSON 加新 namespace。
客户端与服务端的使用模式
客户端
next-intl 的两个核心 hook:
// src/app/[locale]/(auth)/login/page.tsx:118-119
const t = useTranslations("auth");
const tCommon = useTranslations("common");// src/components/dashboard/time-range-selector.tsx:36
const locale = useLocale();useTranslations 接受 namespace 字符串,返回的 t 函数按相对路径取值;useLocale 用于需要按当前语言切换展示逻辑的场景(比如日期格式化)。
服务端
项目中服务端翻译统一走 [locale]/layout.tsx 注入的 messages,没有直接调用 getTranslations。如有 server component 需要单独取翻译,可按 next-intl 文档使用 getTranslations / getLocale。
语言切换组件
src/components/language-switcher.tsx:
const handleLocaleChange = (nextLocale: Locale) => {
if (nextLocale === locale) return;
const queryString = searchParams.toString();
const targetPath = queryString ? `${pathname}?${queryString}` : pathname;
router.replace(targetPath, { locale: nextLocale });
};关键点:
pathname和useRouter均来自@/i18n/navigation,即 i18n-aware 版本,自动处理 locale 前缀替换router.replace(targetPath, { locale: nextLocale })触发跳转的同时,next-intl 中间件会更新NEXT_LOCALECookie,下次访问任意路径都按新语言渲染- 当前 query string 被保留,避免切语言把分页 / 筛选条件清空
UI 用 DropdownMenuRadioGroup 把两种语言渲染为单选项,当前 locale 旁加 ✓ 图标。
文档站 i18n 与应用 i18n 的边界
VitePress 文档站(即你正在阅读的站点)在 docs/.vitepress/config.ts:60-107 单独配置了 locales:
locales: {
root: { label: "简体中文", lang: "zh-CN", themeConfig: { /* 完整侧边栏 */ } },
en: { label: "English", lang: "en-US", link: "/en/", themeConfig: { /* WIP 占位 */ } },
}这是 VitePress 原生机制,与应用层 next-intl 完全独立:
| 维度 | 应用 i18n | 文档站 i18n |
|---|---|---|
| 引擎 | next-intl 4.x | VitePress 原生 |
| 翻译源 | src/messages/*.json | 各页面 docs/ / docs/en/ 下的 Markdown 文件 |
| 路由策略 | /zh-CN/... /en/... 强制前缀 | /...(zh-CN root)/ /en/... |
| 部署 | Next.js 应用主体 | GitHub Pages 静态文档站 |
两套体系各自维护翻译,互不共享。这种分离是有意的:应用面文案与产品交互绑定,迭代节奏快;文档面内容偏稳定,迭代节奏慢,分别交给两套合适的工具维护。当前英文文档仅有「Overview (WIP)」占位页,完整英文化由 Issue #167 的后续阶段跟进。