- 발행일
번역 키를 한국어 원문으로 썼다 — 5,091줄 리소스 파일의 대가
번역 키를 한국어 원문으로 썼다
이 글은 2026년 9월에 당시 코드를 다시 보며 정리한 것입니다. 저장소는 퇴사 전에 스냅샷으로 보관한 것이라 커밋 이력이 없고, 경력기술서와 당시 쓴 이력서에 적은 담당 범위와 코드를 대조해 썼습니다. 날짜는 코드에 남은 시기 단서 기준입니다.
번역 파일을 열면 첫 화면은 평범합니다.
export const localeResources = {
ko: {
translation: {
header: {
where_search: "어디로 떠날까요?",
when_search: "언제 떠날까요?",
who_search: "누구와 떠날까요?",
},
header.where_search 같은 영문 중첩 키. i18next 문서에 나오는 모양이에요. 그런데 조금 내려가면 다른 모양이 섞여 있습니다.
findstay: {
filters: {
"모든 스테이": "모든 스테이",
프로모션: "프로모션",
프리오더: "프리오더",
키가 한국어 원문이고 값도 같은 문장입니다. 컴포넌트 쪽에서는 t("명"), t("확인"), t("예약하기")처럼 부릅니다. 세어 보니 컴포넌트와 페이지에서 한국어를 키로 넘기는 t() 호출이 418곳, 영문 중첩 키로 넘기는 호출이 113곳이었어요. 원문 키가 주류였습니다.
한국어 키가 주는 것
i18next는 키에 해당하는 번역이 없으면 키 문자열을 그대로 돌려줍니다. 키가 한국어 원문이면 한국어 리소스에 그 키를 안 넣어도 화면에 한국어가 나와요. 그래서 ko 블록은 918개 항목뿐이고, 그중 327개는 키와 값이 똑같은 항목입니다. 넣을 필요가 없는데 넣어 둔 것들이에요.
일본어 지원은 경력기술서에 제 담당으로 적혀 있고, 이 리소스 파일은 그 작업의 중심이었습니다. 다만 프런트 4명 팀이라 한국어 키 관행이 제 손에서 시작됐는지, 물려받은 것인지는 이력이 없어 가를 수 없어요. 이미 한국어로 다 만들어진 화면에 언어를 얹는 입장에서는 이게 편했을 겁니다. 문장을 키로 감싸기만 하면 한국어 화면은 그대로 돌고, 번역은 나중에 채우면 되니까요.
그 대가
대가는 세 곳에서 나타났습니다.
| 언어 | 항목 수 | 한국어 키 항목 |
|---|---|---|
| ko | 918 | 372 |
| en | 2,462 | 1,864 |
| ja | 950 | 402 |
영어 리소스가 한국어의 2.7배입니다. 한국어 키는 한국어 리소스에는 안 넣어도 되지만 영어 리소스에는 전부 넣어야 하거든요. fallbackLng가 default: ["en"]이라 영어가 모든 언어의 마지막 그물이기도 했고요. 영어에만 있는 키가 1,562개입니다.
둘째, 일본어에 빠진 키가 영어 기준으로 1,539개입니다. 빠진 키는 영어로 떨어집니다. 일본어 화면 어딘가에는 영어 문장이 섞여 나갔을 거예요. 그중 얼마가 실제 화면에 닿는 키였는지는 스냅샷만으로는 못 셉니다.
셋째, 원문이 키라서 원문을 고치면 세 언어가 같이 끊깁니다. 한국어 문구 하나를 바꾸면 t()의 인자가 바뀌고, 영어·일본어 리소스의 키도 같이 바꿔야 해요. 안 바꾸면 그 자리는 조용히 한국어로 돌아갑니다. 오류가 안 나니까 QA에서 잡힐 때까지 모릅니다.
작은 것 하나 더. 명이라는 키의 영어 값이 빈 문자열이었습니다. 한국어는 "2명", 일본어는 "2人"인데 영어는 "2"로 끝나요. 빈 값을 넣으면 i18next는 빈 문자열을 그대로 돌려주지 키로 돌아가지 않습니다.
언어를 정하는 곳이 둘
리소스 파일만이 아니라 언어를 정하는 자리도 둘이었습니다.
const options = {
order: ["navigator"],
};
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
detection: options,
resources: localeResources,
fallbackLng: {
ko: ["ko"],
default: ["en"],
},
LanguageDetector가 브라우저 언어를 읽어 초기 언어를 정합니다. 그런데 _app.tsx에는 이게 있어요.
if (router.locale && i18n.language !== router.locale) {
i18n.changeLanguage(router.locale);
}
주소에서 온 router.locale이 있으면 렌더 중에 덮어씁니다. 결과적으로 이기는 건 주소예요. 브라우저 감지는 첫 순간에만 잠깐 작동하고 바로 덮입니다. 감지기를 왜 남겨 뒀는지는 코드에 없습니다. 이 if가 훅도 이펙트도 아닌 렌더 본문에 있다는 것도 지금 보면 걸리는데, 그때 문제가 됐다는 기록은 없어요.
경력기술서와 어긋나는 곳
경력기술서 §1에는 "번역 데이터를 JSON으로 관리해 필요한 언어만 동적으로 로딩"이라고 적었습니다. 코드는 TypeScript 객체 하나를 import해서 resources에 통째로 넘기는 구조예요. JSON 파일도, 언어별 분할 로딩도 없습니다. 세 언어 5,091줄이 첫 번들에 다 실립니다. 경력기술서가 i18next 일반론을 옮겨 적은 것이지 이 코드를 보고 쓴 게 아니었습니다.
일본어 조판 CSS는 있었습니다. :lang(ja) 선택자로 word-break를 바꾸는 규칙이 결제·검색 스타일에 남아 있어요. 그 항목은 맞습니다.
검증
public/locales/resources.ts5,091줄을 스냅샷에서 Node로 읽어 언어별 항목 수·한국어 키 수·언어 간 키 차이를 셌습니다.t()호출 수는components·pages를 정규식으로 셌습니다.- 날짜 근거: Rails
db/migrate/20221127…internationalize_content(콘텐츠 테이블에 언어 컬럼을 채운 마이그레이션). 프런트 리소스 파일 자체에는 날짜가 없습니다. - 경력기술서 §1 대조: i18next 도입 일치,
word-break조정 일치, "JSON 관리·동적 로딩" 불일치. - 당시 이력서(2023-12)와 대조: 담당 역할은 "가격·홍보"로 적혀 있어 다국어가 명시돼 있지 않습니다. 일본어 담당은 경력기술서 쪽 기록입니다. 개발 인원 프런트 4명이라 이 파일에 손댄 사람이 저만은 아니었을 겁니다.
- 그때 번역 누락을 어떻게 잡았는지, 누락 키 수를 센 적이 있는지는 기록이 없습니다.
남은 것 · 한계
- 한국어 키 전략을 언제 누가 정했는지 모릅니다. 제가 맡기 전부터 영문 키가 있었고 제 작업 이후 한국어 키가 주류가 됐다는 것까지는 비율로 읽히지만, 그 경계를 커밋 없이 그을 수는 없어요.
- 일본어에 빠진 1,539개 키 중 실제 화면에 닿는 수는 안 셌습니다. 영어에만 있는 키 대부분이 영어 전용 소개 페이지 것일 수 있어요.
- 경력기술서 §1의 "동적 로딩"은 정정 대상입니다. 그 글 본문은 두고 정정 블록을 다는 쪽으로 정리할 생각입니다.
- 3년 뒤 SDMS에서 용어 하나를 96개 파일 667곳에서 바꿀 때 코드 식별자와 화면 문구를 경계로 갈랐습니다. 여기서는 화면 문구가 곧 식별자였어요. 그 경계가 없으면 문구 하나가 코드 변경이 됩니다.
관련 글: 숙박 큐레이션 플랫폼 프런트엔드 2022–2024 · 용어 하나를 96개 파일 667곳에서 바꿨다 · 일본어는 도메인, 영어는 경로 · 클래스 팔레트 20종이 다섯 파일에 복사돼 있고 Redux는 import만 남았다