Next.js(Pages Router) v16에서 파일로부터 번역을 로드하는 방법

번역 가능한 콘텐츠를 코드에서 분리

문제

사용자 대면 문자열을 컴포넌트에 직접 하드코딩하면 콘텐츠와 코드 간에 강한 결합이 생성됩니다. 문구가 변경될 때마다 개발자는 구현 파일을 찾아 수정해야 합니다. 두 번째 언어 지원을 추가하려면 모든 하드코딩된 문자열을 찾아 조건부 로직으로 래핑해야 합니다. 세 번째 언어는 해당 로직을 더욱 확장합니다. 이러한 접근 방식은 번역 워크플로를 코드 배포에 종속시키고 비기술 팀원이 독립적으로 콘텐츠를 업데이트하는 것을 방해합니다.

애플리케이션이 성장함에 따라 번역 관리가 점점 더 어려워집니다. 수십 개의 컴포넌트에 흩어진 문자열은 감사, 복제 또는 일관되게 업데이트하기 어렵습니다. 번역가는 코드베이스 자체에 대한 액세스가 필요하기 때문에 개발자와 병렬로 작업할 수 없습니다.

솔루션

모든 사용자 대면 텍스트를 언어별로 구성된 외부 리소스 파일에 저장하고, 로케일당 하나의 JSON 파일을 사용합니다. 각 메시지는 리터럴 텍스트가 아닌 고유 키로 식별됩니다. 컴포넌트는 하드코딩된 문자열 대신 이러한 키를 참조합니다.

Next.js에서 받은 로케일을 기반으로 적절한 번역 파일을 로드하고 메시지를 react-intl의 provider에 전달합니다. 그러면 애플리케이션은 컴포넌트 코드를 변경하지 않고 다른 파일을 로드하여 언어를 전환할 수 있습니다. 이는 콘텐츠를 구현에서 분리하여 번역가가 표준 JSON 파일에서 작업하는 동안 개발자는 안정적인 메시지 키를 참조할 수 있도록 합니다.

단계

1. 로케일별로 구성된 번역 파일 생성

번역 메시지를 로케일별로 분리된 JSON 파일에 저장합니다. 각 파일에는 키가 안정적인 식별자이고 값이 해당 언어의 번역된 문자열인 키-값 쌍이 포함됩니다.

{
"welcome": "Welcome back",
"greeting": "Hello, {name}",
"itemCount": "You have {count, plural, one {# item} other {# items}}"
}

지원되는 각 로케일마다 하나의 파일을 생성합니다(예: messages/en.json, messages/es.json, messages/fr.json). 프로젝트 루트의 messages 디렉토리에 파일을 배치합니다. react-intl이 활성 로케일에 맞는 올바른 번역을 찾을 수 있도록 모든 파일에서 동일한 키를 사용합니다.

2. getStaticProps에서 메시지 로드

getStaticProps에서 Next.js로부터 받은 로케일을 기반으로 번역 파일을 읽습니다. 이를 통해 메시지를 서버 측에서 사용할 수 있으며 props로 페이지에 전달됩니다.

import { GetStaticProps } from "next";

export const getStaticProps: GetStaticProps = async (context) => {
  const locale = context.locale || "en";
  const messages = (await import(`../messages/${locale}.json`)).default;

  return {
    props: {
      messages,
    },
  };
};

동적 임포트는 현재 로케일에 해당하는 파일만 로드합니다. Next.js는 URL 또는 사용자 기본 설정을 기반으로 locale 값을 자동으로 제공합니다.

3. _app에서 IntlProvider에 메시지 전달

루트 컴포넌트를 IntlProvider로 래핑하고 사용자의 현재 로케일과 해당 번역 메시지로 구성합니다. 각 페이지가 자체 번역을 제공할 수 있도록 pageProps에서 메시지에 액세스합니다.

import { AppProps } from "next/app";
import { IntlProvider } from "react-intl";
import { useRouter } from "next/router";

export default function App({ Component, pageProps }: AppProps) {
  const { locale, defaultLocale } = useRouter();

  return (
    <IntlProvider
      locale={locale || "en"}
      defaultLocale={defaultLocale || "en"}
      messages={pageProps.messages}
    >
      <Component {...pageProps} />
    </IntlProvider>
  );
}

프로바이더는 트리의 모든 컴포넌트에서 메시지를 사용할 수 있도록 합니다. 각 페이지는 getStaticProps를 통해 자체 메시지 파일을 로드하고, _apppageProps를 통해 해당 메시지를 받습니다.

4. 컴포넌트에서 키로 메시지 참조

react-intl의 FormattedMessage 컴포넌트 또는 useIntl 훅을 사용하여 번역된 텍스트를 표시합니다. 문자열을 하드코딩하는 대신 키로 메시지를 참조합니다.

import { FormattedMessage, useIntl } from "react-intl";

export default function HomePage() {
  const intl = useIntl();
  const userName = "Alice";

  return (
    <div>
      <h1>
        <FormattedMessage id="welcome" />
      </h1>
      <p>
        <FormattedMessage id="greeting" values={{ name: userName }} />
      </p>
      <input placeholder={intl.formatMessage({ id: "searchPlaceholder" })} />
    </div>
  );
}

React-intl은 주어진 id에서 번역된 메시지를 조회하고 포맷합니다. 번역이 누락된 경우 defaultMessage가 제공되면 이를 대체 값으로 사용합니다. values prop에 전달된 변수는 메시지 문자열에 삽입됩니다.