Sponsor
ntab.devntab.dev 提升效率的新标签页组件
点击查看
Menu

国际化(i18n)路由

示例

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

目前,i18n 路由支持旨在补充现有的 i18n 库解决方案,如 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',
    // 这是区域设置域名及其应处理的默认区域设置的列表
    // 注意:子域名必须包含在域值中才能匹配,例如 "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,则应使用 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">
      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
// 仅更改区域设置并保留所有其他路由信息,包括 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,它的优先级高于 accept-language 标头。可以使用语言切换器设置此 Cookie,当用户再次访问网站时,它将在从 / 重定向到正确的区域设置位置时使用 Cookie 中指定的区域设置。

例如,如果用户在其 accept-language 标头中偏好 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 返回所有需要预渲染的页面语言变体。除了返回的 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 模式。这允许你仅返回最受欢迎的路径和语言环境以在构建期间预渲染。然后,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 中的中间件通过自定义路由来解决这些限制。