首頁
學習紀錄
遊戲心得影視Life書單案件檔案
Side Projects委託作品與二創互動實驗場
Kurau
百百 BLOG
首頁
學習紀錄
遊戲心得影視Life書單案件檔案
Side Projects委託作品與二創互動實驗場
Kurau

Kurau Blog

「隨心而寫,真真假假,都是我」

一個記錄生活、輸出興趣的個人空間。
遊戲、影視、閱讀、學習……每一段體驗都值得留下文字。

頁面導覽

  • 學習紀錄
  • 遊戲心得
  • 影視Life
  • 書單
  • 委託作品與二創
  • Kurau
  • 合作邀請

找到我

歡迎來 Discord 找我聊天!

“曾經發生的事不可能忘記,只是暫時想不起來而已。”-《神隱少女》

© 2026 Kurau All rights reserved

前端框架

語言切換

By Kurau·2023-03-22·Updated 2026-05-09·3 分鐘閱讀

Next.js 語言切換(i18n)

TL;DR
Next.js i18n 三條主流路:next-i18next(Pages Router 標配)、next-intl(App Router 首選,2026 推薦)、react-i18next(基礎庫)。流程:語言檔放 /public/locales/{lang}/translation.json → Provider 包整個 app → 用 t('key') 取翻譯。

三套套件分工

套件適合維護狀態
react-i18next基礎 i18n 庫,任何 React 環境✅ 活躍
next-i18nextPages Router 整合✅ 但偏向遺留
next-intlApp Router 整合(2026 首選)✅ 現代
怎麼選
  • Pages Router 既有專案 → next-i18next(不用大改)
  • App Router 新專案 → next-intl(Server Components 友善、ICU 訊息格式)
  • 不用 Next.js → react-i18next

方法 1:react-i18next + Pages Router(舊但常見)

npm install i18next react-i18next
bash
// pages/_app.tsx
import { createContext } from 'react';
import type { AppProps } from 'next/app';
import { useTranslation, initReactI18next } from 'react-i18next';
import i18n from 'i18next';

const resources = {
  en: { translation: { 'Welcome to my app': 'Welcome to my app' } },
  zh: { translation: { 'Welcome to my app': '歡迎來到我的 app' } },
  de: { translation: { 'Welcome to my app': 'Willkommen in meiner App' } },
};

i18n.use(initReactI18next).init({
  lng: 'zh',
  fallbackLng: 'en',
  resources,
  interpolation: { escapeValue: false },
});

export const I18nContext = createContext(i18n);

export default function MyApp({ Component, pageProps }: AppProps) {
  const { t } = useTranslation();

  return (
    <I18nContext.Provider value={i18n}>
      <Component {...pageProps} />
    </I18nContext.Provider>
  );
}
tsx

使用方式:

import { useTranslation } from 'react-i18next';

function Header() {
  const { t, i18n } = useTranslation();

  return (
    <header>
      <h1>{t('Welcome to my app')}</h1>
      <button onClick={() => i18n.changeLanguage('en')}>EN</button>
      <button onClick={() => i18n.changeLanguage('zh')}>中</button>
    </header>
  );
}
tsx

方法 2:next-i18next(Pages Router 整合)

npm install next-i18next react-i18next i18next
bash
// next-i18next.config.js
module.exports = {
  i18n: {
    defaultLocale: 'zh',
    locales: ['zh', 'en', 'de'],
  },
};
javascript
// next.config.js
const { i18n } = require('./next-i18next.config');
module.exports = { i18n };
javascript

語言檔放 /public/locales/{lang}/{namespace}.json:

public/
└── locales/
    ├── en/
    │   ├── common.json
    │   └── home.json
    ├── zh/
    │   ├── common.json
    │   └── home.json
    └── de/
        ├── common.json
        └── home.json
// pages/_app.tsx
import { appWithTranslation } from 'next-i18next';

function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}

export default appWithTranslation(MyApp);
tsx
// pages/index.tsx
import { useTranslation } from 'next-i18next';
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';

export default function Home() {
  const { t } = useTranslation('common');
  return <h1>{t('hello')}</h1>;
}

export const getStaticProps = async ({ locale }) => ({
  props: {
    ...(await serverSideTranslations(locale, ['common', 'home'])),
  },
});
tsx

重點是 serverSideTranslations,在 SSR 階段預先載入翻譯,避免 client 端閃爍。


方法 3:next-intl(App Router 首選)

npm install next-intl
bash
// i18n/request.ts
import { getRequestConfig } from 'next-intl/server';

export default getRequestConfig(async ({ locale }) => ({
  messages: (await import(`../messages/${locale}.json`)).default,
}));
typescript
// messages/zh.json
{
  "Header": {
    "welcome": "歡迎來到我的 app",
    "switchLanguage": "切換語言"
  }
}
json
// app/[locale]/layout.tsx
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';

export default async function LocaleLayout({
  children,
  params: { locale },
}: { children: React.ReactNode; params: { locale: string } }) {
  const messages = await getMessages();

  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}
tsx
// 在 Server Component 用
import { getTranslations } from 'next-intl/server';

export default async function Home() {
  const t = await getTranslations('Header');
  return <h1>{t('welcome')}</h1>;
}

// 在 Client Component 用
'use client';
import { useTranslations } from 'next-intl';

function Header() {
  const t = useTranslations('Header');
  return <h1>{t('welcome')}</h1>;
}
tsx
為什麼推 next-intl
  1. Server Components 直接 await getTranslations(...),不用包 Provider
  2. ICU 訊息格式(複數、性別、日期)
  3. TypeScript inference(寫錯 key 會直接報錯)
  4. URL routing 內建(/zh/about / /en/about)

翻譯內進階用法

內插值

{ "greeting": "Hello, {name}!" }
json
t('greeting', { name: 'Bobo' });   // "Hello, Bobo!"
tsx

複數(ICU)

{ "items": "{count, plural, =0 {No items} =1 {1 item} other {# items}}" }
json
t('items', { count: 0 });    // "No items"
t('items', { count: 1 });    // "1 item"
t('items', { count: 5 });    // "5 items"
tsx

react-i18next 也支援,但語法略不同(有 _one / _other 後綴),學一次選一個。


常見地雷

1. 翻譯閃爍(FOUC)
Pages Router 用 next-i18next 時,如果 getServerSideProps / getStaticProps 沒呼叫 serverSideTranslations,client 會先看到英文 key 才換成翻譯。每個有翻譯的頁面都要記得 call。
2. 動態 key 會 break tree-shake
t(`error.${code}`)  // ❌ 打包工具不知道你會用哪些 key
tsx

解法:用 explicit map 或 as const:

const errorKeys = { 404: 'error.notFound', 500: 'error.serverError' } as const;
t(errorKeys[code]);
tsx
3. SEO hreflang
多語站別忘了 <link rel="alternate" hreflang="...">,告訴 Google 各語言版本對應 URL。next-intl 內建,Pages Router 要自己加。

目錄

    ◆ 相關文章

    • Mdx Next.js

      2026-05-09
    • Next.js Add Font Awesome

      2026-05-09
    • 從現在開始改用Next.js

      從現在開始改用Next.js

      2026-05-09
    • Ladle 前端測試工具

      Ladle 前端測試工具

      2026-05-09
    ← 上一篇限制特定網頁下一篇 →如何布置靜態網站 (免費資源) ⇒ Next.js

    ◆ 關於作者

    Kurau

    個人寫作 / 創作的 SoT,記錄遊戲、影視、學習與生活。

    更多 Kurau 的文章