Next.js에 i18n 적용하기 - 다국어 처리하기 (next-intl)

8/11/2024

Next.js로 다국어 처리를 도와주는 다국어를 도와주는 여러가지 모듈이 존재합니다.

https://nextjs.org/docs/pages/building-your-application/routing/internationalization

이 글에서는 next-intl을 이용해 다국어 처리를 해보겠습니다.

https://next-intl-docs.vercel.app/


1. 먼저 Next.js를 CLI로 생성합니다.

이 문서에서는 Typescript와 App router를 사용하겠습니다.

npx create-next-app@14 my-nextjs-i18n

2. 다국어 처리에 사용할 next-itnl을 설치합니다.

npm install next-intl --save

3. 먼저 다국어 지원을 처리할 메시지들을 생성합니다.

├── messages
│   ├── en.json
│   ├── jp.json
│   └── ko.json

루트 경로에 다음과 같이 3개의 json파일을 만들고, 내용을 입력해줍니다.

// ko.json
{
  "Home": {
    "welcome": "우리 웹사이트에 오신 것을 환영합니다!",
    "content": "이 웹사이트는 React와 React-Router를 사용하여 만들어졌습니다."
  },
  "LocaleSwitcher": {
    "label": "Change language",
    "locale": "{locale, select, en {🇺🇸 영어} ko {🇰🇷 한국어} jp {🇯🇵 일본어} other {Unknown}}"
  }
}
// en.json
{
  "Home": {
    "welcome": "Welcome to our website!",
    "content": "This website was built using React and React-Router."
  },
  "LocaleSwitcher": {
    "label": "Change language",
    "locale": "{locale, select, en {🇺🇸 English} ko {🇰🇷 Korean} jp {🇯🇵 Japanese} other {Unknown}}"
  }
}
// jp.json
{
  "Home": {
    "welcome": "私たちのウェブサイトへようこそ!",
    "content": "このウェブサイトはNext.jsを使用して構築されました。"
  },
  "LocaleSwitcher": {
    "label": "Change language",
    "locale": "{locale, select, en {🇺🇸 英語} ko {🇰🇷 韓国語} jp {🇯🇵 日本語} other {Unknown}}"
  }
}

4. 이제 next-intl을 사용하기 위한 설정이 필요합니다.

next.config.mjs 파일을 다음과 같이 설정합니다.

/** next.config.mjs */

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

5. i18n에 관한 설정이 필요합니다.

i18n의 설정과 next-itnl의 미들웨어를 설정합니다.

// /src/config.ts

import {Pathnames, LocalePrefix} from 'next-intl/routing';

// 디폴트로 설정할 로케일
export const defaultLocale = 'en' as const;

// 지원 할 로케일
export const locales = ['en', 'ko', 'jp'] as const;

export type Locale = (typeof locales)[number];

export const pathnames: Pathnames<typeof locales> = {
  '/': '/'
};

export const localePrefix: LocalePrefix<typeof locales> = 'always';

export const port = process.env.PORT || 3000;
export const host = process.env.VERCEL_URL
  ? `https://${process.env.VERCEL_URL}`
  : `http://localhost:${port}`;
 
// /src/i18n.ts

import {notFound} from 'next/navigation';
import {getRequestConfig} from 'next-intl/server';
import {locales} from './config';

export default getRequestConfig(async ({locale}) => {
  // Validate that the incoming `locale` parameter is valid
  if (!locales.includes(locale as any)) notFound();

  return {
    messages: (
      await (locale === 'en'
        ? // When using Turbopack, this will enable HMR for `en`
          import('../messages/en.json')
        : import(`../messages/${locale}.json`))
    ).default
  };
});
// /src/middleware.ts

import createMiddleware from 'next-intl/middleware';
import {localePrefix, defaultLocale, locales, pathnames} from './config';

export default createMiddleware({
  defaultLocale,
  locales,
  localePrefix,
  pathnames
});

export const config = {
  matcher: [
    // Enable a redirect to a matching locale at the root
    '/',

    // Set a cookie to remember the previous locale for
    // all requests that have a locale prefix
    '/(de|en|ko|jp)/:path*',

    // Enable redirects that add missing locales
    // (e.g. `/pathnames` -> `/en/pathnames`)
    '/((?!_next|_vercel|.*\\..*).*)'
  ]
};
 
// navigation.ts

import {createLocalizedPathnamesNavigation} from 'next-intl/navigation';
import {locales, pathnames, localePrefix} from './config';

export const {Link, getPathname, redirect, usePathname, useRouter} =
  createLocalizedPathnamesNavigation({
    locales,
    pathnames,
    localePrefix
  });
 

6. 이제 i18n이 적용된 페이지를 작성합니다.

locale 설정에 따라 http://localhost:3000/ko or http://localhost:3000/en 식으로 경로를 미들웨어에서 바꿔주게 됩니다.

app폴더 아래 [locale] 폴더를 생성해 동적 라우팅을 만들어줍니다.

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

import {NextIntlClientProvider} from 'next-intl';
import {getMessages} from 'next-intl/server';

export default async function LocaleLayout({
  children,
  params: {locale}
}: {
  children: React.ReactNode;
  params: {locale: string};
}) {
  const messages = await getMessages();

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

messages의 json에 미리 정의해둔 문자를 보여주는 간단한 page.tsx 를 생성해보겠습니다.

'use client';

import {useTranslations} from 'next-intl';
import LocaleSwitch from '@/components/LocaleSwitch';

export default function Home() {
  const t = useTranslations('Home');

  return (
    <div>
      <h1>{t('welcome')}</h1>
      <h4>{t('content')}</h4>
    </div>
  );
}
 

또한 현재 기본 locale을 en으로 설정했기 때문에 루트 도메인으로 접속하는 경우 en locale로 리다이렉트 해주겠습니다.

// src/app/page.tsx

import {redirect} from 'next/navigation';

// This page only renders when the app is built statically (output: 'export')
export default function RootPage() {
  redirect('/en');
}

이제 i18n을 위한 설정과 페이지 작업이 마무리 되었습니다. 페이지를 확인해보면 경로에 따라 미리 설정해둔 문구가 보여지는 것을 확인할 수 있습니다.

alt text

alt text

alt text

전체적인 폴더 구조는 이렇게 구성됩니다.


├── messages
│   ├── en.json
│   ├── jp.json
│   └── ko.json
├── next.config.mjs
├── package-lock.json
├── package.json
├── public
├── src
│   ├── app
│   │   ├── [locale]
│   │   │   ├── layout.tsx
│   │   │   └── page.tsx
│   │   ├── layout.tsx
│   │   └── page.tsx
│   ├── components
│   │   ├── LocalSwitchSelect.tsx
│   │   └── LocaleSwitch.tsx
│   ├── config.ts
│   ├── i18n.ts
│   ├── middleware.ts
│   └── navigation.ts

참고자료

https://next-intl-docs.vercel.app/

https://next-intl-docs.vercel.app/examples#app-router

© 2025 Mingu Kim. All rights reserved.