跳至内容

国际化 (i18n) 路由

示例

v10.0.0 版本开始,Next.js 内置支持国际化 (i18n) 路由。您可以提供语言环境列表、默认语言环境以及特定于域的语言环境,Next.js 将自动处理路由。

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

入门

要开始使用,请将 i18n 配置添加到您的 next.config.js 文件中。

语言环境是 UTS 语言环境标识符,这是一种定义语言环境的标准化格式。

通常,语言环境标识符由语言、区域和脚本组成,并用连字符分隔:language-region-script。区域和脚本是可选的。例如

  • en-US - 美式英语
  • nl-NL - 荷兰语(荷兰)
  • nl - 荷兰语,无特定区域

如果用户语言环境为 nl-BE 且未在您的配置中列出,则如果可用,他们将被重定向到 nl,否则将重定向到默认语言环境。因此,如果您不打算支持一个国家的全部地区,则最好包含用作回退的国家/地区语言环境。

next.config.js
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,
      },
    ],
  },
}

语言环境策略

有两种语言环境处理策略:子路径路由和域名路由。

子路径路由

子路径路由将语言环境置于 URL 路径中。

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

使用上述配置,en-USfrnl-NL 将可用于路由,并且 en-US 是默认语言环境。如果您有一个 pages/blog.js,则以下 URL 将可用

  • /blog
  • /fr/blog
  • /nl-nl/blog

默认语言环境没有前缀。

域名路由

通过使用域名路由,您可以配置从不同域名提供服务的语言环境。

next.config.js
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,则以下 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" 语言环境。

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

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

middleware.ts
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

禁用自动语言环境检测

可以使用以下方法禁用自动语言环境检测

next.config.js
module.exports = {
  i18n: {
    localeDetection: false,
  },
}

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

访问语言环境信息

您可以通过 Next.js 路由器访问语言环境信息。例如,使用 useRouter() 钩子,以下属性可用

  • locale 包含当前活动的语言环境。
  • locales 包含所有配置的语言环境。
  • defaultLocale 包含配置的默认语言环境。

使用 预渲染 页面时 getStaticPropsgetServerSideProps,语言环境信息将提供在 传递给函数的上下文 中。

利用 getStaticPaths 时,配置的语言环境将以 locales 的形式提供在函数的上下文参数中,配置的 defaultLocaledefaultLocale 的形式提供。

在语言环境之间切换

您可以使用 next/linknext/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
// change just the locale and maintain all other route information including href's query
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.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 网站管理员文档 中了解有关 hreflang 的更多信息。

这与静态生成如何协作?

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

动态路由和 getStaticProps 页面

对于使用 getStaticProps动态路由 的页面,需要从 getStaticPaths 返回希望预渲染的页面的所有语言环境变体。除了为 paths 返回的 params 对象外,还可以返回一个 locale 字段,指定要渲染的语言环境。例如

pages/blog/[slug].js
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 中配置的语言环境数量,它可能会增加构建时间。

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

要减少使用 getStaticProps 的动态页面的构建时间,请使用 fallback 模式。这允许您仅从 getStaticPaths 返回最受欢迎的路径和语言环境,以便在构建期间进行预渲染。然后,Next.js 会在运行时根据请求构建其余页面。

自动静态优化页面

对于 自动静态优化 的页面,将为每个语言环境生成页面的一个版本。

非动态 getStaticProps 页面

对于非动态 getStaticProps 页面,如上所述,将为每个语言环境生成一个版本。getStaticProps 会使用正在渲染的每个 locale 调用。如果要选择退出某个语言环境的预渲染,可以从 getStaticProps 返回 notFound: true,并且不会生成该页面的此变体。

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 配置的限制

  • locales:100 个语言环境
  • domains:100 个语言环境域名条目

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