Skip to main content

国际化 (i18n) 路由

Examples

v10.0.0 起,Next.js 就内置了对国际化 (国际化) 路由的支持。你可以提供区域设置列表、默认区域设置和特定于域的区域设置,Next.js 将自动处理路由。

¥Next.js has built-in support for internationalized (i18n) routing since v10.0.0. You can provide a list of locales, the default locale, and domain-specific locales and Next.js will automatically handle the routing.

i18n 路由支持目前旨在通过简化路由和语言环境解析来补充现有的 i18n 库解决方案,例如 react-intlreact-i18nextlinguirosettanext-intlnext-translatenext-multilingualtolgeeparaglide-next 等。

¥The i18n routing support is currently meant to complement existing i18n library solutions like react-intl, react-i18next, lingui, rosetta, next-intl, next-translate, next-multilingual, tolgee, paraglide-next and others by streamlining the routes and locale parsing.

入门

¥Getting started

首先,将 i18n 配置添加到 next.config.js 文件中。

¥To get started, add the i18n config to your next.config.js file.

区域设置是 UTS 区域设置标识符,这是定义区域设置的标准化格式。

¥Locales are UTS Locale Identifiers, a standardized format for defining locales.

通常,区域设置标识符由语言、区域和脚本组成,并用破折号分隔:language-region-script。区域和脚本是可选的。一个例子:

¥Generally a Locale Identifier is made up of a language, region, and script separated by a dash: language-region-script. The region and script are optional. An example:

  • en-US - 美国所说的英语

    ¥en-US - English as spoken in the United States

  • nl-NL - 荷兰语

    ¥nl-NL - Dutch as spoken in the Netherlands

  • nl - 荷兰语,无特定地区

    ¥nl - Dutch, no specific region

如果用户区域设置为 nl-BE 并且未在你的配置中列出,则它们将被重定向到 nl(如果可用),否则将重定向到默认区域设置。如果你不打算支持一个国家/地区的所有区域,那么最好将国家/地区区域设置作为后备。

¥If user locale is nl-BE and it is not listed in your configuration, they will be redirected to nl if available, or to the default locale otherwise. If you don't plan to support all regions of a country, it is therefore a good practice to include country locales that will act as fallbacks.

module.exports = {
i18n: {
// These are all the locales you want to support in
// your application
locales: ['en-US', 'fr', 'nl-NL'],
// This is the default locale you want to be used when visiting
// a non-locale prefixed path e.g. `/hello`
defaultLocale: 'en-US',
// This is a list of locale domains and the default locale they
// should handle (these are only required when setting up domain routing)
// Note: subdomains must be included in the domain value to be matched e.g. "fr.example.com".
domains: [
{
domain: 'example.com',
defaultLocale: 'en-US',
},
{
domain: 'example.nl',
defaultLocale: 'nl-NL',
},
{
domain: 'example.fr',
defaultLocale: 'fr',
// an optional http field can also be used to test
// locale domains locally with http instead of https
http: true,
},
],
},
}

区域设置策略

¥Locale Strategies

有两种区域设置处理策略:子路径路由和域路由。

¥There are two locale handling strategies: Sub-path Routing and Domain Routing.

子路径路由

¥Sub-path Routing

子路径路由将区域设置放入 url 路径中。

¥Sub-path Routing puts the locale in the url path.

module.exports = {
i18n: {
locales: ['en-US', 'fr', 'nl-NL'],
defaultLocale: 'en-US',
},
}

通过上述配置,en-USfrnl-NL 将可路由到,en-US 是默认区域设置。如果你有 pages/blog.js,则可以使用以下网址:

¥With the above configuration en-US, fr, and nl-NL will be available to be routed to, and en-US is the default locale. If you have a pages/blog.js the following urls would be available:

  • /blog

  • /fr/blog

  • /nl-nl/blog

默认区域设置没有前缀。

¥The default locale does not have a prefix.

域路由

¥Domain Routing

通过使用域路由,你可以配置从不同域提供服务的区域设置:

¥By using domain routing you can configure locales to be served from different domains:

module.exports = {
i18n: {
locales: ['en-US', 'fr', 'nl-NL', 'nl-BE'],
defaultLocale: 'en-US',

domains: [
{
// Note: subdomains must be included in the domain value to be matched
// e.g. www.example.com should be used if that is the expected hostname
domain: 'example.com',
defaultLocale: 'en-US',
},
{
domain: 'example.fr',
defaultLocale: 'fr',
},
{
domain: 'example.nl',
defaultLocale: 'nl-NL',
// specify other locales that should be redirected
// to this domain
locales: ['nl-BE'],
},
],
},
}

例如,如果你有 pages/blog.js,则以下网址将可用:

¥For example if you have pages/blog.js the following urls will be available:

  • example.com/blog

  • www.example.com/blog

  • example.fr/blog

  • example.nl/blog

  • example.nl/nl-BE/blog

自动区域设置检测

¥Automatic Locale Detection

当用户访问应用根目录(通常是 /)时,Next.js 将尝试根据 Accept-Language 标头和当前域自动检测用户喜欢的区域设置。

¥When a user visits the application root (generally /), Next.js will try to automatically detect which locale the user prefers based on the Accept-Language header and the current domain.

如果检测到默认区域设置以外的区域设置,用户将被重定向到:

¥If a locale other than the default locale is detected, the user will be redirected to either:

  • 使用子路径路由时:区域设置前缀路径

    ¥When using Sub-path Routing: The locale prefixed path

  • 使用域路由时:将该区域设置指定为默认值的域

    ¥When using Domain Routing: The domain with that locale specified as the default

使用域路由时,如果具有 Accept-Language 标头 fr;q=0.9 的用户访问 example.com,他们将被重定向到 example.fr,因为该域默认处理 fr 区域设置。

¥When using Domain Routing, if a user with the Accept-Language header fr;q=0.9 visits example.com, they will be redirected to example.fr since that domain handles the fr locale by default.

使用子路径路由时,用户将被重定向到 /fr

¥When using Sub-path Routing, the user would be redirected to /fr.

为默认区域设置添加前缀

¥Prefixing the Default Locale

使用 Next.js 12 和 中间件,我们可以使用 workaround 添加前缀到默认语言环境。

¥With Next.js 12 and Middleware, we can add a prefix to the default locale with a workaround.

例如,这是一个支持几种语言的 next.config.js 文件。请注意,"default" 语言环境是有意添加的。

¥For example, here's a next.config.js file with support for a few languages. Note the "default" locale has been added intentionally.

module.exports = {
i18n: {
locales: ['default', 'en', 'de', 'fr'],
defaultLocale: 'default',
localeDetection: false,
},
trailingSlash: true,
}

接下来我们可以使用 中间件 来添加自定义路由规则:

¥Next, we can use Middleware to add custom routing rules:

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 路由public 文件(如字体或图片)添加默认前缀。如果向默认区域设置发出请求,我们将重定向到前缀 /en

¥This Middleware skips adding the default prefix to API Routes and public files like fonts or images. If a request is made to the default locale, we redirect to our prefix /en.

禁用自动区域设置检测

¥Disabling Automatic Locale Detection

可以通过以下方式禁用自动区域设置检测:

¥The automatic locale detection can be disabled with:

module.exports = {
i18n: {
localeDetection: false,
},
}

localeDetection 设置为 false 时,Next.js 将不再根据用户的首选区域设置自动重定向,并且仅提供从基于区域设置的域或区域设置路径检测到的区域设置信息(如上所述)。

¥When localeDetection is set to false Next.js will no longer automatically redirect based on the user's preferred locale and will only provide locale information detected from either the locale based domain or locale path as described above.

访问区域设置信息

¥Accessing the locale information

你可以通过 Next.js 路由访问区域设置信息。例如,使用 useRouter() 钩子,可以使用以下属性:

¥You can access the locale information via the Next.js router. For example, using the useRouter() hook the following properties are available:

  • locale 包含当前活动的区域设置。

    ¥locale contains the currently active locale.

  • locales 包含所有配置的区域设置。

    ¥locales contains all configured locales.

  • defaultLocale 包含配置的默认区域设置。

    ¥defaultLocale contains the configured default locale.

pre-rendering 寻呼 getStaticPropsgetServerSideProps 时,区域设置信息在提供给函数的 上下文 中提供。

¥When pre-rendering pages with getStaticProps or getServerSideProps, the locale information is provided in the context provided to the function.

当利用 getStaticPaths 时,配置的区域设置在 locales 下的函数的上下文参数中提供,并在 defaultLocale 下的配置的 defaultLocale 中提供。

¥When leveraging getStaticPaths, the configured locales are provided in the context parameter of the function under locales and the configured defaultLocale under defaultLocale.

区域设置之间的转换

¥Transition between locales

你可以使用 next/linknext/router 在区域设置之间进行转换。

¥You can use next/link or next/router to transition between locales.

对于 next/link,可以提供 locale 属性来转换到与当前活动区域不同的区域设置。如果未提供 locale 属性,则在客户端转换期间使用当前活动的 locale。例如:

¥For next/link, a locale prop can be provided to transition to a different locale from the currently active one. If no locale prop is provided, the currently active locale is used during client-transitions. For example:

import Link from 'next/link'

export default function IndexPage(props) {
return (
<Link href="/another" locale="fr">
To /fr/another
</Link>
)
}

直接使用 next/router 方法时,你可以通过转换选项指定应使用的 locale。例如:

¥When using the next/router methods directly, you can specify the locale that should be used via the transition options. For example:

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 参数作为对象:

¥Note that to handle switching only the locale while preserving all routing information such as dynamic route query values or hidden href query values, you can provide the href parameter as an object:

import { useRouter } from 'next/router'
const router = useRouter()
const { pathname, asPath, query } = router
// change just the locale and maintain all other route information including href's query
router.push({ pathname, query }, asPath, { locale: nextLocale })

有关 router.push 的对象结构的更多信息,请参阅 此处

¥See here for more information on the object structure for router.push.

如果你的 href 已包含区域设置,你可以选择不自动处理区域设置前缀:

¥If you have a href that already includes the locale you can opt-out of automatically handling the locale prefixing:

import Link from 'next/link'

export default function IndexPage(props) {
return (
<Link href="/fr/another" locale={false}>
To /fr/another
</Link>
)
}

¥Leveraging the NEXT_LOCALE cookie

Next.js 允许设置 NEXT_LOCALE=the-locale cookie,该 cookie 优先于接受语言标头。可以使用语言切换器设置此 cookie,然后当用户返回站点时,它将利用 cookie 中指定的区域设置从 / 重定向到正确的区域设置位置。

¥Next.js allows setting a NEXT_LOCALE=the-locale cookie, which takes priority over the accept-language header. This cookie can be set using a language switcher and then when a user comes back to the site it will leverage the locale specified in the cookie when redirecting from / to the correct locale location.

例如,如果用户在接受语言标头中更喜欢区域设置 fr,但在访问 / 时将 NEXT_LOCALE=en cookie 设置为 en 区域设置,则用户将被重定向到 en 区域设置位置,直到 cookie 被删除或过期。

¥For example, if a user prefers the locale fr in their accept-language header but a NEXT_LOCALE=en cookie is set the en locale when visiting / the user will be redirected to the en locale location until the cookie is removed or expired.

搜索引擎优化

¥Search Engine Optimization

由于 Next.js 知道用户正在访问什么语言,因此它会自动将 lang 属性添加到 <html> 标记中。

¥Since Next.js knows what language the user is visiting it will automatically add the lang attribute to the <html> tag.

Next.js 不知道页面的变体,因此你可以使用 next/head 添加 hreflang 元标记。你可以在 Google 网站管理员文档 中了解有关 hreflang 的更多信息。

¥Next.js doesn't know about variants of a page so it's up to you to add the hreflang meta tags using next/head. You can learn more about hreflang in the Google Webmasters documentation.

这如何与静态生成一起使用?

¥How does this work with Static Generation?

请注意,国际化路由不与 output: 'export' 集成,因为它不利用 Next.js 路由层。完全支持不使用 output: 'export' 的混合 Next.js 应用。

¥Note that Internationalized Routing does not integrate with output: 'export' as it does not leverage the Next.js routing layer. Hybrid Next.js applications that do not use output: 'export' are fully supported.

动态路由和 getStaticProps 页面

¥Dynamic Routes and getStaticProps Pages

对于使用 getStaticProps动态路由 的页面,需要预渲染的页面的所有区域设置变体都需要从 getStaticPaths 返回。除了为 paths 返回 params 对象之外,你还可以返回 locale 字段,指定要渲染的区域设置。例如:

¥For pages using getStaticProps with Dynamic Routes, all locale variants of the page desired to be prerendered need to be returned from getStaticPaths. Along with the params object returned for paths, you can also return a locale field specifying which locale you want to render. For example:

export const getStaticPaths = ({ locales }) => {
return {
paths: [
// if no `locale` is provided only the defaultLocale will be generated
{ params: { slug: 'post-1' }, locale: 'en-US' },
{ params: { slug: 'post-1' }, locale: 'fr' },
],
fallback: true,
}
}

对于 自动静态优化 和非动态 getStaticProps 页面,将为每个区域设置生成页面的版本。考虑这一点很重要,因为它可能会增加构建时间,具体取决于 getStaticProps 内配置的语言环境数量。

¥For Automatically Statically Optimized and non-dynamic getStaticProps pages, a version of the page will be generated for each locale. This is important to consider because it can increase build times depending on how many locales are configured inside getStaticProps.

例如,如果你使用 getStaticProps 配置了 50 个语言环境并配置了 10 个非动态页面,则这意味着 getStaticProps 将被调用 500 次。每次构建期间将生成 10 个页面的 50 个版本。

¥For example, if you have 50 locales configured with 10 non-dynamic pages using getStaticProps, this means getStaticProps will be called 500 times. 50 versions of the 10 pages will be generated during each build.

要减少使用 getStaticProps 的动态页面的构建时间,请使用 fallback 模式。这允许你仅返回 getStaticPaths 中最流行的路径和区域设置,以便在构建期间进行预渲染。然后,Next.js 将在运行时根据请求构建剩余页面。

¥To decrease the build time of dynamic pages with getStaticProps, use a fallback mode. This allows you to return only the most popular paths and locales from getStaticPaths for prerendering during the build. Then, Next.js will build the remaining pages at runtime as they are requested.

自动静态优化页面

¥Automatically Statically Optimized Pages

对于 自动静态优化 的页面,将为每个区域设置生成该页面的版本。

¥For pages that are automatically statically optimized, a version of the page will be generated for each locale.

非动态 getStaticProps 页面

¥Non-dynamic getStaticProps Pages

对于非动态 getStaticProps 页面,将为每个区域设置生成一个版本,如上所示。每个正在渲染的 locale 都会调用 getStaticProps。如果你想选择不预渲染某个区域设置,你可以从 getStaticProps 返回 notFound: true,这样就不会生成该页面的变体。

¥For non-dynamic getStaticProps pages, a version is generated for each locale like above. getStaticProps is called with each locale that is being rendered. If you would like to opt-out of a certain locale from being pre-rendered, you can return notFound: true from getStaticProps and this variant of the page will not be generated.

export async function getStaticProps({ locale }) {
// Call an external API endpoint to get posts.
// You can use any data fetching library
const res = await fetch(`https://.../posts?locale=${locale}`)
const posts = await res.json()

if (posts.length === 0) {
return {
notFound: true,
}
}

// By returning { props: posts }, the Blog component
// will receive `posts` as a prop at build time
return {
props: {
posts,
},
}
}

i18n 配置的限制

¥Limits for the i18n config

  • locales:总共 100 个区域设置

    ¥locales: 100 total locales

  • domains:总共 100 个区域设置域项目

    ¥domains: 100 total locale domain items

很高兴知道:最初添加这些限制是为了防止潜在的 构建时的性能问题。你可以使用 Next.js 12 中的 中间件 通过自定义路由来解决这些限制。

¥Good to know: These limits have been added initially to prevent potential performance issues at build time. You can workaround these limits with custom routing using Middleware in Next.js 12.