Next.js 项目中 i18n 多语言实现详解
在现代 Web 开发中,国际化(i18n)已成为构建全球化应用不可或缺的一部分。
Next.js 相关架构下,一般可以基于 next-intl 的国际化实现方式,本文将从代码结构、配置方式到实际使用场景,全面介绍 i18n的使用,包括语言配置、翻译消息加载、路由支持、组件使用等核心知识点。
一、i18n 实现基础
1.1 使用 next-intl 库实现国际化
用 next-intl 库来实现国际化功能。该库为 Next.js 提供了完整的国际化解决方案,包括翻译、日期格式化、数字格式化等功能。
1.2 安装与依赖管理
项目使用 pnpm 作为包管理器,安装 next-intl 及其相关依赖非常简单:
pnpm add next-intl
1.3 语言配置文件
语言配置主要定义在 /src/config/website.tsx 文件中,包含默认语言和所有支持的语言列表:
// /src/config/website.tsx
export const websiteConfig = {
i18n: {
defaultLocale: 'en',
locales: {
en: { flag: '🇺🇸', name: 'English' },
zh: { flag: '🇨🇳', name: '中文' }
}
}
};
此配置不仅用于控制应用的默认语言,还被多个组件引用,如语言切换器、页面标题生成等。
二、翻译消息文件
2.1 翻译消息文件结构
翻译消息存储在 /messages/<locale>.json 文件中,按模块划分,例如 "Common"、"PricingPage" 等:
{
"Common": {
"login": "Log in",
"logout": "Log out",
"language": "Switch language"
},
"PricingPage": {
"title": "Pricing",
"description": "Choose the plan that works best for you"
}
}
2.2 在客户端组件中使用翻译
在客户端组件中,使用 useTranslations 钩子获取翻译内容:
import { useTranslations } from 'next-intl';
export default function DashboardPage() {
const t = useTranslations();
return (
<h1>{t('Dashboard.dashboard.title')}</h1>
);
}
2.3 在服务端组件中使用翻译
在服务器端组件中,使用 getTranslations 获取翻译内容:
export async function generateMetadata({
params,
}: {
params: Promise<{ locale: Locale }>;
}): Promise<Metadata | undefined> {
const { locale } = await params;
const pt = await getTranslations({ locale, namespace: 'AuthPage.login' });
return constructMetadata({
title: pt('title'),
});
}
三、路由国际化配置
3.1 动态路由 **\[locale\]** 支持
Next.js 的 App Router 支持通过动态路由 \[locale\] 来处理不同语言版本的页面请求。路由配置文件位于 /src/i18n/routing.ts,定义了支持的语言列表和默认语言:
// /src/i18n/routing.ts
export const routing = defineRouting({
locales: LOCALES,
defaultLocale: DEFAULT_LOCALE,
localeDetection: false,
localeCookie: {
name: LOCALE_COOKIE_NAME,
},
localePrefix: 'as-needed',
});
3.2 自动检测语言与 Cookie 记忆
设置 localeDetection: false 禁用浏览器自动检测语言的功能。用户语言偏好通过 cookie (NEXT\_LOCALE) 记忆。
四、语言切换组件与导航链接
4.1 语言切换组件
可以实现多个语言切换组件,如 LocaleSwitcher 和 LocaleSelector,允许用户手动切换语言:
// /src/components/layout/locale-switcher.tsx
import { useTranslations } from 'next-intl';
import { useRouter } from 'next/router';
export default function LocaleSwitcher() {
const router = useRouter();
const { locales } = router;
const t = useTranslations('Common');
return (
<select
value={router.locale}
onChange={(e) => router.push(router.asPath, router.asPath, { locale: e.target.value })}
>
{locales?.map((locale) => (
<option key={locale} value={locale}>
{t(`language.${locale}`)}
</option>
))}
</select>
);
}
4.2 带翻译的导航链接
使用自定义的 LocaleLink 组件生成带语言前缀的链接:
import { LocaleLink } from '@/i18n/navigation';
<LocaleLink href="/dashboard">Dashboard</LocaleLink>
五、TypeScript 类型定义与类型安全
为了确保类型安全并提供更好的开发体验,项目中使用全局类型定义文件 global.d.ts 扩展 AppConfig 接口,提供 Locale 和 Messages 类型定义:
// global.d.ts
import type { routing } from '@/i18n/routing';
import type messages from './messages/en.json';
declare module 'next-intl' {
interface AppConfig {
Locale: (typeof routing.locales)[number];
Messages: typeof messages;
}
}
该类型定义文件通常位于项目根目录,并需在 tsconfig.json 中显式包含以确保被识别。
此外,还可以通过 fumadocs-ui 提供的 i18n 工具自动生成 Translations 类型定义,确保类型安全和自动补全支持。
六、新增语言的正确做法
当你需要扩展新的语言或新增翻译字段时,应遵循以下步骤:
- 添加新语言 JSON 文件 到
/messages/目录,如es.json。 - 在
**website.tsx**中添加该语言配置,提供国旗、名称等元信息。 - 在
**routing.ts**中添加语言支持,同步更新语言列表。 - 页面中使用
useTranslations('Namespace.key')即可生效。
这样可以保证国际化系统的一致性和可维护性。
七、国际化布局与上下文注入机制
7.1 LocaleLayout 组件的作用
在 Next.js 的 App Router 中,\[locale\] 路由下的 LocaleLayout 是整个国际化的根布局组件。它的主要职责包括:
- 加载当前语言的翻译消息文件(如
/messages/en.json或/messages/zh.json) - 注入国际化上下文,通过
NextIntlClientProvider - 设置 HTML 标签的语言属性,确保 SEO 和无障碍支持
- 包裹页面内容,使所有子组件都能基于正确的语言环境渲染
示例代码片段:
export default async function LocaleLayout({
children,
params: Promise<{ locale: string }>
}) {
// 加载对应语言的消息文件
const messages = (await import(`@/messages/${locale}.json`)).default;
return (
<html suppressHydrationWarning lang={locale}>
<body {...props}>
<NextIntlClientProvider messages={messages} locale={locale}>
<Providers>{children}</Providers>
</NextIntlClientProvider>
</body>
</html>
);
}
⚠️ 注意:虽然目前项目中未显式导入消息文件,但 NextIntlClientProvider 默认会从 /messages/
.json 自动加载翻译消息。
7.2 NextIntlClientProvider 的作用
NextIntlClientProvider 是 next-intl 提供的核心组件之一,它通过 React Context 将翻译数据传递给所有客户端组件。
主要功能:
| 功能 | 描述 |
|---|---|
| 翻译注入 | 将 messages 注入 React 上下文,使 useTranslations 可用 |
| 客户端支持 | 所有使用 useTranslations 的组件均可访问翻译键值 |
| 本地化格式化 | 支持日期、时间、数字等本地化格式输出 |
示例用法:
import { useTranslations } from 'next-intl';
export default function HomePage() {
const t = useTranslations('HomePage.hero');
return <h1>{t('title')}</h1>;
}
📌 提示:如果你希望手动控制消息加载方式,可以显式传入 messages 属性给 NextIntlClientProvider。
7.3 整体流程图解
[LocaleLayout]
↓
加载 messages/locale.json
↓
包裹 <NextIntlClientProvider> 注入翻译数据
↓
客户端组件通过 useTranslations 获取翻译内容
↓
渲染多语言 UI
4. 最佳实践建议
- ✅ 新增语言时只需添加
/messages/<new-locale>.json文件即可生效 - ✅ 在 website.tsx 中注册语言元信息
- ✅ 在 routing.ts 中同步更新语言列表
- ❗ 不建议修改
LocaleLayout的结构,除非你有特殊需求
通过以上机制,可以实现了简洁、可维护且高效的国际化系统,适合大型 SaaS 应用的多语言需求。
八、翻译消息加载机制详解
8.1 为什么没有显式导入翻译消息?
在 /src/app/\[locale\]/layout.tsx 中,你不会看到类似 **import messages from '@/messages/zh.json'** 的代码片段,这是因为 next-intl 提供了自动加载翻译消息的能力。
✅ 自动加载机制
NextIntlClientProvider 默认会从 /messages/<locale>.json 路径加载对应语言的消息文件。只要项目结构符合预期(如 /messages/en.json 和 /messages/zh.json 存在),就可以无需手动导入。
示例代码如下:
<NextIntlClientProvider>
<Providers>{children}</Providers>
</NextIntlClientProvider>
📌 注意:这里并没有传入 messages 属性,表示使用默认的自动加载方式。
⚠️ 手动加载的适用场景
如果你需要自定义路径、延迟加载或调试,可以显式导入并传入 messages 属性:
const messages = (await import(`@/messages/${locale}.json`)).default;
return (
<html suppressHydrationWarning lang={locale}>
<body {...props}>
<NextIntlClientProvider messages={messages} locale={locale}>
<Providers>{children}</Providers>
</NextIntlClientProvider>
</body>
</html>
);
但目前项目中不推荐这样做,因为结构和配置已完全符合自动加载的要求。
8.2 翻译消息文件路径约定
- 消息文件必须存放在
/messages/<locale>.json。 - 示例:
- /messages/en.json
- /messages/zh.json
💡 建议:新增语言时只需添加对应的 JSON 文件即可生效,无需修改 layout.tsx。
8.3 加载方式对比
| 方式 | 是否需要手动导入 | 使用场景 |
|---|---|---|
| ✅ 自动加载 | 否 | 标准项目结构、消息文件在 /messages/<locale>.json |
| 🔁 手动加载 | 是 | 自定义消息路径、动态加载、服务器渲染等高级用法 |
8.4 最佳实践建议
- ✅ 新增语言时只需添加
/messages/<new-locale>.json文件即可生效 - ✅ 在 website.tsx 中注册语言元信息
- ✅ 在 routing.ts 中同步更新语言列表
- ❗ 不建议修改
LocaleLayout的结构,除非你有特殊需求
通过以上机制,MkSaaS 模板实现了简洁、可维护且高效的国际化系统,适合大型 SaaS 应用的多语言需求。
九、语言配置的职责划分:website.tsx 与 routing.ts 的作用解析
在 MkSaaS 模板中,国际化(i18n)的语言配置分别定义在两个文件中:
- website.tsx:用于提供语言元信息和 UI 显示;
- routing.ts:用于控制路由系统中的多语言行为。
9.1 website.tsx:UI 层语言配置
路径: /src/config/website.tsx
示例代码:
i18n: {
defaultLocale: 'en',
locales: {
en: { flag: '🇺🇸', name: 'English' },
zh: { flag: '🇨🇳', name: '中文' }
}
}
作用:
- 定义网站支持的语言列表及默认语言;
- 提供语言元信息(如国旗、名称),用于 UI 显示;
- 被多个组件引用,例如语言切换器、页面标题生成等。
📌 这是面向前端 UI 的语言配置,主要用于控制语言选择器、显示标签等。
9.2 routing.ts:框架层语言路由配置
路径: /src/i18n/routing.ts
示例代码:
export const routing = defineRouting({
locales: LOCALES,
defaultLocale: DEFAULT_LOCALE,
localeDetection: false,
localeCookie: { name: LOCALE_COOKIE_NAME },
localePrefix: 'as-needed'
});
作用:
- 配置 App Router 中的国际化路由规则;
- 控制 URL 是否添加语言前缀(如
/en/aboutvs/about); - 设置语言检测方式(浏览器自动检测 or cookie);
- 定义多语言 Cookie 名称(如
NEXT\_LOCALE)。
📌 这是面向 Next.js 路由系统的语言配置,主要用于控制语言感知的路由行为和请求处理。
9.3 为什么不统一到一个地方?
虽然从表面上看将语言配置合并到一个文件中似乎更简洁,但实际原因如下:
| 维度 | website.tsx | routing.ts |
|---|---|---|
| 用途 | 提供语言元信息(UI 层) | 控制路由行为(框架层) |
| 使用者 | 前端组件、翻译钩子 | Next.js App Router、中间件 |
| 功能 | 显示语言选项、翻译内容 | 多语言 URL、Cookie 存储、路由匹配 |
| 是否可合并? | ❌ 不建议 | ❌ 不建议 |
✅ 正确做法:同步更新两处配置
当你新增一种语言(如西班牙语 es)时,应执行以下步骤:
1、 在 website.tsx 添加语言配置
i18n: {
defaultLocale: 'en',
locales: {
en: { flag: '🇺🇸', name: 'English' },
zh: { flag: '🇨🇳', name: '中文' },
es: { flag: '🇪🇸', name: 'Español' } // 新增语言
}
}
2、 在 routing.ts 更新语言列表
ts // 自动从 websiteConfig 获取语言列表 export const LOCALES = Object.keys(websiteConfig.i18n.locales); // 包含 ['en', 'zh', 'es']
3、 创建对应语言的消息文件
/messages/es.json
✅ 总结
| 问题 | 回答 |
|---|---|
| 为什么要分开配置? | 因为 website.tsx 提供 UI 元信息,routing.ts 控制路由行为,二者职责不同。 |
| 能否统一? | 不建议,违反单一职责原则且可能导致类型冲突。 |
| 新增语言应怎么做? | 在 website.tsx 添加语言元信息,在 routing.ts 同步更新,并创建对应 JSON 消息文件。 |
这样设计确保了国际化系统的灵活性和可维护性,是符合最佳实践的结构.