Communitygithub.com

mwasobaddy/skills

為 Next.js App Router 應用程式架構與實作國際化(i18n)— 包含資料夾結構、地區前綴路由、型別化訊息檔、ICU 複數格式與本地化 SEO(hreflang)。適用於在 Next.js 網站或應用程式中新增或修復多語言支援,選擇 next-intl、Lingui 或 next-i18next,設定 [locale] 區段搭配 proxy 或 middleware,或將純英文應用遷移至本地化 URL。觸發條件包含 i18n、在地化、多語言、翻譯、地區路由、lang 屬性、hreflang、next-intl。

skills 是什麼?

此技能提供完整的 Next.js App Router 國際化解決方案,涵蓋從零開始的架構設計到既有專案遷移。其核心價值在於指導開發者選擇合適的 i18n 函式庫(預設為 next-intl),並建立標準化的資料夾結構(messages/ 目錄搭配 src/i18n/ 模組)與路由機制([locale] 區段 + proxy.ts)。技能詳細說明了 canonical 架構的每個元件:routing.ts(定義地區)、request.ts(解析請求地區)、navigation.ts(本地化 Link/redirect),並強調了常見陷阱,例如 params 在 Next.js 15+ 為 Promise、setRequestLocale 在 16.3+ 已過時、以及 proxy.ts 僅支援 Node.js 執行環境。對於遷移場景,提供了分階段指引:先盤點字串、選擇路由策略、再移動路由與替換文字,最後加入重定向與 hreflang。此外,該技能還包含了避免常見錯誤的明確建議,例如不要將 messages/ 目錄放在 src/ 內、不要從 next/link 匯入 Link、以及不要手動撰寫地區前綴。它支援多種函式庫比較、ICU 語法、以及 SEO 最佳化,是 Next.js 國際化開發的權威參考。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/mwasobaddy/skills/tree/HEAD/nextjs-i18n

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Next.js i18n

將 Next.js 應用程式放入多語言的結構化指引:資料夾配置應如何、配置錯誤時會發生什麼問題,以及應避免什麼。

先選擇函式庫

對於 App Router 上的新專案,預設使用 next-intl。它不需要建置外掛、巨集步驟或每個地區的實例管理;訊息是基於來源語言的純 JSON 型別。

函式庫適用時機成本
next-intl (預設)新的 App Router 專案,任何團隊規模無 — 純 JSON,無代碼生成
@lingui/react需要編譯時提取和更小的執行時期,或希望一個 i18n 層跨 React/Vue/AstroSWC 或 Babel 巨集外掛,需精確釘版
next-i18next團隊已熟悉 i18next,或正在遷移 Pages Router 程式碼每個請求實例,getT/useT 分開
Next.js 內建 i18n僅限 Pages Router。 絕不用於 App Router不適用

Next.js 的 i18n: { locales, defaultLocale } 配置僅適用於 Pages Router。在 App Router 中它不做任何事 — 那裡的地區路由意味著一個 [locale] 區段加上一個 proxy。不要讓代理程式為 App Router 專案「啟用」內建的 i18n。

完整的比較,包括套件大小和每個函式庫的取捨:references/library-comparison.md。

規範架構

將靜態字典放在 src/ 之外,以便打包和匯入路徑保持明確,並將所有地區相關機制保留在一個資料夾中。

messages/                      # 純靜態字典 — 保持於 /src 之外
├── en.json                     # 原始來源;每個其他地區都鏡像它
├── de.json
└── ja.json
src/
├── i18n/
│   ├── routing.ts              # defineRouting: 地區 + 預設地區
│   ├── request.ts              # getRequestConfig: 解析地區,載入訊息
│   └── navigation.ts           # createNavigation: 感知地區的 Link/redirect
├── proxy.ts                    # 地區偵測 + 重定向 (Next 16;以前是 middleware.ts)
└── app/
    ├── [locale]/               # 包圍整個本地化應用
    │   ├── layout.tsx          # 驗證地區,<html lang>,generateStaticParams
    │   └── page.tsx
    └── not-found.tsx           # 全域 404 位於 [locale] 之外

使此形狀生效的規則:

  • [locale] 是一個根動態區段。 所有本地化內容都位於其下。根層級路由(not-found.tsx、error.tsx、api/)保持在外,且不得無條件呼叫 i18n 掛鉤,否則會在執行時崩潰。
  • 一個來源地區。 messages/en.json 是主檔。每個其他檔案都必須完全鏡像其形狀。
  • 所有地區知識在 src/i18n/ 中。 元件從 @/i18n/navigation 匯入 Link,永遠不從 next/link 匯入。新增語言只需修改一個地方。

設定(全新專案)

依此順序執行。順序很重要 — proxy.ts 依賴 routing.ts,而佈局依賴 request.ts。

  1. 安裝 next-intl,用 createNextIntlPlugin() 包裹 next.config.ts。
  2. 使用 defineRouting 建立 src/i18n/routing.ts。
  3. 建立 src/proxy.ts,匯出 createMiddleware(routing) 加上預設匹配器。
  4. 使用 getRequestConfig 解析地區,建立 src/i18n/request.ts。
  5. 將 app/** 移至 app/[locale]/** 下;新增 generateStaticParams 並驗證參數。
  6. 使用 createNavigation(routing) 建立 src/i18n/navigation.ts。
  7. 建立 messages/en.json 並使用 useTranslations 取代硬編碼字串。
  8. 設定 <html lang> 並新增本地化中繼資料及 hreflang。

每個步驟的完整檔案內容:references/next-intl-setup.md。

常見陷阱

在編寫任何 i18n 程式碼之前,請閱讀本節。這些是需要花費數小時才能解決的失敗。

  • setRequestLocale 是舊版 API。 在 Next.js 16.3+ 上,應在 getRequestConfig 內部使用 next/root-params 讀取地區。大多數指南、教學和 LLM 訓練資料仍然教導 setRequestLocale(params.locale)。它仍然可以運作,並被記錄為向後相容 — 但它是舊 API。僅在 Next < 16.3 上使用它。無論哪種方式,generateStaticParams 仍然是必需的。
  • params 在 Next 15+ 上是 Promise。 const {locale} = await params。將 params.locale 視為型別錯誤,而非執行時錯誤。
  • 驗證地區,否則未知值會渲染。 使用 hasLocale(routing.locales, locale) 和 notFound()。沒有這個,/xx/about 會以虛假 URL 渲染你的英文頁面。
  • proxy.ts,不是 middleware.ts,在 Next 16 上。 middleware.ts 已棄用。Proxy 僅在 Node.js 上執行 — Edge 執行時期不支援。Lingui 和 next-i18next 宣傳 Edge 安全的 proxy;如果你需要 Edge,你面臨的是真正的取捨,而不是免費的勝利。
  • 絕不串接翻譯後的字串。 "You have " + count + " items" 在日語和阿拉伯語中不合語法。使用 ICU:"{count, plural, =1 {One item} other {# items}}"。
  • 絕不在一條訊息內部使用 + 或模板字面量進行插值。 使用具名佔位符—{name}—以便單詞順序可以移動。
  • 日期和數字會導致水合不匹配。 伺服器和瀏覽器上的 new Date() 因時區和時鐘而異。在 getRequestConfig 中設定 timeZone,或對相對時間使用用戶端渲染。症狀:「Text content does not match server-rendered HTML」。
  • 格式化取決於地區。 toLocaleString() 沒有明確的地區時,會根據執行時期地區格式化,而不是請求的地區。使用 useFormatter() / getFormatter()。
  • getRequestConfig 每個請求執行一次,並由 React 快取。 不要在其中放置請求相關的狀態(購物車、用戶偏好)。僅放置與地區相關的配置。
  • Server Actions 和 Route Handlers 沒有根參數。 await locale() 僅在 Server Components 中有效。在 actions 和 handlers 中明確將地區作為引數傳遞。

改造既有應用程式

將此視為會影響 URL 的遷移,而不是配置變更。當您新增 [locale] 前綴時,每個現有 URL 的形狀都會改變。

依此順序規劃:

  1. 盤點字串 — 執行 scripts/find-untranslated.sh 找出硬編碼的 JSX 文字。
  2. 在編碼前選擇路由策略。 始終使用地區前綴(/en/about,/de/about)是預設值;它明確且 hreflang 乾淨。localePrefix: 'as-needed' 在裸路徑上提供預設地區 — URL 更好,SEO 訊號較弱。現在決定;稍後切換是完全重新遷移。
  3. 在接觸任何元件之前先建立路由。 讓 proxy.ts + [locale] 僅使用英文訊息就能運作。
  4. 將路由移至 [locale]/ 下 — 這是一個大型機械性 git mv。在一個提交中完成,以便 git log --follow 保持有用。
  5. 替換字串 按命名空間進行,而不是按檔案。端到端翻譯一個命名空間,以便在關鍵形狀擴散之前得到驗證。
  6. 為每個變更的 URL 新增重定向。 /about → /en/about(或 307 到偵測到的地區)。沒有這個,你將失去所有現有排名。不容妥協。
  7. 僅在每個地區都擁有該路由之後 才新增 hreflang + 本地化中繼資料,否則你會發出指向 404 的替代連結。

包含重定向和回滾說明的完整遷移序列:references/migration.md。

應避免的事項

  • 不要將 messages/ 放在 src/ 內部。 原始碼樹中的純伺服器字典會模糊哪些內容會傳送到用戶端。
  • 不要在本地化路由中從 next/link 匯入 Link。 它會丟失地區前綴,並靜默地連結到錯誤的語言。
  • 不要在 href 中手寫地區前綴(href={/${locale}/about})。使用來自 @/i18n/navigation 的 Link,它會處理帶前綴、按需和域名路由。
  • 不要使用一個扁平的關鍵字命名空間。 按功能/頁面分組。扁平的關鍵字會衝突且無法搜尋。
  • 不要將建置時自動翻譯作為真實來源。 機器輸出是草稿;未經審查就提交會使錯誤永久化且無法追溯。
  • 不要只將一個地區新增到 routing.locales 就稱完成了。 沒有翻譯的 messages/xx.json,每個頁面都會渲染關鍵字或靜默地回退。
  • 不要混合路由策略。 as-needed 加上指向帶前綴 URL 的 hreflang 是自找的 SEO 錯誤。

參考資料

按需閱讀這些內容,而非預先閱讀:

  • references/library-comparison.md — 在 next-intl、Lingui、next-i18next 之間進行選擇;套件大小、遷移觸發條件、每個函式庫的設定形狀。
  • references/next-intl-setup.md — 規範設定中的每個檔案,可直接複製貼上。
  • references/messages-and-keys.md — 命名空間結構、關鍵字命名、ICU 語法、日期/數字/列表格式化、型別化訊息。
  • references/seo.md — hreflang、<html lang>、本地化 generateMetadata、canonical、站點地圖替代、語言切換器 SEO 規則。
  • references/migration.md — 改造既有應用程式、重定向、回滾。

腳本

  • scripts/find-untranslated.sh — 標記專案中可能硬編碼的面向使用者字串,以便你可以在開始前評估工作量。在改造專案的第一步執行它。
  • scripts/run-trigger-eval.sh — 測量此技能的 description 是否在正確的提示上觸發。在變更描述時使用它。

測試描述

description 是代理程式在決定載入此技能之前唯一看到的東西。如果它無法觸發,下面的內容都不重要。

bash scripts/run-trigger-eval.sh --dry-run          # 顯示計畫,不進行 API 呼叫
bash scripts/run-trigger-eval.sh --split train     # 12 個查詢 x 3 次執行
bash scripts/run-trigger-eval.sh --split validation --out report.json

查詢集:evals/trigger-queries.json — 12 個應觸發,8 個不應觸發,按 60/40 分割為固定的訓練集和驗證集。

編輯描述時:

  • 僅使用訓練集修正失敗。最後再觸碰驗證集一次,以檢查變更是否泛化 — 如果訓練集改善而驗證集下降,則你過擬合了。
  • 應觸發的失敗表示描述太窄:新增查詢所代表的概念,而不是查詢的關鍵字。
  • 不應觸發的失敗表示描述太寬泛:說明此技能不涵蓋什麼。
  • 編輯前重新執行。觸發是非確定性的,一次執行不能證明任何事情。

不要從失敗的查詢中新增關鍵字 — 這樣描述會看起來像關鍵字列表,並停止匹配真實提示。

相關技能