어떤 방식으로 할지 논의
프로젝트에서 다국어(i18n) 설정이 필요한 상황이 생겼고, 백엔드와 협업하여 어떻게 처리할지를 논의했습니다.
두 가지 선택지
- 서버에서 현재 언어에 맞춰 텍스트 자체를 내려주는 방식
- 서버는 코드값만 주고, 프론트에서 언어 설정에 따라 번역 처리하는 방식
팀에서는 두 번째 방식을 채택했습니다. 이유는 아래에서 자세히 설명합니다.
서버는 "APPLE" 같은 코드값만 응답하고, 프론트는 클라이언트에 설정된 언어에 따라 해당 코드를 "사과" 혹은 "apple"로 매핑하는 구조입니다.
🤔 왜 첫 번째 방식이 어려운가?
1. 서버의 로직 복잡도 증가
- 서버 API가 모든 응답을 언어에 따라 분기 처리해야 함
- 각 필드별로 언어를 고려한 응답 템플릿 구성 필요
- 테스트 및 유지보수가 훨씬 어려워짐
2. 언어 리소스 관리의 비효율
- 백엔드에서 모든 다국어 리소스를 직접 보유/관리해야 함
- 단순 UI 문구 변경도 서버 배포가 필요함 (프론트 민첩성 저하)
3. UX 및 캐싱 문제
- 언어 변경 시 새 API 호출이 필요해짐 → 화면 깜빡임, UX 지연 발생
- 프론트에서 번역을 처리하면 즉시 전환 가능 (클라이언트 주도 i18n의 장점)
우리가 채택한 방식: 코드 기반 다국어 매핑
서버는 **영어 코드값(또는 숫자 ID 등)**만 내려주고, 프론트는 해당 코드를 클라이언트 언어 상태에 따라 텍스트로 매핑합니다.
예시:
js
export const userRoleType = { ADMIN: { code: "ADMIN", label_kr: "관리자", label_en: "Administrator", }, USER: { code: "USER", label_kr: "일반 사용자", label_en: "User", }, GUEST: { code: "GUEST", label_kr: "게스트", label_en: "Guest", }, MODERATOR: { code: "MODERATOR", label_kr: "중재자", label_en: "Moderator", },};프론트에서는 useLanguagesStore의 상태값을 통해 현재 언어를 가져오고, 아래와 같은 유틸 훅을 통해 매핑합니다:
js
const useCodeMap = () => { const language = useLanguagesStore((state) => state.language); const codeMapFn = useMemo(() => { const codeToLabel = (code, e) => { try { return ( Object.values(e).find((v) => v.code === code)?.[ `label_${language.toLowerCase()}` ] ?? "" ); } catch { return ""; } }; const labelToCode = (label, e) => { try { return ( Object.values(e).find( (v) => v[`label_${language.toLowerCase()}`] === label )?.code ?? "" ); } catch { return ""; } }; const getList = (e) => Object.values(e).map((v) => v[`label_${language.toLowerCase()}`]); const getCountryCodeList = (e) => Object.values(e).map( (v) => `${v.code} (${v[`label_${language.toLowerCase()}`]})` ); return { getCode: labelToCode, getLabel: codeToLabel, getList, getCountryCodeList, }; }, [language]); return codeMapFn;};이 구조 덕분에 서버에서는 언어 관련 로직이 완전히 제거되었고, 프론트는 빠르게 텍스트를 전환할 수 있게 되었습니다.
💡 장점 요약
| 항목 | 설명 |
|---|---|
| 프론트 중심 i18n | 클라이언트에서만 언어 전환 처리 → 서버 로직 단순화 |
| 빠른 텍스트 변경 | 배포 없이 언어 파일만 수정 가능 |
| 유연한 확장성 | 언어 추가 시 서버 수정 없이 대응 가능 |
| 즉각 전환 가능 | React 상태 변경만으로 실시간 언어 변경 지원 |
개선할 수 있는 점
현재는 언어별 텍스트를 객체에 하드코딩하고 있는데, 아래처럼 개선할 수 있습니다.
개선 예시: i18next + JSON 리소스 방식
1. locales/kr/translation.json
json
{ "userRole": { "ADMIN": "관리자", "USER": "일반 사용자", "GUEST": "게스트", "MODERATOR": "중재자" }}2. locales/en/translation.json
json
{ "userRole": { "ADMIN": "Administrator", "USER": "User", "GUEST": "Guest", "MODERATOR": "Moderator" }}3. i18next 설정 (i18n.js)
js
import i18n from "i18next";import { initReactI18next } from "react-i18next";import en from "./locales/en/translation.json";import kr from "./locales/kr/translation.json"; i18n.use(initReactI18next).init({ resources: { en: { translation: en }, kr: { translation: kr }, }, lng: "kr", fallbackLng: "en", interpolation: { escapeValue: false },}); export default i18n;4. 실제 사용 코드
jsx
import { useTranslation } from "react-i18next"; const { t } = useTranslation();const roleCode = "ADMIN"; return <div>{t(`userRole.${roleCode}`)}</div>; // => 관리자 / Administrator