번역 문서 포맷 깨짐 원인 및 해결안
This content is not available in your language yet.
번역 문서 포맷 깨짐 원인 및 해결안
섹션 제목: “번역 문서 포맷 깨짐 원인 및 해결안”번역된 영어 문서(en)가 한국어 원본(ko)과 비교했을 때 마크다운 포맷이 깨져 렌더링됩니다.
비교 대상
섹션 제목: “비교 대상”| 문서 | KO 원본 (예시) | EN 번역본 (문제) |
|---|---|---|
| seeds-api | 각 문단·리스트 항목이 줄 단위로 구분, ---·## 전후 빈 줄 유지 | 본문이 한두 줄로 합쳐짐, 리스트가 한 줄에 이어짐 |
| data-management-api | 동일 | 동일 |
| infrastructure-api | 동일 | 동일 |
| category-reference | 테이블·헤딩이 줄 단위 유지 | 테이블 행·헤딩이 한 줄에 합쳐짐 |
| output-json-scheme | 코드 블록·헤딩·테이블 구조 유지 | `## Pipeline Stages ```` 처럼 헤딩과 코드 블록이 한 줄에 붙음 |
구체적 차이 (KO vs EN 소스)
섹션 제목: “구체적 차이 (KO vs EN 소스)”KO (seeds-api.md) — 정상:
엔진 핵심 인터페이스: ...
---
## Health
시스템 상태 확인용 ...
- **GET /health** — 기본 헬스체크.- **GET /health/ready** — 레디니스.- **GET /health/live** — 라이브니스.
루트 **GET /** ...EN (seeds-api.md) — 깨짐:
Engine Core Interfaces: ... --- ## Health System status verification ...- **GET /health** — Basic health check. - **GET /health/ready** — Readiness. - **GET /health/live** — Liveness. The root **GET /** ...즉, 단일 줄바꿈(\n)과 빈 줄(\n\n)이 번역 결과에서 사라지거나 한 줄로 합쳐지는 형태입니다.
원인 분석
섹션 제목: “원인 분석”1. 번역 파이프라인 구조
섹션 제목: “1. 번역 파이프라인 구조”scripts/translate_docs.py: KO 마크다운을 읽어 본문(body)만 추출 → 코드 블록 치환 → 본문 전체(또는 섹션 단위)를 DeepL에 한 번에 전달 → 치환 복원·후처리(---]→---) → EN 파일로 저장.scripts/translate/engine.py: 긴 텍스트는\n\n+기준으로 청크 분할 후 각 청크 번역, 결과를"".join(translated)로 이어붙임.
2. 근본 원인: DeepL API의 줄바꿈 정규화
섹션 제목: “2. 근본 원인: DeepL API의 줄바꿈 정규화”- DeepL이 번역 시 단일 줄바꿈·공백을 정규화하는 것으로 보임.
preserve_formatting=True를 켜도, 실제 동작에서는:- 문단 내 단일
\n(리스트 항목 구분,---전후,##전후)이 무시되거나 - 여러 줄이 한 줄로 합쳐지는 현상이 발생.
- 문단 내 단일
- 따라서 입력의 “줄 구조”가 출력에서 유지되지 않음. 마크다운은 줄 단위 문법이라 이렇게 되면 포맷이 붕괴됨.
3. 현재 스크립트의 한계
섹션 제목: “3. 현재 스크립트의 한계”- Full 모드: 본문 전체를 한 번에(또는
\n\n+청크만 나눠서) DeepL에 보냄. DeepL이 줄을 합치면 그대로 EN 파일에 기록됨. - Incremental 모드:
##기준으로 섹션을 나누어 섹션별로 번역하지만, 섹션 내부의 줄바꿈은 여전히 DeepL에 맡기므로 섹션 안에서 같은 현상 발생. - 후처리:
---]→---만 하고, 줄바꿈 복구나 구조 복원 로직은 없음.
DeepL API XML 태그 처리 전략 (공식 권장)
섹션 제목: “DeepL API XML 태그 처리 전략 (공식 권장)”DeepL API를 직접 사용할 때, 번역하지 말아야 할 부분을 보호하고 구조를 유지하려면 XML 태그 처리를 사용하는 것이 권장됩니다.
핵심 설정
섹션 제목: “핵심 설정”- tag_handling: API 호출 시
tag_handling="xml"(또는tag_handling=xml)을 설정합니다. API는 태그 바깥의 자연어만 골라 번역합니다. - ignore_tags:
<ignore>와 같은 커스텀 XML 태그로 감싼 영역은 번역·변형되지 않습니다.ignore_tags=["ignore"](또는ignore_tags=ignore)로 전달합니다. - non_splitting_tags: 특정 서식이 포함된 문장이 쪼개지지 않도록 보호할 때 사용합니다. 문장 분할을 막고 싶은 태그를 지정합니다.
전처리: 번역 제외 영역 감싸기
섹션 제목: “전처리: 번역 제외 영역 감싸기”번역 전에 다음 요소를 <ignore>...</ignore>(또는 <keep>...</keep> 등)로 감싸서 전송합니다.
| 대상 | 방법 |
|---|---|
| YAML 프론트매터 | 문서 최상단 --- … --- 블록 전체를 <ignore>로 감싼 뒤 전송. 또는 정규식으로 분리해 텍스트 값(title, description 등)만 번역하고, 나머지는 번역 대상에서 제외한 뒤 나중에 합치는 방식 권장. |
| 코드 블록 | ``` … ``` 전체를 <ignore>로 감싸 코드가 번역·이동되지 않게 합니다. |
| 인라인 코드 | `…` 를 <ignore>(또는 <keep>)로 감싸 API가 내용을 번역하지 않도록 합니다. |
후처리
섹션 제목: “후처리”- API 응답에서
<ignore>(또는 사용한 태그)를 제거하고, 원래 마크다운 형태만 남깁니다. (태그 제거 시replace("<ignore>", "").replace("</ignore>", "")등.)
주의사항
섹션 제목: “주의사항”- YAML 프론트매터:
title,date등 설정값은 정규식으로 따로 분리해 값만 번역하거나, 아예 번역 대상에서 제외한 뒤 나중에 합치는 방식을 권장합니다. - 문장 부호·리스트: 마크다운 리스트 기호(
-,*) 뒤에 공백이 없으면 DeepL이 문장 일부로 오인할 수 있으므로, 전처리에서 공백 유무를 점검하는 것이 좋습니다. - 복잡한 구조: 이미지
에서 alt만 번역하려면 별도 정규식이 필요합니다. 하이퍼링크 텍스트만 번역하는 경우도 동일합니다.
XML 전략과 “줄바꿈 붕괴”와의 관계
섹션 제목: “XML 전략과 “줄바꿈 붕괴”와의 관계”- ignore_tags로 코드 블록·프론트매터·인라인 코드를 보호하면, 해당 부분은 변형되지 않습니다.
- 다만 줄바꿈 붕괴는 번역되는 자연어 구간에서 발생합니다. DeepL이 태그 바깥 텍스트를 번역할 때 단일 줄바꿈·빈 줄을 정규화해 합쳐버리므로, XML 태그만으로는 줄 구조 복원이 보장되지 않습니다.
- 따라서 XML 태그 처리는 “번역 제외 영역 보호”의 필수 수단으로 두고, 줄 구조 유지는 아래 방안 A(줄바꿈 마스킹) 또는 non_splitting_tags 실험과 조합하는 것을 권장합니다.
해결 방안
섹션 제목: “해결 방안”방안 E: DeepL XML 태그 처리 (번역 제외 영역 보호) — 필수 적용
섹션 제목: “방안 E: DeepL XML 태그 처리 (번역 제외 영역 보호) — 필수 적용”- 원리: API 호출 시
tag_handling="xml",ignore_tags=["ignore"]사용. 전처리에서 YAML 프론트매터, 코드 블록``` … ```, 인라인 코드`…`를<ignore>로 감싼 뒤 전송하고, 응답에서 태그만 제거해 복원. - 장점: DeepL 공식 권장 방식. 코드·설정값이 번역되거나 깨지는 것을 원천적으로 막을 수 있음.
- 단점: 줄바꿈 붕괴(자연어 구간의 줄 합침)는 그대로일 수 있음. 구조 유지를 위해 non_splitting_tags 실험이나 방안 A와 병행 필요.
- 구현 포인트:
- 전처리: 정규식으로
---\n.*?\n---,```.*?```,`[^`]+`를 찾아<ignore>…</ignore>로 감싼 문자열을 만든 뒤 DeepL에 전달. - 후처리:
result.text에서<ignore>,</ignore>제거. - (선택) YAML은 파싱 후
title/description등 값만 번역하고, 나머지는 그대로 두고 합치는 방식으로 분리 처리.
- 전처리: 정규식으로
방안 A: 줄바꿈 마스킹 (구조 유지용 권장)
섹션 제목: “방안 A: 줄바꿈 마스킹 (구조 유지용 권장)”- 원리: DeepL에 넘기기 전에 단일 줄바꿈을 고유 플레이스홀더(예:
___NL___)로 치환, 번역 후 결과에서 플레이스홀더를\n으로 복원. - 장점: 구현이 단순하고, 기존 “코드 블록 치환/복원” 패턴과 동일하게 확장 가능. DeepL이 줄을 합쳐도 복원 시 구조가 살아남음.
- 단점: 플레이스홀더가 번역되거나 깨지지 않도록 토큰을 신중히 선택해야 함(이미 사용 중인
___MD_BLOCK_·___GLOSSARY_와 구분, 문장 중간에 나올 가능성 낮은 문자열). - 구현 포인트:
- 본문에서 코드 블록(또는
<ignore>처리된 영역) 추출 후, 남은 본문에서\n→ 플레이스홀더 치환 (빈 줄\n\n은 별도 플레이스홀더 시퀀스로 유지). - DeepL 호출 후 플레이스홀더 →
\n복원. - 방안 E와 병행 시:
<ignore>감싸기 → 그 바깥 텍스트에만 줄바꿈 마스킹 적용 후 API 호출 → 복원 순서로 처리.
- 본문에서 코드 블록(또는
방안 B: 문단/블록 단위 번역 후 재조합
섹션 제목: “방안 B: 문단/블록 단위 번역 후 재조합”- 원리: 본문을 “빈 줄로 구분된 블록” 단위로 쪼개서, 각 블록만 DeepL에 넘기고, 결과를 원본 순서대로 원래 줄바꿈 개수에 맞춰 이어붙임.
- 장점: 블록 사이의 빈 줄은 우리가 강제로 넣을 수 있음.
- 단점: 블록 내부 줄바꿈은 여전히 DeepL에 의존. 블록이 너무 작으면 번역 품질 하락.
방안 C: 번역 후 마크다운 구조 후처리 (휴리스틱)
섹션 제목: “방안 C: 번역 후 마크다운 구조 후처리 (휴리스틱)”- 원리: 번역 결과 문자열에서
---,##,- **등 패턴을 찾아, 그 앞에 줄바꿈을 삽입하는 규칙을 적용. - 장점: API/전처리는 건드리지 않아도 됨.
- 단점: 예외 케이스 많음. 유지보수 부담과 오동작 위험.
방안 D: 마크다운 AST 기반 번역
섹션 제목: “방안 D: 마크다운 AST 기반 번역”- 원리: mdast 등으로 파싱 후 “텍스트 노드”만 추출해 번역하고, 나머지 구조는 그대로 유지.
- 장점: 포맷 붕괴를 원천적으로 막을 수 있음.
- 단점: 구현 비용이 큼.
권장 조합
섹션 제목: “권장 조합”-
필수: 방안 E(DeepL XML 태그 처리) 적용
tag_handling="xml",ignore_tags=["ignore"]사용.- 전처리: YAML 프론트매터, 코드 블록
``` … ```, 인라인 코드`…`를<ignore>로 감싼 뒤 전송. - 후처리: 응답에서
<ignore>,</ignore>제거. - YAML은 값만 번역하거나 번역 대상에서 제외 후 합치는 방식 권장. 리스트 기호(
-,*) 뒤 공백 점검.
-
구조 유지: 방안 A(줄바꿈 마스킹) 또는 non_splitting_tags 실험
- XML로 보호한 뒤에도 자연어 구간에서 줄이 합쳐지면, 본문 내
\n→ 플레이스홀더 치환·복원(방안 A)을 적용. - 또는 DeepL의
non_splitting_tags로 구조 단위(예: 커스텀 태그로 감싼 “한 줄”)가 쪼개지지 않게 하는 방법을 시험.
- XML로 보호한 뒤에도 자연어 구간에서 줄이 합쳐지면, 본문 내
-
검증: v1 API 문서 5개(seeds-api, data-management-api, infrastructure-api, category-reference, output-json-scheme)에 대해 KO→EN 번역을 다시 수행한 뒤, EN 마크다운이 KO와 동일한 줄/단락 구조를 갖는지 확인.
-
기존 유지:
---]→---후처리는 그대로 유지. -
소스 언어 고정:
source_lang="KO"명시- 자동 감지 시 영어로 오인해 원문 그대로 반환(identical to source)하는 문제 방지.
- API 호출 시 항상 KO → EN-US로 고정.
-
XML 유효성:
<ignore>바깥 텍스트에서&,<,>이스케이프- 예: “Research & Seed” → “Research & Seed” (전송 전).
- 응답 후
&,<,>복원. - “Tag handling parsing failed, invalid token” 방지.
구현 현황
섹션 제목: “구현 현황”다음이 코드에 반영되어 있습니다.
| 항목 | 구현 위치 |
|---|---|
| 방안 E (XML 태그 처리) | translate_docs.py: _wrap_body_for_xml() — 코드 블록·인라인 코드를 <ignore>로 감싼 뒤 전송. tag_handling="xml", ignore_tags=["ignore"] 전달. |
| 방안 A (줄바꿈 마스킹) | translate_docs.py: _mask_newlines() / _unmask_newlines() — \n ↔ ___NL___ 치환·복원. |
| XML 이스케이프 | translate_docs.py: _escape_xml_outside_ignore(), _unescape_xml_text() — <ignore> 밖의 &, <, > 이스케이프 및 응답 후 복원. |
| source_lang=KO | engine.py: translate_one/translate_text에 source_lang 인자. translate_docs.py: SOURCE_LANG_KO = "KO", 모든 번역 호출에 전달. |
| 후처리 | _strip_ignore_tags(), _unescape_xml_text(), _postprocess_translated_body() (---] → ---). |
- translate-horizontal-rule-artifact —
---]아티팩트 원인 및 후처리. - 번역 스크립트:
scripts/translate_docs.py,scripts/translate/engine.py,scripts/translate/placeholders.py. - DeepL API:
tag_handling,ignore_tags,non_splitting_tags— DeepL API 문서 (XML Handling 가이드).