테마 전환

Next.js 국제화 완전 가이드: next-intl 모범 사례

Easton editorial illustration: build pipeline conveyor

작년에 다국어 지원이 필요한 Next.js 프로젝트를 맡았습니다. 설정 파일에 가득한 i18n 옵션을 처음 봤을 때는 꽤 막막했습니다. 문서를 한참 살펴본 뒤에야 App Router와 기존 Pages Router가 국제화를 다루는 방식이 완전히 다르다는 사실을 알았습니다. 일주일 동안 씨름한 끝에 next-intl 설정을 마쳤고, 그 과정에서 여러 시행착오도 겪었습니다.

이번 글에서는 Next.js의 국제화 방식, 특히 App Router에서 next-intl로 다국어 기능을 깔끔하게 구현하는 방법을 살펴보겠습니다.

왜 next-intl을 선택해야 할까요?

Next.js에 국제화 기능이 기본으로 들어 있지 않느냐고 생각할 수 있습니다. 실제로 Pages Router 시절에는 Next.js가 i18n 라우팅을 기본 지원했습니다. 하지만 App Router에서는 이 기능이 제거되었습니다.

공식 권장 방식은 서드파티 라이브러리를 사용하는 것입니다. next-intl은 그중 가장 인기 있는 선택지 중 하나입니다.

next-intl의 장점:

  • App Router 기본 지원 - App Router에 맞춰 설계되어 사용하기 편합니다.
  • 타입 안전성 - TypeScript와 함께 사용하면 번역 텍스트의 타입을 검사할 수 있습니다.
  • 유연한 라우팅 방식 - 하위 경로, 도메인, Cookie 등 여러 언어 전환 방식을 지원합니다.
  • 강력한 기능 - 복수형, 날짜 및 숫자 형식화, 리치 텍스트 등을 지원합니다.
  • 뛰어난 성능 - Server Component 친화적이며 정적 렌더링을 지원합니다.

다른 방식과 비교해 next-intl은 문서도 비교적 명확해서 입문 과정이 덜 힘듭니다.

기본 설정: 처음부터 시작하기

1. 의존성 설치

먼저 next-intl을 설치합니다.

npm install next-intl
# 또는
pnpm add next-intl
# 또는
yarn add next-intl

2. 번역 파일 만들기

프로젝트 루트에 messages 폴더를 만들고(locales 등 다른 이름도 가능), 언어별 JSON 파일을 생성합니다.

messages/
├── en.json
├── zh.json
└── ja.json

messages/zh.json:

{
  "HomePage": {
    "title": "欢迎来到我的网站",
    "description": "这是一个支持多语言的 Next.js 应用"
  },
  "Navigation": {
    "home": "首页",
    "about": "关于",
    "contact": "联系我们"
  }
}

messages/en.json:

{
  "HomePage": {
    "title": "Welcome to My Website",
    "description": "This is a multilingual Next.js application"
  },
  "Navigation": {
    "home": "Home",
    "about": "About",
    "contact": "Contact Us"
  }
}

중첩 구조가 필수는 아니지만, 페이지나 컴포넌트별로 묶으면 관리가 훨씬 편합니다.

3. i18n.ts 설정

지원 언어를 설정할 i18n.ts 또는 i18n/config.ts를 만듭니다.

import { getRequestConfig } from 'next-intl/server';

export default getRequestConfig(async ({ locale }) => ({
  messages: (await import(`./messages/${locale}.json`)).default
}));

이 설정은 next-intl에 번역 파일의 위치를 알려 줍니다. locale 매개변수는 URL에서 자동으로 추출됩니다.

4. 미들웨어 만들기

프로젝트 루트에 middleware.ts를 만듭니다. 다국어 라우팅을 처리하는 핵심 파일입니다.

import createMiddleware from 'next-intl/middleware';

export default createMiddleware({
  // 지원 언어 목록
  locales: ['en', 'zh', 'ja'],

  // 기본 언어
  defaultLocale: 'zh',

  // URL에 기본 언어를 항상 표시할지 여부
  localePrefix: 'as-needed'
});

export const config = {
  // api, _next/static, _next/image, favicon.ico를 제외한 모든 경로와 일치
  matcher: ['/', '/(zh|en|ja)/:path*', '/((?!api|_next|_next/static|_next/image|favicon.ico).*)']
};

localePrefix 옵션:

  • 'always' - 기본 언어를 포함한 모든 언어에 접두사를 표시합니다(/zh/about, /en/about).
  • 'as-needed' - 기본 언어에는 접두사를 표시하지 않습니다(/about, /en/about).
  • 'never' - 모든 언어에서 접두사를 표시하지 않습니다. 도메인 같은 다른 언어 식별 방식이 필요합니다.

저는 보통 'as-needed'를 사용합니다. 중국어 사용자에게 편하고 URL도 더 간결해 보이기 때문입니다.

5. app 디렉터리 구조 조정

가장 중요한 단계입니다. 모든 라우트를 [locale] 동적 라우트 아래로 옮겨야 합니다.

변경 전:

app/
├── page.tsx
├── about/
│   └── page.tsx
└── layout.tsx

변경 후:

app/
├── [locale]/
│   ├── page.tsx
│   ├── about/
│   │   └── page.tsx
│   └── layout.tsx
└── layout.tsx (선택 사항, 전역 설정에 사용)

6. 루트 Layout 설정

app/[locale]/layout.tsx에서 언어를 설정합니다.

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

const locales = ['en', 'zh', 'ja'];

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

export default async function LocaleLayout({
  children,
  params: { locale }
}: {
  children: React.ReactNode;
  params: { locale: string };
}) {
  // 지원 언어인지 확인
  if (!locales.includes(locale)) {
    notFound();
  }

  const messages = await getMessages();

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

정적 생성을 사용한다면 generateStaticParams가 어떤 언어의 페이지를 생성해야 하는지 Next.js에 알려 준다는 점에 주의하세요.

컴포넌트에서 번역 사용하기

설정을 마치면 컴포넌트에서 번역을 사용할 수 있습니다.

Server Component에서 사용

import { useTranslations } from 'next-intl';

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

  return (
    <div>
      <h1>{t('title')}</h1>
      <p>{t('description')}</p>
    </div>
  );
}

useTranslations의 인자는 번역 파일의 네임스페이스, 즉 최상위 key입니다. 인자를 전달하지 않으면 t('HomePage.title')처럼 전체 경로를 사용할 수 있습니다.

Client Component에서 사용

Client Component에서도 사용법은 같습니다.

'use client';

import { useTranslations } from 'next-intl';

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

  return (
    <nav>
      <a href="/">{t('home')}</a>
      <a href="/about">{t('about')}</a>
      <a href="/contact">{t('contact')}</a>
    </nav>
  );
}

Server Component와 Client Component의 사용법이 같다는 점이 next-intl의 편리한 부분입니다.

다국어 라우팅 처리

현재 언어 가져오기

import { useLocale } from 'next-intl';

export default function LanguageSwitcher() {
  const locale = useLocale();

  return <div>Current language: {locale}</div>;
}

언어 전환기 만들기

모든 다국어 웹사이트에 필요한 기능입니다.

'use client';

import { useLocale } from 'next-intl';
import { usePathname, useRouter } from 'next/navigation';

export default function LanguageSwitcher() {
  const locale = useLocale();
  const router = useRouter();
  const pathname = usePathname();

  const switchLanguage = (newLocale: string) => {
    // 경로의 언어 부분 교체
    const newPath = pathname.replace(`/${locale}`, `/${newLocale}`);
    router.push(newPath);
  };

  return (
    <select value={locale} onChange={(e) => switchLanguage(e.target.value)}>
      <option value="zh">中文</option>
      <option value="en">English</option>
      <option value="ja">日本語</option>
    </select>
  );
}

다만 이 방식에는 작은 문제가 있습니다. 기본 언어가 중국어인 페이지의 URL이 /about이라면 영어로 전환했을 때 /en/about이 되어야 합니다. 따라서 위 코드를 조금 개선해야 합니다.

const switchLanguage = (newLocale: string) => {
  // 현재 언어 접두사 제거
  let path = pathname;
  if (pathname.startsWith(`/${locale}`)) {
    path = pathname.substring(locale.length + 1);
  }

  // 새 언어 접두사 추가(기본 언어이면서 as-needed 설정인 경우 제외)
  const newPath = newLocale === 'zh' ? path : `/${newLocale}${path}`;
  router.push(newPath);
};

next-intl은 언어 접두사를 자동으로 처리하는 향상된 Link 컴포넌트를 제공합니다.

import { Link } from '@/navigation'; // 사전 설정 필요

<Link href="/about">
  {t('about')}
</Link>

navigation.ts를 설정합니다.

import { createSharedPathnamesNavigation } from 'next-intl/navigation';

export const locales = ['en', 'zh', 'ja'] as const;
export const localePrefix = 'as-needed';

export const { Link, redirect, usePathname, useRouter } =
  createSharedPathnamesNavigation({ locales, localePrefix });

이렇게 내보낸 Link, useRouter 등의 컴포넌트는 언어 경로를 자동으로 처리합니다.

고급 기능

1. 매개변수가 있는 번역

번역 문장에는 변수를 삽입해야 할 때가 많습니다. next-intl에서는 다음과 같이 작성합니다.

messages/zh.json:

{
  "welcome": "欢迎回来,{username}!",
  "items": "你有 {count} 个新消息"
}

사용법:

const t = useTranslations();

<p>{t('welcome', { username: 'John' })}</p>
<p>{t('items', { count: 5 })}</p>

2. 복수형 처리

언어마다 복수형 규칙이 다릅니다. next-intl은 이를 처리할 수 있습니다.

messages/en.json:

{
  "messages": {
    "one": "You have {count} message",
    "other": "You have {count} messages"
  }
}

사용법:

t('messages', { count: 1 })  // "You have 1 message"
t('messages', { count: 5 })  // "You have 5 messages"

중국어에는 복수형 개념이 없으므로 다음과 같이 쓸 수 있습니다.

messages/zh.json:

{
  "messages": "你有 {count} 条消息"
}

3. 날짜와 숫자 형식화

next-intl은 전용 형식화 함수를 제공합니다.

import { useFormatter } from 'next-intl';

export default function DateExample() {
  const format = useFormatter();
  const now = new Date();

  return (
    <div>
      <p>{format.dateTime(now, { dateStyle: 'full' })}</p>
      {/* 중국어: 2025年12月25日星期三 */}
      {/* 영어: Wednesday, December 25, 2025 */}

      <p>{format.number(1234567.89, { style: 'currency', currency: 'CNY' })}</p>
      {/* 중국어: ¥1,234,567.89 */}
      {/* 영어: CN¥1,234,567.89 */}
    </div>
  );
}

4. 리치 텍스트 번역

번역 내용에 HTML 태그나 React 컴포넌트가 포함될 때가 있습니다.

messages/zh.json:

{
  "richText": "我同意<terms>服务条款</terms>和<privacy>隐私政策</privacy>"
}

사용법:

import { useTranslations } from 'next-intl';

export default function Agreement() {
  const t = useTranslations();

  return (
    <p>
      {t.rich('richText', {
        terms: (chunks) => <a href="/terms">{chunks}</a>,
        privacy: (chunks) => <a href="/privacy">{chunks}</a>
      })}
    </p>
  );
}

번역 파일 관리 모범 사례

프로젝트가 커질수록 번역 파일도 관리하기 어려워집니다. 실용적인 팁 몇 가지를 소개합니다.

1. 기능 모듈별로 분리하기

모든 번역을 하나의 거대한 JSON에 넣지 말고 페이지나 기능별로 나눕니다.

messages/
├── zh/
│   ├── common.json      # 공통 번역(버튼, 오류 메시지 등)
│   ├── home.json        # 홈 페이지
│   ├── about.json       # 소개 페이지
│   └── auth.json        # 인증 관련
├── en/
│   ├── common.json
│   ├── home.json
│   ├── about.json
│   └── auth.json

그런 다음 i18n.ts에서 합칩니다.

import { getRequestConfig } from 'next-intl/server';

export default getRequestConfig(async ({ locale }) => {
  const common = (await import(`./messages/${locale}/common.json`)).default;
  const home = (await import(`./messages/${locale}/home.json`)).default;
  const about = (await import(`./messages/${locale}/about.json`)).default;
  const auth = (await import(`./messages/${locale}/auth.json`)).default;

  return {
    messages: {
      common,
      home,
      about,
      auth
    }
  };
});

2. TypeScript 타입 검사 사용하기

제가 특히 유용하다고 느낀 기능입니다. 번역 파일의 타입을 정의하면 오타를 방지할 수 있습니다.

types/i18n.ts:

import zh from '@/messages/zh.json';

type Messages = typeof zh;

declare global {
  interface IntlMessages extends Messages {}
}

TypeScript를 설정합니다(tsconfig.json).

{
  "compilerOptions": {
    "types": ["./types/i18n"]
  }
}

이제 t('xxx')를 사용할 때 해당 key가 없으면 TypeScript가 오류를 표시합니다. 매우 실용적입니다.

3. 공통 번역 추출하기

‘저장’, ‘취소’, ‘확인’ 같은 공통 텍스트는 여러 곳에서 사용되므로 별도로 관리하는 것이 좋습니다.

messages/zh/common.json:

{
  "actions": {
    "save": "保存",
    "cancel": "取消",
    "delete": "删除",
    "confirm": "确认",
    "edit": "编辑"
  },
  "status": {
    "success": "操作成功",
    "error": "操作失败",
    "loading": "加载中..."
  }
}

사용법:

const t = useTranslations('common.actions');
<button>{t('save')}</button>

4. 번역 관리 도구 사용하기

프로젝트 규모가 커지면 JSON 파일을 수동으로 관리하기가 매우 번거롭습니다. 다음 도구를 고려할 수 있습니다.

  • Tolgee - 실시간 편집을 지원하는 오픈 소스 번역 관리 플랫폼
  • Localazy - 자동화된 번역 워크플로
  • i18n Ally (VSCode 확장) - 편집기에서 번역을 직접 관리

저는 주로 i18n Ally를 사용합니다. 코드를 작성하면서 번역 내용을 바로 확인하고 수정할 수 있어 편리합니다.

5. 누락된 번역 처리하기

개발 중에는 특정 언어의 번역이 아직 끝나지 않은 경우가 자주 있습니다. 이럴 때는 폴백 로직을 설정할 수 있습니다.

// i18n.ts
export default getRequestConfig(async ({ locale }) => {
  const messages = (await import(`./messages/${locale}.json`)).default;
  const fallback = locale !== 'zh'
    ? (await import(`./messages/zh.json`)).default
    : {};

  return {
    messages: {
      ...fallback,
      ...messages
    }
  };
});

영어 번역이 누락되면 중국어로 자동 대체됩니다.

성능 최적화

1. 코드 분할

번역 파일이 크다면 필요할 때만 로드할 수 있습니다.

// 필요할 때만 로드
export default function AdminPage() {
  const t = useTranslations('admin'); // admin 네임스페이스만 로드
  // ...
}

2. 정적 생성

자주 바뀌지 않는 페이지는 정적 생성을 사용하면 성능을 크게 높일 수 있습니다.

// app/[locale]/about/page.tsx
export const dynamic = 'force-static';

export function generateStaticParams() {
  return [
    { locale: 'zh' },
    { locale: 'en' },
    { locale: 'ja' }
  ];
}

3. 번역 미리 로드하기

첫 화면에 표시되는 콘텐츠는 번역 파일을 미리 로드할 수 있습니다.

import { getTranslations } from 'next-intl/server';

// 서버에서 미리 로드
export default async function Home() {
  const t = await getTranslations('HomePage');

  return <h1>{t('title')}</h1>;
}

자주 발생하는 문제와 해결 방법

1. 동적 라우트의 언어 전환

동적 라우트(/blog/[slug] 등)에서 언어를 전환할 때는 slug를 유지해야 합니다.

const switchLanguage = (newLocale: string) => {
  const segments = pathname.split('/').filter(Boolean);
  // 이전 언어 접두사 제거
  if (['zh', 'en', 'ja'].includes(segments[0])) {
    segments.shift();
  }
  // 새 언어 접두사 추가(필요한 경우)
  if (newLocale !== 'zh' || localePrefix === 'always') {
    segments.unshift(newLocale);
  }
  router.push('/' + segments.join('/'));
};

2. SEO 최적화

다국어 웹사이트에서는 SEO에 특히 주의해야 합니다.

// app/[locale]/layout.tsx
export async function generateMetadata({ params: { locale } }) {
  const t = await getTranslations({ locale, namespace: 'metadata' });

  return {
    title: t('title'),
    description: t('description'),
    alternates: {
      canonical: `https://example.com/${locale}`,
      languages: {
        'zh-CN': 'https://example.com/zh',
        'en-US': 'https://example.com/en',
        'ja-JP': 'https://example.com/ja'
      }
    }
  };
}

3. 언어 감지

사용자가 처음 방문할 때 언어를 자동으로 감지할 수 있습니다.

// middleware.ts
import createMiddleware from 'next-intl/middleware';
import { NextRequest } from 'next/server';

const intlMiddleware = createMiddleware({
  locales: ['en', 'zh', 'ja'],
  defaultLocale: 'zh',
  localeDetection: true // 자동 감지 활성화
});

export default function middleware(request: NextRequest) {
  return intlMiddleware(request);
}

next-intl은 Accept-Language 요청 헤더를 기준으로 언어를 자동 선택합니다.

4. 언어 기본 설정 유지하기

사용자가 언어를 선택했다면 그 선택을 기억하는 것이 좋습니다.

// Cookie에 저장
const switchLanguage = (newLocale: string) => {
  document.cookie = `NEXT_LOCALE=${newLocale}; path=/; max-age=31536000`;
  router.push(newPath);
};

next-intl 미들웨어가 이 Cookie를 자동으로 읽습니다.

실전 사례: 완성된 국제화 프로젝트

마지막으로 이전에 작업했던 소규모 프로젝트의 핵심 코드를 정리해 소개합니다.

프로젝트 구조:

├── app/
│   ├── [locale]/
│   │   ├── layout.tsx
│   │   ├── page.tsx
│   │   └── blog/
│   │       └── [slug]/
│   │           └── page.tsx
├── components/
│   ├── LanguageSwitcher.tsx
│   └── Navigation.tsx
├── messages/
│   ├── zh/
│   │   ├── common.json
│   │   └── blog.json
│   ├── en/
│   │   ├── common.json
│   │   └── blog.json
├── i18n.ts
├── middleware.ts
└── navigation.ts

navigation.ts(라우팅 설정):

import { createSharedPathnamesNavigation } from 'next-intl/navigation';

export const locales = ['zh', 'en'] as const;
export const localePrefix = 'as-needed';

export const { Link, redirect, usePathname, useRouter } =
  createSharedPathnamesNavigation({ locales, localePrefix });

components/Navigation.tsx:

'use client';

import { Link } from '@/navigation';
import { useTranslations } from 'next-intl';
import LanguageSwitcher from './LanguageSwitcher';

export default function Navigation() {
  const t = useTranslations('common.navigation');

  return (
    <nav className="flex items-center justify-between p-4">
      <div className="flex gap-4">
        <Link href="/">{t('home')}</Link>
        <Link href="/blog">{t('blog')}</Link>
        <Link href="/about">{t('about')}</Link>
      </div>
      <LanguageSwitcher />
    </nav>
  );
}

이 프로젝트를 배포한 뒤 언어 전환은 매우 매끄러웠고 별다른 문제도 발생하지 않았습니다.

정리

App Router 시대의 Next.js 국제화는 분명 조금 복잡하지만, next-intl을 익히면 생각보다 어렵지 않습니다.

  1. 핵심 설정: 미들웨어 + i18n.ts + [locale] 디렉터리
  2. 번역 사용: Server와 Client Component 모두에서 useTranslations Hook 사용
  3. 라우팅 처리: next-intl이 제공하는 Link와 Router 사용
  4. 파일 관리: 모듈별 분리 + TypeScript 타입 검사
  5. 성능 최적화: 정적 생성 + 필요할 때만 로드

솔직히 처음 이 방식을 접했을 때는 꽤 복잡하게 느껴졌습니다. 특히 미들웨어와 동적 라우트 부분이 그랬습니다. 하지만 몇 번 작성해 보면 익숙해지고, 이제는 국제화 프로젝트에서 거의 항상 이 구성을 사용합니다.

다국어 프로젝트를 진행 중이거나 준비하고 있다면 next-intl을 적극 권합니다. 학습 비용은 있지만 장기적으로 충분히 시간을 투자할 가치가 있습니다.

참고 자료

이 글이 Next.js 국제화 때문에 고민하는 분들에게 도움이 되기를 바랍니다!

Next.js 국제화 전체 설정 절차

next-intl 설치부터 다국어 라우팅과 번역 파일 관리 설정까지 다루는 전체 절차

⏱️ Estimated time: 2 hr

  1. 1

    Step 1: next-intl 설치 및 기본 설정

    설치:
    ```bash
    npm install next-intl
    ```

    번역 파일 생성:
    ```
    messages/
    zh.json
    en.json
    ```

    middleware.ts 설정:
    ```ts
    import createMiddleware from 'next-intl/middleware'
    import { routing } from './i18n/routing'

    export default createMiddleware(routing)

    export const config = {
    matcher: ['/', '/(zh|en)/:path*']
    }
    ```

    app/[locale]/layout.tsx 설정:
    ```tsx
    import { NextIntlClientProvider } from 'next-intl'
    import { getMessages } from 'next-intl/server'

    export default async function LocaleLayout({
    children,
    params: { locale }
    }) {
    const messages = await getMessages()

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

    핵심:
    • [locale] 동적 라우트를 사용합니다.
    • layout에서 번역을 제공합니다.
    • middleware로 언어 전환을 처리합니다.
  2. 2

    Step 2: 다국어 라우팅 방식 설정

    방식 1: 하위 경로(권장)
    • URL 형식: /zh/about, /en/about
    • 설정이 간단함
    • SEO 친화적

    방식 2: 도메인
    • URL 형식: zh.example.com, en.example.com
    • 여러 도메인 설정 필요
    • 더 전문적인 구성

    방식 3: Cookie
    • Cookie로 언어 전환
    • URL에 언어 접두사가 없음
    • 한 언어를 주로 쓰는 사용자에게 적합

    하위 경로 설정:
    ```ts
    // i18n/routing.ts
    export const routing = {
    locales: ['zh', 'en'],
    defaultLocale: 'zh'
    }
    ```

    사용법:
    ```tsx
    import { useTranslations } from 'next-intl'

    export function Page() {
    const t = useTranslations('common')
    return <h1>{t('title')}</h1>
    }
    ```

    핵심: 프로젝트에 맞는 라우팅 방식을 선택하세요. 대부분의 프로젝트에는 하위 경로만으로 충분합니다.
  3. 3

    Step 3: 번역 파일 관리

    번역 파일 생성:
    ```json
    // messages/zh.json
    {
    "common": {
    "title": "환영합니다",
    "description": "다국어 웹사이트입니다"
    },
    "nav": {
    "home": "홈",
    "about": "소개"
    }
    }
    ```

    번역 사용:
    ```tsx
    import { useTranslations } from 'next-intl'

    export function Page() {
    const t = useTranslations('common')
    return (
    <div>
    <h1>{t('title')}</h1>
    <p>{t('description')}</p>
    </div>
    )
    }
    ```

    타입 안전성:
    ```ts
    // i18n/request.ts
    import { getRequestConfig } from 'next-intl/server'

    export default getRequestConfig(async ({ locale }) => ({
    messages: (await import(`../messages/${locale}.json`)).default
    }))
    ```

    핵심:
    • 중첩 구조로 번역을 정리합니다.
    • TypeScript와 함께 사용해 타입 안전성을 확보합니다.
    • i18n Ally VSCode 확장으로 개발 효율을 높입니다.

FAQ

App Router에 next-intl이 필요한 이유는 무엇인가요?
App Router에서는 Pages Router에 내장되어 있던 i18n 기능이 제거되었기 때문입니다.

Pages Router:
• i18n 라우팅을 기본 지원
• next.config.js에서 i18n 필드 설정
• 언어 전환 자동 처리

App Router:
• 내장 i18n 기능 제거
• 서드파티 라이브러리 필요
• next-intl이 가장 인기 있는 선택지 중 하나

next-intl의 장점:
• App Router 기본 지원
• 타입 안전성
• 유연한 라우팅 방식
• 복수형과 날짜 형식화 등 강력한 기능
• 뛰어난 성능

권장 사항: App Router를 사용한다면 next-intl을 우선 고려하세요.
next-intl은 몇 가지 라우팅 방식을 지원하나요?
세 가지 방식이 있습니다.

방식 1: 하위 경로(권장)
• URL 형식: /zh/about, /en/about
• 설정이 간단함
• SEO 친화적
• 대부분의 프로젝트에 적합

방식 2: 도메인
• URL 형식: zh.example.com, en.example.com
• 여러 도메인 설정 필요
• 더 전문적인 구성
• 대규모 프로젝트에 적합

방식 3: Cookie
• Cookie로 언어 전환
• URL에 언어 접두사가 없음
• 한 언어를 주로 쓰는 사용자에게 적합
• 설정이 복잡함

선택 기준:
• 대부분의 프로젝트 → 하위 경로
• 대규모 프로젝트 → 도메인
• 특수 요구 사항 → Cookie

핵심: 프로젝트에 맞는 방식을 고르되, 대부분은 하위 경로로 충분합니다.
next-intl은 어떻게 설정하나요?
설치:
```bash
npm install next-intl
```

번역 파일 생성:
```
messages/
zh.json
en.json
```

middleware.ts 설정:
```ts
import createMiddleware from 'next-intl/middleware'
import { routing } from './i18n/routing'

export default createMiddleware(routing)

export const config = {
matcher: ['/', '/(zh|en)/:path*']
}
```

app/[locale]/layout.tsx 설정:
```tsx
import { NextIntlClientProvider } from 'next-intl'
import { getMessages } from 'next-intl/server'

export default async function LocaleLayout({
children,
params: { locale }
}) {
const messages = await getMessages()

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

핵심:
• [locale] 동적 라우트를 사용합니다.
• layout에서 번역을 제공합니다.
• middleware로 언어 전환을 처리합니다.
번역 파일은 어떻게 관리하나요?
번역 파일 생성:
```json
// messages/zh.json
{
"common": {
"title": "환영합니다",
"description": "다국어 웹사이트입니다"
},
"nav": {
"home": "홈",
"about": "소개"
}
}
```

번역 사용:
```tsx
import { useTranslations } from 'next-intl'

export function Page() {
const t = useTranslations('common')
return (
<div>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
</div>
)
}
```

타입 안전성:
```ts
// i18n/request.ts
import { getRequestConfig } from 'next-intl/server'

export default getRequestConfig(async ({ locale }) => ({
messages: (await import(`../messages/${locale}.json`)).default
}))
```

핵심:
• 중첩 구조로 번역을 정리합니다.
• TypeScript와 함께 사용해 타입 안전성을 확보합니다.
• i18n Ally VSCode 확장으로 개발 효율을 높입니다.

권장 사항: 기능 모듈별로 번역 파일을 나눠 파일 하나가 지나치게 커지지 않게 하세요.
next-intl은 어떤 기능을 지원하나요?
핵심 기능:
• 텍스트 번역(t 함수)
• 복수형 처리
• 날짜 형식화
• 숫자 형식화
• 리치 텍스트 지원

사용 예:
```tsx
import { useTranslations, useFormatter } from 'next-intl'

export function Page() {
const t = useTranslations('common')
const format = useFormatter()

return (
<div>
<h1>{t('title')}</h1>
<p>{format.dateTime(new Date(), { dateStyle: 'long' })}</p>
<p>{format.number(1234.56, { style: 'currency', currency: 'USD' })}</p>
</div>
)
}
```

장점:
• 강력한 기능
• 타입 안전성
• 뛰어난 성능
• Server Component 친화적

권장 사항: next-intl의 기능을 충분히 활용해 사용자 경험을 개선하세요.
언어 전환은 어떻게 구현하나요?
Link 컴포넌트 사용:
```tsx
import { Link } from '@/i18n/navigation'

<Link href="/about" locale="en">
English
</Link>
<Link href="/about" locale="zh">
中文
</Link>
```

useRouter 사용:
```tsx
'use client'
import { useRouter, usePathname } from '@/i18n/navigation'

export function LanguageSwitcher() {
const router = useRouter()
const pathname = usePathname()

const switchLanguage = (locale: string) => {
router.replace(pathname, { locale })
}

return (
<button onClick={() => switchLanguage('en')}>
English
</button>
)
}
```

핵심:
• next-intl이 제공하는 Link와 useRouter를 사용합니다.
• 현재 경로는 유지하고 언어만 전환합니다.
• 좋은 사용자 경험을 제공합니다.

권장 사항: 내비게이션 바나 푸터에 언어 전환 버튼을 추가하세요.

6분 읽기 · 게시일: 2025년 12월 25일 · 수정일: 2026년 9월 8일

댓글

GitHub로 로그인하여 댓글을 남기세요

Easton BlogEaston Blog