iT邦幫忙

2023 iThome 鐵人賽

DAY 22
0

稍微做個延伸,建立 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 中系統有支援的語系,再沒有的話就是系統預設語系。

剩下就是判斷幾個情境:

  1. 路徑有語系字串,且該語系系統有支援,就直接回傳該路徑。另外用 cookie 紀錄為最後使用的語系。
  2. 路徑有語系字串,但該語系系統不支援,就將該字串替換為 getLocale 判定的語系。
  3. 路徑沒有設定語系字串,就導向 getLocale 判定的語系。

這樣設定為 Server component 的頁面就能根據選擇的語系進行渲染,並且防止沒輸入或輸入不存在的語系。


上一篇
Next App Router 設定 i18n
下一篇
Next.js Client Component 偵測與記憶語系
系列文
組裝前端開發工具箱,從 NX 入手30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言