튜토리얼//12 최소 읽기 시간

React i18n: React 앱 국제화 완벽 가이드

Eray Gündoğmuş
공유

React i18n: React 앱 국제화 완벽 가이드

국제화(i18n)는 겉으로 보면 단순해 보이지만 구현을 시작하면 몇 주가 걸리는 토끼굴이 되는 기능 중 하나입니다. 영어로 사용자를 맞이하는 React 앱이 진정한 글로벌 오디언스를 위해서는 단순한 문자열 교체 이상이 필요합니다. 통화 형식 지정, 복수형 규칙, 날짜 현지화, 오른쪽에서 왼쪽 레이아웃 지원, 그리고 개발자가 키를 바꿀 때마다 깨지지 않는 번역 워크플로우가 필요합니다.

이 가이드는 2026년에 프로덕션 준비가 된 다국어 React 앱을 출시하기 위해 알아야 할 모든 것을 안내합니다. 올바른 라이브러리 선택부터 나중에 시간을 낭비하게 만드는 실수를 피하는 방법까지 다룹니다.


TL;DR / 핵심 요약

  • React에는 내장 i18n이 없습니다. 브라우저의 Intl API가 형식 지정을 처리하지만, 번역 문자열 관리, 동적 로딩, 로케일 라우팅을 위해서는 전용 라이브러리가 필요합니다.
  • react-intl(FormatJS)과 react-i18next는 큰 커뮤니티와 수년간의 프로덕션 사용 실적을 가진 가장 널리 채택된 옵션입니다.
  • LinguiJS는 컴파일 타임 추출과 최소 런타임 번들 크기를 원할 때 최선의 선택입니다.
  • Better i18n은 최신 진입자로, AI 기반 번역과 CDN 전달을 추가하지만 커뮤니티는 확립된 대안보다 작습니다.
  • 올바른 라이브러리는 팀 규모, SSR 요구 사항, 그리고 번역 워크플로우 자동화와 에코시스템 성숙도 중 어느 것을 더 중시하느냐에 따라 달라집니다.

React에 i18n 라이브러리가 필요한 이유

React는 UI를 렌더링합니다. 번역을 관리하거나, 브라우저 로케일을 감지하거나, 언어 파일을 비동기적으로 로드하거나, 언어마다 다른 복잡한 형식 규칙을 처리하지 않습니다. 이것은 설계에 의한 것입니다. React는 뷰 라이브러리입니다.

브라우저에는 Intl API가 탑재되어 있으며, 이것은 정말 강력합니다. Intl.DateTimeFormat, Intl.NumberFormat, Intl.PluralRules는 외부 의존성 없이 로케일 인식 형식 지정을 제공합니다. 하지만 Intl은 형식 지정을 해결하지, 문자열 관리는 하지 않습니다.

Intl 외에 여전히 필요한 것:

  • 문자열 카탈로그 — 로케일과 메시지 ID로 키가 지정된 번역 문자열을 저장하는 구조.
  • React 바인딩 — 카탈로그에서 읽고 로케일이 변경될 때 다시 렌더링하는 훅과 컴포넌트.
  • 동적 로딩 — 모든 문자열을 한 번에 로드하는 것이 아니라 현재 로케일의 문자열만 로드.
  • 복수형과 보간"아이템 1개""아이템 3개" 처리 및 사용자 이름과 같은 동적 값을 메시지에 삽입.
  • 번역 워크플로우 — 번역가가 새 문자열을 받고, 번역하고, 그 번역이 앱으로 다시 돌아오는 방법.

이 모든 것을 직접 구현하는 것은 가능하지만 권장하지 않습니다. 아래 라이브러리들은 수백만 건의 프로덕션 배포에서 이러한 문제를 해결해왔습니다. 그 중 하나로 시작하세요.


인기 있는 React i18n 라이브러리 비교

라이브러리번들 크기ICU 지원TypeScript 지원SSR 지원학습 곡선
react-intl (FormatJS)~20KB gzipped완전 지원강함있음중간
react-i18next~6KB gzipped플러그인 경유강함있음낮음~중간
LinguiJS~2KB runtime완전 지원강함있음중간~높음
Better i18n React SDK~2KB부분적완전 (추론)있음낮음

번들 크기는 대략적인 수치이며 트리 쉐이킹과 사용하는 기능에 따라 달라집니다. ICU(International Components for Unicode) 메시지 형식은 복잡한 복수형, 성별 선택, 조건부 텍스트의 표준입니다. 모든 라이브러리가 기본적으로 지원하는 것은 아닙니다.


react-intl (FormatJS)

react-intl은 Formatly(이전 Yahoo)가 관리하는 FormatJS 스위트의 일부입니다. 수년간 React에서 복잡한 i18n의 사실상의 표준이었으며 에코시스템에서 ICU 메시지 형식의 가장 완전한 구현을 제공합니다.

설치

npm install react-intl

IntlProvider로 앱 래핑하기

IntlProvider는 전체 컴포넌트 트리에 대한 로케일 컨텍스트를 설정합니다. 그 아래의 모든 FormattedMessageuseIntl 호출은 이 컨텍스트에서 읽습니다.

// src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { IntlProvider } from "react-intl";
import App from "./App";
import enMessages from "./locales/en.json";
import frMessages from "./locales/fr.json";

const messages: Record<string, Record<string, string>> = {
  en: enMessages,
  fr: frMessages,
};

const userLocale = navigator.language.split("-")[0] ?? "en";
const activeLocale = userLocale in messages ? userLocale : "en";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <IntlProvider locale={activeLocale} messages={messages[activeLocale]}>
    <App />
  </IntlProvider>
);

메시지 카탈로그는 플랫 JSON 파일입니다:

// src/locales/en.json
{
  "greeting": "Hello, {name}!",
  "itemCount": "{count, plural, one {# item} other {# items}}",
  "welcomeBack": "Welcome back, <bold>{name}</bold>!"
}

FormattedMessage 컴포넌트

번역에 리치 텍스트 형식이나 인라인 JSX가 필요할 때 FormattedMessage를 사용합니다:

// src/components/Welcome.tsx
import React from "react";
import { FormattedMessage } from "react-intl";

interface WelcomeProps {
  name: string;
  itemCount: number;
}

export function Welcome({ name, itemCount }: WelcomeProps) {
  return (
    <div>
      <h1>
        <FormattedMessage
          id="greeting"
          values={{ name }}
        />
      </h1>
      <p>
        <FormattedMessage
          id="itemCount"
          values={{ count: itemCount }}
        />
      </p>
      <p>
        <FormattedMessage
          id="welcomeBack"
          values={{
            name,
            bold: (chunks) => <strong>{chunks}</strong>,
          }}
        />
      </p>
    </div>
  );
}

useIntl 훅

JSX 외부, 이벤트 핸들러 내부, 또는 aria-label 속성을 위해 문자열을 형식화하는 명령형 사용에는 useIntl 훅을 사용합니다:

// src/components/SearchBar.tsx
import React from "react";
import { useIntl } from "react-intl";

export function SearchBar() {
  const intl = useIntl();

  const placeholder = intl.formatMessage({
    id: "search.placeholder",
    defaultMessage: "Search products...",
  });

  const formattedPrice = intl.formatNumber(29.99, {
    style: "currency",
    currency: "USD",
  });

  const formattedDate = intl.formatDate(new Date(), {
    year: "numeric",
    month: "long",
    day: "numeric",
  });

  return (
    <div>
      <input type="search" placeholder={placeholder} aria-label={placeholder} />
      <span>{formattedPrice}</span>
      <time>{formattedDate}</time>
    </div>
  );
}

react-intl 문서는 formatjs.io/docs/react-intl에 있습니다.


react-i18next

react-i18next는 가장 널리 사용되는 JavaScript i18n 프레임워크인 i18next의 React 바인딩입니다. 강점은 유연성입니다. React, React Native, Node.js, 그리고 React가 아닌 환경에서도 동작합니다. 팀이 다른 곳에서 이미 i18next를 사용하고 있다면, 공유된 멘탈 모델은 실질적인 이점입니다.

설치

npm install i18next react-i18next

설정

// src/i18n/config.ts
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import enCommon from "./locales/en/common.json";
import enDashboard from "./locales/en/dashboard.json";
import frCommon from "./locales/fr/common.json";
import frDashboard from "./locales/fr/dashboard.json";

i18n
  .use(initReactI18next)
  .init({
    resources: {
      en: {
        common: enCommon,
        dashboard: enDashboard,
      },
      fr: {
        common: frCommon,
        dashboard: frDashboard,
      },
    },
    lng: "en",
    fallbackLng: "en",
    defaultNS: "common",
    interpolation: {
      escapeValue: false, // React가 이미 값을 이스케이프합니다
    },
  });

export default i18n;

컴포넌트가 렌더링되기 전에 앱의 진입점에서 설정을 임포트합니다:

// src/main.tsx
import "./i18n/config";
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

ReactDOM.createRoot(document.getElementById("root")!).render(<App />);

useTranslation 훅과 네임스페이스 분리

네임스페이스 분리는 대규모 애플리케이션에서 react-i18next의 가장 유용한 기능 중 하나입니다. 하나의 거대한 JSON 파일을 로드하는 대신, 번역을 기능이나 도메인별로 분리하고 필요에 따라 네임스페이스를 로드합니다.

// src/i18n/locales/en/common.json
{
  "nav.home": "Home",
  "nav.dashboard": "Dashboard",
  "nav.settings": "Settings",
  "button.save": "Save",
  "button.cancel": "Cancel"
}
// src/i18n/locales/en/dashboard.json
{
  "title": "Your Dashboard",
  "stats.users": "{{count}} active user",
  "stats.users_other": "{{count}} active users",
  "welcome": "Welcome back, {{name}}!"
}
// src/components/Dashboard.tsx
import React from "react";
import { useTranslation } from "react-i18next";

interface DashboardProps {
  userName: string;
  activeUsers: number;
}

export function Dashboard({ userName, activeUsers }: DashboardProps) {
  // "dashboard" 네임스페이스를 특정하여 로드
  const { t } = useTranslation("dashboard");

  return (
    <main>
      <h1>{t("title")}</h1>
      <p>{t("welcome", { name: userName })}</p>
      <p>{t("stats.users", { count: activeUsers })}</p>
    </main>
  );
}
// src/components/Nav.tsx
import React from "react";
import { useTranslation } from "react-i18next";

export function Nav() {
  // 기본 "common" 네임스페이스 사용
  const { t, i18n } = useTranslation();

  const switchLanguage = (locale: string) => {
    i18n.changeLanguage(locale);
  };

  return (
    <nav>
      <a href="/">{t("nav.home")}</a>
      <a href="/dashboard">{t("nav.dashboard")}</a>
      <button onClick={() => switchLanguage("fr")}>Francais</button>
      <button onClick={() => switchLanguage("en")}>English</button>
    </nav>
  );
}

react-i18next 문서는 react.i18next.com에 있습니다.


LinguiJS

LinguiJS는 근본적으로 다른 접근 방식을 취합니다. ID로 참조하는 JSON 파일에서 번역 키를 관리하는 대신, JSX에서 직접 자연어로 UI를 작성합니다. 그런 다음 CLI가 그 문자열들을 카탈로그로 추출하고 번역가가 채웁니다. 빌드 시 메시지는 최적화된 바이너리 형식으로 컴파일됩니다.

결과는 더 작은 런타임 번들과 깨진 키 참조의 위험 감소입니다. 하지만 다른 라이브러리들이 요구하지 않는 빌드 단계가 필요합니다.

설치

npm install @lingui/react @lingui/core
npm install --save-dev @lingui/cli @lingui/macro @lingui/vite-plugin

설정

// lingui.config.js
/** @type {import('@lingui/conf').LinguiConfig} */
module.exports = {
  locales: ["en", "fr", "de"],
  sourceLocale: "en",
  catalogs: [
    {
      path: "src/locales/{locale}/messages",
      include: ["src"],
    },
  ],
  format: "po",
};

@lingui/macro 사용

매크로는 컴파일 시 자연어 문자열을 변환하므로 소스 코드는 간단한 영어처럼 읽히면서도 완전히 지역화 가능합니다:

// src/components/ProductCard.tsx
import React from "react";
import { Trans, Plural } from "@lingui/macro";
import { useLingui } from "@lingui/react";

interface ProductCardProps {
  productName: string;
  price: number;
  reviewCount: number;
}

export function ProductCard({ productName, price, reviewCount }: ProductCardProps) {
  const { i18n } = useLingui();

  const formattedPrice = i18n.number(price, {
    style: "currency",
    currency: "USD",
  });

  return (
    <article>
      <h2>
        <Trans>Buy {productName} today</Trans>
      </h2>
      <p>{formattedPrice}</p>
      <p>
        <Plural
          value={reviewCount}
          one="# customer review"
          other="# customer reviews"
        />
      </p>
      <p>
        <Trans>
          Free shipping on orders over <strong>$50</strong>.
        </Trans>
      </p>
    </article>
  );
}

추출 워크플로우

컴포넌트를 작성한 후 CLI를 실행하여 문자열을 추출합니다:

# 소스 파일에서 모든 문자열을 카탈로그로 추출
npx lingui extract

# .po 파일을 번역 (또는 번역 서비스로 보내기)
# 그런 다음 프로덕션용으로 컴파일
npx lingui compile

컴파일된 카탈로그는 작고 빠르게 로드됩니다. LinguiJS는 번역가 친화적인 PO 파일 워크플로우와 가능한 한 작은 런타임 페이로드를 원하는 팀에게 특히 매력적입니다.

전체 문서는 lingui.dev에 있습니다.


Better i18n React SDK

Better i18n은 React i18n 공간의 새로운 진입자입니다. 순수하게 번역 렌더링 라이브러리로 경쟁하는 대신, 완전한 워크플로우 플랫폼을 번들로 제공합니다. 브랜드 컨텍스트 인식을 갖춘 AI 기반 번역, AST 스캐닝을 통한 자동 키 발견, 그리고 리빌드 없이 번역 업데이트가 라이브로 적용되는 CDN 전달입니다.

선택 전에 트레이드오프에 대해 솔직해지세요: 커뮤니티는 react-intl이나 i18next의 몇 분의 일 크기입니다. Stack Overflow 답변이 적습니다. 엣지 케이스에 부딪히면 커뮤니티 솔루션보다 공식 문서나 지원 채널에 의존할 가능성이 높습니다. 라이브러리는 잘 관리되고 활발히 개발되고 있지만 확립된 옵션들이 가진 오랜 검증 실적은 아직 없습니다.

설치

npm install @better-i18n/react

설정

// src/providers/I18nProvider.tsx
import React, { type ReactNode } from "react";
import { BetterI18nProvider } from "@better-i18n/react";

interface I18nProviderProps {
  children: ReactNode;
  locale: string;
}

export function I18nProvider({ children, locale }: I18nProviderProps) {
  return (
    <BetterI18nProvider
      projectId={process.env.VITE_BETTER_I18N_PROJECT_ID!}
      locale={locale}
    >
      {children}
    </BetterI18nProvider>
  );
}
// src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { I18nProvider } from "./providers/I18nProvider";

const locale = navigator.language.split("-")[0] ?? "en";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <I18nProvider locale={locale}>
    <App />
  </I18nProvider>
);

useTranslations 훅

// src/components/HeroSection.tsx
import React from "react";
import { useTranslations } from "@better-i18n/react";

interface HeroSectionProps {
  userName: string;
  planName: string;
}

export function HeroSection({ userName, planName }: HeroSectionProps) {
  const t = useTranslations("hero");

  return (
    <section>
      <h1>{t("title", { name: userName })}</h1>
      <p>{t("subtitle", { plan: planName })}</p>
      <a href="/signup">{t("cta")}</a>
    </section>
  );
}

번역 키는 Better i18n 대시보드에서 정의되거나 CLI 스캐너에 의해 소스 코드에서 자동으로 발견됩니다. 번역가가 변경 사항을 승인하면 Cloudflare의 CDN 엣지 네트워크를 통해 앱에 전달됩니다. 리빌드나 재배포가 필요 없습니다.

Better i18n 문서는 better-i18n.com/docs에 있습니다.

대안보다 Better i18n을 선택할 때: 팀이 처음부터 AI 지원 번역을 원하고, 완전히 독립된 라이브러리보다 관리형 플랫폼에 편안하며, 워크플로우 자동화(자동 키 발견, CDN 전달, GitHub PR 기반 동기화)가 에코시스템 폭보다 더 가치 있을 때입니다.

다른 것을 선택할 때: 가능한 한 큰 커뮤니티, 수년간의 Stack Overflow 답변, 또는 외부 서비스 의존성 없는 완전한 자체 호스팅 솔루션이 필요할 때입니다. 그 경우 react-i18next나 react-intl이 더 안전한 선택입니다.


핵심 구현 패턴

언어 전환

언어 전환은 상태를 가져야 하고 URL에 반영되어야 합니다. 그래야 사용자가 현지화된 링크를 공유할 수 있습니다. 가장 단순한 접근 방식은 로케일을 상태에 저장하고 프로바이더에 전달하는 것이지만, URL 기반 접근 방식이 SSR과 딥 링킹에 더 견고합니다.

// src/hooks/useLocale.ts
import { useState, useCallback } from "react";

const SUPPORTED_LOCALES = ["en", "fr", "de", "es"] as const;
type SupportedLocale = (typeof SUPPORTED_LOCALES)[number];

function getInitialLocale(): SupportedLocale {
  const fromUrl = window.location.pathname.split("/")[1] as SupportedLocale;
  if (SUPPORTED_LOCALES.includes(fromUrl)) return fromUrl;

  const fromBrowser = navigator.language.split("-")[0] as SupportedLocale;
  if (SUPPORTED_LOCALES.includes(fromBrowser)) return fromBrowser;

  return "en";
}

export function useLocale() {
  const [locale, setLocale] = useState<SupportedLocale>(getInitialLocale);

  const switchLocale = useCallback((next: SupportedLocale) => {
    setLocale(next);
    document.documentElement.lang = next;
    // 선택 사항: URL 기반 라우팅을 위해 라우터 히스토리에 푸시
  }, []);

  return { locale, switchLocale, supportedLocales: SUPPORTED_LOCALES };
}

번역 지연 로딩

모든 로케일 번들을 미리 로드하면 대역폭이 낭비됩니다. 활성 로케일만 로드하고 다른 것은 필요에 따라 가져옵니다:

// src/i18n/loader.ts
const localeCache = new Map<string, Record<string, string>>();

export async function loadLocale(locale: string): Promise<Record<string, string>> {
  if (localeCache.has(locale)) {
    return localeCache.get(locale)!;
  }

  // 동적 임포트 — 번들러가 로케일별로 별도 청크로 분리
  const messages = await import(`./locales/${locale}.json`);
  const resolved = messages.default as Record<string, string>;

  // 인플레이스 변경 대신 새 맵 항목 반환
  const updated = new Map(localeCache);
  updated.set(locale, resolved);
  localeCache.clear();
  updated.forEach((v, k) => localeCache.set(k, v));

  return resolved;
}

SSR / SSG 고려 사항

서버에서 렌더링할 때, 로케일 감지는 navigator.language에서 HTTP 헤더(Accept-Language) 또는 URL 세그먼트로 이동합니다. 서버와 클라이언트가 동일한 HTML을 생성하도록 렌더링이 시작되기 전에 로케일을 해결해야 합니다.

// src/app/[locale]/layout.tsx (Next.js App Router 예제)
import { notFound } from "next/navigation";
import { type ReactNode } from "react";

const SUPPORTED_LOCALES = ["en", "fr", "de"];

interface LocaleLayoutProps {
  children: ReactNode;
  params: { locale: string };
}

export default function LocaleLayout({ children, params }: LocaleLayoutProps) {
  if (!SUPPORTED_LOCALES.includes(params.locale)) {
    notFound();
  }

  return (
    <html lang={params.locale} dir={params.locale === "ar" ? "rtl" : "ltr"}>
      <body>{children}</body>
    </html>
  );
}

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

URL 기반 로케일 라우팅

검색 엔진은 example.com/ko/productsexample.com/en/products를 별도의 색인 가능한 페이지로 처리합니다. URL 기반 라우팅은 SEO를 위해 쿠키 기반이나 헤더 기반 감지보다 강하게 선호됩니다. 루트에 [locale] 세그먼트로 라우트를 구성하고 지원되는 각 로케일의 <head>hreflang 링크 태그를 추가하세요.

복수형

여기서 다루는 모든 라이브러리는 복수형을 다르게 처리하지만, 기본 규칙은 CLDR(Common Locale Data Repository)에서 옵니다. 영어에는 두 가지 복수형이 있습니다. 아랍어에는 여섯 가지가 있습니다. 복수형 로직을 직접 하드코딩하지 마세요.

react-i18next 사용 시 (_one / _other 접미사 사용):

{
  "notification": "알림 {{count}}개",
  "notification_other": "알림 {{count}}개"
}
t("notification", { count: 1 });  // "알림 1개"
t("notification", { count: 5 });  // "알림 5개"

날짜 및 숫자 형식

라이브러리의 내장 형식 유틸리티를 사용하거나 직접 Intl로 폴백하세요. 형식화된 값을 문자열로 연결하지 마세요. 단위 순서는 로케일마다 다릅니다.

// Intl 직접 사용 (어떤 라이브러리에서도 작동)
const formatter = new Intl.NumberFormat(locale, {
  style: "currency",
  currency: "EUR",
});

// en-US에서는 "€1,234.56", fr-FR에서는 "1 234,56 €" — 같은 숫자, 다른 형식
formatter.format(1234.56);

흔한 실수

문자열 연결. +나 템플릿 리터럴로 번역된 조각을 합쳐서 문장을 만들지 마세요. 단어 순서는 언어마다 다릅니다. "안녕하세요 " + t("세계")는 명사 뒤에 인사가 오는 언어에서 깨집니다. 항상 보간 변수를 번역 함수에 전달하세요.

컨텍스트 잊기. 영어 단어 "File"은 명사(문서)나 동사(신청하다) 모두가 될 수 있습니다. 번역가는 올바른 단어를 선택하기 위해 컨텍스트가 필요합니다. 대부분의 라이브러리는 context 매개변수나 설명 필드를 지원합니다. 사용하세요.

로딩 상태 처리 안 하기. 번역이 비동기적으로 로드될 때, 문자열이 사용 가능해지기 전에 컴포넌트가 렌더링되는 창이 있습니다. 스켈레톤, 스피너, 또는 아무것도 렌더링하세요. 하지만 "dashboard.title"과 같은 원시 번역 키를 사용자에게 렌더링하지 마세요.

모든 로케일을 한꺼번에 번들링. 영어만 읽는 사용자에게 프랑스어, 독일어, 스페인어, 일본어 번역을 보내는 것은 대역폭을 낭비하고 Time to Interactive를 증가시킵니다. 필요한 것만 로드하기 위해 동적 임포트나 CDN 기반 전달 전략을 사용하세요.

로케일 특정 형식 하드코딩. MM/DD/YYYY 같은 날짜 형식을 하드코딩하지 마세요. 독일 사용자는 DD.MM.YYYY를 기대합니다. 항상 Intl이나 라이브러리의 형식 유틸리티에 형식 지정을 위임하세요.

번역된 HTML을 원시 마크업으로 렌더링. 번역 문자열을 원시 HTML로 주입하는 것은 번역 소스가 손상될 경우 XSS 위험입니다. 라이브러리의 안전한 리치 텍스트 컴포넌트를 사용하세요. react-intl의 FormattedMessage와 요소 값, react-i18next의 Trans 컴포넌트를 사용하세요. HTML 문자열을 직접 렌더링하지 마세요.


자주 묻는 질문

Q: ICU 메시지 형식 지원이 필요합니까? ICU는 복수형, 성별 선택, 조건부 텍스트를 포함하는 복잡한 메시지의 표준입니다. 앱에 "John과 3명이 이것을 좋아합니다"와 같은 문장이 있다면 ICU가 그 규칙을 표현하는 것을 깔끔하고 이식 가능하게 만듭니다. 앱이 단순한 문자열 교체만 필요하다면 ICU는 선택적 오버헤드입니다. react-intl과 LinguiJS는 ICU를 기본으로 지원합니다. react-i18next는 i18next-icu 플러그인이 필요합니다.

Q: Next.js App Router와 가장 잘 작동하는 라이브러리는 무엇입니까? react-i18next와 react-intl 모두 일부 설정으로 App Router를 지원합니다. Next.js 에코시스템은 Server Components의 일급 지원으로 인해 App Router 프로젝트에서 next-intl로 수렴했습니다. Better i18n도 전용 Next.js SDK를 제공합니다. LinguiJS는 작동하지만 빌드 파이프라인에서 컴파일 단계의 신중한 처리가 필요합니다.

Q: 번역 파일을 어떻게 구조화해야 합니까? 소규모 앱의 경우 로케일당 단일 JSON 파일(en.json, fr.json)이 괜찮습니다. 대규모 앱의 경우 기능이나 도메인별로 분리하세요(i18next의 네임스페이스, LinguiJS의 별도 카탈로그). 깊게 중첩된 객체는 피하세요. 플랫하거나 한 레벨 깊이의 키가 대규모에서 관리하기 쉽고 병합 충돌이 적습니다.

Q: 굵은 텍스트나 링크 같은 마크업을 포함하는 번역은 어떻게 처리합니까? 대부분의 라이브러리는 render prop이나 슬롯 시스템을 통해 리치 텍스트를 지원합니다. react-intl에서는 JSX 요소를 FormattedMessage의 값으로 전달합니다. react-i18next에서는 Trans 컴포넌트를 사용합니다. 이러한 접근 방식은 HTML을 React에 유지하여 적절히 이스케이프하고 삭제할 수 있습니다.

Q: i18n에 대해 언제부터 생각하기 시작해야 합니까? 생각보다 일찍입니다. 기존 코드베이스에 i18n을 추가하는 것은 모든 하드코딩된 문자열, 모든 날짜 형식, 모든 복수형 가정, 그리고 독일어의 긴 단어로 깨지는 모든 레이아웃을 찾는 것을 의미합니다. 앱이 영어 이외의 화자에게 도달할 가능성이 조금이라도 있다면, 첫날에 영어만 제공하더라도 초기 설정에 i18n 인프라를 추가하세요.


결론

React i18n에는 유일한 정답이 없습니다. 올바른 라이브러리는 팀의 워크플로우, 앱의 복잡성, 에코시스템 리스크에 대한 허용도에 맞는 것입니다.

새 프로젝트를 시작하고 최대한의 커뮤니티 지원과 깊은 에코시스템을 원한다면, react-i18next가 안전한 기본값입니다. 네임스페이스 시스템은 잘 확장되고, 문서는 철저하며, Stack Overflow에서 대부분의 엣지 케이스에 대한 답을 찾을 수 있습니다.

앱에 정말 복잡한 메시지 형식 요구 사항이 있다면(조건부 성별, 다단계 복수형, 리치 인라인 마크업), react-intl이 React 에코시스템에서 ICU 표준의 가장 완전한 구현입니다.

가장 작은 런타임 번들과 컴파일러로 강제되는 추출 워크플로우를 원한다면, LinguiJS는 빌드 단계 추가에 편안한 팀에게 특히 설득력 있는 대안을 제공합니다.

AI 지원 번역, 리빌드 사이클 없는 CDN 전달, 번역 파이프라인을 자동화하는 관리형 플랫폼을 원한다면, Better i18n은 평가할 가치가 있습니다. 단, 커뮤니티가 더 작은 젊은 에코시스템에 베팅하고 있다는 솔직한 주의 사항이 있습니다.

무엇을 선택하든, 일찍 결정하고, 번역 파일을 정리하고, i18n을 뒷생각이 아닌 일급 기능으로 취급하세요. 해외 사용자들은 차이를 느낄 것입니다.


참고 자료

Comments

Loading comments...