國際化路由 (i18n Routing)

範例

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

目前 i18n 路由支援旨在透過簡化路由與語言環境解析,與現有 i18n 函式庫解決方案如 react-intlreact-i18nextlinguirosettanext-intlnext-translatenext-multilingualtolgee 等互補。

開始使用

要開始使用,請在 next.config.js 檔案中加入 i18n 設定。

語言環境採用 UTS Locale Identifiers 標準格式定義。

一般來說,語言環境識別碼由語言、地區和文字以連字號分隔組成:語言-地區-文字。地區和文字為可選項目。例如:

  • 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) 頁面時,語言環境資訊會提供給函式的 context

使用 getStaticPaths 時,設定的語言環境會在函式的 context 參數中以 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 同時保留所有路由資訊(如 動態路由 (dynamic route) 查詢值或隱藏的 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 Webmasters 文件 中了解更多關於 hreflang 的資訊。

這如何與靜態生成 (Static Generation) 配合使用?

請注意,國際化路由不與 output: 'export' 整合,因為它不利用 Next.js 路由層。不使用 output: 'export' 的混合 Next.js 應用程式完全支援。

動態路由和 getStaticProps 頁面

對於使用 getStaticProps動態路由 (Dynamic Routes) 的頁面,所有需要預渲染的語言環境變體都需要從 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 會針對每個正在渲染的語言環境被呼叫。如果您希望選擇不預渲染某些語言環境,可以從 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) 進行自訂路由來解決這些限制。