國際化路由 (i18n Routing)

範例

Next.js 自 v10.0.0 起內建支援國際化 (i18n) 路由功能。您可以提供語言環境清單、預設語言環境及網域專屬語言環境,Next.js 會自動處理路由。

目前的 i18n 路由支援旨在與現有的 i18n 函式庫解決方案如 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',
    // 這是語言環境網域清單及其應處理的預設語言環境
    // (僅在設定網域路由時需要)
    // 注意:子網域必須包含在網域值中才能匹配,例如 "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,
      },
    ],
  },
}

語言環境策略

有兩種語言環境處理策略:子路徑路由 (Sub-path Routing) 和網域路由 (Domain Routing)。

子路徑路由

子路徑路由將語言環境置於網址路徑中。

next.config.js
module.exports = {
  i18n: {
    locales: ['en-US', 'fr', 'nl-NL'],
    defaultLocale: 'en-US',
  },
}

使用上述設定後,en-USfrnl-NL 將可供路由使用,且 en-US 為預設語言環境。若您有 pages/blog.js,則以下網址將可用:

  • /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,則以下網址將可用:

  • 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 及 中介軟體 (Middleware) 中,我們可以透過解決方案為預設語言環境添加前綴。

例如,以下是一個支援幾種語言的 next.config.js 檔案。請注意,我們特意添加了 "default" 語言環境。

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

接著,我們可以使用中介軟體 (Middleware) 來添加自訂路由規則:

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

中介軟體 (Middleware) 會跳過為API 路由公開檔案(如字型或圖片)添加預設前綴。若請求是針對預設語言環境發出的,我們會重新導向至帶有前綴 /en 的路徑。

停用自動語言環境偵測

可以透過以下方式停用自動語言環境偵測:

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

localeDetection 設為 false 時,Next.js 將不再根據使用者的偏好語言環境自動重新導向,僅提供從語言環境網域或語言環境路徑偵測到的資訊,如上所述。

存取語言環境資訊

您可以透過 Next.js 路由器存取語言環境資訊。例如,使用 useRouter() 鉤子時,以下屬性可供使用:

  • locale 包含目前使用的語言環境。
  • locales 包含所有已設定的語言環境。
  • defaultLocale 包含已設定的預設語言環境。

當使用 getStaticPropsgetServerSideProps 預渲染 (pre-rendering) 頁面時,語言環境資訊會提供給函式的上下文

當利用 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">
      前往 /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=the-locale 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 的資訊。

這如何與靜態生成 (Static Generation) 配合運作?

請注意,國際化路由不與 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,
  }
}

對於自動靜態優化 (Automatically Statically Optimized) 和非動態 getStaticProps 頁面,會為每個語言環境生成一個頁面版本。這點很重要,因為根據 getStaticProps 中設定的語言環境數量,可能會增加建置時間。

例如,若您設定了 50 個語言環境與 10 個使用 getStaticProps 的非動態頁面,這意味著 getStaticProps 將被呼叫 500 次。每次建置時會生成這 10 個頁面的 50 個版本。

若要減少使用 getStaticProps 的動態頁面建置時間,請使用 fallback 模式。這允許您從 getStaticPaths 僅返回最受歡迎的路徑和語言環境進行建置時的預渲染。接著,Next.js 會在請求時於執行階段建置其餘頁面。

自動靜態優化頁面

對於自動靜態優化 (Automatically Statically Optimized) 的頁面,會為每個語言環境生成一個頁面版本。

非動態 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 中,您可以使用中介軟體 (Middleware) 透過自訂路由來解決這些限制。