Next.js 16 入门教程
国际化 i18n
本教程共 42 篇 · 第 31 篇 · 更新于 2026-07-30 · 约 7 分钟阅读
Next.jsNext.js 16 入门教程国际化i18n多语言本地化
31. 国际化 i18n
本节目标:学会在 Next.js 中实现多语言支持,包括路由设计、翻译管理和语言自动检测。
基本概念
国际化(Internationalization,简称 i18n)包含两个方面:
- 路由国际化:不同语言用不同 URL 路径(如
/en/about、/zh/about) - 内容本地化:根据用户语言显示对应翻译
Locale 是标识一组语言偏好的字符串,比如:
en-US:美式英语zh-CN:简体中文nl:荷兰语(不指定地区)
路由设计
Next.js 推荐用动态路由段 [lang] 来处理多语言:
app/
├── [lang]/
│ ├── layout.tsx
│ ├── page.tsx
│ └── about/
│ └── page.tsx
所有 app/ 下的特殊文件都要放在 [lang] 目录内,这样 Next.js 路由器就能动态处理不同语言。
// app/[lang]/page.tsx
export default async function Page({ params }) {
const { lang } = await params
// lang 就是当前语言,比如 "en" 或 "zh"
return <h1>当前语言: {lang}</h1>
}
语言检测
推荐根据浏览器的 Accept-Language 头来判断用户偏好。可以用 negotiator 和 @formatjs/intl-localematcher 这两个库:
// lib/locale.js
import { match } from '@formatjs/intl-localematcher'
import Negotiator from 'negotiator'
export function getLocale(request) {
const headers = { 'accept-language': request.headers.get('accept-language') || '' }
const languages = new Negotiator({ headers }).languages()
const locales = ['en-US', 'zh-CN', 'ja']
const defaultLocale = 'en-US'
return match(languages, locales, defaultLocale)
}
然后在 proxy.ts 中做重定向:
// proxy.ts
import { NextResponse } from 'next/server'
const locales = ['en-US', 'zh-CN', 'ja']
export function proxy(request) {
const { pathname } = request.nextUrl
// 检查路径是否已包含语言前缀
const hasLocale = locales.some(
locale => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
)
if (hasLocale) return
// 没有语言前缀,检测并重定向
const locale = getLocale(request)
request.nextUrl.pathname = `/${locale}${pathname}`
return NextResponse.redirect(request.nextUrl)
}
翻译管理
字典文件
为每种语言创建独立的 JSON 文件:
// dictionaries/en.json
{
"products": {
"cart": "Add to Cart",
"title": "Our Products"
}
}
// dictionaries/zh.json
{
"products": {
"cart": "加入购物车",
"title": "我们的产品"
}
}
加载字典
// app/[lang]/dictionaries.ts
import 'server-only'
const dictionaries = {
en: () => import('./dictionaries/en.json').then(m => m.default),
zh: () => import('./dictionaries/zh.json').then(m => m.default),
}
export type Locale = keyof typeof dictionaries
export const hasLocale = (locale: string): locale is Locale =>
locale in dictionaries
export const getDictionary = async (locale: Locale) => dictionaries[locale]()
注意:这里用了
import 'server-only'确保代码只在服务端运行,翻译文件不会打包到客户端。
在页面中使用
// app/[lang]/page.tsx
import { notFound } from 'next/navigation'
import { getDictionary, hasLocale } from './dictionaries'
export default async function Page({ params }) {
const { lang } = await params
if (!hasLocale(lang)) notFound()
const dict = await getDictionary(lang)
return <button>{dict.products.cart}</button>
}
hasLocale 函数有两个作用:一是类型收窄,二是遇到不支持的语言时返回 404。
静态生成
如果语言组合有限,可以用 generateStaticParams 预生成所有语言版本:
// app/[lang]/layout.tsx
export function generateStaticParams() {
return [
{ lang: 'en-US' },
{ lang: 'zh-CN' },
{ lang: 'ja' },
]
}
export default async function RootLayout({ children, params }) {
const { lang } = await params
return (
<html lang={lang}>
<body>{children}</body>
</html>
)
}
这样构建时会为每种语言生成静态页面,访问速度更快。
语言切换
语言切换本质上就是路由跳转。做一个简单的语言选择器:
'use client'
import { useRouter, usePathname } from 'next/navigation'
export function LanguageSwitcher() {
const router = useRouter()
const pathname = usePathname()
const switchLocale = (locale: string) => {
// 替换路径中的语言前缀
const newPath = pathname.replace(/^\/[a-z]{2}(-[A-Z]{2})?/, `/${locale}`)
router.push(newPath)
}
return (
<select onChange={e => switchLocale(e.target.value)} defaultValue="en-US">
<option value="en-US">English</option>
<option value="zh-CN">中文</option>
<option value="ja">日本語</option>
</select>
)
}
第三方库推荐
如果项目比较复杂,可以考虑这些专门做国际化的库:
- next-intl:功能最全,支持复数、日期格式化、数字格式化
- next-international:轻量级,API 简洁
- paraglide-next:基于消息 ID 的类型安全方案
- lingui:成熟的 React 国际化方案
注意事项
- HTML lang 属性:
<html lang={lang}对 SEO 和屏幕阅读器都很重要 - 文本方向:阿拉伯语、希伯来语是从右到左(RTL),需要额外处理
- 日期和数字:不同地区的格式不同,用
Intl.DateTimeFormat和Intl.NumberFormat - 翻译键命名:用嵌套结构(如
products.cart)比扁平结构更易维护
国际化不是一次性工作。随着产品迭代,翻译内容会不断增加,提前设计好结构能省很多事。