如何实现多语言(国际化)
1. 选择方案:next-intl + App Router
Next.js 官方提供了两种路由模式:Pages Router 和 App Router。
- 如果使用 Pages Router,可以利用 Next.js 内置的
i18n配置,配合next-i18next等库。 - 如果使用 App Router(推荐),
next-intl是目前最流行的解决方案,它完美支持服务端组件、静态生成和中间件路由。
本文将以 App Router + next-intl 为例,因为它是未来主流且代码更简洁。
2. 安装依赖
npm install next-intl
# 或
yarn add next-intl
# 或
pnpm add next-intl
3. 创建翻译文件
在项目根目录下创建 messages 文件夹,用于存放不同语言的 JSON 翻译文件。例如:
messages/
en.json
zh.json
messages/en.json
{
"Index": {
"title": "Hello World",
"description": "This is a multi-language example."
},
"Navigation": {
"home": "Home",
"about": "About"
}
}
messages/zh.json
{
"Index": {
"title": "你好,世界",
"description": "这是一个多语言示例。"
},
"Navigation": {
"home": "首页",
"about": "关于"
}
}
4. 配置 next.config.js
在 next.config.js 中引入 next-intl 插件,并指定要支持的语言和默认语言。
const withNextIntl = require('next-intl/plugin')();
/** @type {import('next').NextConfig} */
const nextConfig = {
// 其他 Next.js 配置
};
module.exports = withNextIntl(nextConfig);
接下来创建 i18n.ts 文件(放在项目根目录或 src 下),用于定义支持的语言、默认语言以及翻译文件的加载方式。
import {getRequestConfig} from 'next-intl/server';
export default getRequestConfig(async ({locale}) => ({
messages: (await import(`./messages/${locale}.json`)).default
}));
注意:这个文件告诉
next-intl如何根据当前locale动态加载对应的翻译文件。
5. 设置中间件(可选但推荐)
为了根据用户请求的 URL 或浏览器偏好自动设置语言,我们可以创建一个中间件 middleware.ts:
import createMiddleware from 'next-intl/middleware';
export default createMiddleware({
// 支持的语言列表
locales: ['en', 'zh'],
// 默认语言
defaultLocale: 'en'
});
export const config = {
// 匹配所有路径,除了 Next.js 内部路径
matcher: ['/((?!api|_next|.*\\..*).*)']
};
这样,当用户访问 / 时,中间件会自动重定向到 /en(如果浏览器语言不是中文)或 /zh(如果浏览器语言为中文)。你也可以手动在 URL 中切换语言,例如 /en/about 和 /zh/about。
6. 在布局中提供语言上下文
在 app/[locale]/layout.tsx(注意目录结构变化)中,使用 NextIntlClientProvider 将翻译消息注入到客户端组件中,并设置 <html> 的 lang 属性。
首先,调整你的 app 目录结构,使其支持动态语言段:
app/
[locale]/
layout.tsx
page.tsx
about/
page.tsx
app/[locale]/layout.tsx
import {NextIntlClientProvider} from 'next-intl';
import {getMessages} from 'next-intl/server';
import {notFound} from 'next/navigation';
export default async function LocaleLayout({
children,
params: {locale}
}: {
children: React.ReactNode;
params: {locale: string};
}) {
// 验证 locale 是否支持
const isValidLocale = ['en', 'zh'].includes(locale);
if (!isValidLocale) notFound();
// 加载对应的翻译文件
const messages = await getMessages();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
7. 在页面中使用翻译
在任意服务端组件或客户端组件中,都可以通过 useTranslations 钩子获取翻译函数。
app/[locale]/page.tsx
import {useTranslations} from 'next-intl';
export default function HomePage() {
const t = useTranslations('Index');
return (
<div>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
</div>
);
}
如果需要在客户端组件中使用,只需加上 'use client' 指令即可。
8. 创建语言切换器
一个简单的语言切换组件可以让用户手动切换语言。next-intl 提供了 useRouter 和 usePathname 来改变当前语言。
components/LanguageSwitcher.tsx
'use client';
import {useRouter, usePathname} from 'next/navigation';
import {useLocale} from 'next-intl';
export default function LanguageSwitcher() {
const router = useRouter();
const pathname = usePathname();
const currentLocale = useLocale();
const switchLanguage = (newLocale: string) => {
// 将当前路径中的语言段替换为新语言
const newPathname = pathname.replace(`/${currentLocale}`, `/${newLocale}`);
router.push(newPathname);
};
return (
<div>
<button onClick={() => switchLanguage('en')} disabled={currentLocale === 'en'}>
English
</button>
<button onClick={() => switchLanguage('zh')} disabled={currentLocale === 'zh'}>
中文
</button>
</div>
);
}
将该组件放在布局或导航栏中,用户点击按钮即可切换语言,URL 也会相应改变。
9. 导航链接
在 App Router 中,所有内部链接都应该使用 next-intl 提供的 Link 组件,它会自动处理语言前缀。
import {Link} from 'next-intl';
export default function Navigation() {
const t = useTranslations('Navigation');
return (
<nav>
<Link href="/">{t('home')}</Link>
<Link href="/about">{t('about')}</Link>
</nav>
);
}
注意:
next-intl的Link组件会基于当前语言自动添加正确的语言前缀,例如/en/about或/zh/about。
10. 静态生成与动态路由
如果你希望生成静态页面(例如 generateStaticParams),可以结合 next-intl 的 getMessages 和 getTranslations 在构建时生成所有语言版本。next-intl 官方文档有详细说明,这里不再展开。
总结
通过以上步骤,我们实现了:
- 基于 URL 路径的多语言路由(如
/en/...、/zh/...) - 翻译文件的模块化管理
- 服务端和客户端组件的无缝翻译
- 语言切换器与导航链接的自动处理
这套方案足够简洁,同时保留了很强的扩展性。如果你的项目较小,甚至不需要中间件,直接在 layout.tsx 中根据参数加载翻译即可。