Intlayer 및 Next.js 14의 App Router와 함께 국제화(i18n) 시작하기

    Intlayer란 무엇인가?

    Intlayer는 현대 웹 애플리케이션에서 다국어 지원을 간소화하기 위해 설계된 혁신적이고 오픈 소스인 국제화(i18n) 라이브러리입니다. Intlayer는 최신 Next.js 14 프레임워크와 강력한 App Router와 원활하게 통합됩니다. 이 라이브러리는 서버 컴포넌트와의 효율적인 렌더링을 위해 최적화되어 있으며, Turbopack (Next.js >= 15부터 지원)과 완벽히 호환됩니다.

    Intlayer를 사용하면 다음을 수행할 수 있습니다:

    • 컴포넌트 수준에서 선언적 사전을 사용하여 번역을 쉽게 관리합니다.
    • 메타데이터, 라우트 및 콘텐츠를 동적으로 로컬라이즈합니다.
    • 클라이언트 측 및 서버 측 컴포넌트에서 번역에 액세스합니다.
    • TypeScript 지원을 보장하며, 자동 생성된 타입으로 자동 완성과 오류 감지를 개선합니다.
    • 동적 로케일 감지 및 전환과 같은 고급 기능을 활용합니다.

    Intlayer는 Next.js 12, 13, 14 및 15와 호환됩니다. Next.js Page Router를 사용하는 경우, 이 가이드를 참조하세요. Next.js 15 (Turbopack 포함 또는 미포함)를 사용하는 경우, 이 가이드를 참조하세요.


    Next.js 애플리케이션에서 Intlayer 설정 단계별 가이드

    1단계: 종속성 설치

    npm을 사용하여 필요한 패키지를 설치합니다:

    bash
    npm install intlayer next-intlayer
    • intlayer

      구성 관리, 번역, 콘텐츠 선언, 트랜스파일링 및 CLI 명령을 위한 국제화 도구를 제공하는 핵심 패키지입니다.

    • next-intlayer

      Intlayer를 Next.js와 통합하는 패키지입니다. Next.js 국제화를 위한 컨텍스트 제공자 및 훅을 제공합니다. 또한 Intlayer를 Webpack 또는 Turbopack과 통합하기 위한 Next.js 플러그인과 사용자의 선호 로케일을 감지하고, 쿠키를 관리하며, URL 리디렉션을 처리하는 미들웨어를 포함합니다.

    2단계: 프로젝트 구성

    애플리케이션의 언어를 구성하기 위한 설정 파일을 만듭니다:

    intlayer.config.ts
    import { Locales, type IntlayerConfig } from "intlayer";const config: IntlayerConfig = {  internationalization: {    locales: [      Locales.ENGLISH,      Locales.FRENCH,      Locales.SPANISH,      // 다른 로케일 추가    ],    defaultLocale: Locales.ENGLISH,  },};export default config;

    이 구성 파일을 통해 로컬라이즈된 URL, 미들웨어 리디렉션, 쿠키 이름, 콘텐츠 선언의 위치 및 확장자, 콘솔에서 Intlayer 로그 비활성화 등을 설정할 수 있습니다. 사용 가능한 매개변수의 전체 목록은 구성 문서를 참조하세요.

    3단계: Next.js 구성에 Intlayer 통합

    Intlayer를 사용하도록 Next.js 설정을 구성합니다:

    next.config.mjs
    import { withIntlayer } from "next-intlayer/server";/** @type {import('next').NextConfig} */const nextConfig = {};export default withIntlayer(nextConfig);

    withIntlayer() Next.js 플러그인은 Intlayer를 Next.js와 통합하는 데 사용됩니다. 이는 콘텐츠 선언 파일의 빌드를 보장하고 개발 모드에서 이를 모니터링합니다. 또한 Webpack 또는 Turbopack 환경 내에서 Intlayer 환경 변수를 정의합니다. 추가적으로 성능 최적화를 위한 별칭을 제공하며 서버 컴포넌트와의 호환성을 보장합니다.

    4단계: 로케일 감지를 위한 미들웨어 구성

    사용자의 선호 로케일을 감지하기 위한 미들웨어를 설정합니다:

    src/middleware.ts
    export { intlayerMiddleware as middleware } from "next-intlayer/middleware";export const config = {  matcher:    "/((?!api|static|assets|robots|sitemap|sw|service-worker|manifest|.*\..*|_next).*)",};

    intlayerMiddleware는 사용자의 선호 로케일을 감지하고 구성에 지정된 적절한 URL로 리디렉션합니다. 추가적으로 사용자의 선호 로케일을 쿠키에 저장할 수 있습니다.

    matcher 매개변수를 애플리케이션의 라우트에 맞게 조정하세요. 자세한 내용은 Next.js 문서의 matcher 구성을 참조하세요.

    5단계: 동적 로케일 라우트 정의

    RootLayout에서 모든 내용을 제거하고 다음 코드를 추가합니다:

    src/app/layout.tsx
    import type { PropsWithChildren, FC } from "react";import "./globals.css";const RootLayout: FC<PropsWithChildren> = ({ children }) => children;export default RootLayout;

    RootLayout 컴포넌트를 비워두면 langdir 속성을 <html> 태그에 설정할 수 있습니다.

    동적 라우팅을 구현하려면 [locale] 디렉토리에 새 레이아웃을 추가하여 로케일 경로를 제공합니다:

    src/app/[locale]/layout.tsx
    import type { Next14LayoutIntlayer } from "next-intlayer";import { Inter } from "next/font/google";import { getHTMLTextDir } from "intlayer";const inter = Inter({ subsets: ["latin"] });const LocaleLayout: Next14LayoutIntlayer = ({  children,  params: { locale },}) => (  <html lang={locale} dir={getHTMLTextDir(locale)}>    <body className={inter.className}>{children}</body>  </html>);export default LocaleLayout;

    [locale] 경로 세그먼트는 로케일을 정의하는 데 사용됩니다. 예: /en-US/abouten-US를, /fr/aboutfr을 참조합니다.

    그런 다음, 애플리케이션 레이아웃에 generateStaticParams 함수를 구현합니다.

    src/app/[locale]/layout.tsx
    export { generateStaticParams } from "next-intlayer"; // 삽입할 줄const LocaleLayout: Next14LayoutIntlayer = ({  children,  params: { locale },}) => {  /*... 나머지 코드*/};export default LocaleLayout;

    generateStaticParams는 애플리케이션이 모든 로케일에 필요한 페이지를 사전 빌드하도록 보장하여 런타임 계산을 줄이고 사용자 경험을 향상시킵니다. 자세한 내용은 Next.js 문서의 generateStaticParams를 참조하세요.

    6단계: 콘텐츠 선언

    번역을 저장하기 위해 콘텐츠 선언을 생성하고 관리합니다:

    src/app/[locale]/page.content.ts
    import { t, type Dictionary } from "intlayer";const pageContent = {  key: "page",  content: {    getStarted: {      main: t({        en: "Get started by editing",        fr: "Commencez par éditer",        es: "Comience por editar",        ko: "편집을 시작하세요",      }),      pageLink: "src/app/page.tsx",    },  },} satisfies Dictionary;export default pageContent;

    콘텐츠 선언은 애플리케이션 어디에서나 정의할 수 있으며, contentDir 디렉토리(기본값: ./src)에 포함되고 콘텐츠 선언 파일 확장자(기본값: .content.{ts,tsx,js,jsx,mjs,cjs})와 일치해야 합니다. 자세한 내용은 콘텐츠 선언 문서를 참조하세요.

    7단계: 코드에서 콘텐츠 활용

    애플리케이션 전반에서 콘텐츠 사전에 액세스합니다:

    src/app/[locale]/page.tsx
    import { ClientComponentExample } from "@components/ClientComponentExample";import { ServerComponentExample } from "@components/ServerComponentExample";import { type Next14PageIntlayer, IntlayerClientProvider } from "next-intlayer";import { IntlayerServerProvider, useIntlayer } from "next-intlayer/server";const Page: Next14PageIntlayer = ({ params: { locale } }) => {  const content = useIntlayer("page", locale);  return (    <>      <p>        {content.getStarted.main}        <code>{content.getStarted.pageLink}</code>      </p>      <IntlayerServerProvider locale={locale}>        <IntlayerClientProvider locale={locale}>          <ServerComponentExample />          <ClientComponentExample />        </IntlayerClientProvider>      </IntlayerServerProvider>    </>  );};export default Page;
    • IntlayerClientProvider는 클라이언트 측 컴포넌트에 로케일을 제공하는 데 사용됩니다. 이는 레이아웃을 포함한 모든 상위 컴포넌트에 배치할 수 있습니다. 그러나 레이아웃에 배치하는 것이 권장됩니다. Next.js는 레이아웃 코드를 페이지 간에 공유하므로 더 효율적입니다. 레이아웃에서 IntlayerClientProvider를 사용하면 각 페이지에 대해 이를 다시 초기화하지 않아도 되므로 성능이 향상되고 애플리케이션 전반에 걸쳐 일관된 로컬라이제이션 컨텍스트를 유지할 수 있습니다.
    • IntlayerServerProvider는 서버 자식에게 로케일을 제공하는 데 사용됩니다. 이는 레이아웃에 설정할 수 없습니다.

      레이아웃과 페이지는 공통 서버 컨텍스트를 공유할 수 없습니다. 서버 컨텍스트 시스템은 요청별 데이터 저장소(React의 캐시 메커니즘을 통해)에 기반하기 때문에 애플리케이션의 다른 세그먼트에 대해 각 "컨텍스트"가 다시 생성됩니다. 공유 레이아웃에 제공자를 배치하면 이 격리가 깨져 서버 컨텍스트 값이 서버 컴포넌트로 올바르게 전파되지 않습니다.

    src/components/ClientComponentExample.tsx
    "use client";import type { FC } from "react";import { useIntlayer } from "next-intlayer";const ClientComponentExample: FC = () => {  const content = useIntlayer("client-component-example"); // 관련 콘텐츠 선언 생성  return (    <div>      <h2>{content.title} </h2>      <p>{content.content}</p>    </div>  );};
    src/components/ServerComponentExample.tsx
    import type { FC } from "react";import { useIntlayer } from "next-intlayer/server";const ServerComponentExample: FC = () => {  const content = useIntlayer("server-component-example"); // 관련 콘텐츠 선언 생성  return (    <div>      <h2>{content.title} </h2>      <p>{content.content}</p>    </div>  );};

    alt, title, href, aria-label 등과 같은 string 속성에서 콘텐츠를 사용하려면 함수의 값을 호출해야 합니다:

    jsx
    <img src={content.image.src.value} alt={content.image.value} />

    useIntlayer 훅에 대해 자세히 알아보려면 문서를 참조하세요.

    (선택 사항) 8단계: 메타데이터의 국제화

    페이지 제목과 같은 메타데이터를 국제화하려면 Next.js에서 제공하는 generateMetadata 함수를 사용할 수 있습니다. 함수 내부에서 getTranslation 함수를 사용하여 메타데이터를 번역합니다.

    src/app/[locale]/layout.tsx 또는 src/app/[locale]/page.tsx
    import {  type IConfigLocales,  getTranslation,  getMultilingualUrls,} from "intlayer";import type { Metadata } from "next";import type { LocalParams } from "next-intlayer";export const generateMetadata = ({  params: { locale },}: LocalParams): Metadata => {  const t = <T>(content: IConfigLocales<T>) => getTranslation(content, locale);  /**   * 각 로케일에 대한 모든 URL을 포함하는 객체를 생성합니다.   *   * 예:   * ```ts   *  getMultilingualUrls('/about');   *   *  // 반환값   *  // {   *  //   en: '/about',   *  //   fr: '/fr/about',   *  //   es: '/es/about',   *  // }   * ```   */  const multilingualUrls = getMultilingualUrls("/");  return {    title: t<string>({      en: "My title",      fr: "Mon titre",      es: "Mi título",      ko: "내 제목",    }),    description: t({      en: "My description",      fr: "Ma description",      es: "Mi descripción",      ko: "내 설명",    }),    alternates: {      canonical: "/",      languages: { ...multilingualUrls, "x-default": "/" },    },    openGraph: {      url: multilingualUrls[locale],    },  };};// ... 나머지 코드

    메타데이터 최적화에 대해 자세히 알아보려면 Next.js 공식 문서를 참조하세요.

    (선택 사항) 9단계: sitemap.xml 및 robots.txt의 국제화

    sitemap.xmlrobots.txt를 국제화하려면 Intlayer에서 제공하는 getMultilingualUrls 함수를 사용할 수 있습니다. 이 함수는 사이트맵에 다국어 URL을 생성할 수 있도록 합니다.

    src/app/sitemap.ts
    import { getMultilingualUrls } from "intlayer";import type { MetadataRoute } from "next";const sitemap = (): MetadataRoute.Sitemap => [  {    url: "https://example.com",    alternates: {      languages: getMultilingualUrls("https://example.com"),    },  },  {    url: "https://example.com/login",    alternates: {      languages: getMultilingualUrls("https://example.com/login"),    },  },  {    url: "https://example.com/register",    alternates: {      languages: getMultilingualUrls("https://example.com/register"),    },  },];export default sitemap;
    src/app/robots.ts
    import type { MetadataRoute } from "next";import { getMultilingualUrls } from "intlayer";const getAllMultilingualUrls = (urls: string[]) =>  urls.flatMap((url) => Object.values(getMultilingualUrls(url)) as string[]);const robots = (): MetadataRoute.Robots => ({  rules: {    userAgent: "*",    allow: ["/"],    disallow: getAllMultilingualUrls(["/login", "/register"]),  },  host: "https://example.com",  sitemap: `https://example.com/sitemap.xml`,});export default robots;

    사이트맵 최적화에 대해 자세히 알아보려면 Next.js 공식 문서를 참조하세요. robots.txt 최적화에 대해 자세히 알아보려면 Next.js 공식 문서를 참조하세요.

    (선택 사항) 10단계: 콘텐츠 언어 변경

    콘텐츠 언어를 변경하려면 useLocale 훅에서 제공하는 setLocale 함수를 사용할 수 있습니다. 이 함수는 애플리케이션의 로케일을 설정하고 콘텐츠를 업데이트할 수 있도록 합니다.

    src/components/LocaleSwitcher.tsx
    "use client";import {  Locales,  getHTMLTextDir,  getLocaleName,  getLocalizedUrl,} from "intlayer";import { useLocale } from "next-intlayer";import { type FC } from "react";import Link from "next/link";const LocaleSwitcher: FC = () => {  const { locale, pathWithoutLocale, availableLocales, setLocale } =    useLocale();  return (    <div>      <button popoverTarget="localePopover">{getLocaleName(locale)}</button>      <div id="localePopover" popover="auto">        {availableLocales.map((localeItem) => (          <Link            href={getLocalizedUrl(pathWithoutLocale, localeItem)}            hrefLang={localeItem}            key={localeItem}            aria-current={locale === localeItem ? "page" : undefined}            onClick={(e) => {              e.preventDefault();              setLocale(localeItem);            }}          >            <span>              {/* 로케일 - 예: FR */}              {localeItem}            </span>            <span>              {/* 로케일 자체 언어 - 예: Français */}              {getLocaleName(localeItem, locale)}            </span>            <span dir={getHTMLTextDir(localeItem)} lang={localeItem}>              {/* 현재 로케일 언어 - 예: Francés (현재 로케일이 Locales.SPANISH로 설정된 경우) */}              {getLocaleName(localeItem)}            </span>            <span dir="ltr" lang={Locales.ENGLISH}>              {/* 영어로 된 언어 - 예: French */}              {getLocaleName(localeItem, Locales.ENGLISH)}            </span>          </Link>        ))}      </div>    </div>  );};

    문서 참조:

    (선택 사항) 11단계: 로컬라이즈된 링크 컴포넌트 생성

    애플리케이션의 탐색이 현재 로케일을 준수하도록 보장하려면 사용자 정의 Link 컴포넌트를 생성할 수 있습니다. 이 컴포넌트는 내부 URL에 현재 언어를 자동으로 접두사로 추가합니다. 예를 들어, 프랑스어 사용자가 "About" 페이지로 이동하는 링크를 클릭하면 /about 대신 /fr/about로 리디렉션됩니다.

    이 동작은 여러 이유로 유용합니다:

    • SEO 및 사용자 경험: 로컬라이즈된 URL은 검색 엔진이 언어별 페이지를 올바르게 색인화하고 사용자가 선호하는 언어로 콘텐츠를 제공할 수 있도록 돕습니다.
    • 일관성: 애플리케이션 전반에서 로컬라이즈된 링크를 사용하면 탐색이 현재 로케일 내에서 유지되며, 예상치 못한 언어 전환을 방지합니다.
    • 유지 관리 용이성: 로컬라이제이션 로직을 단일 컴포넌트에 중앙 집중화하면 URL 관리를 간소화하고 애플리케이션이 성장함에 따라 코드베이스를 더 쉽게 유지 관리하고 확장할 수 있습니다.

    아래는 TypeScript에서 로컬라이즈된 Link 컴포넌트를 구현한 예제입니다:

    src/components/Link.tsx
    "use client";import { getLocalizedUrl } from "intlayer";import NextLink, { type LinkProps as NextLinkProps } from "next/link";import { useLocale } from "next-intlayer";import { forwardRef, PropsWithChildren, type ForwardedRef } from "react";/** * 주어진 URL이 외부 링크인지 확인하는 유틸리티 함수. * URL이 http:// 또는 https://로 시작하면 외부 링크로 간주됩니다. */export const checkIsExternalLink = (href?: string): boolean =>  /^https?:///.test(href ?? "");/** * 현재 로케일에 따라 href 속성을 조정하는 사용자 정의 Link 컴포넌트. * 내부 링크의 경우 `getLocalizedUrl`을 사용하여 URL에 로케일을 접두사로 추가합니다 (예: /fr/about). * 이를 통해 탐색이 동일한 로케일 컨텍스트 내에서 유지되도록 보장합니다. */export const Link = forwardRef<  HTMLAnchorElement,  PropsWithChildren<NextLinkProps>>(({ href, children, ...props }, ref: ForwardedRef<HTMLAnchorElement>) => {  const { locale } = useLocale();  const isExternalLink = checkIsExternalLink(href.toString());  // 링크가 내부 링크이고 유효한 href가 제공된 경우, 로컬라이즈된 URL을 가져옵니다.  const hrefI18n: NextLinkProps["href"] =    href && !isExternalLink ? getLocalizedUrl(href.toString(), locale) : href;  return (    <NextLink href={hrefI18n} ref={ref} {...props}>      {children}    </NextLink>  );});Link.displayName = "Link";

    작동 방식

    • 외부 링크 감지:
      헬퍼 함수 checkIsExternalLink는 URL이 외부 링크인지 확인합니다. 외부 링크는 로컬라이제이션이 필요하지 않으므로 변경되지 않습니다.

    • 현재 로케일 검색:
      useLocale 훅은 현재 로케일(예: fr 프랑스어)을 제공합니다.

    • URL 로컬라이제이션:
      내부 링크(즉, 외부가 아닌 링크)의 경우, getLocalizedUrl을 사용하여 URL에 현재 로케일을 자동으로 접두사로 추가합니다. 이를 통해 사용자가 프랑스어를 사용하는 경우 /abouthref로 전달하면 /fr/about으로 변환됩니다.

    • 링크 반환:
      컴포넌트는 로컬라이즈된 URL을 가진 <a> 요소를 반환하여 탐색이 로케일과 일치하도록 보장합니다.

    Link 컴포넌트를 애플리케이션 전반에 통합하면 일관되고 언어를 인식하는 사용자 경험을 유지하면서 SEO 및 사용성을 개선할 수 있습니다.

    TypeScript 구성

    Intlayer는 TypeScript의 모듈 확장을 사용하여 코드베이스를 더 강력하게 만듭니다.

    alt text

    alt text

    자동 생성된 타입을 포함하도록 TypeScript 구성을 설정하세요.

    tsconfig.json
    {  // ... 기존 TypeScript 구성  "include": [    // ... 기존 TypeScript 구성    ".intlayer/**/*.ts", // 자동 생성된 타입 포함  ],}

    Git 구성

    Intlayer에서 생성된 파일을 Git에 커밋하지 않도록 설정하는 것이 좋습니다.

    이를 위해 .gitignore 파일에 다음 지침을 추가할 수 있습니다:

    .gitignore
    # Intlayer에서 생성된 파일 무시.intlayer

    더 나아가기

    더 나아가려면 시각적 편집기를 구현하거나 CMS를 사용하여 콘텐츠를 외부화할 수 있습니다.

    이 문서를 개선할 아이디어가 있으시면 GitHub에 풀 리퀘스트를 제출하여 자유롭게 기여해 주세요.

    문서에 대한 GitHub 링크