AngelPlayer`s Diary

 

 

 

 

개발 환경

- Windows
- VSCode

- Next.js 15 (App Router)
- Typescript

- next-intl ^3.26.3

 

 

 

목표

다국어 (영어, 한국어)를 지원하는 웹 페이지 제작

 

 

 

다국어 및 i18n 소개

최근 Next.js에서 다국어 기능을 지원하는 서비스를 개발하고 있습니다.

 

https://developer.mozilla.org/ko/docs/Glossary/Internationalization

 

국제화 (internationalization, I18N) - MDN Web Docs 용어 사전: 웹 용어 정의 | MDN

국제화 (i18n ("internationalization" 부터 유래, 20자로 이루어진 단어))는 제품이나 서비스를 모든 대상 문화에 쉽게 적용할 수 있도록 하는 모범 사례입니다.

developer.mozilla.org

다국어를 지원하는 웹사이트는 국제화(Internationalization, i18n) 과정을 통해 여러 국가의 사람들이 쉽게 사용할 수 있도록 개발됩니다.

국제화(이하 i18n)는 단순히 언어를 번역하는 것뿐만 아니라, 지역별 날짜 및 시간 형식, 숫자 및 통화 단위, 측정 단위 등을 사용자의 환경에 맞게 출력하는 것을 목표로 합니다. 

이를 통해 사용자는 자신의 지역이나 문화적 맥락에 맞는 경험을 제공받을 수 있습니다.

 

 

 

국제화를 지원하기 위해서는 사용자의 언어 정보를 라우팅에 반영하여야 합니다.

 

Next 환경에서 i18n을 지원하기 위한 대표적인 라우팅 방법으로는

- Prefix-based routing

- Domain-based routing

두 가지 방식이 있습니다.

 

Prefix-based routing는 URL 경로에 언어 정보를 접두어(Prefix)로 포함하는 방식입니다.

- 절대 경로 뒤에 /en, /ko와 같이 언어 정보를 엔드 포인트로 전달합니다.

- ex) angelplayer.tistory.com/en

 

Domain-based routing는 언어별로 서로 다른 서브도메인 또는 도메인을 사용하는 방식입니다.

- ex1) en.angelplayer.tistory.com (서브 도메인 사용 예제)

- ex2) angelplayer.kr (국가별 상위 도메인 사용 예제)

 

 

 

Next.js에서 다국어를 지원하는 라이브러리는 크게 next-i18next와 next-intl가 있습니다.

 

과거에는 next-i18next를 선호하는 사용자가 압도적으로 많았지만, 최근에는 next-intl을 채택하는 사례가 점점 증가하고 있다는 것을 패키지 다운로드 수 등으로 확인할 수 있습니다.

 

 

https://locize.com/blog/next-app-dir-i18n/

 

i18n with Next.js 13/14 and app directory / App Router (an i18next guide)

Looking for a way to internationalize your Next.js 13/14 project with the new app directory / App Router paradigm? Then this guide is for you!

locize.com

next-i18next의 경우 i18next 라이브러리를 기반으로 국제화를 지원하기 때문에 플러그인 및 부가 기능이 굉장히 많습니다. 

 

한편 초기 구성 파일이나 의존 패키지가 많다는 점이 아쉬웠습니다.

 

특히 저는 Typescript 환경에서 개발을 원하였으나, 공식 블로그에서는 기본적으로 Javascript 환경의 개발 방법에 대해서 설명하고 있어, 전환이 쉽지 않았습니다. 

 

 

 

https://next-intl.dev/

 

next-intl – Internationalization (i18n) for Next.js

Using next-intl too? Let us know!

next-intl.dev

 

next-intl의 경우 공식 document를 제공하며, typescript 환경에서의 예제 코드도 제공하고 있기 때문에 매우 편리하였습니다.

 

또한 설치 및 설정 단계에서 필요한 패키지가 next-intl 하나뿐이라는 점도 매력적으로 다가왔습니다.

 

 

따라서 저는 next-intl을 사용하여 i18n을 지원하는 서비스를 만들기로 결정하였습니다.

(앞으로 생성할 프로젝트는 상단에 링크로 제공된 next-intl의 Document에 기반으로 제작하였습니다.) 

 

 

 

 

프로젝트 생성 및 환경 구축

npm install next-intl

먼저 next-intl 패키지를 설치해줍니다.

 

 

 

Root
├── public
│   └── messages
│      ├── en.json (1)
│      └── ko.json
├── next.config.mjs (2)
└── src
    ├── i18n
    │   ├── routing.ts (3)
    │   └── request.ts (5)
    ├── middleware.ts (4)
    └── app
        └── [locale]
            ├── layout.tsx (6)
            └── page.tsx (7)

저희가 구축할 프로젝트의 전체적인 파일 구조는 위와 같습니다.

 

 

 

(1) public/message/[locale].json

{
    "HomePage": {
        "title": "Hello world!",
        "about": "Go to the about page"
    },
    "AboutPage": {
        "name": "AngelPlayer"
    }
}

다국어 번역 데이터를 저장하는 파일입니다.

 

파일의 최상위 key는 각 페이지의 namespace로 사용합니다.

 

하위에 중접된 key-value는 각각 텍스트의 식별자와 번역 문자열을 저장하고 있습니다.

 

 

 

(2) next.config.mjs

import createNextIntlPlugin from 'next-intl/plugin';

const withNextIntl = createNextIntlPlugin();

/** @type {import('next').NextConfig} */
const nextConfig = {};

export default withNextIntl(nextConfig);

next-intl 플러그인을 사용하여 Next.js에서 국제화를 설정하기 위한 설정 파일입니다.

 

 

 

(3) src/i18n/routing.ts

import { defineRouting } from 'next-intl/routing';
import { createNavigation } from 'next-intl/navigation';

export const routing = defineRouting({
    // A list of all locales that are supported
    locales: ['en', 'ko'],

    // Used when no locale matches
    defaultLocale: 'en',
});

// Lightweight wrappers around Next.js' navigation APIs
// that will consider the routing configuration
export const { Link, redirect, usePathname, useRouter, getPathname } = createNavigation(routing);

routing.ts는 next-intl 라이브러리에서 제공하는 라우팅 및 네비게이션 기능을 설정하는 파일입니다.

이 파일에서는 지원 언어및 기본 언어를 정의하여 프로젝트에서 다국어를 지원할 수 있도록 설정합니다.

Link, redirect, usePathname, useRouter, getPathname과 같은 네비게이션 유틸리티를 생성하여, 프로젝트 내부에서 언어별 라우팅과 네비게이션을 쉽게 처리할 수 있도록 도와줍니다.

 

 

 

(4) src/middleware.ts

import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';

export default createMiddleware(routing);

export const config = {
    // Match only internationalized pathnames
    matcher: ['/', '/(ko|en)/:path*'],
};

Next.js의 Middleware 기능을 활용하여 애플리케이션 요청(request)을 가로채고 처리하는 설정 파일입니다. 


- createMiddleware
createMiddleware는 요청이 들어올 때 라우팅 및 언어 관련 처리를 수행하는 Middleware를 생성합니다.

routing.ts에서 정의한 라우팅 설정(locales, defaultLocale)을 사용하여 언어 정보를 처리하며, 사용자의 언어 설정에 따라 적절한 경로로 리다이렉트합니다.


- config
Next.js의 Middleware가 작동할 경로를 정의합니다.

matcher를 통해 Middleware가 적용될 URL 패턴을 지정합니다.

 

 

 

(5) src/i18n/request.ts

import { getRequestConfig } from 'next-intl/server';
import { routing } from './routing';
import path from 'path';
import fs from 'fs';

export default getRequestConfig(async ({ requestLocale }) => {
    // This typically corresponds to the `[locale]` segment
    let locale = await requestLocale;

    // Ensure that a valid locale is used
    if (!locale || !routing.locales.includes(locale as any)) {
        locale = routing.defaultLocale;
    }

    const messagesPath = path.join(process.cwd(), 'public', 'messages', `${locale}.json`);
    const messages = JSON.parse(fs.readFileSync(messagesPath, 'utf8'));

    return {
        locale,
        messages,
    };
});

request.ts는 요청에 따라 번역 데이터를 동적으로 로드하고 처리하는 설정 파일입니다.

URL을 통해 언어 정보를 받아오고, 유효성 검증을 수행합니다.

언어 정보를 기반으로 번역 데이터를 로드합니다.

번역 데이터를 페이지에서 사용할 수 있도록 파싱을 진행합니다.

 

 

 

(6) src/app/[locale]/layout.tsx

import type { Metadata } from 'next';
import './../globals.css';
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { routing } from '@/i18n/routing';

export const metadata: Metadata = {
    title: 'AngelPlayer`s i18n',
    description: 'Sample code that applies next-intl in Next.js (App Router) and TypeScript environments.',
};

export default async function LocaleLayout({
    children,
    params,
}: {
    children: React.ReactNode;
    params: { locale: string };
}) {
    // `params`에서 `locale`을 비동기로 가져옵니다.
    const { locale } = await params;

    // `locale`이 유효하지 않으면 404를 반환합니다.
    if (!routing.locales.includes(locale as any)) {
        notFound();
    }

    // 메시지를 비동기로 가져옵니다.
    const messages = await getMessages(locale as any);

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

next-intl 라이브러리를 활용하여 i18n를 처리할 수 있는 환경을 구축합니다.

getMessages()를 통해 요청된 언어에 맞는 번역 데이터를 비동기적으로 로드합니다.

NextIntlClientProvider 컨텍스트를 통해서 번역 데이터를 제공함으로써 하위 컴포넌트에서 번역 데이터를 읽고 출력할 수 있습니다.


 

(7) src/app/[locale]/page.tsx

import { useTranslations } from 'next-intl';
import { Link } from '@/i18n/routing';

export default function HomePage() {
    const t = useTranslations('HomePage');
    return (
        <div>
            <h1>{t('title')}</h1>
            <Link href='/about'>{t('about')}</Link>
        </div>
    );
}

 

 

(7-1) src/app/[locale]/about/page.tsx

import { useTranslations } from 'next-intl';
import React from 'react';

export default function AboutPage() {
    const t = useTranslations('AboutPage');
    return (
        <div>
            <h1>{t('name')}</h1>
        </div>
    );
}

page.tsx에서는 useTranslations()를 통해서 번역 데이터를 받아와 사용할 수 있습니다.

useTranslations('namespace')에서 namespace를 입력하면, 해당 네임스페이스의 번역 데이터를 가져옵니다.
가져온 번역 데이터는 t 변수에 저장합니다.

t('key')를 호출하면, 지정된 키에 해당하는 번역 문자열을 텍스트로 출력할 수 있습니다.

 

 

 

결과적으로 기대했던 다국어 기능을 구현할 수 있었습니다.

 

 

 

 

공유하기

facebook twitter kakaoTalk kakaostory naver band