Next.js 국제화 Internationalization 설정하기 (feat. next-intl, next.js v15)

Next.js를 사용한 웹사이트에서 국제화 (Internationalization) 설정하는 방법을 알아보겠습니다. next-intl 라이브러리를 사용하여 간단하게 다국어 지원을 구현할 수 있습니다.


소개

웹 애플리케이션을 개발할 때 다국어 지원은 글로벌 사용자를 위한 필수적인 기능입니다. Next.js에서는 국제화(i18n)를 구현하기 위한 여러 가지 방법이 있는데, next-intl 라이브러리를 사용하여 효율적으로 다국어 지원을 구현하는 방법을 알아보겠습니다.

Next.js v15.0.1 App Router 기반으로 작성되었습니다. Page Router에서는 적용되지 않습니다.
Next.js v15.0.1 버전을 사용했지만 14버전과 다른점은 다이나믹 라우트 '[locale]'에서 params 사용시 15버전에서 사용시 비동기로 변경되었습니다.
https://nextjs.org/docs/messages/sync-dynamic-apis

목차

  1. next-intl 소개
  2. 프로젝트 폴더 구조
  3. 프로젝트 설정
  4. routing.ts 파일 설정
  5. request.ts 파일 설정
  6. middleware.ts 파일 설정
  7. layout 파일 설정
  8. 메시지 파일 구성
  9. 번역 사용하기
  10. useTranslations 훅
  11. 동적 콘텐츠 처리
  12. 페이지간 이동 Link 사용하기
  13. 마치며
  14. Github Repository
  15. 참고

1. next-intl 소개

next-intl은 Next.js 애플리케이션에서 국제화를 구현하기 위한 강력한 라이브러리입니다. 다음과 같은 주요 기능을 제공합니다.

  • 타입스크립트 지원
  • 메시지 포맷팅
  • 날짜, 시간, 숫자의 지역화
  • SEO 최적화
  • 서버 컴포넌트 지원
  • 경량화된 번들 사이즈

2. 프로젝트 폴더 구조

다음은 next-intl을 사용하여 국제화를 구현하기 위한 프로젝트 폴더 구조입니다.

중요! 공식문서는 src 폴더를 사용하지만, 저는 src 폴더를 선호하지 않기 때문에 `app 폴더` 기준으로 블로그를 작성하였습니다. src 폴더를 사용하는 경우 src 경로를 사용하시되 next.config.mjs 파일의 경로를 수정해주시기 바랍니다. 자세한 내용은 아래에서 확인해주세요.
root
├── messages
   ├── en.json (1)
   ├── ko.json
   ├── ja.json
   └── ...
├── next.config.mjs (2)
├── i18n
   ├── routing.ts (3)
   └── request.ts (5)
├── middleware.ts (4)
└── app
    └── [locale]
        ├── layout.tsx (6)
        └── page.tsx (7)
  1. messages 폴더: 메시지 파일을 저장하는 디렉토리입니다. en.json, ko.json 등의 파일로 구성됩니다.
  2. next.config.mjs: next-intl 플러그인을 설정하는 파일입니다.
  3. i18n 폴더: 국제화 설정을 위한 파일을 저장하는 디렉토리입니다.
  4. middleware.ts: 라우팅 구성을 참고하여 미들웨어를 설정하는 파일입니다.
  5. i18n/request.ts: 요청을 처리하는 파일입니다.
  6. app/[locale]/layout.tsx: 국제화 번역을 적용한 레이아웃 컴포넌트입니다.
  7. app/[locale]/page.tsx: 국제화 번역을 적용한 페이지 컴포넌트입니다.

3. 프로젝트 설정

먼저 next-intl 라이브러리를 설치합니다.

npm install next-intl

또한, next.config.mjs 파일을 생성하여 next-intl 플러그인을 설정합니다.

next.config.mjs
import createNextIntlPlugin from 'next-intl/plugin';
 
const withNextIntl = createNextIntlPlugin('./i18n/request.ts'); // app 폴더를 사용하는 경우 경로 설정
// -> src 폴더를 사용하는 경우 인자를 생략합니다.
 
/** @type {import('next').NextConfig} */
const nextConfig = {};
 
export default withNextIntl(nextConfig);

위 코드는 module 형식의 next.config.mjs 파일입니다. CommonJS 형식의 next.config.js 파일을 사용할 경우, 다음과 같이 설정합니다.

next.config.js
const createNextIntlPlugin = require('next-intl/plugin');
 
const withNextIntl = createNextIntlPlugin('./i18n/request.ts'); // app 폴더를 사용하는 경우 경로 설정
// -> src 폴더를 사용하는 경우 인자를 생략합니다.
 
/** @type {import('next').NextConfig} */
const nextConfig = {};
 
module.exports = withNextIntl(nextConfig);
만약 src 폴더를 사용하고 계신다면 다음과 같이 설정합니다.
next.config.mjs
import createNextIntlPlugin from 'next-intl/plugin';
 
const withNextIntl = createNextIntlPlugin(); // src 폴더를 사용하는 경우 인자를 생략합니다.
 
// ... 생략

4. routing.ts 파일 설정

다음으로, 라우팅 설정을 위한 i18n/routing.ts 파일을 생성합니다.

i18n/routing.ts
import { defineRouting } from 'next-intl/routing';
import { createNavigation } from 'next-intl/navigation';
 
export const routing = defineRouting({
  // 지원하는 로케일 설정
  locales: ['ko', 'en', 'ja'],
 
  // 기본 로케일 설정
  defaultLocale: 'ko',
});
 
// Next.js 탐색 API에 대한 경량 래퍼
// 라우팅 구성 설정
// 기존 next js에서 제공하는 Link, redirect, usePathname, useRouter를 대체합니다.
export const { Link, redirect, usePathname, useRouter } =
  createNavigation(routing);

export const { Link, redirect, usePathname, useRouter } = createNavigation(routing); 코드는 Next.js의 Link, redirect, usePathname, useRouter를 사용할 수 있도록 래핑한 것입니다. 이를 사용하여 기존 next j에서 제공하는 API를 대체할 수 있습니다.

Link 예시로 import 할때 import { Link } from "@/i18n/routing"; 이렇게 사용할 수 있습니다.

기존 import Link from 'next/link';는 사용하지 않음.

5. request.ts 파일 설정

다음으로, 요청을 처리하는 i18n/request.ts 파일을 생성합니다.

i18n/request.ts
import { getRequestConfig } from 'next-intl/server';
import { routing } from './routing';
 
export default getRequestConfig(async ({ requestLocale }) => {
  // 이는 일반적으로 `[locale]` 세그먼트에 해당합니다.
  let locale = await requestLocale;
 
  // 유효한 로캘이 사용되었는지 확인하세요.
  if (!locale || !routing.locales.includes(locale as never)) {
    locale = routing.defaultLocale;
  }
 
  return {
    locale,
    messages: (await import(`../messages/${locale}.json`)).default, // 상대 경로 확인, messages 폴더에 JSON 파일이 있어야 함
  };
});

locale을 설정하고, 해당 locale에 맞는 메시지 파일을 불러옵니다.

6. middleware.ts 파일 설정

middleware.ts
// root
// ├── middleware.ts
import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing'; // 경로를 잘 설정해주세요.
 
export default createMiddleware(routing);
 
export const config = {
  // 국제화된 경로명만 일치
  matcher: ['/', '/(ko|ja|en)/:path*'], // "/" 경로와 "/:path*" 경로를 지원합니다.
};

7. layout 파일 설정

app/[locale]/layout.tsx 파일을 생성하여 레이아웃을 구성합니다.

app/[locale]/layout.tsx
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { routing } from '@/i18n/routing';
 
type Params = Promise<{ locale: never }>;
 
export default async function LocaleLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: Params;
}) {
  const { locale } = await params; // next js 15버전부터 Params 사용시 비동기로 변경됨
  // https://nextjs.org/docs/messages/sync-dynamic-apis
 
  // 들어오는 `로케일`이 유효한지 확인하세요.
  if (!routing.locales.includes(locale)) {
    notFound();
  }
 
  // 클라이언트에게 모든 메시지 제공
  const messages = await getMessages();
 
  return (
    <html lang={locale}>
      <NextIntlClientProvider messages={messages}>
        <body>{children}</body>
      </NextIntlClientProvider>
    </html>
  );
}

app/[locale]/page.tsx 파일은 기존 하던것과 동일하게 사용하면 됩니다.

app/[locale]/page.tsx
export default function Home() {
  return (
    <main>
      <h1>Hello, World!</h1>
    </main>
  );
}

npm run dev를 실행하고 http://localhost:3000/로 접속하여 http://localhost:3000/ko로 이동되면(기본 ko 설정) 기본 세팅은 완료된 것입니다.

8. 메시지 파일 구성

messages 폴더에 메시지 파일을 생성합니다. 메시지 파일은 JSON 형식으로 작성하며, 다음과 같이 구성합니다.

messages/en.json
{
  "Home": {
    "title": "Home",
    "description": "Welcome to the home page."
  }
}
messages/ko.json
{
  "Home": {
    "title": "홈",
    "description": "홈 페이지에 오신 것을 환영합니다."
  }
}
messages/ja.json
{
  "Home": {
    "title": "ホーム",
    "description": "ホームページへようこそ。"
  }
}

각 *.json 파일은 /ko, /en, /ja 등의 :path 이름과 일치 해야합니다.

9. 번역 사용하기

app/[locale]/page.tsx 파일에서 번역을 사용하려면 useTranslations 훅을 사용합니다.

app/[locale]/page.tsx
import { useTranslations } from 'next-intl';
 
export default function Home() {
  const t = useTranslations('Home');
 
  return (
    <main>
      <h1>{t('title')}</h1>
      <p>{t('description')}</p>
    </main>
  );
}

해당 코드를 실행하면, http://localhost:3000/ko로 접속하면 ko.json 파일의 번역이 적용되어 화면에 출력됩니다.

각 path 별로 이동하여 번역이 잘 적용되는지 확인합니다.

10. useTranslations 훅

useTranslations 훅은 정적 메세지를 가져오는 훅입니다.

메세지 구성 예시

messages/en.json
{
  "Home": {
    "title": "Home",
    "description": "Welcome to the home page."
  }
}

위 메세지를 가정하에 인수(argument)로 Home을 넣어서 사용하면 해당 메세지를 가져옵니다.

대소문자를 구분합니다. Home과 home은 다른 메세지입니다.
const t = useTranslations('Home');
 
return (
  <main>
    <h1>{t('title')}</h1>
    <p>{t('description')}</p>
  </main>
);

t 변수는 메시지를 가져오는 함수입니다. t('title')은 'Home' 메시지 파일에서 'title' 키에 해당하는 값을 가져옵니다.

변수 이름은 자유롭게 사용가능 하나, t를 사용하는 것이 일반적입니다.

또는 다음과 같이 인수(argument)없이 사용할 수 있습니다.

const t = useTranslations();
 
return (
  <main>
    <h1>{t('Home.title')}</h1>
    <p>{t('Home.description')}</p>
  </main>
);

11. 동적 콘텐츠 처리

동적 콘텐츠를 처리할 때는 useTranslations 훅을 사용하여 동적으로 메시지를 가져올 수 있습니다.

우선 message에서 보간을 사용하여 동적으로 메시지를 가져오는 방법을 알아보겠습니다.

보간은 문자열에서 {} 중괄호를 사용하여 동적으로 값을 삽입하는 방법입니다.

messages/en.json
{
  "Home": {
    "title": "Home",
    "description": "Welcome to the home page, {name}."
  }
}
const t = useTranslations('Home');
 
return (
  <main>
    <h1>{t('title')}</h1>
    <p>{t('description', { name: 'John' })}</p>
  </main>
);

위 코드처럼 { name: 'John' } 객체를 전달하여 {name} 부분에 동적으로 값을 삽입할 수 있습니다.

next-intl은 이외에도 다양한 기능을 제공합니다. 자세한 내용은 `next-intl 메시지 사용법`을 참고하세요.

앞서 말씀드린 바와 같이 routing.ts 파일에서 설정한 Link를 사용하여 페이지간 이동을 할 수 있습니다.

import { Link } from '@/i18n/routing';
 
<Link href="/about">About Page</Link>;

@/i18n/routing Link를 사용하면 현재 사용중인 번역(국제화) 페이지 언어에 맞게 해당 링크로 이동합니다.

단, 기본 Next Link를 사용하면 국제화가 적용되지 않습니다. 따라서 @/i18n/routing Link를 사용하는 것을 권장합니다.

또한, 가끔 Next Link@/i18n/routing Link를 혼용하여 사용할 경우가 있는데,

다음과 같이 이름을 변경하여 사용할 수 있습니다.

import { Link as I18bLink } from '@/i18n/routing';
import Link from 'next/link';
 
<I18bLink href="/about">About Page</I18bLink>;
<Link href="/ko">한국어</Link>
<Link href="/en">English</Link>
<Link href="/ja">日本語</Link>

위 코드와 같이 Link as I18bLink를 사용하여 이름을 변경하면 I18bLink로 사용할 수 있습니다.

마치며

이상으로 Next.js에서 국제화를 구현하는 방법을 알아보았습니다. next-intl 라이브러리를 사용하면 간단하게 다국어 지원을 구현할 수 있습니다.

라이브러리 특성상 messages 폴더에 JSON 파일을 생성하여 번역을 적용하므로, 번역이 필요한 페이지에 대한 메시지 파일을 작성하여 국제화를 구현할 수 있습니다. (다소 노가다가 필요할 수 있습니다.)

이런 방법이 번거롭다면 Google Translate API를 사용하여 번역을 적용하는 방법과 번역을 위한 다른 라이브러리를 사용하는 방법도 있습니다.

또는 제가 포스팅한 google 무료 번역 api 사용법을 참고하시면 번역을 쉽게 적용할 수 있습니다.

`Next.js에서 Google 무료 번역 API 사용하기`

하지만 next-intl 라이브러리를 글로벌 SEO 최적화 및 경량화된 번들 사이즈를 제공하므로, API를 사용하는 것보다 더 효율적으로 국제화를 구현할 수 있습니다.

내용이 길었습니다. 다음에는 Next.js에서 국제화를 구현할 때 주의할 점과 추가적인 설정 방법에 대해 알아보겠습니다.

Github Repository

`Next.js 국제화 예시 저장소`

참고

`next-intl 공식문서` `Next.js v15.0.1 App Router`

관련 태그