# [ 살펴보기 ] Next-Intl - Basics

Next-Intl은 next application에서 다국어 기능을 제공할 때 사용할 수 있는 library다. Page title, subtitle등과 같이 page에 사용되는 text를 각 언어별 json 파일로 구분해두고 유저가 선택한 언어에 따라 다른 json 파일을 적용에 선택한 언어 맞는 content를 제공할 수 있다.

해당 포스트는 NextJS App route를 통해 next-intl을 사용하는 방법을 살펴본다. Next-intl을 통해 다국어 기능을 적용하는 방법은 두 가지다. `example.com/en/about`과 같이 langauge정보가 pathname에 포함하는 방식이 있고 language 정보가 pathname에 포함하지 않는 방법이 있다. 해당 포스트에선 language 정보가 pathname에 포함되는 방식을 살펴본다.

우선 테스트를 진행하기 위해 next-intl package를 설치한다.

```plaintext
npm install next-intl
```

## With I18n Routing

테스트를 위한 project 구조는 다음과 같다.

```plaintext
...
next.config.ts
/src
    - middlewarets
    /app
        [locale]
            - layout.tsx
            - page.tsx
            /user
                - page.tsx
    /i18n
        - routing.ts
        - request.ts
    
/messages
    - en.json
    - ko.json
```

`/messages`경로에 각 언어마다 적용될 content 내용을 추가한다. 아래 예제에선 user page에서 사용될 title, about 영어 정보는 en.json 파일에 title, about 한글 정보는 ko.json 파일에서 관리되고 있다.

*messages/en.json*

```json
{
  "userPageInfo": {
    "title": "User page",
    "about": "This is an user page"
  }
}
```

*messages/ko.json*

```json
{
  "userPageInfo": {
    "title": "유저 페이지",
    "about": "유저 페이지 소개"
  }
}
```

그리고 next.config.ts 파일을 다음과 같이 설정해준다.

*next.config.ts*

```typescript
import createNextIntlPlugin from 'next-intl/plugin';
 
const withNextIntl = createNextIntlPlugin();
 
/** @type {import('next').NextConfig} */
const nextConfig = {};
 
export default withNextIntl(nextConfig);
```

Next-Intl을 통해 translation 기능을 추가할 때 internationalization 관련 설정은 next.config.ts에 바로 추가 하지 않고 다음과 같이 별도의 경로에 따로 추가하여 사용한다.

*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'
});
 
export const {Link, redirect, usePathname, useRouter, getPathname} =
  createNavigation(routing);
```

그리고 위에서 추가한 configuration을 middleware에 적용한다.

*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/request.ts`파일을 통해 request pathname에 포함된 language 정보에 따라 어떤 language translation 파일을 사용해야 하는지 설정한다.

*src/i18n/request.ts*

```typescript
import {getRequestConfig} from 'next-intl/server';
import {routing} from './routing';
 
export default getRequestConfig(async ({requestLocale}) => {
  let locale = await requestLocale;
 
  if (!locale || !routing.locales.includes(locale as any)) {
    locale = routing.defaultLocale;
  }
  return {
    locale,
    messages: (await import(`../../messages/${locale}.json`)).default
  };
});
```

위의 설정에서 현재 request pathname에 포함된 language가 `src/i18nrouting.ts`에서 추가한 language option에 포함되어 있는 language인지 검사하고 만약 `src/i18n/routing.ts` 설정에서 추가한 language option이 아닌 다른 language가 포함되어 있다면 default로 설정한 langauge를 사용한다.

예를 들어 우리는 위에서 English ( en )와 Korean ( kr ) 두 가지 언어를 `routing.ts` 설정 파일에 추가하고 default 언어는 en로 설정했으므로 `example.com/fr/user`와 같이 `routing.ts` 파일에 추가하지 않은 language가 request pathname에 포함되어 있으면 default 언어인 en를 사용한다.

이제 next application의 각 page나 component에서 현재 request pathname에 포함된 langauge에 따라 `/messages` 경로에 추가한 각 translation파일 내용을 사용할 수 있도록 `layout.tsx`에 다음과 같이 설정해준다.

*src/app/\[locale\]/layout.tsx*

```typescript
import "./globals.css";
import type { Metadata } from "next";
import { NextIntlClientProvider } from "next-intl";
import { getMessages } from "next-intl/server";

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default async function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  const messages = await getMessages();

  return (
    <html>
      <body>
        <NextIntlClientProvider messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}
```

위의 예제에서 볼 수 있듯이 layout의 children을 NextIntlClientProvier로 감싸고 provider에 message property를 전달하고 있다. getMessage function은 현재 request의 pathname에 포함된 language에 따른 translation json 파일을 object 형태로 return해준다.

만약 `example.com/en/user`와 같이 pathname에 포함된 language가 english면 en.json 내용을 object로 return하고 `example.com/ko/user`와 같이 pathname에 포함된 language가 korean이면 ko.json 내용을 object로 return한다.

이제 page나 component에서 translation 파일이 제공하는 내용을 사용할 준비가 기본적인 설정이 끝났다. 이제 user page에 translation 파일의 내용을 적용해보자.

*src/app/\[locale\]/user/page.tsx*

```typescript
import { useTranslations } from "next-intl";
import React from "react";

const UserPage = () => {
  const t = useTranslations("userPageInfo");
  return (
    <div>
      <h3>User</h3>
      <div>{t("title")}</div>
      <div>{t("about")}</div>
    </div>
  );
};

export default UserPage;
```

이제 위의 페이지를 `example.com/en/user`와 `example.com/ko/user`와 같이 pathname에 language를 변경하며 접근해보면 각 언어별 json file에 추가한 값이 pathname에 포함된 langauge에 맞게 적용되는 것을 확인할 수 있다.

## Static render

위의 예제와 같이 routing을 통해 다국어 기능을 제공하면 모든 page가 dynamic route에 포함 되므로 build시 모든 page가 default로 dynamic render로 취급된다. Application Build시 `[locale]` dynamic route 아래 각 page를 다시 static render로 취급하기 위해선 다음과 같은 추가 작업이 필요하다.

만약 `[locale]` dynamic route 하위 모든 page에 static render를 적용하고자 한다면 application 최상단 layout 파일에 다음과 같이 `generateStaticParams`와 `setRequestLocale`를 추가 해준다.

*src/layout.tsx*

```typescript
import { routing } from "@/i18n/routing";
import "./globals.css";
import type { Metadata } from "next";
import { getMessages, setRequestLocale } from "next-intl/server";
import { NextIntlClientProvider } from "next-intl";

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export function generateStaticParams() {
  return routing.locales.map((locale) => ({ locale }));
}

export default async function RootLayout({
  children,
  params,
}: Readonly<{
  children: React.ReactNode;
  params: Promise<{ locale: string }>;
}>) {

  const locale = (await params).locale;
  setRequestLocale(locale);
  const messages = await getMessages();

  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}
```

위의 예제와 같이 layout의 params parameter를 통해 dynamic route의 parameter를 조회할 수 있다. 우리의 경우에는 dynamic route를 `[locale]` 형식으로 사용하고 있으므로 params은 `param: { locale: string }` 구조를 갖는다. 또한 dynamic route의 param을 조회하는 방법은 NextJS의 version에 따라 조금씩 차이가 있으므로 주의하자. 위의 예제는 15.1.0 version을 기준으로 작성되었다. ( *Reference* - [Dynamic route](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes) )

layout에 `generateStaticParams`와 `setRequestLocale`를 추가하고 application을 build 해보면 `[locale]` dynamic route 하위 page가 다시 static render가 되는 것을 확인할 수 있다.

주의할 점은 위의 변경 사항으로 인해 모든 page가 static render가 되는 것은 아니다. NextJS가 제공하는 `cookie`나 `header`와 같은 function을 사용하는 경우와 같이 원래 dynamic render로 취급하는 경우는 그대로 dynamic render가 적용된다.
