---
# System prepended metadata

title: 載入與初始設置 next-intl
tags: [Next.Js 14 with APP Router]

---

載入與初始設置 next-intl
===

###### With Next.js App Router
:::info
:radio_button: [回到目錄攻略](https://hackmd.io/@hJJ8etrATgudRfKA1gryew/BJiplPwMC)
:::

0.多國語系套件選擇
---
選擇要使用哪個多國語套件也是一項難題，其實Next.js本身也有[內建多國語系](https://nextjs.org/docs/app/building-your-application/routing/internationalization)，但無法做客製化的參數，著實腦人。經過幾番探索與研究，最後選擇==next-intl==作為本次專案的多國語系套件。

:round_pushpin:入坑參考資料：
[How to Choose an i18n Library for a Next.js 14 Application](https://blog.stackademic.com/how-to-choose-an-i18n-library-for-a-next-js-14-application-part-1-861ec8889128)

1.next-intl套件下載
---
在Terminal輸入指令：
```
npm install next-intl
```

2.目錄結構
---

```
├── messages (1)
│   ├── en
│   │   ├──1_global.json
│   │   └── ...
│   └── zh
│       ├──1_global.json
│       └── ...
├── next.config.js (2)
└── src
    ├── lib (3)
    │   ├──config.ts
    │   ├──i18n.ts
    │   └──navigation.ts
    ├── middleware.ts (4)
    └── app
        └── [locale]
            ├── layout.tsx (5)
            └── main (6，主網頁)
                └── page.tsx
```
與[next-intl官網](https://next-intl-docs.vercel.app/docs/getting-started/app-router)提供的參考架構有些不一樣，主要的不同之處如下：
- ```messages```下各建立不同語言的資料夾，使得能夠依網頁的不同而做語言分類。
- ```src```下再建立```lib```資料夾，將各種關於語言同步化、語言載入媒介等工具集中管理於此。
- 原本在`loclae`裡有直接夾帶門面`page.tsx`，但基於本專案有分主網站與子網站，所以門面的網站必須帶有主網站名稱，這也就先刪掉原本的門面。

3.初建語言檔案
---

> @/messages/en/1_global.json

```json=
{
  "main-title": "Welcome to Taiwan!",
  "not-found-title": "404 NOT FOUND!",
  "not-found-return": "RETURN"
}
```

> @/messages/zh/1_global.json
```json=
{
  "main-title": "歡迎來台灣！",
  "not-found-title": "404 頁面找不到啦哭！",
  "not-found-return": "返回"
}
```

4.初設在地化語言/路徑配置
---

> @src/lib/config.ts
```typescript=
import { Pathnames } from "next-intl/navigation";

export const locales = ["en", "zh"] as const;

/// 鎖死不同語言對應的網址
export const pathnames = {
  "/main": "/main",
} satisfies Pathnames<typeof locales>;

/// 前綴都要帶語言，例如：en/main、zh/main
export const localePrefix = "always";

export type AppPathnames = keyof typeof pathnames;
```

> @src/lib/navigation.ts
```typescript=
import { createLocalizedPathnamesNavigation } from "next-intl/navigation";
import { localePrefix, locales, pathnames } from "./config";

// 導入所需變數，內建自己的導航系統
export const { Link, redirect, usePathname, useRouter } =
  createLocalizedPathnamesNavigation({
    locales,
    localePrefix,
    pathnames,
  });
```

兩個檔案建置好後，即能確保在點選其他網址後，也能同步更新語言。

5.建置語言載入媒介
---
> @src/lib/i18n.ts
```typescript=
import { getRequestConfig } from "next-intl/server";
import { notFound } from "next/navigation";
import { locales } from "./config";

export default getRequestConfig(async ({ locale }) => {
  if (!locales.includes(locale as any)) notFound();

  const messages = {
    ...(await import(`@/messages/${locale}/1_global.json`)).default,
  };

  return {
    messages,
  };
});
```

6.導入中介軟體
---
middleware.ts為轉換語言的媒介，而本專案所設的**預設語言**，<font color="#AC19C9">主要是吃瀏覽器的語言喜好</font>，因此要先載入**判斷瀏覽器語言喜好的套件-negotiator**。

在撰寫middleware.ts前，就先在Terminal輸入指令吧：
```
npm install negotiator @types/negotiator
```
載好後就開始寫吧！
> @src/middleware.ts
```typescript=
import { match as matchLocale } from "@formatjs/intl-localematcher";
import { localePrefix, locales, pathnames } from "@lib/config";
import Negotiator from "negotiator";
import createIntlMiddleware from "next-intl/middleware";
import { NextRequest } from "next/server";

function getLocale(request: NextRequest): string | undefined {
  const negotiatorHeaders: Record<string, string> = {};
  request.headers.forEach((value, key) => (negotiatorHeaders[key] = value));

  // @ts-ignore locales are readonly
  const setLocales: string[] = locales;
  const setDefaultLocale: string = "en";

  // languages為瀏覽器設定語言喜好順序陣列
  const languages = new Negotiator({ headers: negotiatorHeaders }).languages();

  // matchLocale會做陣列比對，取最多次出現且順序第一的語言
  const locale = matchLocale(languages, setLocales, setDefaultLocale);

  return locale;
}

export default async function middleware(request: NextRequest) {
  const locale = getLocale(request);
  const defaultLocale = locale === "zh" || locale === "zh-TW" ? "zh" : "en";
  const handleI18nRouting = createIntlMiddleware({
    locales,
    defaultLocale,
    localePrefix,
    localeDetection: false,
    pathnames,
  });
  const response = handleI18nRouting(request);
  return response;
}

export const config = {
  // Match only internationalized pathnames
  matcher: [
    // Enable a redirect to a matching locale at the root
    "/",

    // Set a cookie to remember the previous locale for
    // all requests that have a locale prefix
    "/(zh|en)/:path*",

    // Enable redirects that add missing locales
    // (e.g. `/pathnames` -> `/en/pathnames`)
    "/((?!api|_next|_vercel|.*\\..*).*)",
  ],
};
```
這樣寫好後，就會根據瀏覽器的語言去顯示網頁預設語言了！

進一步說，本網站只有中文/英文去做轉換，因此只有喜好語言將中文設為最前面時（再往前沒有英文），其預設語言便會為中文；而其他情況下，預設語言就都會是英文。假設情境如下：

:::info
:bulb:[ 情境一 ] 
**瀏覽器喜好語言排序：中 > 英 > 西 > 日**
**所得出的預設語言為：zh**
:::

:::info
:bulb:[ 情境二 ] 
**瀏覽器喜好語言排序：西 > 中 > 日**
**所得出的預設語言為：zh**
:::

:::info
:bulb:[ 情境三 ] 
**瀏覽器喜好語言排序：西 > 日**
**所得出的預設語言為：en**
:::

:::info
:bulb:[ 情境四 ] 
**瀏覽器喜好語言排序：西 > 日 > 英 > 中**
**所得出的預設語言為：en**
:::

7.設定路徑管理
---
> @/next.config.js
```javascript=
// @ts-check

// 向伺服器元件提供 i18n 配置
const withNextIntl = require("next-intl/plugin")("./src/lib/i18n.ts");

/**
 * @type {import('next').NextConfig}
 */

const nextConfig /** @type {import('next').NextConfig} */ = {
  async redirects() {
    return [
      {
        source: "/",
        permanent: false,
        destination: "/main",
      },
      {
        source: "/en",
        permanent: false,
        destination: "/en/main",
      },
      {
        source: "/zh",
        permanent: false,
        destination: "/zh/main",
      },
    ];
  },
};

// @ts-ignore
module.exports = withNextIntl(nextConfig);
```
透過```redirects```撰寫預設網址，<font color="#AC19C9">不管是有沒有帶語言前綴，都可以直接導向主網頁</font>。

8.結果呈現
---
中英文切換的樣子，以及網址的呈現：

![20240508_093227](https://hackmd.io/_uploads/By9FL8uzR.gif)



參考
---
- [NEXT.JS: Getting Started](https://next-intl-docs.vercel.app/docs/getting-started/app-router)
- [Navigation APIs: Localized pathnames](https://next-intl-docs.vercel.app/docs/routing/navigation#localized-pathnames)


