Skip to content

Commit a8835d7

Browse files
committed
fix(translate): 이미지 마크다운 전체를 placeholder로 보호 [skip-notion]
DeepL(tag_handling=html)이 self-closing 태그 뒤 닫는 괄호를 다음 줄로 밀어내 이미지 구문이 깨지는 문제. URL만 감추던 방식을 ![alt](url) 전체를 감추는 방식으로 변경하고 _protect_images/_restore_images로 분리. 테스트 3건 추가.
1 parent 7b97e50 commit a8835d7

2 files changed

Lines changed: 67 additions & 11 deletions

File tree

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import os
2+
import re
3+
import sys
4+
5+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
6+
import translate_to_en as T
7+
8+
9+
def test_protect_images_hides_parens_from_translation():
10+
"""이미지 마크다운 전체가 placeholder로 치환되어 DeepL이 괄호를 못 본다."""
11+
body = "본문\n\n:::\n\n![image](/img/blog/x/img-00.png)\n\n뒷 문장"
12+
protected, store = T._protect_images(body)
13+
assert "![image](" not in protected # 괄호가 노출되지 않음
14+
assert "/img/blog/x/img-00.png" not in protected
15+
assert len(store) == 1
16+
17+
18+
def test_restore_images_survives_deepl_paren_relocation():
19+
"""DeepL이 self-closing placeholder 앞뒤로 빈 줄을 삽입해도(실제 버그 재현)
20+
복원 후 이미지 마크다운이 한 줄로 온전히 살아난다."""
21+
body = "본문\n\n:::\n\n![image](/img/blog/x/img-00.png)\n\n뒷 문장"
22+
protected, store = T._protect_images(body)
23+
key = next(iter(store))
24+
# DeepL이 태그를 다른 줄로 밀어낸 상황 시뮬레이션
25+
deepl_out = protected.replace(key, f"\n\n{key}\n\n")
26+
restored = T._restore_images(deepl_out, store)
27+
assert "![image](/img/blog/x/img-00.png)" in restored
28+
# 닫는 괄호 없이 끊긴 이미지 라인이 없어야 함
29+
assert not re.search(r"!\[[^\]]*\]\([^)\n]*$", restored, re.MULTILINE)
30+
31+
32+
def test_protect_images_multiple_and_korean_path():
33+
"""여러 이미지 + 한글 경로 보존."""
34+
body = (
35+
"![image](/img/blog/09-맥락-기반/img-00.png)\n\n"
36+
"가운데\n\n"
37+
"![image](/img/blog/09-맥락-기반/img-01.png)"
38+
)
39+
protected, store = T._protect_images(body)
40+
assert len(store) == 2
41+
restored = T._restore_images(protected, store)
42+
assert restored == body

scripts/translate_to_en.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,27 @@ def _restore_code_blocks(body, store):
6565
return body
6666

6767

68+
def _protect_images(body):
69+
"""이미지 마크다운 전체(![alt](/url))를 placeholder 태그로 보호.
70+
URL만 보호하면 DeepL(tag_handling=html)이 self-closing 태그 뒤 ) 를
71+
다음 줄로 밀어내 이미지 구문을 깨뜨린다 — 구문 전체를 감춰 괄호를 못 보게 한다."""
72+
store = {}
73+
74+
def _sub(m):
75+
key = f'<x id="IMG{len(store)}"/>'
76+
store[key] = m.group(0)
77+
return key
78+
79+
protected = re.sub(r'!\[[^\]]*\]\((/[^)]+)\)', _sub, body)
80+
return protected, store
81+
82+
83+
def _restore_images(body, store):
84+
for key, val in store.items():
85+
body = body.replace(key, val)
86+
return body
87+
88+
6889
def _protect_inline_code(body):
6990
store = {}
7091
result = []
@@ -384,15 +405,9 @@ def _protect_ol(m: re.Match) -> str:
384405
return f'{m.group(1)}{key}. ' # group 1 = leading spaces
385406

386407
body_protected = re.sub(r'(?m)^( *)(\d+)\. ', _protect_ol, body_protected)
387-
# Protect image URLs — DeepL translates Korean path segments (e.g. /img/milvus-설치-.../)
388-
_img_store: dict[str, str] = {}
389-
390-
def _protect_img(m: re.Match) -> str:
391-
key = f'<x id="IMG{len(_img_store)}"/>'
392-
_img_store[key] = m.group(2)
393-
return f'![{m.group(1)}]({key})'
394-
395-
body_protected = re.sub(r'!\[([^\]]*)\]\((/[^)]+)\)', _protect_img, body_protected)
408+
# Protect images — hide the whole ![alt](/url) so DeepL can't translate Korean
409+
# path segments nor relocate the closing ) onto a new line (breaks the image)
410+
body_protected, _img_store = _protect_images(body_protected)
396411
translated = translate_with_deepl(body_protected) if body_no_inline.strip() else body_protected
397412
for key, num in _ol_store.items():
398413
translated = translated.replace(key, num)
@@ -431,8 +446,7 @@ def _protect_img(m: re.Match) -> str:
431446
en_body = _restore_code_blocks(en_body, code_store)
432447
for key, val in _bq_store.items():
433448
en_body = en_body.replace(key, val)
434-
for key, url in _img_store.items():
435-
en_body = en_body.replace(key, url)
449+
en_body = _restore_images(en_body, _img_store)
436450
# Safety net: DeepL relocates blockquote '> ' marker mid-sentence
437451
# e.g. "If an> `code` ..." → "> If an `code` ..."
438452
en_body = re.sub(r'^([A-Za-z][^>\n]*\S)(> +)(\S)', r'> \1 \3', en_body, flags=re.MULTILINE)

0 commit comments

Comments
 (0)