跳至內容

國際化 (i18n) 路由

範例

Next.js 從 v10.0.0 版本開始,就內建支援國際化 (i18n) 路由。您可以提供語系列表、預設語系和網域特定的語系,Next.js 會自動處理路由。

目前 i18n 路由支援的目的是透過簡化路由和語系解析,來輔助現有的 i18n 函式庫解決方案,例如 react-intlreact-i18nextlinguirosettanext-intlnext-translatenext-multilingualtolgeeparaglide-next 等。

開始使用

要開始使用,請將 i18n 設定新增至您的 next.config.js 檔案。

語系設定是 UTS 語系識別碼,一種定義語系設定的標準格式。

一般來說,語系識別碼是由語言、地區和字體,以連字號分隔而成:language-region-script。地區和字體是選用的。範例:

  • en-US - 美國使用的英語
  • nl-NL - 荷蘭使用的荷蘭語
  • nl - 荷蘭語,無特定地區

如果使用者語系設定是 nl-BE 且未列在您的設定中,他們將會被重新導向至 nl(如果有的話),否則會導向至預設語系設定。如果您不打算支援一個國家所有的地區,建議您加入國家語系設定作為備用方案。

next.config.js
module.exports = {
  i18n: {
    // These are all the locales you want to support in
    // your application
    locales: ['en-US', 'fr', 'nl-NL'],
    // This is the default locale you want to be used when visiting
    // a non-locale prefixed path e.g. `/hello`
    defaultLocale: 'en-US',
    // This is a list of locale domains and the default locale they
    // should handle (these are only required when setting up domain routing)
    // Note: subdomains must be included in the domain value to be matched e.g. "fr.example.com".
    domains: [
      {
        domain: 'example.com',
        defaultLocale: 'en-US',
      },
      {
        domain: 'example.nl',
        defaultLocale: 'nl-NL',
      },
      {
        domain: 'example.fr',
        defaultLocale: 'fr',
        // an optional http field can also be used to test
        // locale domains locally with http instead of https
        http: true,
      },
    ],
  },
}

語系設定策略

有兩種語系設定處理策略:子路徑路由和網域路由。

子路徑路由

子路徑路由會將語系設定放在網址路徑中。

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: [
      {
        // Note: subdomains must be included in the domain value to be matched
        // e.g. www.example.com should be used if that is the expected hostname
        domain: 'example.com',
        defaultLocale: 'en-US',
      },
      {
        domain: 'example.fr',
        defaultLocale: 'fr',
      },
      {
        domain: 'example.nl',
        defaultLocale: 'nl-NL',
        // specify other locales that should be redirected
        // to this domain
        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 和中介軟體,我們可以透過一個解決方案為預設語系新增前綴。

例如,以下是一個支援多種語言的 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 將不再根據使用者偏好的語系自動重新導向,並且只會提供從上述基於語系的網域或語系路徑偵測到的語系資訊。

存取語系資訊 useRouter() hook,可以使用以下屬性:

  • locale 包含目前作用中的語系。
  • locales 包含所有已設定的語系。
  • defaultLocale 包含已設定的預設語系。

使用 getStaticPropsgetServerSideProps 預渲染 頁面時,語系資訊會在提供給函式的 context 中提供。

使用 getStaticPaths 時,已設定的語系會在函式的 context 參數中的 locales 下提供,而設定的預設語系則在 defaultLocale 下提供。

語系間的轉換
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
// change just the locale and maintain all other route information including href's query
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=地區設定 的 Cookie,其優先順序高於 accept-language 標頭。這個 Cookie 可以使用語言切換器設定,然後當使用者返回網站時,它會在從 / 重新導向到正確的地區設定位置時利用 Cookie 中指定的地區設定。

例如,如果使用者在其 accept-language 標頭中偏好 fr 地區設定,但設定了 NEXT_LOCALE=en Cookie,則在存取 / 時,使用者將被重新導向到 en 地區設定位置,直到 Cookie 被移除或過期。

搜尋引擎最佳化

由於 Next.js 知道使用者正在存取哪種語言,它會自動將 lang 屬性新增到 <html> 標籤。

Next.js 無法辨識頁面的不同語言版本,因此您需要使用 next/head 新增 hreflang meta 標籤。您可以在 Google Search Console 說明文件中深入瞭解 hreflang

這如何與靜態生成一起運作?

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

動態路由和 getStaticProps 頁面 動態路由 的頁面,所有想要預先渲染的頁面地區設定版本都需要從 getStaticPaths 返回。除了為 paths 返回的 params 物件之外,您還可以返回一個 locale 欄位,指定您想要渲染的地區設定。例如:

pages/blog/[slug].js
export const getStaticPaths = ({ locales }) => {
  return {
    paths: [
      // if no `locale` is provided only the defaultLocale will be generated
      { params: { slug: 'post-1' }, locale: 'en-US' },
      { params: { slug: 'post-1' }, locale: 'fr' },
    ],
    fallback: true,
  }
}

對於 自動靜態優化 和非動態 getStaticProps 頁面,**將會為每個地區設定產生一個頁面版本**。考量這一點很重要,因為它可能會根據在 getStaticProps 中設定的地區設定數量而增加建置時間。

舉例來說,如果您設定了 50 個語系,並使用 getStaticProps 建立 10 個非動態頁面,這表示 getStaticProps 將會被呼叫 500 次。每次建置時,會產生 10 個頁面的 50 個不同語系版本。

為了縮短使用 getStaticProps 建置動態頁面的時間,請使用 fallback 模式。這讓您可以在建置期間,僅從 getStaticPaths 返回最常用的路徑和語系以進行預渲染。接著,Next.js 會在執行階段,依請求建置其餘的頁面。

自動靜態優化頁面

對於自動靜態優化的頁面,每個語系都會產生一個頁面版本。

非動態 getStaticProps 頁面

如同上述,對於非動態 getStaticProps 頁面,每個語系都會產生一個版本。系統會針對每個要渲染的 locale 呼叫 getStaticProps。如果您不想預先渲染特定語系,可以從 getStaticProps 返回 notFound: true,這樣就不會產生該頁面的語系版本。

export async function getStaticProps({ locale }) {
  // Call an external API endpoint to get posts.
  // You can use any data fetching library
  const res = await fetch(`https://.../posts?locale=${locale}`)
  const posts = await res.json()
 
  if (posts.length === 0) {
    return {
      notFound: true,
    }
  }
 
  // By returning { props: posts }, the Blog component
  // will receive `posts` as a prop at build time
  return {
    props: {
      posts,
    },
  }
}

i18n 設定的限制

  • locales:最多 100 個語系
  • domains:最多 100 個語系網域項目

注意事項:最初新增這些限制是為了防止在建置時可能發生的效能問題。在 Next.js 12 中,您可以使用中介軟體自訂路由來解決這些限制。