Menu

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

示例

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

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

开始使用

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

语言区域是 UTS 语言区域标识符,这是一种用于定义语言区域的标准化格式。

通常,语言区域标识符由语言、地区和文字组成,用破折号分隔:language-region-script。地区和文字是可选的。例如:

  • en-US - 美国使用的英语
  • nl-NL - 荷兰使用的荷兰语
  • nl - 荷兰语,没有特定地区

如果用户语言区域是 nl-BE 且它未列在你的配置中,他们将被重定向到 nl(如果可用),否则重定向到默认语言区域。 如果你不打算支持某个国家的所有地区,因此包含将作为后备的国家语言区域是一个好的做法。

next.config.js
module.exports = {
  i18n: {
    // 这些是你想要在应用程序中支持的所有语言区域
    locales: ['en-US', 'fr', 'nl-NL'],
    // 这是访问非语言区域前缀路径时要使用的默认语言区域
    // 例如 `/hello`
    defaultLocale: 'en-US',
    // 这是语言区域域名列表及其应处理的默认语言区域
    // (仅在设置域名路由时需要)
    // 注意:子域名必须包含在域名值中才能匹配,例如 "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: [
      {
        // 注意:子域名必须包含在域名值中才能匹配
        // 例如,如果预期主机名是 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 和 Proxy,我们可以通过变通方法为默认语言区域添加前缀。

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

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

接下来,我们可以使用 Proxy 添加自定义路由规则:

proxy.ts
import { NextRequest, NextResponse } from 'next/server'
 
const PUBLIC_FILE = /\.(.*)$/
 
export async function proxy(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)
    )
  }
}

Proxy 跳过为 API Routespublic 文件(如字体或图像)添加默认前缀。如果对默认语言区域发出请求,我们将重定向到我们的前缀 /en

禁用自动语言区域检测

可以使用以下方式禁用自动语言区域检测:

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

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

访问语言区域信息

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

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

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

当使用 getStaticPaths 时,配置的语言区域会在函数的上下文参数中的 locales 下提供,配置的 defaultLocale 在 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
// 仅更改 locale 并保持所有其他路由信息,包括 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}>
      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: [
      // 如果未提供 `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 会使用正在渲染的每个 locale 调用。如果你想选择退出某个语言区域的预渲染,可以从 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 中使用 Proxy 的自定义路由来解决这些限制。