번역 문구 파일이 점점 늘어나면서 "이 문구를 어디에 넣어야 하지" 하고 헤매는 순간이 자주 왔어요. img-compressor 저장소의 messages/ko.json, messages/en.json이 이 문제를 어떻게 나눠서 풀었는지 볼게요. 두 파일은 최상위 키 구조는 같고 값만 언어별로 달라요.
최상위 키를 기능 단위로 나눴어요
{
"common": { "windowTitle": "...", "menu": {...}, "status": {...}, "startMenu": {...} },
"compressor": { /* 압축 도구 화면 전용 문구 */ },
"language": { /* 언어 전환 UI 문구 */ },
"seo": { /* 메타 설명, OG 문구 */ }
}
common은 메뉴, 상태 표시, 시작 메뉴처럼 여러 화면에서 재사용되는 문구를 모아둔 네임스페이스고, compressor는 압축 기능 화면에서만 쓰는 문구, language는 LanguageSwitcher 전용, seo는 메타 태그용 문구예요. 화면 단위가 아니라 "여러 곳에서 재사용되나, 한 곳에서만 쓰이나"를 기준으로 나눴어요.
컴포넌트는 필요한 네임스페이스만 가져다 써요
const t = useTranslations("language");
// ...
<button aria-label={t("switchLanguage")}>
{locale === "ko" ? t("korean") : t("english")}
</button>
LanguageSwitcher는 useTranslations("language")로 language 네임스페이스만 가져와요. 전체 메시지 객체를 다 넘기는 대신 필요한 것만 지정하니까, 이 컴포넌트가 뭘 참조하는지 코드만 보고 바로 알 수 있고, common이나 compressor 쪽 문구가 바뀌어도 이 컴포넌트는 영향 안 받는다는 게 확실해지더라고요.
이 구조에서 자주 놓치는 부분
네임스페이스가 늘수록 어떤 문구를 어디에 넣을지 판단이 애매해지는 경우가 생기더라고요. 예를 들어 압축 화면에도 나오고 시작 메뉴에도 나오는 문구는, common이랑 compressor 중 어디에 둘지 팀 규칙이 없으면 같은 뜻 문구가 두 곳에 중복 선언될 수 있어요. 지금은 언어가 두 개뿐이라 이 문제가 크게 안 드러났는데, 언어나 네임스페이스가 늘어나면 따로 중복 검사가 필요해질 것 같아요.