목차
JSON 번역 파일: i18n을 위한 형식, 구조, 그리고 모범 사례
두 개 이상의 언어로 출시되는 거의 모든 웹 애플리케이션은 JSON 파일에 번역 데이터를 저장합니다. 이 형식은 매우 보편적이지만, 그 관례는 통일되어 있지 않습니다. 프레임워크마다 기대하는 형태가 다릅니다. 플랫 키-값 객체, 깊이 중첩된 네임스페이스 트리, ICU 문법이 내장된 메시지, 또는 고유한 디스크립터 구조를 가진 Chrome 확장 매니페스트 등 다양합니다. 초기에 잘못된 형식을 선택하거나 나중에 형식 간에 마이그레이션하는 것은 모두 실질적인 비용을 수반합니다.
이 가이드는 오늘날 사용되는 주요 JSON 번역 형식, 대규모에서 번역 파일을 구성하는 방법, 키 명명 관례, 복수화 패턴, 유효성 검사 도구, 그리고 JSON과 XLIFF 및 PO와 같은 대안 형식 간에 전환하는 방법을 다룹니다.
요약 / 핵심 내용
- JSON은 JavaScript에 기본 내장되어 있고, 사람이 읽기 쉬우며, 버전 관리와 잘 호환되기 때문에 웹 i18n의 지배적인 형식입니다. 그러나 단일한 "JSON i18n 형식"은 존재하지 않습니다. 주요 라이브러리마다 고유한 관례를 정의합니다.
- 가장 일반적인 변형은 플랫 키-값(단순하지만 대규모에서 유지 관리가 어려움), 중첩/네임스페이스(체계적이지만 장황함), i18next(네임스페이스에 복수형 키 접미사 내장), FormatJS/react-intl(값에 ICU MessageFormat 사용), chrome.i18n(message 필드와 선택적 description이 있는 디스크립터 객체)입니다.
- 파일 구성은 형식만큼 중요합니다. 네임스페이스 또는 기능 도메인별로 번역을 분리하면 파일이 작아지고, 병합 충돌이 줄어들며, 지연 로딩이 가능해집니다.
- 복수화 및 변수 보간 문법은 라이브러리마다 다릅니다. 값에 ICU MessageFormat을 사용하면 가장 이식성이 높고 표현력이 풍부한 옵션을 제공하지만, 호환 가능한 런타임이 필요합니다.
- CI 유효성 검사(누락된 키, 잘못된 형식의 JSON, 번역되지 않은 문자열 확인)는 깨진 번역이 프로덕션에 도달하는 것을 방지하는 가장 효과적인 방법입니다.
왜 번역에 JSON을 사용하는가?
형식을 비교하기 전에, JSON이 웹 i18n의 기본값이 된 이유와 그 선택이 항상 명확하지 않은 이유를 이해하는 것이 중요합니다.
사람이 읽기 쉽고 diff에 친화적입니다. JSON 번역 파일은 도구 없이도 읽을 수 있습니다. pull request의 diff는 어떤 문자열이 변경되었는지, 추가되었는지, 제거되었는지를 정확히 보여줍니다. 같은 변경이 여러 요소의 XML 속성에 묻혀 있는 XLIFF 2.0이나 diff를 읽을 수 없는 컴파일된 바이너리 형식과 비교해 보십시오.
JavaScript에 기본 내장되어 있습니다. JSON.parse()는 모든 JavaScript 런타임에 내장되어 있습니다. 설치할 파싱 의존성도, 구성할 형식 어댑터도 없습니다. Next.js 애플리케이션이 브라우저용 번역을 번들링할 때 일반 JSON을 제공합니다. 직렬화 오버헤드도, 커스텀 파서도 없습니다.
프로그래밍 방식으로 생성하고 소비하기 쉽습니다. 모든 언어에 JSON 라이브러리가 있습니다. 데이터베이스, 스프레드시트 내보내기, 또는 번역 관리 시스템 API에서 번역 파일을 생성하는 것은 간단한 JSON 직렬화 작업입니다.
버전 관리와 잘 호환됩니다. JSON 파일은 텍스트입니다. 다른 키의 변경이 있을 때 깔끔하게 병합되고, 읽기 쉬운 blame 히스토리를 생성하며, 표준 diff 도구와 함께 작동합니다.
대안과 그 트레이드오프
| 형식 | 강점 | 약점 |
|---|---|---|
| XLIFF 2.0 | 풍부한 메타데이터, 번역자 노트, 상태 추적, 업계 표준 | 장황한 XML, 낮은 diff 가독성, 복잡한 도구 |
| PO / Gettext | 성숙한 에코시스템, msgid/msgstr 모델이 컨텍스트 지원, 복수형 내장 | 전문화된 파서 필요, JavaScript에서 덜 일반적 |
| YAML | 가독성 높음, 자연스럽게 여러 줄 문자열 지원 | 들여쓰기에 민감(오류 유발), 기본 브라우저 파서 없음 |
| ARB (Application Resource Bundle) | Flutter/Dart 기본, 키별 메타데이터 지원 | Dart 특화 에코시스템, 웹 도구 제한적 |
| JSON | 어디서나 사용 가능, 기본 JavaScript, 훌륭한 도구 | 기본 메타데이터 없음, 복수형 처리가 라이브러리에 따라 다름 |
XLIFF는 번역이 CAT 도구를 사용하는 전문 번역 에이전시를 통해 진행될 때 올바른 선택입니다. PO는 Gettext 에코시스템(Poedit, GNU gettext 유틸리티)을 활용하려는 프로젝트에 적합합니다. React, Vue, Angular, 또는 Svelte로 구축된 대부분의 웹 애플리케이션에서 JSON은 실용적인 기본값입니다.
일반적인 JSON 번역 형식
플랫 키-값
가장 단순한 JSON 번역 파일은 모든 키가 문자열 식별자이고 모든 값이 번역된 문자열인 단일 레벨 객체입니다.
{
"welcome_message": "플랫폼에 오신 것을 환영합니다",
"login_button": "로그인",
"logout_button": "로그아웃",
"error_not_found": "페이지를 찾을 수 없습니다",
"error_server": "문제가 발생했습니다. 다시 시도해 주세요."
}
이 형식은 학습 곡선이 없으며 모든 JSON 파서와 함께 작동합니다. 문제는 규모에서 나타납니다. 대규모 애플리케이션에 500개의 키가 있는 플랫 파일은 탐색이 불가능해집니다. 시각적 그룹화도 없고, 문자열이 어디서 사용되는지 표시도 없으며, 지연 로딩을 위해 번역의 일부를 추출할 방법도 없습니다.
적합한 경우: 소규모 프로젝트, 프로토타입, 또는 번역 키가 100개 미만인 애플리케이션.
중첩/네임스페이스 JSON
중첩 JSON은 키를 애플리케이션 구조를 반영하는 계층으로 구성합니다.
{
"auth": {
"login": {
"title": "계정에 로그인",
"email_label": "이메일 주소",
"password_label": "비밀번호",
"submit_button": "로그인",
"forgot_password": "비밀번호를 잊으셨나요?"
},
"logout": {
"confirm": "정말 로그아웃하시겠습니까?"
}
},
"dashboard": {
"greeting": "좋은 아침입니다, {{name}}님",
"stats": {
"total_users": "총 사용자",
"active_sessions": "활성 세션"
}
}
}
vue-i18n과 angular/localize 같은 라이브러리는 중첩 JSON을 기본적으로 지원합니다. 키는 점 표기법으로 접근합니다: t('auth.login.title'). 이 구조는 컴포넌트 계층에 자연스럽게 매핑되며 각 문자열이 어디서 사용되는지 쉽게 식별할 수 있게 해줍니다.
트레이드오프는 깊이 중첩된 객체가 더 신중한 키 관리를 요구하고 긴 접근 경로를 생성할 수 있다는 것입니다. 대부분의 팀은 실용적인 최대치로 두 세 단계의 중첩을 선택합니다.
i18next 형식
i18next는 JavaScript 에코시스템에서 가장 널리 사용되는 i18n 라이브러리이며 중첩 JSON 위에 자체 관례를 가지고 있습니다. 가장 중요한 것은 복수형 키 접미사 시스템입니다.
{
"common": {
"save": "저장",
"cancel": "취소",
"loading": "로딩 중..."
},
"items": {
"count_one": "항목 {{count}}개",
"count_other": "항목 {{count}}개",
"count_zero": "항목 없음"
},
"notifications": {
"new_one": "새 알림이 {{count}}개 있습니다",
"new_other": "새 알림이 {{count}}개 있습니다"
}
}
i18next는 기본 키에 접미사를 추가하여 복수형을 처리합니다. 접미사는 CLDR 복수형 규칙 카테고리를 따릅니다: zero, one, two, few, many, other. 영어의 경우 one과 other만 적용됩니다. 아랍어나 러시아어의 경우 전체 세트가 중요합니다.
i18next는 파일 레벨에서 네임스페이스도 지원합니다. 각 JSON 파일이 하나의 네임스페이스입니다. t('common:save')를 호출하면 i18next는 활성 locale에 대한 common.json을 로드합니다. 이를 통해 react-i18next를 사용하는 React 애플리케이션에서 네임스페이스 레벨 지연 로딩이 가능합니다.
{
"title": "환영합니다, {{name}}님!",
"description": "{{date, datetime}}에 가입하셨습니다",
"price": "{{price, currency}}"
}
{{value, formatType}} 문법은 날짜, 숫자, 통화에 대해 Intl API와 통합되는 i18next의 포매팅 파이프라인을 트리거합니다.
FormatJS / react-intl 형식
FormatJS(react-intl 기반)는 키 접미사 대신 문자열 값 자체에 ICU MessageFormat 문법을 직접 내장합니다. 이를 통해 모든 복수화 및 변수 로직이 메시지 문자열 내부에 위치하게 됩니다.
{
"welcome": "{name}님, 환영합니다!",
"item_count": "{count, plural, =0 {항목 없음} one {항목 #개} other {항목 #개}}",
"last_login": "마지막 로그인: {date, date, medium}",
"account_status": "{gender, select, male {그는} female {그녀는} other {이 사용자는}} 인증된 회원입니다.",
"notification_badge": "{count, plural, =0 {} one {({count})} other {({count})}}"
}
ICU MessageFormat은 복잡한 복수형 및 성별 일치 규칙에 사용 가능한 가장 표현력 있는 옵션입니다. select 키워드는 조건부 문자열을 처리하고, plural은 카운트 기반 형식을 처리하며, 형식 유형(date, number, currency)은 Intl API에 위임합니다.
키는 일반적으로 컴포넌트 경로나 의미적 디스크립터와 일치하는 플랫 문자열입니다. FormatJS는 깊이 중첩된 구조 대신 설명적인 플랫 키를 권장합니다.
react-intl과 함께 사용:
import { useIntl, FormattedMessage } from 'react-intl';
function ItemList({ count }) {
const intl = useIntl();
return (
<p>
<FormattedMessage id="item_count" values={{ count }} />
</p>
);
}
chrome.i18n 형식
Chrome 확장은 chrome.i18n API에서 정의한 특정 JSON 형식을 사용합니다. 각 키는 일반 문자열 대신 디스크립터 객체에 매핑됩니다.
{
"extensionName": {
"message": "내 확장",
"description": "Chrome 웹 스토어에 표시되는 확장 이름입니다."
},
"welcomeMessage": {
"message": "안녕하세요, $USER$님!",
"description": "확장이 열릴 때 표시되는 인사말",
"placeholders": {
"user": {
"content": "$1",
"example": "홍길동"
}
}
},
"itemCount": {
"message": "$COUNT$개 항목 선택됨",
"placeholders": {
"count": {
"content": "$1",
"example": "3"
}
}
}
}
description 필드는 최종 사용자에게 표시되지 않습니다. 번역자와 Chrome 웹 스토어 검토 프로세스를 위한 컨텍스트입니다. 플레이스홀더는 별도의 placeholders 객체에 콘텐츠와 예시 값을 정의하는 $PLACEHOLDER_NAME$ 관례를 사용합니다.
이 형식은 Chrome 확장에서 필수적입니다. 범용 웹 애플리케이션 i18n에는 적합하지 않습니다.
파일 구성 패턴
locale당 단일 파일
가장 단순한 구성은 애플리케이션의 모든 번역을 포함하는 locale당 하나의 JSON 파일입니다.
locales/ en.json fr.json de.json ja.json es.json
이는 소규모 애플리케이션에는 효과적이지만 대규모에서 문제가 발생합니다. 파일이 커지고, 관련 없는 기능이 동일 파일에서 병합 충돌을 유발하며, 사용자가 방문하지 않은 섹션의 번역을 지연 로딩할 수 없습니다.
네임스페이스 분리
중간 및 대규모 애플리케이션에서 가장 일반적인 패턴은 네임스페이스별로 번역을 분리하는 것입니다. locale당 기능 영역마다 하나의 JSON 파일입니다.
locales/
en/
common.json
auth.json
dashboard.json
settings.json
errors.json
fr/
common.json
auth.json
dashboard.json
settings.json
errors.json
de/
common.json
auth.json
dashboard.json
settings.json
errors.json
각 네임스페이스 파일은 작고 집중적입니다. common 네임스페이스는 애플리케이션 전체에서 사용되는 문자열(버튼 레이블, 양식 필드 레이블, 일반 오류 메시지)을 포함합니다. 기능 네임스페이스는 해당 기능에 특정한 문자열을 포함합니다.
i18next와 react-i18next는 이 패턴을 기본적으로 사용합니다: t('auth:login.title')은 활성 locale의 auth.json을 로드합니다. next-i18next를 사용하는 Next.js 애플리케이션은 관례적으로 이 구조를 따릅니다.
기능 기반 분리
매우 큰 애플리케이션이나 각 패키지가 자체 번역을 소유하는 모노레포의 경우, 기능 기반 구조는 번역 파일을 해당 파일을 사용하는 코드 옆에 배치합니다.
src/
features/
auth/
locales/
en.json
fr.json
components/
LoginForm.tsx
SignupForm.tsx
dashboard/
locales/
en.json
fr.json
components/
DashboardHeader.tsx
checkout/
locales/
en.json
fr.json
이 패턴은 응집도를 극대화합니다. 번역 파일이 해당 파일을 사용하는 컴포넌트 옆에 있습니다. 빌드 시스템이나 i18n 로더는 런타임이나 빌드 시에 네임스페이스 파일을 병합합니다. 단점은 주어진 locale의 모든 번역을 찾으려면 전체 소스 트리를 순회해야 하므로, 번역자와 번역 관리 시스템의 워크플로가 복잡해진다는 것입니다.
권장 사항: 대부분의 애플리케이션에는 네임스페이스 분리를 사용하십시오. 다른 팀이 다른 기능을 소유하고 번역 파일에서 기능 간 병합 충돌을 피해야 할 만큼 팀이 충분히 큰 경우에만 기능 기반 분리를 사용하십시오.
번역 파일 형식 비교표
| 형식 | 가독성 | 도구 지원 | 메타데이터 지원 | ICU 지원 | Diff 친화성 |
|---|---|---|---|---|---|
| JSON (플랫) | 높음 | 우수 | 없음 | 아니오 (기본값) | 우수 |
| JSON (중첩) | 높음 | 우수 | 없음 | 아니오 (기본값) | 우수 |
| JSON (FormatJS) | 중간 | 양호 | 없음 | 예 (기본) | 양호 |
| XLIFF 2.0 | 낮음 | 우수 (CAT 도구) | 풍부 (상태, 노트, 대안 번역) | 예 | 낮음 |
| PO / Gettext | 중간 | 양호 (Poedit, Weblate) | 중간 (주석, 컨텍스트) | 부분적 | 중간 |
| YAML | 높음 | 중간 | 없음 | 아니오 | 양호 |
| ARB | 높음 | 양호 (Flutter) | 키별 메타데이터 | 아니오 | 양호 |
XLIFF의 메타데이터 우위는 전문 번역 에이전시와 협업할 때 중요합니다. 이 형식은 번역 상태(신규, 번역됨, 검토됨, 최종)를 추적하고, 대안 번역을 지원하며, Phrase, memoQ, SDL Trados와 같은 대부분의 전문 CAT 도구의 기본 형식입니다. 번역 워크플로에 에이전시 핸드오프가 포함된 경우, 원시 JSON 대신 JSON 소스에서 XLIFF를 생성하면 번역자 생산성이 향상되고 오류가 줄어듭니다.
키 명명 관례
일관된 키 명명은 유지 관리하기 쉬운 번역 파일과 버그와 혼란의 원인이 되는 파일의 차이입니다.
점 표기법 vs. 중첩
점이 있는 플랫 키와 진정한 중첩 객체는 모두 계층을 나타내지만 다르게 작동합니다. 점이 있는 플랫 키는 단일 문자열입니다 — "auth.login.title". 중첩 객체는 auth 안의 login 안의 title 키가 있는 실제 객체 구조입니다. 대부분의 라이브러리는 둘 다 지원하지만, 같은 파일에서 두 가지를 혼용하면 모호함이 발생합니다.
권장 사항: JSON 파일에서 진정한 중첩을 사용하고 코드에서 키에 접근할 때만 점 표기법을 사용하십시오. 동일한 중첩 레벨 내의 키 이름에 점을 사용하지 마십시오.
{
"auth": {
"login": {
"title": "로그인"
}
}
}
코드에서 t('auth.login.title')로 접근합니다. 라이브러리가 순회를 처리합니다.
snake_case vs. camelCase
두 가지 모두 일반적입니다. snake_case는 단어들이 시각적으로 혼합되지 않기 때문에 JSON 파일에서 더 읽기 쉽습니다. camelCase는 JavaScript 관례에 맞으며 FormatJS 프로젝트에서 일반적입니다.
{
"submit_button": "제출",
"error_message": "오류가 발생했습니다"
}
{
"submitButton": "제출",
"errorMessage": "오류가 발생했습니다"
}
하나를 선택하고 린터로 강제하십시오. 프로젝트 내 불일치는 어느 선택보다 더 나쁩니다.
설명적 vs. 간결
간결한 키는 짧지만 컨텍스트를 빠르게 잃습니다:
{
"btn_sub": "구독",
"btn_cncl": "취소",
"err_404": "페이지를 찾을 수 없습니다"
}
설명적 키는 값을 읽지 않고도 목적을 전달합니다:
{
"newsletter_subscribe_button": "구독",
"modal_cancel_button": "취소",
"error_page_not_found_title": "페이지를 찾을 수 없습니다"
}
권장 사항: UI 컨텍스트(컴포넌트 유형 또는 위치)와 콘텐츠 유형(버튼, 제목, 설명, 플레이스홀더)을 포함하는 설명적 키를 사용하십시오. 이를 통해 컴포넌트 코드를 읽지 않고도 키가 어떻게 사용되는지 이해할 수 있습니다.
계층적 그룹화
중첩 JSON을 사용할 때 먼저 UI 영역별로, 그 다음 컴포넌트 또는 액션별로, 그 다음 요소 유형별로 그룹화하십시오:
{
"checkout": {
"cart": {
"title": "장바구니",
"empty_message": "장바구니가 비어 있습니다",
"item_count_one": "항목 {{count}}개",
"item_count_other": "항목 {{count}}개"
},
"payment": {
"title": "결제 정보",
"card_number_label": "카드 번호",
"expiry_label": "유효 기간",
"submit_button": "지금 결제"
}
}
}
JSON에서 복수형과 변수 처리
ICU MessageFormat (FormatJS, vue-i18n, 기타)
ICU MessageFormat은 복수형과 변수에 사용 가능한 가장 표현력 있고 이식성 높은 문법입니다. FormatJS에서 기본적으로 지원되며, vue-i18n 및 다른 라이브러리에서도 구성할 수 있습니다.
{
"file_count": "{count, plural, =0 {파일 없음} one {파일 #개} other {파일 #개}}",
"upload_progress": "전체 {total}개 중 {current}개 업로드 중",
"last_seen": "마지막 접속: {time, date, relative}",
"user_role": "{role, select, admin {관리자} editor {편집자} other {뷰어}}"
}
복수형 분기 내의 # 기호는 형식화된 카운트로 대체됩니다. select 키워드는 숫자 대신 문자열 값을 기반으로 분기합니다.
i18next 복수형 접미사 관례
i18next는 키에 접미사를 추가하여 복수형을 처리합니다. 영어의 경우:
{
"message_count_one": "메시지 {{count}}개",
"message_count_other": "메시지 {{count}}개",
"message_count_zero": "메시지 없음"
}
t('message_count', { count: 5 })로 호출하면 i18next는 message_count_other를 선택하고 count를 보간합니다. 복수형이 더 많은 언어(러시아어는 네 가지)의 경우 추가 접미사 변형이 정의됩니다:
{
"file_one": "{{count}} файл",
"file_few": "{{count}} файла",
"file_many": "{{count}} файлов",
"file_other": "{{count}} файлов"
}
CLDR 복수형 규칙 카테고리(zero, one, two, few, many, other)는 i18next가 사용하는 접미사에 매핑됩니다.
변수 보간
모든 라이브러리는 어떤 형태의 변수 보간을 지원합니다. 문법은 다릅니다:
{
"greeting_i18next": "안녕하세요, {{name}}님!",
"greeting_icu": "안녕하세요, {name}님!",
"greeting_vue": "안녕하세요, {name}님!",
"greeting_angular": "안녕하세요, {{ name }}님!"
}
i18next는 기본적으로 이중 중괄호를 사용합니다. ICU는 단일 중괄호를 사용합니다. Angular의 $localize는 JSON 값의 런타임 보간 문법 대신 코드의 태그된 템플릿 리터럴을 사용합니다.
같은 프로젝트에서 보간 문법을 혼용하지 마십시오. 라이브러리에 맞는 문법을 선택하고 JSON 스키마 유효성 검사에서 강제하십시오.
도구와 유효성 검사
번역 파일을 위한 JSON Schema
JSON Schema 정의를 사용하면 CI 파이프라인, 편집기, pre-commit 훅에서 번역 파일 구조를 유효성 검사할 수 있습니다. 다음은 플랫 키-값 번역 파일을 위한 최소한의 스키마입니다:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"additionalProperties": {
"type": "string"
},
"minProperties": 1
}
중첩 파일의 경우 스키마가 재귀적이 됩니다. 대부분의 팀은 구조적 유효성 검사를 위한 JSON Schema와 의미적 검사를 위한 전용 i18n 린팅 도구를 조합하여 사용합니다.
누락된 키 린팅
가장 흔한 프로덕션 i18n 버그는 누락된 번역 키입니다. 영어에는 있지만 locale 파일에 없는 키입니다. 이로 인해 키 이름 자체나 빈 문자열로 폴백이 발생하여 UI가 깨집니다.
i18next-parser는 소스 코드에서 t('key') 호출을 스캔하고 번역 파일을 생성하거나 유효성 검사합니다:
npx i18next-parser --config i18next-parser.config.js
eslint-plugin-i18n-json은 JSON 번역 파일을 직접 유효성 검사합니다:
npm install --save-dev eslint-plugin-i18n-json
번역 파일 유효성 검사를 위한 기본 ESLint 설정:
{
"plugins": ["i18n-json"],
"rules": {
"i18n-json/valid-json": "error",
"i18n-json/sorted-keys": ["warn", { "order": "asc" }],
"i18n-json/identical-keys": [
"error",
{ "filePath": "src/locales/en.json" }
]
}
}
identical-keys 규칙은 모든 locale 파일이 참조 locale 파일(일반적으로 영어)과 동일한 키 세트를 포함하는지 확인합니다. 누락된 키와 존재하지 않아야 하는 추가 키 모두 플래그합니다.
완전성을 위한 CI 검사
locale 파일이 불완전할 때 병합을 차단하는 CI 단계는 번역 부채가 축적되는 것을 방지합니다. 간단한 셸 스크립트 검사:
#!/usr/bin/env bash
BASE="src/locales/en.json"
LOCALES=("fr" "de" "ja" "es")
for locale in "${LOCALES[@]}"; do
FILE="src/locales/${locale}.json"
BASE_KEYS=$(jq 'keys | length' "$BASE")
LOCALE_KEYS=$(jq 'keys | length' "$FILE")
if [ "$BASE_KEYS" != "$LOCALE_KEYS" ]; then
echo "ERROR: ${FILE}에 ${LOCALE_KEYS}개 키가 있습니다. 예상: ${BASE_KEYS}개"
exit 1
fi
done
echo "모든 locale 파일이 완전합니다."
중첩 파일의 경우 jq 경로 순회가 필요합니다. Better i18n과 같은 플랫폼은 JSON을 기본 형식으로 사용하고 CLI를 통해 번역을 동기화합니다. 이는 완전성 검사를 번역이 커밋되기 전 플랫폼 측에서 강제할 수 있음을 의미합니다. 불완전한 파일에 대한 CI 레벨 검사의 필요성을 줄여줍니다.
편집기 통합
"i18n Ally"와 같은 VS Code 확장은 인라인 번역 미리보기, 누락된 키 강조 표시, 편집기에서 직접 기계 번역 제안을 제공합니다. 이는 유효성 검사를 앞당깁니다. 개발자는 CI 중이 아닌 코드를 작성하는 동안 누락된 번역을 확인하게 됩니다.
형식 간 마이그레이션
JSON에서 XLIFF로
XLIFF는 번역을 전문 에이전시에 보낼 때 필요합니다. JSON에서 XLIFF 2.0으로 변환하는 것은 키-값 쌍을 <segment> 자식이 있는 <unit> 요소에 매핑하는 것을 포함합니다.
i18next-conv 도구는 양방향 변환을 처리합니다:
npm install -g i18next-conv # JSON에서 XLIFF로 i18next-conv -l en -s locales/en.json -t locales/en.xliff # XLIFF에서 JSON으로 i18next-conv -l fr -s locales/fr.xliff -t locales/fr.json
FormatJS 프로젝트의 경우 @formatjs/cli가 XLIFF 내보내기를 제공합니다:
npx formatjs compile-folder --ast src/locales/en.json > compiled/en.json
JSON에서 PO로
PO (Portable Object) 형식은 Gettext 에코시스템에서 사용되며 Poedit와 Weblate와 같은 도구가 지원합니다. i18next-conv 도구는 PO도 지원합니다:
# JSON에서 PO로 i18next-conv -l en -s locales/en.json -t locales/en.po # PO에서 JSON으로 i18next-conv -l fr -s locales/fr.po -t locales/fr.json
vue-i18n 프로젝트의 경우 @intlify/vue-i18n-loader는 JSON의 대안으로 .po 파일을 직접 지원합니다. 이는 Vue 프로젝트가 Gettext를 사용하는 서버 사이드 애플리케이션과 번역 메모리를 공유할 때 유용합니다.
일반적인 변환 도구
| 도구 | 변환 | 참고 |
|---|---|---|
i18next-conv | JSON ↔ PO, JSON ↔ XLIFF, JSON ↔ CSV | i18next 형식에 가장 완전 |
@formatjs/cli | FormatJS JSON → XLIFF, 추출된 메시지 → locale 파일 | FormatJS/react-intl 프로젝트에 필수 |
gettext-converter | PO ↔ JSON (플랫) | 단순, 프레임워크 가정 없음 |
xliff-simple-merge | XLIFF 병합 및 분리 | angular/localize를 사용하는 Angular 프로젝트에 유용 |
| 온라인 XLIFF 편집기 | XLIFF ↔ JSON (UI를 통해) | Phrase, Lokalise, Crowdin 모두 가져오기/내보내기 제공 |
형식 간에 마이그레이션할 때 커밋하기 전에 출력을 유효성 검사하십시오. 자동화된 변환 도구는 가끔 컨텍스트 문자열을 잃거나, 주석을 제거하거나, 비표준 복수형 규칙을 가진 언어의 복수형을 잘못 처리합니다.
FAQ
새 React 프로젝트에 어떤 JSON 형식을 사용해야 하나요?
react-i18next(가장 일반적인 선택)를 사용하는 경우 i18next 관례에 따라 네임스페이스 분리된 중첩 JSON으로 시작하십시오. react-intl 또는 FormatJS 기반 설정을 사용하는 경우 값에 ICU MessageFormat 문법이 있는 플랫 키를 사용하십시오. 선택하는 라이브러리가 형식을 결정합니다. 프로젝트 요구 사항에 따라 먼저 라이브러리를 선택한 다음 문서화된 관례를 따르십시오.
JSON 키가 영어 문자열 자체여야 하나요, 아니면 의미적 식별자여야 하나요?
의미적 식별자(예: auth.login.submit_button)가 대부분의 애플리케이션에서 선호됩니다. 영어 문자열 자체를 키로 사용하는 것(Gettext 스타일: t('Submit'))은 소규모 프로젝트에서는 작동하지만 대규모에서 문제가 발생합니다. 같은 네임스페이스에서 컨텍스트가 다른 두 개의 다른 "제출" 버튼을 가질 수 없고, 영어 텍스트를 변경하면 코드의 모든 키 참조를 업데이트해야 합니다.
UI를 깨뜨리지 않고 런타임에 누락된 번역을 어떻게 처리하나요?
모든 주요 i18n 라이브러리는 키가 누락되면 설정된 기본 locale로 폴백합니다. 영어 locale 파일을 폴백으로 사용하도록 라이브러리를 구성하십시오. 이는 프랑스어 번역이 누락되면 키 이름 대신 영어 문자열을 표시한다는 것을 의미합니다. 개발 중에 누락된 키를 로그에 기록하십시오(대부분의 라이브러리는 missingKeyHandler 콜백을 지원합니다). 이렇게 하면 테스트 중에 볼 수 있습니다.
JSON 번역 파일에서 주석을 사용해도 안전한가요?
표준 JSON은 주석을 지원하지 않습니다. 일부 도구는 확장으로 JSON5 또는 JSONC(주석이 있는 JSON)를 지원하지만 표준 JSON 파서와의 호환성이 깨집니다. 번역자를 위해 컨텍스트로 문자열에 주석을 달아야 하는 경우 키 레벨에서 별도의 description 필드를 사용하거나(chrome.i18n이 하듯이) 별도의 메타데이터 파일을 유지하십시오. 더 간단한 방법은 인라인 주석 없이도 컨텍스트를 전달하는 설명적 키 이름을 사용하는 것입니다.
JSON 파일에서 오른쪽에서 왼쪽(RTL) 언어를 어떻게 처리해야 하나요?
RTL 언어(아랍어, 히브리어, 페르시아어)의 JSON 구조는 LTR 언어와 동일합니다. 파일 형식은 변경되지 않습니다. RTL 지원은 CSS 및 레이아웃 관련 사항이지 번역 파일 관련 사항이 아닙니다. i18n 라이브러리는 활성 locale의 텍스트 방향을 감지하는 방법을 제공합니다(일반적으로 Intl.Locale API 또는 locale 메타데이터 객체를 통해). 애플리케이션은 문서나 컴포넌트에 dir="rtl"을 적용합니다. RTL 특화 CSS는 번역 값이 아닌 스타일시트에 유지하십시오.
결론
JSON 번역 파일은 하나의 보편적인 표준을 따르지 않습니다. 해당 파일을 읽는 라이브러리의 관례를 따릅니다. 라이브러리가 기대하는 형식과 그 이유를 이해하는 것이 "올바른" 방법을 찾는 것보다 더 유용합니다.
실용적인 지침은 다음과 같습니다. React, Vue, 또는 Node.js 애플리케이션을 구축하고 가장 큰 도구 및 예제 에코시스템을 원한다면 i18next 관례에 따른 중첩 JSON을 사용하십시오. 많은 locale에 걸쳐 복잡한 복수형, 성별 일치, 또는 풍부한 포매팅을 처리해야 하고 표현식이 가능한 한 이식 가능하기를 원한다면 값에 ICU MessageFormat을 사용하십시오. 인지 오버헤드가 조직 구조보다 중요한 소규모 프로젝트나 빠른 프로토타이핑에는 플랫 키를 사용하십시오.
파일 구성, 키 명명, CI 유효성 검사는 형식 선택만큼 중요합니다. 일관된 명명과 자동화된 완전성 검사가 있는 플랫 파일은 도구도 강제도 없는 신중하게 네임스페이스로 구분된 구조보다 소규모 팀에 더 잘 서비스합니다.
번역 부채는 조용히 축적됩니다. 여기서 누락된 키, 저기서 오래된 문자열이 쌓이다가 번역된 사용자들이 깨진 UI 문자열을 만나게 됩니다. 이 결과를 피하는 팀은 번역 파일을 다른 코드 아티팩트와 동일하게 취급합니다. pull request에서 검토되고, CI에서 유효성 검사되며, 미래의 유지 관리자를 염두에 두고 구성됩니다.
참고 자료
- i18next 문서: 복수형
- i18next 문서: 보간
- FormatJS: ICU 메시지 문법
- react-intl 문서
- vue-i18n 문서
- Chrome 확장: chrome.i18n API
- XLIFF 2.0 명세 (OASIS)
- Unicode CLDR 복수형 규칙
- i18next-conv: 변환 CLI
- eslint-plugin-i18n-json
- i18n Ally VS Code 확장
- ICU 메시지 형식 참고 자료 (Unicode)
최종 업데이트: 2026년 3월