国际化路由 (i18n Routing)

示例

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

当前的国际化路由支持旨在通过简化路由和语言环境解析,与现有国际化库方案如 react-intlreact-i18nextlinguirosettanext-intlnext-translatenext-multilingualtypesafe-i18ntolgee 等形成互补。

快速开始

首先,在您的 next.config.js 文件中添加 i18n 配置。

语言环境采用 UTS 语言标识符 标准格式定义。

通常,语言标识符由语言、地区和文字三部分组成,用短横线连接:语言-地区-文字。其中地区和文字为可选部分。例如:

  • en-US - 美国英语
  • nl-NL - 荷兰荷兰语
  • nl - 荷兰语(无特定地区)

如果用户的语言环境是 nl-BE 但未在配置中列出,系统会将其重定向至 nl(若可用)或默认语言环境。因此,若您不打算支持某个国家的所有地区,最佳实践是包含将作为备选的国家级语言环境。

next.config.js
module.exports = {
  i18n: {
    // 这是您应用中希望支持的所有语言环境
    locales: ['en-US', 'fr', 'nl-NL'],
    // 当访问不带语言前缀的路径(如 `/hello`)时使用的默认语言环境
    defaultLocale: 'en-US',
    // 域名路由配置(仅在设置域名路由时需要)
    // 注意:子域名必须包含在 domain 值中才能匹配,例如 "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 路径中。

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: [
      {
        // 注意:子域名必须包含在 domain 值中才能匹配
        // 例如若预期主机名为 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" 语言环境:

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 提供,配置的默认语言环境通过 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">
      跳转至 /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' })
      }}
    >
      跳转至 /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}>
      跳转至 /fr/another
    </Link>
  )
}

Next.js 支持通过 NEXT_LOCALE=语言环境 Cookie 覆盖 accept-language 请求头。可通过语言切换器设置此 Cookie,当用户再次访问站点时,从 / 重定向至正确语言位置时将优先使用 Cookie 中指定的语言环境。

例如,若用户请求头偏好 fr 语言环境但设置了 NEXT_LOCALE=en Cookie,访问 / 时用户将被重定向至 en 语言位置,直到 Cookie 被删除或过期。

搜索引擎优化

由于 Next.js 知晓用户访问的语言,它会自动为 <html> 标签添加 lang 属性。

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: [
      // 若未提供 `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 会针对每个正在渲染的语言环境被调用。若您希望排除某些语言环境的预渲染,可从 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 中,您可以通过 中间件 使用自定义路由来绕过这些限制。