BookTranslator
BookTranslator

키나 플레이스홀더를 깨뜨리지 않고 JSON 언어 파일을 번역하는 방법

en.json 파일에서 어떤 값이 번역되는지, 파일 번역기가 무엇을 다시 쓰는지(주석, 키 순서, 큰 ID), 무료 스크립트와 테스트 파일로 플레이스홀더, ICU 복수형, 키를 확인하는 방법을 알아보세요.

BookTranslator

BookTranslator Team

24분 읽기

en.json과 같은 JSON 언어 파일을 번역하려면 JSON을 이해하는 번역기에 파일 전체를 맡기세요. 원시 텍스트를 채팅 창이나 웹 번역기에 붙여넣는 것은 위험한 방법입니다. JSON을 인식하는 번역기는 파일을 읽고 텍스트 값만 번역한 뒤 동일한 키를 그대로 다시 기록하므로 앱에서 파일을 계속 불러올 수 있습니다. 복사하여 붙여넣기를 하면 키가 번역되거나 따옴표가 바뀌거나 중괄호가 빠질 수 있습니다. 그중 하나라도 발생하면 파일이 깨집니다.

키를 유지하는 것은 쉬운 부분입니다. 문제는 대부분 그다음에 생깁니다. {name} 플레이스홀더가 {nom}으로 돌아오거나, ICU 복수형에서 other 케이스가 사라지거나, URL이 다른 텍스트처럼 번역을 위해 전송되거나, 번역기가 파일 자체를 몇 군데 변경하는 경우입니다. 이 가이드에서는 바로 그런 점들을 다룹니다.

우리는 그 모든 사례를 담은 테스트 파일을 만들고, 원본 파일과 번역본을 비교하는 무료 검사기를 작성했습니다. 또한 BookTranslator가 사용하는 JSON 파싱 및 쓰기 코드의 로컬 복사본에 모델 대신 마커를 넣어 테스트 파일을 실행하여, 어떤 값이 전송되고 파일이 어떻게 돌아오는지 정확히 확인할 수 있게 했습니다.

빠른 답변: 파일의 각 부분은 어떻게 되는가

파일의 부분테스트에서 가져온 예시BookTranslator가 하는 일확인할 사항
키"greeting", "error"그대로 유지됨없음
일반 텍스트 값"Reading list"번역됨의미, 화면에서의 길이
플레이스홀더가 있는 값"Welcome back, {name}!", "Saved %s of %d chapters"번역되며, 플레이스홀더는 전송되는 텍스트에 포함됨플레이스홀더 이름과 개수(검사기가 확인함)
ICU 복수형 메시지"{count, plural, one {# book} other {# books}}"하나의 문자열로 전송됨other 케이스 및 대상 언어의 복수형 범주
인라인 HTML"accept the <b>Terms</b>."번역됨; 번역 후 태그 개수를 확인함태그 이름과 개수 (검사기가 확인)
텍스트로 저장된 URL"https://example.com/help"번역을 위해 전송됨변경 없이 반환됨
글자가 없는 값"2.4.1", "—", ""그대로 유지됨없음
숫자, true/false, null20, true그대로 유지됨매우 큰 ID (아래 참조)
문자열 배열["Open", "Save", "Export as EPUB"]번역 파일에서는 번역됨, 이중 언어 파일에서는 번역되지 않음이중 언어 파일 (사용하는 경우)
주석 (JSONC)// Shown on the home screen제거됨. 값에 //도 포함되어 있으면 파일 로드에 실패할 수 있음업로드 전에 주석 제거

이 가이드의 나머지 부분에서는 각 행의 출처와 대처 방법을 설명합니다.

1단계: 업로드 전에 원본 파일 확인

번역할 파일에 검사기를 실행하세요. Python 3만 있으면 되고 다른 것은 필요 없습니다:

python3 check_i18n_json.py en.json

테스트 파일에서 출력된 결과는 다음과 같습니다:

WARN  limits.projectId: 9007199254740993 is above 2^53 and can change in a JavaScript round trip; store IDs as strings
WARN  error.network: a flat key with dots and a nested key resolve to the same dot path; tools that address strings by dot path can mix them up
INFO  (root): 3 keys look like integers; JavaScript sorts those first and in numeric order when it rewrites the object
INFO  source: 17 strings, 2 numbers

두 개의 경고와 정수형 키에 대한 참고 사항은 각각 테스트 파일이 번역기를 거쳤을 때 확인된 변경 사항과 일치합니다. 파일에 이러한 사항이 전혀 없다면 3단계로 건너뛰어도 됩니다.

검사기는 또한 주석이나 후행 쉼표가 있는 파일처럼 엄격한 JSON이 아닌 파일을 거부합니다. 이는 다음 문제이기 때문입니다.

테스트 파일에서 반환된 결과

테스트는 2026년 11월 2일에 BookTranslator 백엔드의 현재 JSON 처리 방식을 대상으로 실행되었습니다. 모델을 호출하는 대신, 번역을 위해 전송될 모든 값을 [ZH] 와 원본 텍스트로 대체했습니다. 이렇게 하면 결과를 예측할 수 있습니다: 앞에 [ZH]가 붙은 것은 전송된 것이고, 붙지 않은 것은 전송되지 않은 것입니다. 두 출력물이 모두 게시되어 있습니다: 번역 파일과 이중 언어 파일.

번역된 파일

모든 키가 변경되지 않은 채로 반환되었고, 파일도 여전히 파싱되었습니다. 글자가 포함된 값들은 전송되었습니다. 여기에는 {name}, %s, ICU 메시지, <b> 태그, 도움말 URL이 포함됩니다. 버전 문자열 "2.4.1", 대시, 빈 문자열은 글자가 없기 때문에 전송되지 않았습니다. 숫자와 불리언은 그대로 두었습니다.

다른 네 가지 변경 사항이 있었습니다:

  1. 하나의 문자열이 잘못된 키로 들어갔습니다. 테스트에서 파일에는 평면 키 "error.network"에 "Check your connection."이라는 텍스트가 있었고, 중첩된 "error": { "network": "Network error" }도 있었습니다. 번역기는 각 문자열을 부분 사이에 점을 넣은 경로로 식별하므로 두 문자열 모두 같은 주소 error.network를 가졌습니다. 출력에서 두 키 모두 "Network error"의 번역을 담았습니다. "Check your connection."은 사라졌습니다. 이 문제는 같은 파일에 두 스타일이 모두 있어야 발생하며 드물지만 조용히 실패합니다. 검사기가 업로드 전에 경고합니다.
  2. 정수 같은 키의 순서가 바뀌었습니다. "steps": { "10": ..., "2": ..., "1": ... }는 "1", "2", "10"으로 반환되었습니다. JavaScript는 객체를 재구성할 때 항상 정수 같은 키를 숫자 순서로 먼저 나열합니다. 키와 값은 여전히 일치하므로 앱은 문제없지만, 원본 파일과의 diff는 지저분해집니다.
  3. 큰 ID가 정밀도를 잃었습니다. 9007199254740993은 9007199254740992로 반환되었습니다. JavaScript는 숫자를 64비트 부동 소수점으로 저장하며, 2^53 이상의 정수는 모두 정확히 표현할 수 없습니다(MDN의 Number.MAX_SAFE_INTEGER). 긴 ID는 문자열로 저장하거나 언어 파일에서 빼두세요.
  4. 서식이 다시 작성되었습니다. 출력은 두 칸 들여쓰기를 사용하고 줄바꿈으로 끝납니다. 원본이 탭이나 네 칸 들여쓰기를 사용했다면 diff에서 공백 변경이 있을 것으로 예상하세요.

테스트 파일에는 중복 키가 없었지만 알아둘 가치가 있습니다: JavaScript의 JSON 파서는 반복된 키에 대해 마지막 값만 유지하므로 이전 텍스트는 번역되지 않습니다. 앱은 동일한 마지막 값을 로드하므로 눈에 보이는 문제는 없지만, 검사기가 중복 키를 나열하므로 정리할 수 있습니다.

이중 언어 파일

이중 언어 파일은 각 원본 값을 유지하고 끝에 _translated가 붙은 형제 키를 추가합니다:

"greeting": "Welcome back, {name}!",
"greeting_translated": "[ZH] Welcome back, {name}!",

이것은 검토에 유용합니다. 원본과 번역이 나란히 있기 때문입니다. 하지만 모든 키에 쌍둥이가 생겼으므로 앱이 로드할 수 있는 파일은 아닙니다. 앱에는 번역된 파일을 사용하세요.

한 가지 간극: 배열 안의 문자열에는 _translated 형제가 생기지 않습니다. 배열 항목에는 추가할 키가 없기 때문입니다. 테스트에서 "menu": ["Open", "Save", "Export as EPUB"]는 이중 언어 파일에서는 영어로 반환되었지만 번역된 파일에서는 번역되었습니다. 이중 언어 파일로 검토한다면 번역된 파일의 배열을 확인하세요.

주석이 있는 파일

많은 프로젝트가 언어 파일에 주석을 둡니다(JSONC). 엄격한 JSON은 이를 허용하지 않습니다. 번역기는 먼저 엄격한 JSON을 시도합니다. 실패하면 주석과 후행 쉼표를 제거하고 다시 시도합니다. 주석 제거는 한 줄에서 // 뒤의 모든 것을 삭제하는 것을 의미하며, 여기에는 https://의 //도 포함됩니다.

주석과 URL이 있는 우리 파일은 전혀 로드되지 않았습니다. 첫 번째 주석에서 파싱이 실패했습니다. 주석은 있지만 값 안에 //가 없는 파일은 잘 로드되었고, 주석은 출력에서 제거되었습니다.

따라서 파일에 주석이 있으면 업로드 전에 제거하거나, 주석이 있는 사본을 유지하고 번역에 주석이 없을 것을 감수하세요.

2단계: 파일 준비

번역하기 전에 대부분의 문제를 해결할 수 있습니다:

  • 엄격한 JSON으로 만드세요. 주석과 후행 쉼표를 제거하세요. 빌드 도구가 JSONC를 읽는다면 주석이 있는 파일을 원본으로 유지하고 번역용으로 깨끗한 사본을 내보내세요.
  • 하나의 키 스타일을 사용하세요. 키를 중첩하거나 점으로 평면화하되, 한 파일에 둘 다 사용하지 마세요. i18next를 사용한다면 keySeparator 옵션이 키의 점을 읽는 방식을 제어하므로, 평면화하기 전에 해당 설정을 확인하세요.
  • 텍스트가 아닌 데이터를 옮기세요. ID, URL, 기능 플래그, 제한은 언어 파일이 아니라 구성에 속합니다. URL을 유지해야 한다면 유지하되 번역 후 확인할 것으로 예상하세요. 번역하지 말아야 할 가이드는 원래 언어로 유지해야 할 다른 항목을 결정하는 방법을 다룹니다.
  • 플레이스홀더를 모든 곳에서 같은 방식으로 작성하세요. {name}, {{name}}, %s를 섞는 것보다 {name} 같은 한 가지 스타일이 확인하기 쉽습니다.

3단계: 전체 파일 번역

정리된 파일을 BookTranslator의 문서 번역기에 업로드하세요. 이 번역기는 허용하는 다른 현지화 및 구조화 텍스트 형식과 함께 .json을 나열합니다. 가격은 결제 전에 표시됩니다. 작업이 완료되면 기록 페이지에서 번역된 파일과 이중 언어 파일을 별도 다운로드로 제공합니다.

텍스트가 번역되는 방식과 관련해 결과에 영향을 미치는 두 가지가 있습니다:

  • 모델은 키를 보지 못합니다. 각 값은 키 이름 없이 번호가 매겨진 세그먼트로 전송됩니다. "button.open"과 "status.open"이 모두 "Open"이라고 되어 있다면, 모델은 어느 것이 동사이고 어느 것이 형용사인지 알 방법이 없습니다. 짧고 모호한 문자열은 직접 확인해야 할 대상입니다.
  • 각 값은 개별적으로 번역됩니다. 두 키를 연결하는 것이 없으므로, 같은 단어가 두 곳에 있으면 서로 다르게 번역되어 돌아올 수 있고, 서로 다른 번역이 필요한 단어가 같은 번역으로 돌아올 수도 있습니다. BookTranslator의 선택적 자동 용어집은 이름, 지명, 전문 용어를 수집해 일관성을 유지하지만, 직접 고정 번역을 지정할 수는 없습니다. 반드시 하나의 정확한 방식으로 번역되어야 하는 용어의 경우 용어 목록을 유지하고 번역 후 해당 문자열을 확인하세요. 용어집 가이드에서 계획 방법을 설명합니다.

인라인 HTML 태그는 번역 후에 개수가 확인되며, 태그가 일치하지 않는 세그먼트는 재시도됩니다. 중괄호 플레이스홀더, %s 스타일 플레이스홀더, ICU 구문은 텍스트의 일부로 전송되지만 같은 방식으로 검사되지는 않습니다. 모든 번역에 대해 4단계를 실행하세요.

채팅 모델이나 로컬 모델로 번역하고 싶다면, 원본 파일이 아닌 값만 전송하고 스크립트로 키에 맞춰 다시 넣으세요. 아래 검사는 어느 경우든 적용됩니다.

4단계: 번역된 파일 확인

번역을 원본과 비교하세요:

python3 check_i18n_json.py en.json fr.json

무엇을 잡아내는지 보여드리기 위해 오류를 심어 놓은 프랑스어 파일을 만들었습니다. 모델 출력이 아닌 직접 만든 파일입니다. 1단계와 동일한 원본 경고 후 검사기가 출력한 내용은 다음과 같습니다:

ERROR app.greeting: placeholders differ; lost ['name'] extra ['nom']
ERROR app.help_url: urls differ; lost ['https://example.com/help'] extra ['https://example.com/aide']
ERROR app.items: ICU message has no 'other' case
ERROR app.legacy: placeholders differ; lost ['%d'] extra []
ERROR limits.maxFiles: type changed from int to str
ERROR limits.projectId: non-text value changed: 9007199254740993 -> 9007199254740992
INFO  menu[1]: identical to source: 'Save'
INFO  compared 20 source values, 1 unchanged strings

각 항목은 앱에서 버그로 나타납니다: 빈 이름, 깨진 링크, 복수형이 렌더링될 때의 충돌 또는 원시 메시지, 누락된 챕터 수, 텍스트로 비교되는 한도. INFO 줄은 변경되지 않은 채 돌아온 문자열을 나열합니다. 브랜드 이름처럼 괜찮은 것도 있지만, 프랑스어 메뉴의 "Save"는 아닙니다.

검사기는 키, 값 유형, 비텍스트 값, 플레이스홀더({name}, {{name}}, %s, %1$s, %@), ICU 인수 이름, HTML 태그, URL을 비교합니다. 번역 자체를 판단하지는 않습니다.

복수형 케이스는 바뀌는 것이 정상입니다

ICU 복수형은 복수 범주별로 하나의 메시지를 나열합니다. 영어에는 one과 other 두 범주가 있지만, 많은 언어는 다른 집합을 가집니다. items 메시지의 좋은 러시아어 번역은 보통 one, few, many, other를 사용하고, 일본어 번역은 other만 사용합니다. 따라서 여기서의 불일치가 항상 오류는 아닙니다.

범주는 Unicode CLDR 복수 규칙에서 나오며, other는 모든 언어에 있는 범주입니다. 다음은 CLDR 48이 적용된 Node.js의 Intl.PluralRules가 보고하는 기수 범주입니다:

언어복수 범주
영어, 독일어one, other
프랑스어, 스페인어, 포르투갈어one, many, other
러시아어, 폴란드어one, few, many, other
아랍어zero, one, two, few, many, other
일본어, 중국어, 한국어기타

프랑스어, 스페인어, 포르투갈어는 백만과 같은 매우 큰 정수에만 many를 사용하므로, 작은 수에 대한 메시지에는 보통 필요하지 않습니다. 검사기는 누락된 other를 오류로 보고하고, 그 밖의 범주 변경은 이 표와 비교하도록 경고로 알려줍니다. 원하는 언어의 목록을 직접 출력할 수도 있습니다:

node -e 'console.log(new Intl.PluralRules("ru").resolvedOptions().pluralCategories)'

번역기에는 영어 사례가 제공되므로, 결과를 확인하세요. 러시아어나 아랍어 메시지에 여전히 one과 other만 있다면, 누락된 사례를 직접 추가하거나 번역가에게 맡기세요.

그런 다음 앱에 로드하세요

검사기는 파일이 로드하기에 안전하다는 것을 증명할 뿐, 잘 읽힌다는 것을 증명하지는 않습니다. 앱을 새 언어로 전환하고 화면을 살펴보세요: 독일어나 핀란드어의 긴 단어가 버튼을 넘치지는 않는지, 아랍어나 히브리어의 오른쪽에서 왼쪽으로 쓰는 텍스트, 그리고 코드에서 언어 파일과 별도로 형식화하는 날짜나 숫자도 확인하세요. 시장마다 단어 외에 무엇이 달라지는지는 번역과 현지화 가이드에서 다룹니다.

흔한 문제

번역된 파일이 내 앱에서 로드되지 않습니다. 검사기를 실행해 보세요. 검사기가 파일을 구문 분석할 수 없다면, 번역 후에 파일이 편집되었거나 처음부터 유효하지 않았던 것입니다. 구문 분석이 된다면 키나 유형에 관한 ERROR 줄을 찾아보세요.

문자열이 {count, plural, ...} 같은 원시 ICU 메시지로 표시됩니다. 번역 중에 ICU 구문이 깨진 것으로, 보통 중괄호가 빠졌거나 other 사례가 누락된 경우입니다. 원본에서 구조를 복사하고 각 사례 안의 단어만 번역하세요.

내 파일은 YAML, .po 파일 또는 Markdown입니다. BookTranslator는 이런 형식도 지원하지만, 각 형식에는 고유한 규칙이 있습니다. 문서의 경우 Markdown 번역 가이드를, 웹 페이지의 경우 HTML 파일 가이드를 참고하세요.

새 키만 번역할 수 있나요? 한 번의 업로드에서는 불가능합니다. 파일 전체가 번역됩니다. 새 키를 별도 파일에 넣고 번역한 다음, 결과를 키 기준으로 병합하세요. 검사기가 병합된 파일에 모든 키가 있는지 확인해 줍니다.

키를 번역해야 하나요? 아니요. 키는 코드에서 조회하는 식별자입니다. 값만 사람이 읽을 텍스트입니다.

관련 글