Next.js 16 + next-intl App Router 国际化实现指南
引言
✨ 随着全球化的发展,为网站添加国际化支持已经成为现代前端开发的标配。本文将基于真实项目代码,详细介绍如何在 Next.js 16 项目中使用 next-intl 实现基于 App Router 的国际化功能。
技术栈
- Next.js: 16.0.5 (App Router)
- next-intl: 4.5.7 (国际化核心库)
- TypeScript: 类型安全保障
一、项目国际化架构概述
App Router 路由方案
本项目采用 Next.js 13 及以上版本引入的 App Router 架构,通过动态路由 [locale] 实现国际化。这种方案的优势在于:
- 支持 SEO 友好的语言前缀路由(如
/zh/page、/en/page) - 提供服务端组件和客户端组件的灵活组合
- 内置数据流和布局系统,简化国际化实现
项目目录结构
项目采用清晰的模块化结构,将国际化相关代码集中在 src/i18n 目录下,便于维护和扩展:
src/
├── app/
│ ├── [locale]/ # 动态语言路由
│ │ ├── layout.tsx # 语言特定布局
│ │ └── page.tsx # 语言特定页面
│ ├── layout.tsx # 根布局
│ └── page.tsx # 根页面(重定向用)
├── components/
│ └── LocaleSwitcher.tsx # 语言切换组件
└── i18n/ # 国际化核心目录
├── config.ts # 语言配置
├── navigation.ts # 导航配置
├── request.ts # 翻译加载配置
├── routing.ts # 路由配置
└── messages/ # 翻译文件目录
├── en/ # 英文翻译
│ ├── index_page.json
│ └── locale_switcher.json
└── zh/ # 中文翻译
├── index_page.json
└── locale_switcher.json
二、核心配置文件
1. 语言配置 (config.ts)
定义支持的语言和默认语言:
typescript
// src/i18n/config.ts
export type Locale = (typeof locales)[number];
export const locales = ['zh', 'en'] as const; // 支持的语言:中文、英文
export const defaultLocale: Locale = 'zh'; // 默认语言:中文
2. 路由配置 (routing.ts)
配置国际化路由规则:
typescript
// src/i18n/routing.ts
import { defineRouting } from 'next-intl/routing';
import { defaultLocale, locales } from './config';
export const routing = defineRouting({
locales,
defaultLocale
});
3. 导航配置 (navigation.ts)
创建支持国际化的导航工具:
typescript
// src/i18n/navigation.ts
import { createNavigation } from 'next-intl/navigation';
import { routing } from './routing';
// 创建国际化导航工具集
export const { Link, getPathname, redirect, usePathname, useRouter } =
createNavigation(routing);
4. 翻译加载配置 (request.ts)
实现动态加载翻译文件的核心逻辑:
typescript
// src/i18n/request.ts
import { hasLocale } from 'next-intl';
import { getRequestConfig } from 'next-intl/server';
import { routing } from './routing';
/**
* 翻译模块配置接口
*/
export interface TranslationModuleConfig {
namespace: string; // 翻译键的命名空间
fileName: string; // 文件名
}
// 定义所有翻译模块
const TRANSLATION_MODULES: readonly TranslationModuleConfig[] = [
{ namespace: 'index_page', fileName: 'index_page' },
{ namespace: 'locale_switcher', fileName: 'locale_switcher' },
];
// 动态加载单个翻译模块
const importTranslationModule = async (
locale: string,
moduleConfig: TranslationModuleConfig
) => {
try {
const modulePath = `./messages/${locale}/${moduleConfig.fileName}.json`;
const importedModule = await import(modulePath);
return {
namespace: moduleConfig.namespace,
content: importedModule.default,
success: true,
};
} catch (error) {
console.error(
`[i18n] Failed to load module "${moduleConfig.fileName}" for locale "${locale}":`,
error
);
return {
namespace: moduleConfig.namespace,
content: {},
success: false,
error,
};
}
};
// 加载指定语言的所有翻译消息
const loadMessages = async (locale: string) => {
// 并行导入所有翻译模块以提高性能
const moduleLoadPromises = TRANSLATION_MODULES.map(moduleConfig =>
importTranslationModule(locale, moduleConfig)
);
const loadResults = await Promise.all(moduleLoadPromises);
// 合并所有成功加载的模块内容
return loadResults.reduce((acc, result) => {
if (result.success) {
acc[result.namespace] = result.content;
}
return acc;
}, {});
};
// 验证语言代码是否有效
const validateLocale = (locale: string | undefined): string => {
if (typeof locale !== 'string') {
console.warn(`[i18n] Invalid locale type: ${typeof locale}, falling back to default`);
return routing.defaultLocale;
}
if (!hasLocale(routing.locales, locale)) {
console.warn(
`[i18n] Locale "${locale}" not supported, falling back to default (${routing.defaultLocale})`
);
return routing.defaultLocale;
}
return locale;
};
// 导出请求配置
export default getRequestConfig(async ({ requestLocale }) => {
const requestedLocale = await requestLocale;
const validatedLocale = validateLocale(requestedLocale);
const messages = await loadMessages(validatedLocale);
return {
locale: validatedLocale,
messages,
};
});
三、Next.js 配置
1. 修改 next.config.ts
typescript
// next.config.ts
import { NextConfig } from 'next';
import createNextIntlPlugin from 'next-intl/plugin';
const nextConfig: NextConfig = {
// 其他配置
};
const withNextIntl = createNextIntlPlugin();
export default withNextIntl(nextConfig);
四、翻译文件结构
项目采用模块化的翻译文件结构,每个功能模块对应独立的翻译文件:
json
// src/i18n/messages/zh/index_page.json
{
"description": "这是一个基本示例,演示了如何使用 <code>next-intl</code> 与 Next.js App Router 配合使用。尝试在右上角切换语言,看看内容如何变化。",
"title": "next-intl 示例"
}
json
// src/i18n/messages/en/index_page.json
{
"description": "This is a basic example that demonstrates the usage of <code>next-intl</code> with the Next.js App Router. Try changing the locale in the top right corner and see how the content changes.",
"title": "next-intl example"
}
json
// src/i18n/messages/zh/locale_switcher.json
{
"label": "切换语言",
"locale": {
"zh": "中文",
"en": "English"
}
}
json
// src/i18n/messages/en/locale_switcher.json
{
"label": "Switch language",
"locale": {
"zh": "Chinese",
"en": "English"
}
}
五、App Router 国际化实现
1. 动态语言路由布局 ([locale]/layout.tsx)
typescript
// src/app/[locale]/layout.tsx
import { Inter } from "next/font/google"
import { hasLocale, NextIntlClientProvider } from "next-intl"
import { routing } from "@/i18n/routing";
import { notFound } from "next/navigation";
import { setRequestLocale } from "next-intl/server";
import { Locale } from "@/i18n/config";
const inter = Inter({
subsets: ["latin"],
display: "swap",
})
// 静态生成所有语言的路由
export function generateStaticParams() {
return routing.locales.map((locale) => ({ locale }));
}
type Props = {
children: React.ReactNode;
params: Promise<{
locale: Locale;
}>;
};
export default async function RootLayout({ children, params }: Props) {
const { locale } = await params;
// 验证语言是否支持,不支持则返回404
if (!hasLocale(routing.locales, locale)) {
notFound();
}
// 设置请求的语言
setRequestLocale(locale);
return (
<html className={inter.className} suppressHydrationWarning lang={locale}>
<head />
<body>
{/* 提供客户端翻译上下文 */}
<NextIntlClientProvider>
{children}
</NextIntlClientProvider>
</body>
</html>
)
}
2. 语言特定页面 ([locale]/page.tsx)
typescript
// src/app/[locale]/page.tsx
import LocaleSwitcher from "@/components/LocaleSwitcher";
import { useTranslations } from "next-intl";
import { setRequestLocale } from "next-intl/server";
import { use } from "react";
import { Locale } from "@/i18n/config";
type PageProps = {
params: Promise<{
locale: Locale;
}>;
};
export default function IndexPage({params}: PageProps) {
// 使用React 18的use()钩子处理异步params
const { locale } = use(params);
// 设置请求语言
setRequestLocale(locale);
// 获取翻译函数
const t = useTranslations();
return (
<>
{/* 使用翻译键获取翻译内容 */}
<h1>{t('index_page.title')}</h1>
{/* 语言切换组件 */}
<LocaleSwitcher/>
</>
)
}
3. 根页面重定向 (page.tsx)
typescript
// src/app/page.tsx
import { defaultLocale } from '@/i18n/config';
import { redirect } from '@/i18n/navigation';
export default function RootPage() {
// 重定向到默认语言的首页
redirect(`/${defaultLocale}`);
}
六、语言切换组件
LocaleSwitcher.tsx
typescript
// src/components/LocaleSwitcher.tsx
'use client';
import { useTransition } from 'react';
import { useLocale, useTranslations } from 'next-intl';
import { useRouter, usePathname } from '@/i18n/navigation';
import { Locale } from '@/i18n/config';
import { routing } from '@/i18n/routing';
export default function LocaleSwitcher() {
const locale = useLocale() as Locale;
const t = useTranslations('locale_switcher');
const [isPending, startTransition] = useTransition();
const router = useRouter();
const pathname = usePathname();
const items = routing.locales;
function onChange(value: Locale) {
// 使用React的并发特性处理路由切换
startTransition(() => {
router.replace(
{ pathname },
{ locale: value }
);
});
}
return (
<div style={{ position: 'absolute', top: '20px', right: '20px' }}>
<select
value={locale}
onChange={(e) => onChange(e.target.value as Locale)}
disabled={isPending}
>
{items.map((cur) => (
<option key={cur} value={cur}>
{t('locale', { locale: cur })}
</option>
))}
</select>
</div>
);
}
七、实现原理与流程
1. 路由处理流程
- 用户访问
/→ 重定向到默认语言/zh - 用户访问
/en→ 加载英文版本 generateStaticParams预生成所有语言的路由页面
2. 翻译加载流程
- 服务端接收请求,解析语言参数
- 通过
request.ts动态加载对应语言的翻译模块 - 通过
NextIntlClientProvider将翻译消息传递给客户端 - 客户端组件通过
useTranslations()获取翻译内容
3. 语言切换流程
- 用户点击语言切换按钮
- 触发
startTransition开始路由转换 - 使用
useRouter().replace()切换到对应语言的相同路径 - 服务端重新加载对应语言的翻译内容
- 客户端更新界面显示新语言内容
八、技术亮点与最佳实践
1. 类型安全
- 使用 TypeScript 确保语言代码和翻译键的类型安全
Locale类型基于实际支持的语言数组自动生成
2. 性能优化
- 翻译模块并行加载,提高加载效率
- 使用
generateStaticParams预生成静态页面 - Next.js 的自动静态优化机制
3. 可维护性
- 模块化的翻译文件结构,便于管理
- 集中的语言和路由配置,便于扩展
- 清晰的代码组织,遵循 Next.js 最佳实践
4. 用户体验
- SEO 友好的语言前缀路由
- 平滑的语言切换过渡
- 支持浏览器语言检测(由 next-intl 自动处理)
九、扩展建议
1. 添加更多语言支持
- 在
config.ts中添加新语言代码 - 创建对应的翻译文件目录和内容
- 在语言切换组件中自动支持新语言
2. 优化翻译加载
- 考虑使用 CDN 托管翻译文件
- 实现翻译内容的缓存机制
- 支持按需加载大型翻译模块
3. 增强翻译功能
- 添加日期、数字的本地化格式化
- 支持复数形式和性别中立表达
- 实现动态翻译内容的更新机制
总结
🎉 本项目成功实现了基于 Next.js App Router 的国际化方案,通过 next-intl 库提供了优雅、高效、类型安全的多语言支持。
核心实现包括:
- 基于动态路由
[locale]的国际化路由 - 模块化的翻译文件管理
- 服务端与客户端的翻译上下文传递
- 平滑的语言切换体验
这种方案充分利用了 Next.js App Router 的特性,为现代 Web 应用提供了高质量的国际化解决方案。希望这篇指南能帮助你理解和实现自己的 Next.js 国际化项目! 🫶