TanStack Start v1에서 파일로부터 번역을 로드하는 방법
번역 가능한 콘텐츠를 코드에서 분리
문제
사용자 대면 문자열을 컴포넌트에 직접 하드코딩하면 콘텐츠와 코드 간에 강한 결합이 생성됩니다. 문자열이 변경되거나 새로운 언어가 추가될 때마다 개발자는 소스 파일을 찾아 수정한 다음 애플리케이션을 재배포해야 합니다. 이러한 접근 방식은 번역 워크플로를 엔지니어링 주기에 종속시키고 비기술 팀원이 문구를 업데이트하는 것을 방해합니다. 지원되는 언어 수가 증가함에 따라 올바른 문자열을 선택하기 위한 조건부 로직은 다루기 어렵고 오류가 발생하기 쉬워집니다. 그 결과 반복 속도가 느려지고 유지보수 비용이 증가하며 다른 곳에 있어야 할 번역 가능한 콘텐츠로 코드베이스가 복잡해집니다.
해결책
애플리케이션 코드에서 번역 가능한 모든 문자열을 분리하여 로케일별로 하나씩 외부 JSON 파일에 저장하세요. 각 메시지에 대해 안정적인 키를 정의하고 컴포넌트에서 리터럴 텍스트 대신 해당 키를 참조하세요. 런타임에 사용자의 로케일에 따라 적절한 번역 파일을 로드하고 해당 메시지를 react-intl의 IntlProvider에 제공하세요. 이렇게 하면 콘텐츠와 코드가 분리됩니다. 번역가는 JSON 파일로 직접 작업할 수 있고, 문구 변경 시 코드 수정이 필요 없으며, 새로운 언어를 추가할 때 컴포넌트를 건드리지 않고 새 파일만 추가하면 됩니다.
단계
1. 각 로케일에 대한 번역 파일 생성
번역을 전용 디렉토리의 JSON 파일로 구성하며, 모든 메시지에 대한 키-값 쌍을 포함하는 로케일당 하나의 파일을 만듭니다.
{
"welcome": "Welcome back",
"greeting": "Hello, {name}",
"itemCount": "{count, plural, =0 {No items} one {One item} other {# items}}"
}
이것을 app/translations/en.json로 저장하세요. app/translations/es.json 및 app/translations/fr.json와 같이 다른 로케일에 대한 병렬 파일을 생성하되, 동일한 키에 번역된 값을 사용하세요.
2. 서버 함수를 사용하여 번역 로드
요청된 로케일을 기반으로 디스크에서 번역 파일을 읽는 서버 함수를 사용하여 SSR 중에는 서버 측에서 번역이 로드되고 탐색 중에는 클라이언트에서 가져오도록 합니다.
import { createServerFn } from "@tanstack/react-start";
import * as fs from "node:fs";
export const getMessages = createServerFn({ method: "GET" }).handler(
async ({ request }) => {
const url = new URL(request.url);
const locale = url.searchParams.get("locale") || "en";
const filePath = `app/translations/${locale}.json`;
const content = await fs.promises.readFile(filePath, "utf-8");
return JSON.parse(content);
},
);
이 함수는 주어진 로케일에 대한 JSON 파일을 읽고 파싱된 메시지 객체를 반환합니다. 서버에서만 실행되어 파일 시스템 액세스를 안전하게 유지합니다.
3. 사용자의 로케일을 결정하는 헬퍼 생성
요청에서 로케일을 추출하거나 기본값으로 폴백하는 작은 유틸리티를 정의하여 라우트 전반에서 재사용할 수 있도록 합니다.
export function getLocaleFromRequest(request: Request): string {
const url = new URL(request.url);
const localeParam = url.searchParams.get("locale");
if (localeParam) return localeParam;
const acceptLanguage = request.headers.get("accept-language");
if (acceptLanguage) {
const match = acceptLanguage.split(",")[0].split("-")[0];
return match || "en";
}
return "en";
}
이 함수는 먼저 쿼리 매개변수를 확인한 다음 Accept-Language 헤더를 확인하고, 기본값으로 영어를 사용합니다. 로케일 감지를 위한 단일 진실 공급원을 제공합니다.
4. 라우트 로더에서 메시지 로드
라우트 로더를 사용하여 렌더링 전에 현재 로케일에 대한 메시지를 가져와 컴포넌트 트리에서 사용할 수 있도록 합니다.
import { createFileRoute } from "@tanstack/react-router";
import { getMessages, getLocaleFromRequest } from "../lib/i18n";
export const Route = createFileRoute("/")({
loader: async ({ context }) => {
const locale = getLocaleFromRequest(context.request);
const messages = await getMessages({ data: { locale } });
return { locale, messages };
},
component: HomePage,
});
function HomePage() {
const { locale, messages } = Route.useLoaderData();
return (
<div>
<p>{messages.welcome}</p>
</div>
);
}
로더는 서버 함수를 호출하여 메시지를 가져오고, 컴포넌트는 useLoaderData를 통해 메시지에 액세스합니다. 이 패턴은 SSR과 클라이언트 측 네비게이션 모두에서 작동합니다.
5. react-intl에 메시지 제공
컴포넌트 트리를 IntlProvider로 래핑하고, 로드된 로케일과 메시지를 전달하여 모든 하위 컴포넌트가 번역에 액세스할 수 있도록 하세요.
import { IntlProvider } from "react-intl";
function HomePage() {
const { locale, messages } = Route.useLoaderData();
return (
<IntlProvider locale={locale} messages={messages}>
<AppContent />
</IntlProvider>
);
}
function AppContent() {
return (
<div>
<FormattedMessage id="welcome" />
</div>
);
}
IntlProvider는 모든 react-intl 컴포넌트와 훅에서 로케일과 메시지를 사용할 수 있게 합니다. 이제 컴포넌트는 FormattedMessage 또는 useIntl를 사용하여 키로 메시지를 참조할 수 있으며, 로드된 로케일에 따라 올바른 번역이 렌더링됩니다.
6. 컴포넌트에서 키로 메시지 참조
react-intl의 FormattedMessage 컴포넌트 또는 useIntl 훅을 사용하여 JSON 파일에 정의된 키를 참조하여 번역된 문자열을 표시하세요.
import { FormattedMessage, useIntl } from "react-intl";
function UserGreeting({ name }: { name: string }) {
const intl = useIntl();
const title = intl.formatMessage({ id: "greeting" }, { name });
return (
<div>
<h1 title={title}>
<FormattedMessage id="greeting" values={{ name }} />
</h1>
<p>
<FormattedMessage id="itemCount" values={{ count: 5 }} />
</p>
</div>
);
}
FormattedMessage는 번역된 문자열을 인라인으로 렌더링하고, useIntl().formatMessage는 속성이나 JavaScript 로직에서 사용할 문자열을 반환합니다. 두 함수 모두 보간을 위해 values를 허용하며 복수형 및 형식 지정을 위한 ICU 메시지 구문을 지원합니다.