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

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

문제

사용자에게 표시되는 문자열을 컴포넌트에 직접 하드코딩하면 콘텐츠와 코드 간에 강한 결합이 생깁니다. 텍스트가 변경될 때마다 개발자는 구현 파일을 찾아 수정해야 합니다. 두 번째 언어를 지원하려면 모든 하드코딩된 문자열을 찾아 조건부 로직으로 감싸야 합니다. 세 번째 언어는 그 로직을 더욱 확장시킵니다. 이러한 접근 방식은 번역 워크플로우가 코드 배포에 의존하게 만들고 비기술적 팀원이 독립적으로 콘텐츠를 업데이트하는 것을 방해합니다.

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

해결책

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

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

단계

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

로케일별로 구분된 JSON 파일에 번역 메시지를 넣습니다. 각 파일은 키-값 쌍을 포함하며, 키는 안정적인 식별자이고 값은 해당 언어의 번역된 문자열입니다.

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

프로젝트 루트의 messages 디렉토리에 지원되는 로케일당 하나의 파일(예: messages/en.json, messages/es.json, messages/fr.json)을 생성합니다. 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으로 전달된 변수는 메시지 문자열에 삽입됩니다.