React에서 확장 가능한 i18n 시스템 구축하기: 글로벌 앱 개발 핵심 가이드
2025-05-02 22:25:55React에서 확장 가능한 i18n 시스템 구축하기
오늘날의 기술 환경에서 국제화를 통해 전 세계의 다양한 사용자를 대상으로 앱을 만드는 것은 필수입니다. 특히 React 애플리케이션에서는 번역 파일의 자동 처리와 언어 선호도의 지속적인 유지 기능을 갖춘 모듈화되고 확장 가능한 i18n 시스템이 필요합니다. 이번 글에서는 이러한 시스템을 어떻게 구축할 수 있는지 살펴보겠습니다.
국제화 기본 개념과 프로젝트 설정
한국에서 사업을 운영하거나, 한국을 포함한 전 세계에 서비스를 제공하기 위해서는 국제화(i18n)는 필수 요소입니다. 이는 텍스트 콘텐츠의 번역, 날짜 및 숫자의 지역별 형식화, 복수형 처리 등을 포함합니다.
프로젝트 초기 설정
먼저 필요한 패키지를 설치합니다.
pnpm add i18next react-i18next i18next-browser-languagedetector js-cookie @types/js-cookie
이제 더 안전한 유형 시스템을 위해 Language 열거형을 정의합니다:
// src/domain/enums/language.ts
export enum Language {
EN = "en",
PT_BR = "pt-br",
}
자동 구성: 번역의 자동 탐지의 마법
i18n 시스템의 핵심은 번역 파일을 자동으로 발견하고 로드하는 config.ts 파일입니다. 이 파일은 Vite의 glob import 기능을 활용하여 JSON 파일을 동기적으로 로드합니다.
// src/i18n/config.ts
import i18n from "i18next";
import Cookies from "js-cookie";
import { initReactI18next } from "react-i18next";
import { Language } from "@/domain/enums/language";
import LanguageDetector from "i18next-browser-languagedetector";
type Context = Record<string, { default: Record<string, string> }>;
type Resources = Record<string, Record<string, Record<string, string>>>;
function loadTranslationFiles() {
const resources: Resources = {};
Object.values(Language).forEach((lang) => {
resources[lang] = {};
});
const enContext: Context = import.meta.glob<{
default: Record<string, string>;
}>("./locales/en/**/*.json", { eager: true });
const ptBrContext: Context = import.meta.glob<{
default: Record<string, string>;
}>("./locales/pt-br/**/*.json", { eager: true });
const processContext = (context: Context, lang: Language) => {
Object.keys(context).forEach((path) => {
const filename = path.match(/\/([^/]+)\.json$/)?.[1];
const module = context[path];
resources[lang][filename] =
"default" in module ? module.default : module;
});
};
processContext(enContext, Language.EN);
processContext(ptBrContext, Language.PT_BR);
return resources;
}
const resources = loadTranslationFiles();
const cookieOptions = { expires: 365 };
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources,
fallbackLng: Language.EN,
detection: {
order: ["cookie", "navigator"],
lookupCookie: "i18next",
},
interpolation: { escapeValue: false },
ns: Object.keys(resources.en),
});
export const changeLanguage = (lang: Language) => {
i18n.changeLanguage(lang);
Cookies.set("i18next", lang, cookieOptions);
};
export default i18n;
구성의 세부 분석
a) 언어 열거형
언어의 타입 안전성을 확보하기 위해 Language 열거형을 정의합니다. 이는 프로젝트 전체에서 타입 안전성을 제공합니다.
b) 리소스 구조
TypeScript의 타입을 활용하여 번역 리소스를 타이핑합니다. 이를 통해 코드의 유지 보수가 쉬워집니다.
c) 자동 리소스 탐지
loadTranslationFiles 함수는 Vite의 glob import를 사용하여 로컬 디렉토리에서 JSON 번역 파일을 찾고 즉시 로드합니다.
d) 리소스 처리
리소스를 각각의 네임스페이스로 구분하여 Language 및 파일명을 기준으로 조직화합니다.
e) 언어 탐지 및 유지
언어 탐지 과정에서 쿠키와 브라우저 설정을 사용하며, 'i18next'라는 이름의 쿠키를 선호 언어로 저장합니다.
f) 언어 변경 헬퍼
changeLanguage 함수는 언어를 변경하고 쿠키에 해당 설정을 저장하여 애플리케이션 전반에 걸쳐 일관성을 유지합니다.
번역의 조직화 및 사용
번역을 기능이나 맥락에 따라 조직합니다. 예를 들어 체계적인 디렉토리 구조를 통해 새로운 번역 파일을 추가하면 자동으로 감지되고 처리됩니다.
예시 번역 파일:
// src/i18n/locales/en/index.json
{
"greeting": "Hello, World!",
"languageSelector": "Select a language",
"languages": {
"en": "English",
"pt-BR": "Portuguese"
}
}
// src/i18n/locales/pt-BR/index.json
{
"greeting": "Olá, Mundo!",
"languageSelector": "Selecione um idioma",
"languages": {
"en": "Inglês",
"pt-BR": "Português"
}
}
새로운 번역을 추가하는 것은 파일을 해당 디렉토리에 추가하는 것만으로 가능합니다. 이때 코드 변경이 필요 없습니다.
컴포넌트에서 번역 사용
다음과 같은 방식으로 컴포넌트에서 번역을 사용할 수 있습니다.
const App = () => {
const { t } = useTranslation();
return (
<div className="min-h-screen flex flex-col bg-[#101010]">
<Header />
<main className="flex-1 flex items-center justify-center p-6">
<p className="text-white text-2xl">{t("index:greeting")}</p>
</main>
</div>
);
};