i18nPic

在 NextJS 中使用 i18n 的指南

目前專案在 NextJS App router, SSG/ISR 環境中 使用到 i18n, 此篇紀錄設定

安裝需求

在專案中安裝以下套件:

  1. i18next:核心國際化框架
  2. react-i18next:React 綁定
  3. i18next-browser-languagedetector:用於檢測瀏覽器語言
  4. i18next-resources-to-backend:用於動態加載翻譯資源
  5. react-cookie:用於管理語言偏好設置的 cookie

Installation

npm install i18next react-i18next i18next-browser-languagedetector i18next-resources-to-backend react-cookie

配置步驟

1. 路徑和文件夾設置

在 app 文件夾中設置動態路由文件夾 [lng]。所有其他路徑(文件夾)將存儲在此文件夾下。

app
├── [lng]
│   └── 所有路徑文件夾
└── i18n
    ├── locales
    ├── client.ts  // Client Component 的配置和 useTranslation 函數
    ├── index.ts   // Server Component 的配置和 useTranslation 函數
    ├── setting.ts
    └── lang.ts    // 從 URL 獲取當前語言

2. 建立 i18n 配置文件

client.ts : 建立Client Component i18n 設置的配置文件

"use client";

import { useEffect, useState } from "react";
import i18next from "i18next";
import {
    initReactI18next,
    useTranslation as useTranslationOrg,
} from "react-i18next";
import { useCookies } from "react-cookie";
import resourcesToBackend from "i18next-resources-to-backend";
import LanguageDetector from "i18next-browser-languagedetector";
import { getOptions, languages, cookieName } from "./setting";

// 檢查代碼是否在Server端運行
const runsOnServerSide = typeof window === "undefined";

// 初始化 i18next
i18next
    .use(initReactI18next) // 將 i18n 傳遞給 react-i18next
    .use(LanguageDetector) // 使用語言檢測器
    .use(
        resourcesToBackend(
            (lng: string, ns: string) => import(`./locales/${lng}/${ns}.json`)
        )
    ) // 動態加載翻譯
    .init({
        ...getOptions(),
        lng: undefined, // 讓Client檢測語言
        detection: {
            order: ["path", "htmlTag", "cookie", "navigator"],
        },
        preload: runsOnServerSide ? languages : [], // 在Server端預加載所有語言
    });

// 自定義 useTranslation hook
export function useTranslation(lng?: string, ns?: string, options?: {}) {
    const [cookies, setCookie] = useCookies([cookieName]);
    const ret = useTranslationOrg(ns, options);
    const { i18n } = ret;

    if (runsOnServerSide && lng && i18n.resolvedLanguage !== lng) {
        i18n.changeLanguage(lng);
    } else {
        // Client語言處理
        const [activeLng, setActiveLng] = useState(i18n.resolvedLanguage);
        
        // 當解析的語言變更時更新活動語言
        useEffect(() => {
            if (activeLng === i18n.resolvedLanguage) return;
            setActiveLng(i18n.resolvedLanguage);
        }, [activeLng, i18n.resolvedLanguage]);
        
        // 當 lng prop 變更時改變語言
        useEffect(() => {
            if (!lng || i18n.resolvedLanguage === lng) return;
            i18n.changeLanguage(lng);
        }, [lng, i18n]);
        
        // 當語言變更時更新 cookie
        useEffect(() => {
            if (cookies.i18next === lng) return;
            setCookie(cookieName, lng, { path: "/" });
        }, [lng, cookies.i18next, setCookie]);
    }
    return { ...ret };
}

3. index.ts : 建立Server Component i18n 設置的配置文件

// 默認語言
export const fallbackLng = "en";

// 支持的語言
export const languages = [fallbackLng, "zh-Hant"];

// 存儲語言偏好的 cookie 名稱
export const cookieName = 'i18next'

// 默認命名空間
export const defaultNS = "translation";

// 獲取 i18next 選項的函數
export function getOptions(lng = fallbackLng, ns = defaultNS) {
    return {
        debug: true,
        supportedLngs: languages,
        fallbackLng,
        lng,
        fallbackNS: defaultNS,
        defaultNS,
        ns,
    };
}

4. 建立翻譯文件

在 app/i18n/locales/ 目錄下為每種語言建立翻譯文件,例如:

  • app/i18n/locales/en/common.json
  • app/i18n/locales/zh/common.json

5. 使用翻譯

在Client Component 中:

"use client";
import { useTranslation } from "../i18n/client";
import { CurrentLang } from "../i18n/lang";

export default function ClientComponent() {
  const lang = CurrentLang(); // 獲取當前語言
  const { t } = useTranslation(lang, "common");

  return <h1>{t('welcome')}</h1>; // 使用翻譯
}

在Server端 Component 中:

import { useTranslation } from "../i18n";

export default async function ServerComponent({ params: { lng } }) {
  const { t } = await useTranslation(lng, "common");

  return <h1>{t('welcome')}</h1>; // 使用翻譯
}

注意事項

  • 確保在 next.config.js 中正確配置了 i18n 設置。
  • 對於動態路由,確保在 app/[lng] 文件夾中正確處理語言設置。
  • 獲取當前語言設置:可以從 params 傳下來,若無法從 params 傳下來,則使用 CurrentLang() 函數來獲取當前語言設置。
  • 在Client Component 和Server端 Component 中使用不同的 useTranslation 導入方式。
  • Client和Server端的 i18next 配置略有不同,需要注意區分使用。