Skip to main content

Next.js 國際化完整指南

為 App Router 設定 next-intl、設定語言路由,並利用 AI 自動翻譯。

1

安裝 next-intl

next-intl 是一個用於 Next.js App Router 的套件,負責語言路由、訊息載入和翻譯 hook。

Terminal
npm install next-intl
為什麼選擇 next-intl 而不是 next-i18next?next-intl 專為 App Router 和伺服器元件打造。next-i18next 原本為 Pages Router 設計,對 App Router 的支援有限。
2

建立 i18n 請求設定

建立兩個檔案:src/i18n/request.ts 用於載入訊息,src/i18n/routing.ts 用於定義語言。它們用於設定 next-intl 解析訊息和路由的方式。

src/i18n/routing.ts
// src/i18n/routing.ts
import { defineRouting } from 'next-intl/routing';
import { createNavigation } from 'next-intl/navigation';

export const routing = defineRouting({
  locales: ['en', 'de', 'ja', 'es'],
  defaultLocale: 'en',
  localePrefix: 'as-needed',  // /about for en, /de/about for de
});

export const { Link, redirect, usePathname, useRouter } =
  createNavigation(routing);
Turbopack(Next.js 15 的預設打包器)要求在 next.config.js 中設定 experimental.turbo.resolveAlias,否則會出現 "Couldn't find next-intl config file" 錯誤。
3

設定中介軟件

新增 middleware.ts 以處理語言檢測、URL 重寫和重定向。中介軟件會攔截每個請求,並確保使用正確語言。

middleware.ts
// middleware.ts  <- Must be in project ROOT, not src/
import createMiddleware from 'next-intl/middleware';
import { routing } from './src/i18n/routing';

export default createMiddleware(routing);

export const config = {
  matcher: ['/((?!api|_next|.*\\..*).*)'],
};
middleware.ts 必須位於項目根目錄,而不能放在 src/ 中。這是 next-intl 最常見的設定錯誤。
4

設定 [locale] 資料夾結構

將應用程式路由移入 app/[locale]/。新增 generateStaticParams,在構建時為每種語言產生頁面。這樣會建立 /en/about、/de/about 等 URL 結構。

app/[locale]/layout.tsx
// app/[locale]/layout.tsx
import { routing } from '@/i18n/routing';

export function generateStaticParams() {
  return routing.locales.map((locale) => ({ locale }));
}
每個使用譯文的 page.tsx 和 layout.tsx 都必須呼叫 setRequestLocale(locale)。否則 Next.js 會回退到動態渲染,構建性能會顯著下降。
5

更新根佈局

使用 getMessages() 載入訊息,並在根語言佈局中傳給 NextIntlClientProvider。根據語言參數設定 html 的 lang 屬性。

app/[locale]/layout.tsx
// app/[locale]/layout.tsx
import { NextIntlClientProvider } from 'next-intl';
import { getMessages, setRequestLocale } from 'next-intl/server';
import { routing } from '@/i18n/routing';
import { notFound } from 'next/navigation';

export function generateStaticParams() {
  return routing.locales.map((locale) => ({ locale }));
}

export default async function LocaleLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: { locale: string };
}) {
  const { locale } = await params;
  if (!routing.locales.includes(locale as any)) notFound();

  setRequestLocale(locale);
  const messages = await getMessages();

  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider locale={locale} messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}
NextIntlClientProvider 要求明確提供 locale 屬性。省略該屬性會在客戶端元件中造成難以調試的細微錯誤。
6

在元件中使用譯文

伺服器元件使用 getTranslations(非同步,需 await),客戶端元件使用 useTranslations(hook)。應根據元件渲染位置選擇;伺服器元件能讓譯文完全不進入 JavaScript bundle。

app/[locale]/page.tsx
// Server Component (default)
import { getTranslations, setRequestLocale } from 'next-intl/server';

export default async function AboutPage({
  params,
}: { params: { locale: string } }) {
  const { locale } = await params;
  setRequestLocale(locale);
  const t = await getTranslations('AboutPage');

  return <h1>{t('title')}</h1>;
}

// Client Component ('use client')
'use client';
import { useTranslations } from 'next-intl';

export default function SearchBar() {
  const t = useTranslations('SearchBar');
  return <input placeholder={t('placeholder')} />;
}
翻譯內容應優先使用伺服器元件。它們不會把翻譯字串放入客戶端 JavaScript bundle,可減少用戶載入時間。
7

新增 SEO:元數據與 Hreflang

使用 generateMetadata 產生特定語言的頁面標題和描述。為 hreflang 標籤新增 alternates.languages,讓搜尋引擎發現每個頁面的所有語言版本。

app/[locale]/layout.tsx
// app/[locale]/layout.tsx or any page.tsx
import { getTranslations } from 'next-intl/server';
import { routing } from '@/i18n/routing';

export async function generateMetadata({
  params,
}: { params: { locale: string } }) {
  const { locale } = await params;
  const t = await getTranslations({ locale, namespace: 'Metadata' });

  return {
    title: t('title'),
    description: t('description'),
    alternates: {
      languages: Object.fromEntries(
        routing.locales.map((l) => [l, `/${l}`])
      ),
    },
  };
}
如果未設定 metadataBase,Vercel 構建可能會把 localhost 產生為規範 URL。請務必在根佈局中將 metadataBase 設定為生產域名。
8

處理錯誤頁和未找到頁面

error.tsx 和 not-found.tsx 可能在正常語言佈局之外渲染,因此需要特殊處理。根 not-found.tsx 需要單獨設定 i18n provider 才能顯示本地化錯誤訊息。

app/[locale]/error.tsx
// app/[locale]/error.tsx
'use client';
import { useTranslations } from 'next-intl';

export default function Error() {
  const t = useTranslations('Error');
  return (
    <div>
      <h1>{t('title')}</h1>
      <p>{t('description')}</p>
    </div>
  );
}

// app/not-found.tsx (root level -- needs own provider)
import { routing } from '@/i18n/routing';

export default async function GlobalNotFound() {
  return (
    <html lang={routing.defaultLocale}>
      <body>
        <h1>404 - Page Not Found</h1>
      </body>
    </html>
  );
}
只有程式碼明確呼叫 notFound() 時,Next.js 才會渲染本地化 404 頁面。沒有匹配頁面的未知路由會顯示預設 Next.js 404,而不是你的本地化版本。
9

自動翻譯

完成 i18n 設定後,直接從 IDE 使用 AI 翻譯訊息檔案,或在 CI/CD 管線中使用 i18n Agent CLI,在每次部署時自動翻譯。

Terminal
# In your IDE, ask your AI assistant:
> Translate messages/en.json to German, Japanese, and Spanish

✓ messages/de.json created (1.1s)
✓ messages/ja.json created (1.4s)
✓ messages/es.json created (1.0s)
使用 next-intl-localechain 實現智能語言回退:當巴西葡萄牙語不可用時,pt-BR 用戶會看到 pt-PT 譯文,而不是回退到英語。

自動保證翻譯質素

使用 i18n-validate 在發佈前發現缺失鍵和損壞的預留位置。真實譯文完成前,可使用 i18n-pseudo 產生偽譯文來測試 UI。

Next.js i18n 開源工具

這些開源套件可解決 Next.js 國際化工作流中的常見痛點。

next-intl-localechain

標準 next-intl 在缺少譯文時會直接回退到預設語言。巴西葡萄牙語用戶會看到英語,而不是完全可用的 pt-PT 譯文。next-intl-localechain 會新增智能回退鏈,深度合併相關語言的譯文,讓區域用戶始終看到最接近的可用譯文。

src/i18n/request.ts
import { getRequestConfig } from 'next-intl/server';
import { withLocaleChain } from 'next-intl-localechain';

export default getRequestConfig(withLocaleChain({
  loadMessages: (locale) =>
    import(`../../messages/${locale}.json`).then(m => m.default),
  defaultLocale: 'en'
}));
自動深度合併語言回退鏈中的譯文
內置葡萄牙語、西班牙語、法語、德語等語言的回退鏈
平穩跳過缺失的訊息檔案,不會報錯
一行設定,用於包裝現有 getRequestConfig
在 GitHub 上查看

@i18n-agent/cli

一款命令列工具,讓你無需離開終端即可翻譯 Next.js 訊息檔案。可直接翻譯檔案、檢查任務狀態並下載結果。通過 API 密鑰進行身份驗證後,可在 CI/CD 管線中實現全自動本地化工作流。

Terminal
# Install the CLI
npm install -g @i18n-agent/cli

# Authenticate
i18nagent login

# Translate your message files
i18nagent translate ./messages/en.json --lang de,ja,es

# Or use in CI/CD with an API key
export I18N_AGENT_API_KEY=your-key-here
i18nagent translate ./messages/en.json --lang de,ja,es
從終端翻譯 JSON、YAML、PO 等 i18n 檔案格式
可用於 CI/CD,通過環境變數進行身份驗證以實現自動化管線
跟蹤任務狀態、恢復失敗任務並下載結果
提供機器可讀的 JSON 輸出,用於腳本和自動化
在 GitHub 上查看

常見問題

"Unable to find next-intl locale"

中介軟件沒有匹配請求。請檢查:middleware.ts 是否位於項目根目錄?matcher 模式是否正確排除靜態檔案?路由設定中是否包含該語言?

意外的動態渲染

頁面或佈局缺少 setRequestLocale(locale)。如果未呼叫,next-intl 會使用標頭或 Cookie 檢測語言,從而強制動態渲染並阻止靜態產生。

並行路由與 i18n 衝突

並行路由(@modal)和攔截路由((.)photo)與 [locale] 動態區段存在已知相容問題。這些高級路由模式可改用基於中介軟件的路由作為變通方案。

切換語言後丟失當前路由

切換語言時,使用 usePathname() 保留當前路徑,只替換語言區段。請注意動態路由參數,它們需要針對新語言重新解析。

推薦的檔案結構

Project Structure
my-nextjs-app/
├── middleware.ts              # Locale routing (project root!)
├── next.config.mjs
├── messages/
│   ├── en.json                # Source messages
│   ├── de.json
│   └── ja.json
├── src/
│   ├── i18n/
│   │   ├── request.ts         # Message loading config
│   │   └── routing.ts         # Locale definitions
│   └── app/
│       └── [locale]/
│           ├── layout.tsx     # Root locale layout
│           ├── page.tsx       # Home page
│           ├── error.tsx      # Localized error page
│           ├── not-found.tsx  # Localized 404
│           └── about/
│               └── page.tsx
└── package.json

立即試用 i18n Agent

將翻譯檔案拖放到此處

JSON, YAML, PO, XML, CSV, Markdown, Properties

或點擊選擇檔案

目標語言

無需註冊即時估價

使用 next-intl-localechain 實現語言回退

如果 pt-BR 等區域語言缺少翻譯鍵,next-intl 會直接跳到預設語言,而不會先檢查父語言 pt。

Terminal
npm install next-intl-localechain
Configuration
import { withLocaleChain } from 'next-intl-localechain';

export default withLocaleChain({
  fallbacks: {
    'pt-BR': ['pt', 'en'],
    'zh-Hant-HK': ['zh-Hant', 'zh', 'en'],
  },
  defaultLocale: 'en',
  loadMessages: (locale) => import(`./messages/${locale}.json`),
});

查看語言回退指南,瞭解受支援框架的完整列表和 75 條內置回退鏈。 Learn more →

常見問題