

Next.js 有两份官方的 i18n 文档页面,它们描述的是完全不同的系统,且彼此之间没有任何交叉引用。
一篇介绍了 Pages Router,另一篇介绍了 App Router。但两者均未说明二者之间的关联、应使用哪些库,以及如何处理多语言 SEO 中的 hreflang 属性。
我们将实现两个路由器的桥接。我们将详细探讨路由策略、库的选择、中间件的配置、服务器端组件的翻译以及多语言SEO。
我们的目标是为那些被要求为 Next.js 应用添加语言支持、且在确定具体方案前需要全面了解情况的开发者,提供一份实用的参考资料。让我们开始吧!
国际化分为三个层次:
Next.js 仅提供路由功能。
自 v10.0.0 版本起,Pages Router 便内置了国际化(i18n)路由功能。next.config.js文件中一个包含三个字段的配置块会自动处理语言环境检测、URL 前缀添加以及重定向。
App Router 则有所不同。其国际化(i18n)文档页面介绍了一种模式,你可以通过中间件自行实现,一种 [locale] 动态段和一个库。
无论使用哪种路由器,翻译和格式设置都需自行处理。App Router 的文档列出了所有兼容的库,但并未提供选择建议。这两份文档均未涉及 hreflang 标签或多语言网站地图。
Pages Router 的实现方式只需修改配置。在next.config.js中添加一个 i18n 块,包含以下三个字段:
// next.config.js
module.exports = {
i18n: {
locales: ['en', 'fr', 'de'],
defaultLocale: 'en',
},
}就这样。
Next.js 会自动从 接受语言 标题,遵循 NEXT_LOCALE 覆盖 Cookie,并通过 useRouter().locale. 您无需使用任何中间件或重新组织目录结构。
App Router 没有对应的配置。您需要通过以下三个部分自行构建路由:
@formatjs/intl-localematcher 以及 谈判代表 进行解析 接受语言 标头。官方文档中,实际的检测逻辑被省略了: function getLocale(request) { ... }. 我们将在下一节中补充说明。[locale] 动态段落会包裹你的整个 app/ 目录。通过文件夹结构,每条路由都能识别语言环境。生成静态参数 在构建时预渲染每个语言环境变体。在这三个组件中,generateStaticParams 取代了 Pages Router 在构建时自动处理的功能。
export function generateStaticParams() {
return [{ locale: 'en' }, { locale: 'fr' }, { locale: 'de' }]
}🚨 Pages Router 内置的 i18n 功能无法与 输出:'export'. 如果您需要完全静态的导出,则需要改用基于文件夹的手动路由。此限制仅适用于内置配置。这两种路由器在手动配置的情况下均支持静态导出。
在选择库之前,先确定路由策略。这会影响 SEO、DNS 配置以及你需要编写多少中间件:
next-intl 将其作为首选方案予以支持。对于SEO至关重要的公共网站而言,子路径路由是正确的默认选择。只有当明确需要强大的地理定位信号时,域名路由的复杂性才值得采用。
如果您不想手动管理 URL 结构,Weglot 的反向代理会自动处理子目录和子域名的路由,包括 hreflang 标签的插入以及翻译后的 URL。
正如我们之前所说,官方文档中列出了各种库,但并未推荐任何特定库。以下是主要选项的对比:
“
next-intl是 App Router 的事实标准。它内置了 RSC 支持,集成了路由功能,且无需额外配置即可使用 TypeScript 自动补全功能。
如果您正在使用 Pages Router,或者在技术栈的其他地方已经使用了 i18next,那么react-i18next会是更合适的选择。App Router 虽然也能支持,但需要更多手动配置。
Lingui适用于已拥有.po文件处理流程的团队。翻译内容会在构建时被提取,因此未使用的消息绝不会被发布。
💡Paraglide JS虽未跻身主流之列,但作为一款基于编译器的替代方案,其生成的包体积更小,值得关注。不过,如果你对此感兴趣,就必须接受这样一个事实:它的生态系统比其他方案更为年轻。
在日期和数字格式化方面,next-intl 通过 useFormatter 封装了原生的 Intl API。其余组件则委托给FormatJS,或者由您直接调用 Intl.DateTimeFormat 和 Intl.NumberFormat。
💡 这些库负责渲染工作。但仍需有人负责生成翻译内容,并在内容变更时保持其同步。Weglot 则完全Weglot 在不同的层面上,通过代码库外部的反向代理来管理翻译内容。请不要将其视为对这些库的替代方案。
Next.js 16 将middleware.ts重命名为proxy.ts。之所以这样做,是因为“middleware”一词容易引起误解,暗示其为通用应用内逻辑。实际上,该文件在网络边界运行,像代理一样在请求到达应用程序之前进行拦截和修改。
以下是一个完整的区域设置检测和重定向实现,它填补了官方文档中前述的空白:
// proxy.ts
import { match } from '@formatjs/intl-localematcher'
import Negotiator from 'negotiator'
import { NextRequest, NextResponse } from 'next/server'
const locales = ['en', 'fr', 'de']
const defaultLocale = 'en'
function getLocale(request: NextRequest): string {
const cookie = request.cookies.get('NEXT_LOCALE')?.value
if (cookie && locales.includes(cookie)) return cookie
const headers = { 'accept-language': request.headers.get('accept-language') ?? '' }
const languages = new Negotiator({ headers }).languages()
return match(languages, locales, defaultLocale)
}
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl
const hasLocale = locales.some(l => pathname.startsWith(`/${l}/`) || pathname === `/${l}`)
if (hasLocale) return
const locale = getLocale(request)
request.nextUrl.pathname = `/${locale}${pathname}`
return NextResponse.redirect(request.nextUrl)
}“
文件嵌套在 app/[语言环境]/,带有 词典/ 文件夹,该文件夹位于您的布局和页面旁边。根布局将接收 区域设置 作为参数,并将其设置在 html 标签:
export default async function RootLayout({ children, params }) {
const { locale } = await params
return (
<html lang={locale}>
<body>{children}</body>
</html>
)
}“
关于翻译,官方文档展示了一种无需依赖库的原始字典模式:
const dictionaries = {
en: () => import('./dictionaries/en.json').then(m => m.default),
fr: () => import('./dictionaries/fr.json').then(m => m.default),
}
export const getDictionary = async (locale: string) => dictionaries[locale]()“
这虽然可行,但不支持复数形式和插值。 next-intl 同时涵盖了这两者 getTranslations() 以及 useTranslations() 在 服务器组件和客户端组件 分别。
无论哪种情况,翻译文件都在服务器端进行处理,只有生成的 HTML 才会发送到浏览器,因此消息文件的大小不会影响客户端包的大小。
对于客户端组件,请尽可能从服务器组件父组件中将翻译后的字符串作为 props 传递。使用 NextIntlClientProvider 仅当客户端组件需要直接处理翻译时,才使用带作用域的消息子集。
对于语言切换器,服务器组件负责渲染区域设置标签,而客户端组件则负责处理 useRouter() 调用:
// LocaleSwitcher.tsx (Server Component)
import LocaleSwitcherClient from './LocaleSwitcherClient'
export default function LocaleSwitcher({ locale }) {
return <LocaleSwitcherClient locale={locale} labels={{ en: 'English', fr: 'Français' }} />
}客户端会监听选项的变化,并推送新的区域设置路由:
// LocaleSwitcherClient.tsx
'use client'
import { useRouter } from 'next/navigation'
export default function LocaleSwitcherClient({ locale, labels }) {
const router = useRouter()
return (
<select value={locale} onChange={e => router.push(`/${e.target.value}`)}>
{Object.entries(labels).map(([value, label]) => (
<option key={value} value={value}>{label}</option>
))}
</select>
)
}如果您使用Weglot以上内容均不适用。Weglot 内置 Weglot 语言切换小工具,无需进行代理配置或使用中间件。
这两款路由器均无法自动处理多语言SEO:
html lang 虽然实现正确,但其文档中提到 hreflang 的实现“由您自行决定”。借助 App Router,元数据 API 能让您最接近自动化。在 生成元数据 通过 alternates.languages 对象和 Next.js 的自动注入 link rel="alternate" hreflang="..." 标签:
export async function generateMetadata({ params: { locale } }) {
return {
alternates: {
languages: {
'en': 'https://example.com/en',
'fr': 'https://example.com/fr',
'de': 'https://example.com/de',
},
},
}
}手动操作的部分在于为每页的每个语言版本收集正确的URL。该逻辑需要由您自行编写。
无论使用哪种路由器,还有两件事需要手动设置。必须根据不同语言环境设置规范 URL,以避免出现重复内容的信号。您的 sitemap.ts 需求 xhtml:link 每个页面的每个语言版本的条目。
在 hreflang 值本身,使用 hreflang="en-US" 在针对特定地区时,以及 hreflang="en" 当目标受众是所有英语使用者时。混淆这两者会向搜索引擎发送矛盾的信号。
next-intl 增加了一项部分自动化功能:其代理会自动注入 hreflang 属性 链接 在使用本地化路径名路由时,响应头会自动生成。其他路由配置仍需手动实现。
随着网站的不断发展,这将带来相当大的持续维护工作量。幸运的是Weglot 的反向代理会在服务器端自动处理所有事宜,因此翻译后的页面无需额外代码即可被搜索引擎完全收录。
事实上Weglot反向代理会自动处理上一节中提到的所有内容:hreflang 标签、翻译后的 URL、规范标签以及网站地图的生成。翻译后的页面是在服务器端生成的,因此搜索引擎可以完全对其进行索引。
⚠️Weglot JavaScriptsnippet 无法带来 SEO 效益。客户端翻译无法被搜索引擎抓取。如果 SEO 很重要,则必须配置反向代理。
另一个好处是构建时间。Weglot 通过其自有 CDNWeglot 翻译后的页面,而非在构建时生成这些页面。为一个Weglot网站添加 10 种语言,不会影响 下一个构建 需要。
正确的选择取决于您的优化目标。
如果您正在基于 App Router 进行开发且是从头开始, next-intl 是首选方案。它在一个包中集成了路由、翻译、TypeScript 自动补全以及 RSC 支持。
如果您正在使用 Pages Router,或者您的技术栈中已经包含 i18next, react-i18next 这是阻力最小的路径。
如果多语言搜索引擎优化(SEO)和持续的翻译管理是您的首要任务,那么这两款工具都无法完全解决问题。您仍然需要实施 hreflang 标签、网站地图,并建立一套工作流程,以确保随着内容的更新,翻译内容也能及时更新。Weglot反向代理会自动处理所有这些工作。
Weglot 组件级库并不相互排斥。Weglot 在 HTML 和代理层Weglot ,因此它可以与您的组件所使用的任何库共存。
如果这种方法适合您的情况,不妨先浏览一下 Weglot JavaScript 集成页面,并尝试我们的14 天免费试用!
要Weglot 强大功能Weglot 最好的方式Weglot 亲自体验。立即免费试用,无需任何承诺。
若您尚未准备好连接自己的网站,控制面板中已提供演示网站。