如何在 Next.js 中实现国际化
示例
Next.js 自 v10.0.0
以来内置支持国际化(i18n)路由。你可以提供语言环境列表、默认语言环境和特定域名的语言环境,Next.js 将自动处理路由。
i18n 路由支持目前旨在补充现有的 i18n 库解决方案,如 react-intl
、react-i18next
、lingui
、rosetta
、next-intl
、next-translate
、next-multilingual
、tolgee
、paraglide-next
、next-intlayer
等,通过简化路由和语言环境解析。
开始使用
要开始使用,在你的 next.config.js
文件中添加 i18n
配置。
语言环境是 UTS 语言环境标识符,这是定义语言环境的标准化格式。
通常,语言环境标识符由语言、地区和脚本组成,用破折号分隔:language-region-script
。地区和脚本是可选的。例如:
en-US
- 美国英语nl-NL
- 荷兰荷兰语nl
- 荷兰语,无特定地区
如果用户语言环境是 nl-BE
且在你的配置中未列出,他们将重定向到 nl
(如果可用),否则重定向到默认语言环境。
如果你不计划支持一个国家的所有地区,那么在配置中包含将作为后备的国家语言环境是一个好习惯。
module.exports = {
i18n: {
// 这些是你在应用程序中想要支持的所有语言环境
locales: ['en-US', 'fr', 'nl-NL'],
// 这是当你访问非语言环境前缀路径(如 `/hello`)时想要使用的默认语言环境
defaultLocale: 'en-US',
// 这是语言环境域名列表和它们应该处理的默认语言环境
// (这些仅在设置域名路由时需要)
// 注意:子域名必须包含在域名值中才能匹配,例如 "fr.example.com"。
domains: [
{
domain: 'example.com',
defaultLocale: 'en-US',
},
{
domain: 'example.nl',
defaultLocale: 'nl-NL',
},
{
domain: 'example.fr',
defaultLocale: 'fr',
// 也可以使用可选的 http 字段来测试
// 本地语言环境域名使用 http 而不是 https
http: true,
},
],
},
}
语言环境策略
有两种语言环境处理策略:子路径路由和 域名路由。
子路径路由
子路径路由将语言环境放在 URL 路径中。
module.exports = {
i18n: {
locales: ['en-US', 'fr', 'nl-NL'],
defaultLocale: 'en-US',
},
}
使用上述配置,en-US
、fr
和 nl-NL
将可用于路由,en-US
是默认语言环境。如果你有 pages/blog.js
,以下 URL 将可用:
/blog
/fr/blog
/nl-nl/blog
默认语言环境没有前缀。
域名路由
通过使用域名路由,你可以配置语言环境从不同域名提供服务:
module.exports = {
i18n: {
locales: ['en-US', 'fr', 'nl-NL', 'nl-BE'],
defaultLocale: 'en-US',
domains: [
{
// 注意:子域名必须包含在域名值中才能匹配
// 例如,如果 www.example.com 是预期的主机名,应该使用它
domain: 'example.com',
defaultLocale: 'en-US',
},
{
domain: 'example.fr',
defaultLocale: 'fr',
},
{
domain: 'example.nl',
defaultLocale: 'nl-NL',
// 指定应该重定向到此域名的其他语言环境
locales: ['nl-BE'],
},
],
},
}
例如,如果你有 pages/blog.js
,以下 URL 将可用:
example.com/blog
www.example.com/blog
example.fr/blog
example.nl/blog
example.nl/nl-BE/blog
自动语言环境检测
当用户访问应用程序根目录(通常是 /
)时,Next.js 将尝试基于 Accept-Language
头和当前域名自动检测用户偏好的语言环境。
如果检测到除默认语言环境之外的语言环境,用户将被重定向到:
- 使用子路径路由时: 语言环境前缀路径
- 使用域名路由时: 指定该语言环境为默认值的域名
使用域名路由时,如果具有 Accept-Language
头 fr;q=0.9
的用户访问 example.com
,他们将被重定向到 example.fr
,因为该域名默认处理 fr
语言环境。
使用子路径路由时,用户将被重定向到 /fr
。
为默认语言环境添加前缀
使用 Next.js 12 和中间件,我们可以通过变通方法为默认语言环境添加前缀。
例如,这是一个支持几种语言的 next.config.js
文件。注意 "default"
语言环境是故意添加的。
module.exports = {
i18n: {
locales: ['default', 'en', 'de', 'fr'],
defaultLocale: 'default',
localeDetection: false,
},
trailingSlash: true,
}
接下来,我们可以使用中间件添加自定义路由规则:
import { NextRequest, NextResponse } from 'next/server'
const PUBLIC_FILE = /\.(.*)$/
export async function middleware(req: NextRequest) {
if (
req.nextUrl.pathname.startsWith('/_next') ||
req.nextUrl.pathname.includes('/api/') ||
PUBLIC_FILE.test(req.nextUrl.pathname)
) {
return
}
if (req.nextUrl.locale === 'default') {
const locale = req.cookies.get('NEXT_LOCALE')?.value || 'en'
return NextResponse.redirect(
new URL(`/${locale}${req.nextUrl.pathname}${req.nextUrl.search}`, req.url)
)
}
}
这个中间件跳过为 API 路由和公共文件(如字体或图像)添加默认前缀。如果对默认语言环境发出请求,我们重定向到我们的前缀 /en
。
禁用自动语言环境检测
可以使用以下方式禁用自动语言环境检测:
module.exports = {
i18n: {
localeDetection: false,
},
}
当 localeDetection
设置为 false
时,Next.js 将不再基于用户偏好的语言环境自动重定向,并且只会提供从基于语言环境的域名或语言环境路径检测到的语言环境信息,如上所述。
访问语言环境信息
你可以通过 Next.js 路由器访问语言环境信息。例如,使用 useRouter()
钩子,以下属性可用:
locale
包含当前活动的语言环境。locales
包含所有配置的语言环境。defaultLocale
包含配置的默认语言环境。
当使用 getStaticProps
或 getServerSideProps
预渲染页面时,语言环境信息在上下文中提供给函数。
当利用 getStaticPaths
时,配置的语言环境在函数的上下文参数中的 locales
下提供,配置的 defaultLocale 在 defaultLocale
下提供。
在语言环境之间切换
你可以使用 next/link
或 next/router
在语言环境之间切换。
对于 next/link
,可以提供 locale
属性来从当前活动的语言环境切换到不同的语言环境。如果没有提供 locale
属性,在客户端切换期间使用当前活动的 locale
。例如:
import Link from 'next/link'
export default function IndexPage(props) {
return (
<Link href="/another" locale="fr">
To /fr/another
</Link>
)
}
当直接使用 next/router
方法时,你可以通过切换选项指定应该使用的 locale
。例如:
import { useRouter } from 'next/router'
export default function IndexPage(props) {
const router = useRouter()
return (
<div
onClick={() => {
router.push('/another', '/another', { locale: 'fr' })
}}
>
to /fr/another
</div>
)
}
注意,要处理仅切换 locale
而保留所有路由信息(如动态路由查询值或隐藏的 href 查询值),你可以将 href
参数作为对象提供:
import { useRouter } from 'next/router'
const router = useRouter()
const { pathname, asPath, query } = router
// 仅更改语言环境并维护所有其他路由信息,包括 href 的查询
router.push({ pathname, query }, asPath, { locale: nextLocale })
有关 router.push
对象结构的更多信息,请参见这里。
如果你有一个已经包含语言环境的 href
,你可以选择退出自动处理语言环境前缀:
import Link from 'next/link'
export default function IndexPage(props) {
return (
<Link href="/fr/another" locale={false}>
To /fr/another
</Link>
)
}
利用 NEXT_LOCALE
cookie
Next.js 允许设置 NEXT_LOCALE=the-locale
cookie,它优先于 accept-language 头。可以使用语言切换器设置此 cookie,然后当用户回到网站时,它将利用 cookie 中指定的语言环境,在从 /
重定向到正确的语言环境位置时使用。
例如,如果用户在 accept-language 头中偏好 fr
语言环境,但设置了 NEXT_LOCALE=en
cookie,当访问 /
时,用户将被重定向到 en
语言环境位置,直到 cookie 被删除或过期。
搜索引擎优化
由于 Next.js 知道用户访问的语言,它将自动将 lang
属性添加到 <html>
标签。
Next.js 不知道页面的变体,所以你需要使用 next/head
添加 hreflang
元标签。你可以在 Google Webmasters 文档中了解更多关于 hreflang
的信息。
这与静态生成如何工作?
注意,国际化路由与
output: 'export'
不集成,因为它不利用 Next.js 路由层。不使用output: 'export'
的混合 Next.js 应用程序完全受支持。
动态路由和 getStaticProps
页面
对于使用 getStaticProps
的页面,使用动态路由,所有需要预渲染的页面语言环境变体都需要从 getStaticPaths
返回。除了为 paths
返回的 params
对象外,你还可以返回一个 locale
字段,指定你想要 渲染的语言环境。例如:
export const getStaticPaths = ({ locales }) => {
return {
paths: [
// 如果没有提供 `locale`,只会生成 defaultLocale
{ params: { slug: 'post-1' }, locale: 'en-US' },
{ params: { slug: 'post-1' }, locale: 'fr' },
],
fallback: true,
}
}
对于自动静态优化和非动态 getStaticProps
页面,将为每个语言环境生成页面版本。这很重要,需要考虑,因为它可能会增加构建时间,具体取决于在 getStaticProps
中配置了多少语言环境。
例如,如果你配置了 50 个语言环境,有 10 个使用 getStaticProps
的非动态页面,这意味着 getStaticProps
将被调用 500 次。在每次构建期间将生成 10 个页面的 50 个版本。
要减少使用 getStaticProps
的动态页面的构建时间,使用 fallback
模式。这允许你从 getStaticPaths
返回仅最受欢迎的路径和语言环境,以便在构建期间预渲染。然后,Next.js 将在运行时构建剩余的页面,因为它们被请求。
自动静态优化页面
对于自动静态优化的页面,将为每个语言环境生成页面版本。
非动态 getStaticProps 页面
对于非动态 getStaticProps
页面,如上所述,为每个语言环境生成一个版本。getStaticProps
被调用时使用正在渲染的每个 locale
。如果你想选择退出某个语言环境的预渲染,你可以从 getStaticProps
返回 notFound: true
,这个页面变体将不会被生成。
export async function getStaticProps({ locale }) {
// 调用外部 API 端点获取帖子。
// 你可以使用任何数据获取库
const res = await fetch(`https://.../posts?locale=${locale}`)
const posts = await res.json()
if (posts.length === 0) {
return {
notFound: true,
}
}
// 通过返回 { props: posts },Blog 组件
// 将在构建时接收 `posts` 作为 prop
return {
props: {
posts,
},
}
}
i18n 配置的限制
locales
:总共 100 个语言环境domains
:总共 100 个语言环境域名项目
提示:这些限制最初是为了防止构建时的潜在性能问题。你可以使用 Next.js 12 中的中间件通过自定义路由来解决这些限制。