語言切換
Next.js 語言切換(i18n)
TL;DRNext.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-i18next | Pages Router 整合 | ✅ 但偏向遺留 |
next-intl | App 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
// 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>
);
}
使用方式:
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>
);
}
方法 2:next-i18next(Pages Router 整合)
npm install next-i18next react-i18next i18next
// next-i18next.config.js
module.exports = {
i18n: {
defaultLocale: 'zh',
locales: ['zh', 'en', 'de'],
},
};
// next.config.js
const { i18n } = require('./next-i18next.config');
module.exports = { i18n };
語言檔放 /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);
// 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'])),
},
});
重點是 serverSideTranslations,在 SSR 階段預先載入翻譯,避免 client 端閃爍。
方法 3:next-intl(App Router 首選)
npm install next-intl
// i18n/request.ts
import { getRequestConfig } from 'next-intl/server';
export default getRequestConfig(async ({ locale }) => ({
messages: (await import(`../messages/${locale}.json`)).default,
}));
// messages/zh.json
{
"Header": {
"welcome": "歡迎來到我的 app",
"switchLanguage": "切換語言"
}
}
// 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>
);
}
// 在 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>;
}
為什麼推 next-intl
- Server Components 直接
await getTranslations(...),不用包 Provider- ICU 訊息格式(複數、性別、日期)
- TypeScript inference(寫錯 key 會直接報錯)
- URL routing 內建(
/zh/about//en/about)
翻譯內進階用法
內插值
{ "greeting": "Hello, {name}!" }
t('greeting', { name: 'Bobo' }); // "Hello, Bobo!"
複數(ICU)
{ "items": "{count, plural, =0 {No items} =1 {1 item} other {# items}}" }
t('items', { count: 0 }); // "No items"
t('items', { count: 1 }); // "1 item"
t('items', { count: 5 }); // "5 items"
react-i18next 也支援,但語法略不同(有 _one / _other 後綴),學一次選一個。
常見地雷
1. 翻譯閃爍(FOUC)Pages Router 用 next-i18next 時,如果 getServerSideProps / getStaticProps 沒呼叫 serverSideTranslations,client 會先看到英文 key 才換成翻譯。每個有翻譯的頁面都要記得 call。
2. 動態 key 會 break tree-shaket(`error.${code}`) // ❌ 打包工具不知道你會用哪些 key解法:用 explicit map 或
as const:const errorKeys = { 404: 'error.notFound', 500: 'error.serverError' } as const; t(errorKeys[code]);
3. SEO hreflang多語站別忘了 <link rel="alternate" hreflang="...">,告訴 Google 各語言版本對應 URL。next-intl 內建,Pages Router 要自己加。