如何在 Next.js 中实现国际化

示例

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

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

快速开始

首先在 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,
      },
    ],
  },
}

语言环境策略

Next.js 提供两种语言环境处理策略:子路径路由和域名路由。

子路径路由

子路径路由将语言代码置于 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 属性,客户端切换时将使用当前活动语言环境。例如:

import Link from 'next/link'

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

直接使用 next/router 方法时,可通过过渡选项指定目标语言环境。例如:

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>
  )
}

注意:若需在保留所有路由信息(如 动态路由 查询参数或隐藏的 href 查询值)的同时仅切换语言环境,可将 href 参数作为对象传递:

import { useRouter } from 'next/router'
const router = useRouter()
const { pathname, asPath, query } = router
// 仅切换语言环境,保留其他所有路由信息
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 中指定的语言环境。

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

搜索引擎优化

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

Next.js 不了解页面的语言变体,因此需要您使用 next/head 手动添加 hreflang 元标签。有关 hreflang 的更多信息,请参阅 Google 站长文档

静态生成如何运作?

注意:国际化路由不与 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 页面,每个语言环境都会生成对应的页面版本。这需要特别注意,因为根据配置的语言环境数量,可能会显著增加构建时间。

例如若配置了 50 个语言环境,且有 10 个非动态页面使用 getStaticProps,则 getStaticProps 将被调用 500 次。每次构建时会生成这 10 个页面的 50 个语言版本。

要减少动态页面的构建时间,可使用 fallback 模式。这样您只需在 getStaticPaths 中返回最受欢迎的路径和语言环境进行预渲染,剩余页面将在运行时按需生成。

自动静态优化页面

对于 自动静态优化 的页面,每个语言环境都会生成对应的页面版本。

非动态 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,
    }
  }

  return {
    props: {
      posts,
    },
  }
}

i18n 配置限制

  • locales:最多支持 100 个语言环境
  • domains:最多支持 100 个域名配置项

须知:这些限制最初是为了避免 构建时的性能问题。在 Next.js 12 中,您可以通过 中间件 使用自定义路由来突破这些限制。