DOJO004

  • Dashool 創辦人
  • 喜歡調酒
  • Rails、Nextjs、TypeScript

Next.js 如何設置 i18n (use app router)

JavaScript

 
  • 需要先了解、設置 middleware
  • 新增 [lang] rounter。
  • 設置 layout.tsx 。
  • 新增 Dictionaries。

新增 middleware.ts

 
  • 與 /src/app 同個層級。

可以看到預設語言雖然是 en-US ,但在 getLocale() 裡面會依照瀏覽器的語言設定。
若瀏覽器使用的語言是 zh-TW ,在 getLocale() 就會回傳 zh-TW 。
 
// src/middleware.ts

import { match as matchLocale } from "@formatjs/intl-localematcher";
import Negotiator from "negotiator";
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";

const locales = ["en-US", "zh-TW", "zh-CN"];
const defaultLocale = "en-US";

// Get the preferred locale
function getLocale(request: NextRequest): string {
  // Negotiator expects plain object so we need to transform headers
  const negotiatorHeaders: Record<string, string> = {};
  request.headers.forEach((value, key) => {
    negotiatorHeaders[key] = value;
  });

  // Use negotiator and intl-localematcher to get best locale
  const languages = new Negotiator({ headers: negotiatorHeaders }).languages(
    locales
  );

  const matchedLocale = matchLocale(languages, locales, defaultLocale);

  return matchedLocale;
}

export function middleware(request: NextRequest) {
  // Check if there is any supported locale in the pathname
  const { pathname } = request.nextUrl;
  const pathnameHasLocale = locales.some(
    (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
  );

  if (pathnameHasLocale) return NextResponse.next();

  // Redirect if there is no locale
  const locale = getLocale(request);
  request.nextUrl.pathname = `/${locale}${pathname}`;

  // e.g. incoming request is /products
  // The new URL is now /en-US/products
  return NextResponse.redirect(request.nextUrl);
}

export const config = {
  matcher: [
    // Skip all internal paths (_next)
    "/((?!_next).*)",
    // Optional: only run on root (/) URL
    // '/'
  ],
};


新增 [lang] router

 
  • [”name”] 為 next.js 的約定檔案,可以動態產生路由。詳請看這邊
  • 在 /app、/app/page 中間加入一層 [lang]
└── 📁app 
    └── 📁[lang]
        └── some page...

設置 layout.tsx

 
  • 從 props 取出 params、lang。
  • 放在 html 標籤上。
import type { Metadata } from "next";
import { Roboto_Condensed } from "next/font/google";
import "./globals.css";

const roboto = Roboto_Condensed({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "your_title",
  description: "your_description",
};

export default function RootLayout(props: {
  children: React.ReactNode;
  params: { lang: string };
}) {
  return (
    <html lang={props.params.lang}>
      <body className={`${roboto.className} px-4 `}>{props.children}</body>
    </html>
  );
}

新增 Dictionaries

 
  • 建立 discionaries 資料夾、底下新增欲翻譯的語言。
  • 新增 get-dictionary.ts 。

新增 discionaries 資料夾
└── 📁src 
    └── 📁dictionaries
        └── en.json 
        └── zh-CN.json
        └── zh-TW.json
 
// en.json
{
    "root":{
        "welcome":"Welcome",
        "name":"CLMS"
    }
}

新增一個 get-dictionary.ts 用來載入欲翻譯的語言。
// src/get-dictionary.ts
import "server-only";

const dictionaries: Record<string, () => Promise<any>> = {
  en: () => import("./dictionaries/en.json").then((module) => module.default),
  "zh-TW": () =>
    import("./dictionaries/zh-TW.json").then((module) => module.default),
  "zh-CN": () =>
    import("./dictionaries/zh-CN.json").then((module) => module.default),
};

export const getDictionary = async (locale: string): Promise<any> => {
  try {
    const dictionaryLoader = dictionaries[locale] ?? dictionaries["en"];
    return await dictionaryLoader();
  } catch (error) {
    console.error(`Failed to load dictionary for locale '${locale}':`, error);
    throw new Error(`Failed to load dictionary for locale '${locale}'`);
  }
};

use it!
import { getDictionary } from "@/get-dictionary";
import Login from "../../components/login/login";

interface Props {
  params: {
    lang: string;
  };
}

export default async function Page({ params: { lang } }: Props) {
  const dict = await getDictionary(lang);
  return (
    <div className="flex flex-col items-center mt-40">
      <h1 className="my-4 ">
        {dict.root.welcome}
        <span className="text-blue-500">{dict.root.name}</span>
      </h1>
      <Login />
    </div>
  );
}

版權所有 © 2023 DOJO004

Deployed on Zeabur