# [ 살펴보기 ] Next-Intl - Routing, Middleware

pathname에 locale ( langauge ) 정보를 포함하는 i18n routing 형식으로 다국어 기능을 제공할 때 설정을 통해 default language를 설정하거나 혹은 default language를 사용할 때 language 정보를 pathname에서 제외하는 등 여러가지 설정을 추가할 수 있다. 해당 포스트를 통해 Next-Intl을 사용할 때 필요한 Routing, Middleware 관련 설정 방법을 살펴보자.

## Redirect unprefixed pathname

Next-intl은 default로 unprefixed pathname ( pathname에 locale 정보가 포함되지 않은 ) 으로 접근하면 prefixed pathname( pathname에 locale 정보가 포함된 )로 redirect 시킨다.

*src/i18n/routing.ts*

```typescript
import {defineRouting} from 'next-intl/routing';
 
export const routing = defineRouting({
  // ...
  localePrefix: 'always' // default value
});
```

unprefixed pathname이 locale 정보가 포함된 prefixed pathname으로 redirect 되려면middleware를 unprefixed pathname request도 처리할 수 있도록 matcher를 수정해주어야 한다. ( Reference - [Pathnames without a locale prefix](https://next-intl.dev/docs/routing/middleware#matcher-no-prefix) )

*src/middleware.ts*

```typescript
import createMiddleware from "next-intl/middleware";
import { routing } from "./i18n/routing";

export default createMiddleware(routing);

export const config = {
  // /api, /_next, /_vercel로 시작하는 pathname 제외하고 모든 pathname과 match
  matcher: ["/((?!api|_next|_vercel|.*\\..*).*)"],
};
```

위와 같이 설정하고 `http://localhost:3000/user`와 같이 unprefixed uri로 접근하면 `http://localhost:3000/en/user`와 같이 prefixed uri로 redirect되는 것을 확인할 수 있다.

## unprefixed pathname에 default locale 적용

아래의 설정을 통해 unprefixed pathname으로 페이지에 접근할 때 redirect 시키는 것이 아닌 default locale을 적용할 수도 있다.

*src/i18n/routing.ts*

```typescript
import {defineRouting} from 'next-intl/routing';
 
export const routing = defineRouting({
  locales: ["en", "ko"],
  defaultLocale: "ko",
  localePrefix: 'as-needed'
});
```

위의 경우에도 마찬가지로 middleware에서 unprefixed pathname request를 처리할 수 있어야 하므로 다음과 같이 matcher를 수정한다.

*src/middleware.ts*

```typescript
import createMiddleware from "next-intl/middleware";
import { routing } from "./i18n/routing";

export default createMiddleware(routing);

export const config = {
  // /api, /_next, /_vercel로 시작하는 pathname 제외하고 모든 pathname과 match
  matcher: ["/((?!api|_next|_vercel|.*\\..*).*)"],
};
```

위와 같이 설정하고 `http://localhost:3000/user`와 같이 unprefixed pathname를 통해 page에 접근하면 redirection 되는 것이 아닌 defaultLocale로 설정한 locale 값이 적용되는 것을 확인할 수 있다. 위의 예제에선 `ko` locale이 적용된다.

## Custom prefixes

pathname에 포함되는 locale을 각 locale json 파일 이름이 아닌 custom prefix name을 설정하여 사용할 수도 있다.

*src/i18n/routing.ts*

```typescript
import { defineRouting } from "next-intl/routing";
import { createNavigation } from "next-intl/navigation";

export const routing = defineRouting({
  locales: ["en", "ko"],
  defaultLocale: "en",
  localePrefix: {
    mode: "always",
    prefixes: {
      en: "/uk",
    },
  },
});


export const { Link, redirect, usePathname, useRouter, getPathname } =
  createNavigation(routing);
```

위의 예제에서 `en` locale을 `/uk` prefix로 mapping하고 있다. 위와 같이 설정하고 `http://localhost:3000/uk/user` pathname으로 page에 접근하면 prefixes property에서 mapping한 `en` locale이 적용된다.

만약 middleware matcher가 위의 예제처럼 모든 pathname request를 처리하도록 설정되어 있다면 추가로 수정할 부분은 없지만 만약 그렇지 않다면 custom prefix ( 위의 예제에선 `/uk` )를 사용하는 pathname로 middleware가 처리할 수 있도록 matcher를 수정해주어야 한다.

## Locale cookie

Next-Intl을 위해 설정한 middleware는 default로 `NEXT_LOCALE`이라는 이름의 cookie에 가장 최근 사용한 locale 정보를 저장한다. 그리고 추후 user가 사이트를 다시 방문할 때 unprefixed pathname을 통해 접근할 때 해당 cookie 정보를 이용하여 prefixed pathname으로 redirect 시킨다. ( 위에서 살펴본 defineRouting 설정에서 localePrefix option이 always일 때 )

`NEXT_LOCALE` cookie는 default로 maxAge가 1년이고 sameSite가 lax로 설정된다. 이를 변경하고자 한다면 다음과 같이 localeCookie property를 통해 변경할 수 있다.

*src/i18n/routing.ts*

```typescript
import { defineRouting } from "next-intl/routing";
import { createNavigation } from "next-intl/navigation";

export const routing = defineRouting({
  locales: ["en", "ko"],
  defaultLocale: "ko",
  localeCookie: {
    maxAge: 60 * 60 * 24,
  },
});

export const { Link, redirect, usePathname, useRouter, getPathname } =
  createNavigation(routing);
```

## Middleware 설정

위의 예제를 통해 살펴 보았듯이 routing을 사용해 locale 기능을 제공하고자 한다면 middleware 역시 그에 맞게 설정해주어야 한다. Routing 기반 next-intl 사용을 위한 middleware의 기본 설정은 다음과 같다. Locale은 ko.json, en.json 두 가지 언어를 제공한다고 가정한다.

src/middleware.ts

```typescript
import createMiddleware from 'next-intl/middleware';
import {routing} from './i18n/routing';
 
export default createMiddleware(routing);
 
export const config = {
  matcher: ['/', '/(ko|en)/:path*']
};
```

*src/i18n/routing.ts*

```typescript
import {defineRouting} from 'next-intl/routing';
 
export const routing = defineRouting({
  locales: ["en", "ko"],
  defaultLocale: "ko"
});
```

위의 예제는 `/` pathname과 `/ko` 또는 `/en`로 시작하는 pathname에 middleware를 적용하는 예제다. 위의 예제에서 살펴볼 수 있듯이 createMiddleware에 routing.ts에서 설정한 routing configuration object를 전달하면 locale 적용에 필요한 기본적인 설정과 redirect 기능을 처리해준다.

Middleware에서 현재 request의 locale을 결정할 때 사용하는 순서는 다음과 같다.

* pathname에 포함된 locale 정보가 있다면 해당 locale을 사용한다.
    
* pathname에 locale 정보가 없다면 `NEXT_LOCALE` cookie에 설정된 값을 사용한다.
    
* `NEXT_LOCALE` cookie도 없다면 request header의 `accept-language` header에 존재하는 locale 중 사용 가능한 locale을 사용한다.
    
* `accept-language` header도 없다면 routing 설정 중 defaultLocale property에 설정된 locale을 사용한다.
    

## Composing middleware

`createMiddleware` function을 실행하면 다음과 같은 function을 return한다.

```typescript
function middleware(request: NextRequest): NextResponse;
```

그렇기에 nex-intl `createMiddleware`를 실행하기 전에 request를 수정하거나 response를 수정하는 등 기타 추가 작업을 해야 할 경우 다음과 같이 middleware를 구성할 수 있다.

```typescript
import createMiddleware from 'next-intl/middleware';
import {NextRequest} from 'next/server';
 
export default async function middleware(request: NextRequest) {
  const defaultLocale = request.headers.get('x-my-locale') || 'en';
 
  const handleI18nRouting = createMiddleware({
    locales: ['en', 'ko'],
    defaultLocale
  });

  const response = handleI18nRouting(request);
 
  return response;
}
 
export const config = {
  matcher: ['/', '/(ko|en)/:path*']
};
```

위의 예제는 request header에 `x-my-locale`이라는 header가 존재한다면 해당 header의 값을 defaultLocale로 사용하고 그렇지 않으면 `en`를 default locale로 사용하는 예제다.

물론 기존처럼 routing 설정을 별개로 구성하여 사용해도 무관하다.

*src/i18n/routing.ts*

```typescript
import { defineRouting } from "next-intl/routing";
import { createNavigation } from "next-intl/navigation";

export const routing = defineRouting({
  locales: ["en", "ko"],
  defaultLocale: "en",
  localePrefix: "always",
});

export const { Link, redirect, usePathname, useRouter, getPathname } =
  createNavigation(routing);
```

*src/middleware.ts*

```typescript
import { routing } from "@/i18n/routing";
import createMiddleware from "next-intl/middleware";
import { NextRequest } from "next/server";

export default async function middleware(request: NextRequest) {
  const handleI18nRouting = createMiddleware(routing);

  const response = handleI18nRouting(request);

  return response;
}

export const config = {
  matcher: ["/((?!api|_next|_vercel|.*\\..*).*)"],
};
```

## Navigation

Next-Intl은 현재 locale을 유지하거나 변경하여 navigation할 수 있도록 `Link`, `userRouter`와 같은 NextJS navigation API을 기반으로한 navigation API를 제공한다.

src/i18n/request.ts

```typescript
import { defineRouting } from "next-intl/routing";
import { createNavigation } from "next-intl/navigation";

export const routing = defineRouting({
  locales: ["en", "ko"],
  defaultLocale: "en",
  localePrefix: "always",
});

export const { Link, redirect, usePathname, useRouter, getPathname } =
  createNavigation(routing);
```

위의 예제와 같이 `createNavigation`을 실행하면 `Link, redirect, userRouter`와 같은 function을 return 해준다. Application navigation을 구현할 때 NextJS navigation이 아닌 위의 function을 사용해서 구성한다. function name이 NextJS navigation에서 제공하는 name과 동일하므로 import할 때 주의하자.

```typescript
import { Link } from "@/i18n/routing";
import React from "react";

const Nav = () => {
  return (
    <div className="flex flex-row space-x-3">
      <Link href="/user">User</Link>
      <Link href="/order">Order</Link>
    </div>
  );
};

export default Nav;
```

위의 예제처럼 locale prop을 별도로 설정하지 않으면 현재 locale 정보가 유지되며 navigation이 발생하고 아래와 같이 locale prop을 함께 명시해주면 navigation이 발생할 때 해당 locale로 변경되어 이동한다.

```typescript
import { Link } from "@/i18n/routing";
import React from "react";

const Nav = () => {
  return (
    <div className="flex flex-row space-x-3">
      <Link locale="ko" href="/user">
        User
      </Link>
      <Link locale="en" href="/order">
        Order
      </Link>
    </div>
  );
};

export default Nav;
```

Navigation link를 통한 navigation이 아닌 programatic하게 navigaton을 수행해야 할 때도 NestJS navigation에 제공하는 userRoute가 아닌 next-intl createNavigation function에서 return된 useRoute를 사용할 수 있다.

```typescript
"use client";
import { useRouter } from "@/i18n/routing";
import React from "react";

const Order = () => {
  const router = useRouter();
  const handleRouter = () => {
    router.push("/user");
  };
  return (
    <div>
      <div>
        <button onClick={handleRouter}>test button</button>
      </div>
    </div>
  );
};

export default Order;
```

userRoute를 통해 navigation을 수행할 때 locale을 함께 변경하고 싶다면 다음과 같이 locale property도 함께 전달해준다.

```typescript
"use client";
import { useRouter } from "@/i18n/routing";
import React from "react";

const Order = () => {
  const router = useRouter();
  const handleRouter = () => {
    router.push("/user", { locale: "en" });
  };
  return (
    <div>
      <div>
        <button onClick={handleRouter}>test button</button>
      </div>
    </div>
  );
};

export default Order;
```

언어 변경을 위한 가능 역시 다음과 같이 replace method를 통해 쉽게 구현할 수 있다.

```typescript
"use client";
import { routing, usePathname, useRouter } from "@/i18n/routing";
import React from "react";

const LocaleSwitch = () => {
  const router = useRouter();
  const pathname = usePathname();
  const handleLocaleSwitch = (lang: string) => () => {
    router.replace({ pathname }, { locale: lang });
  };
  return (
    <div className="flex flex-row space-x-2">
      {routing.locales.map((lang) => (
        <button key={lang} onClick={handleLocaleSwitch(lang)}>
          {lang}
        </button>
      ))}
    </div>
  );
};

export default LocaleSwitch;
```
