跳到内容

国际化 (i18n) 路由

示例

Next.js 从 v10.0.0 版本开始,内置了对国际化(i18n)路由的支持。你可以提供一个区域设置列表、默认区域设置和特定于域名的区域设置,Next.js 将自动处理路由。

i18n 路由支持目前旨在补充现有的 i18n 库解决方案,例如 react-intlreact-i18nextlinguirosettanext-intlnext-translatenext-multilingualtolgeeparaglide-nextnext-intlayer 等,通过简化路由和区域设置解析。

开始使用

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

禁用自动区域设置检测

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

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

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

访问区域设置信息

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

  • locale 包含当前活动的区域设置。
  • locales 包含所有已配置的区域设置。
  • defaultLocale 包含配置的默认区域设置。

当使用 getStaticPropsgetServerSideProps 预渲染 页面时,区域设置信息在提供给函数的上下文中提供。

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

在区域设置之间转换

你可以使用 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,该 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 个区域设置,其中 10 个非动态页面使用 getStaticProps,这意味着 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 中的 中间件 通过自定义路由来解决这些限制。