稍微做個延伸,建立 Next 識別用戶預設語系跟記憶語系選擇的功能。
同樣主要參考這篇: i18n with Next.js 13 and app directory / App Router
一樣先裝套件。
pnpm add -D negotiator @types/negotiator @formatjs/intl-localematcher
首先是 Server component 的部分,要用 middleware 對送到的請求做判別跟處理。
middleware 放在跟 app 目錄同層的位置。
ironman-nextjs
├── app
└── middleware.ts
// middleware.ts
import { NextResponse, type NextRequest } from 'next/server';
import { locales, fallbackLng, cookieName } from './src/i18n/settings';
import Negotiator from 'negotiator';
import { match } from '@formatjs/intl-localematcher';
const getLocale = (request: NextRequest) => {
if (request.cookies.has(cookieName)) {
return request.cookies.get(cookieName)?.value;
}
const acceptLanguages = new Negotiator({
headers: {
'accept-language': request.headers.get('accept-language') as
| string
| string[]
| undefined,
},
}).languages();
return match(acceptLanguages, locales, fallbackLng);
};
export function middleware(request: NextRequest) {
// Check if there is any supported locale in the pathname
const { pathname } = request.nextUrl;
const pathStartsWithLocalePattern =
/\/[a-z]{2,4}(-[A-Z][a-z]{3})?(-([A-Z]{2}|[0-9]{3}))?(\/|$)/i;
const pathnameHasLocale = pathname.match(pathStartsWithLocalePattern);
const pathLocale = pathnameHasLocale?.[0].slice(1);
// path have supported locale
if (pathLocale && locales.includes(pathLocale)) {
if (
!request.cookies.has(cookieName) ||
(request.cookies.has(cookieName) &&
request.cookies.get(cookieName)?.value !== pathLocale)
) {
const response = NextResponse.next();
// record last selected locale in cookie
response.cookies.set(cookieName, pathLocale);
return response;
}
return;
}
// path have no locale string, nav to supported locale
if (!pathLocale) {
let locale = getLocale(request);
if (!locale) {
locale = fallbackLng;
}
request.nextUrl.pathname = `/${locale}${pathname}`;
}
// path has unsupported locale, nav to supported locale
if (pathLocale && !locales.includes(pathLocale)) {
let locale = getLocale(request);
if (!locale) {
locale = fallbackLng;
}
request.nextUrl.pathname = pathname.replace(
pathStartsWithLocalePattern,
`/${locale}`,
);
}
return Response.redirect(request.nextUrl);
}
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
};
先看到 getLocale 用於確認要使用的語系,如果請求中有帶指定的 cookie ,就用 cookie 記錄的語系,不然就用 accept-language 中系統有支援的語系,再沒有的話就是系統預設語系。
剩下就是判斷幾個情境:
這樣設定為 Server component 的頁面就能根據選擇的語系進行渲染,並且防止沒輸入或輸入不存在的語系。