owned this note
owned this note
Published
Linked with GitHub
載入與初始設置 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.結果呈現
---
中英文切換的樣子,以及網址的呈現:

參考
---
- [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)