BookTranslator
BookTranslator

태그, 속성, 링크를 깨뜨리지 않고 HTML 파일을 번역하는 방법

HTML 파일에서 번역할 부분, 그대로 둘 부분, 그리고 검증하는 방법. 샘플 페이지, 무료 검사기, 9회 로컬 LLM 테스트 포함.

BookTranslator

BookTranslator Team

27분 읽기

HTML 파일을 번역하려면 사람이 읽는 텍스트와 독자에게 보이는 단어를 담은 속성, 예를 들어 alt, title, placeholder, aria-label, 그리고 페이지의 메타 설명을 번역하세요. 마크업은 그대로 두세요: 태그 이름, href, src, id, class, 스크립트, 스타일, 그리고 translate="no"로 표시된 모든 것. 그런 다음 잊기 쉬운 값 하나를 변경하세요. <html> 요소의 lang 속성입니다. 그래야 브라우저, 스크린 리더, 검색 엔진이 페이지가 이제 어떤 언어인지 알 수 있습니다.

태그를 그대로 유지하는 것은 쉬운 부분이었습니다. 59줄짜리 HTML 도움말 페이지를 로컬 모델로 세 가지 방법을 사용해 중국어 간체로 아홉 번 번역하고, 모든 출력을 스크립트로 검사했습니다. 모든 실행에서 모든 태그, 링크, ID가 유지되었습니다. 문제는 더 조용했습니다. 모델은 전체 파일을 보낸 여섯 번의 실행 모두에서 <pre> 코드 블록 안의 한 줄을 번역했고, 여섯 번 모두에서 내비게이션의 aria-label을 영어로 남겼으며, 설계상 마크업을 가장 안전하게 유지하는 방법인 텍스트 조각을 하나씩 번역하는 방식은 세 번의 실행 모두에서 독자에게 잘못된 행동을 하라고 지시하는 문장을 만들어냈습니다.

빠른 답변: 번역할 것과 그대로 둘 것

파일의 일부샘플에서 가져온 예번역할까요?
보이는 텍스트제목, 단락, 표 셀, 링크 텍스트, <label>예
<title> 및 메타 설명<title>Export Your Library | Shelfmark Help</title>예. 둘 다 검색 결과와 브라우저 탭에 표시됩니다.
og:title 및 기타 소셜 메타 태그<meta property="og:title" content="Export Your Library">예, content 값만
alt, title, placeholder, aria-labelalt="The Export dialog with EPUB selected", aria-label="Breadcrumb"예
<html>의 lang<html lang="en">대상 언어로 변경하세요. 예: zh-Hans
태그 및 속성 이름<a>, <img>, width, aria-label 자체아니요
href, src, id, class, for, name, type, data-*href="#choose-format", id="q"아니요
<code>, <pre>, <script>, <style>shelfmark export --format epub아니요. 코드 주석과 샘플 출력에 대해서는 별도로 결정하세요.
CSS로 설정된 텍스트.note::before { content: "Note: "; }가능하지만 스타일시트에 있으므로 미리 계획하세요
JSON-LD 구조화 데이터"name": "Export your Shelfmark library"name 및 description 값을 번역하고 키와 JSON은 유효하게 유지하세요
translate="no"가 있는 요소<span translate="no">Shelfmark</span>아니요
HTML 주석<!-- Generated by the help-center build... -->선택 사항입니다. 독자는 절대 볼 수 없습니다.
문자 참조&lt;library&gt;, &nbsp;, &copy;이스케이프된 것은 이스케이프된 상태로 유지하세요. &lt;는 실제 <가 되어서는 안 됩니다.

이 목록은 우리가 만든 것이 아닙니다. HTML 표준의 translate 속성 섹션은 "번역 가능한 속성"을 정의합니다: 모든 요소의 title, 이미지의 alt, 입력의 placeholder, 이름이 번역 가능한 것으로 알려진 meta 요소의 content, 그리고 새 언어에 맞게 변경해야 한다고 명시된 lang입니다. 또한 다른 사양에서 추가할 수 있는 속성의 예로 ARIA의 aria-label을 언급하며, translate="no" 요소 내에서는 텍스트와 속성 값이 "페이지를 현지화할 때 그대로 유지되어야 한다"고 명시합니다.

테스트한 내용

우리는 Markdown 번역 테스트에서 사용한 가상의 독서 앱 Shelfmark의 도움말 센터 페이지인 sample-source.html을 작성했습니다. 위 표의 각 행에 대한 예시 하나씩과 몇 가지 함정이 포함되어 있습니다:

  • 인라인 태그로 분할된 문장: "내보내기 전에 노트를 백업하세요. 내보내기는 책을 복사하지만 Shelfmark에서 아무것도 삭제하지 않습니다." 제품 이름은 translate="no" 안에 있습니다.
  • 순서가 중요한 두 개의 인라인 <code> 요소가 있는 문장: "--out 뒤에 <library> 폴더 경로를 입력하세요"
  • 명령과 한 줄의 샘플 출력이 있는 <pre><code> 블록: # Export finished: 128 books
  • 단락 앞에 "Note:"라는 단어를 삽입하는 CSS 규칙
  • #choose-format에 대한 페이지 내 링크, mailto: 링크, 검색 양식, JSON-LD 블록

그런 다음 로컬 모델에 세 가지 방식으로 중국어 간체로 번역하도록 요청했습니다:

  1. 전체 파일. 전체 파일을 붙여넣고 번역된 파일을 반환하도록 요청합니다.
  2. 지시됨. 동일하지만 여덟 가지 규칙을 추가합니다: 모든 태그와 속성 이름 유지, href, src, id, class 및 유사한 값 변경 금지, 나열된 텍스트 속성 및 메타 태그 번역, JSON-LD name 및 description을 제외한 <code>, <pre>, <script>, <style>는 그대로 두기, translate="no" 존중, 문자 참조 및 주석 유지, lang을 zh-Hans로 설정.
  3. 세그먼트. HTML에서 텍스트를 추출하는 도구의 접근 방식: 스크립트가 파일을 태그와 텍스트로 분할하고 <code>, <pre>, <script>, <style> 및 translate="no" 요소를 건너뛰고 나머지 텍스트 조각과 텍스트 속성을 번호가 매겨진 JSON 목록으로 모델에 보낸 다음 번역을 손대지 않은 마크업에 다시 씁니다. 또한 lang="zh-Hans"를 자체적으로 설정합니다.

모델은 LM Studio의 Qwen 3.6 27B (Q4_K_M)였으며, 사고 기능을 끄고 온도 0.3으로 설정했으며, Apple M4 Max 64GB 메모리에서 실행했습니다. 2026년 10월 27일에 각 방법을 세 번씩 총 아홉 번 실행했습니다. 스크립트는 run_llm_html_test.py이며, 아홉 개의 출력 모두 편집 없이 게시되었습니다. README에 모든 파일이 나열되어 있습니다. 로컬 모델을 설정한 적이 없다면 로컬 LLM 번역 가이드에서 설정을 다룹니다.

이것은 하나의 모델, 하나의 언어 쌍, 하나의 페이지에 대한 작은 테스트였습니다. 어떤 문제가 발생하는지, 어떻게 잡아낼 수 있는지를 보여줍니다. 모델이나 도구의 순위를 매기는 것은 아닙니다.

결과: 마크업은 살아남았지만 세 가지 결정은 그렇지 않았습니다

우리는 check_html_translation.py로 모든 출력을 확인했습니다. 이 검사기는 Python 표준 라이브러리만 사용합니다. 전체 출력은 check-results.txt에 있으며, results.csv에는 실행당 한 행씩 기록되어 있습니다.

검사전체 파일 (3회 실행)지시됨 (3회 실행)세그먼트 (3회 실행)
동일한 58개 태그가 순서대로 유지됨; 모든 href, src, id, class 변경 없음3/33/33/3
translate="no" 텍스트 유지됨, 페이지 내 링크 여전히 작동, &lt;library&gt; 여전히 이스케이프됨3/33/33/3
<pre> 내부의 샘플 출력 줄 번역됨3/33/30/3 (설계상 건너뜀)
CSS content: "Note: " 번역됨3/30/30/3 (설계상 건너뜀)
JSON-LD name 및 description 번역됨3/33/33개 중 0개 (설계상 건너뜀)
aria-label="Breadcrumb" 번역됨3개 중 0개3개 중 0개3개 중 3개
인라인 <code> 요소 두 개가 포함된 문장이 올바른 의미를 전달함3개 중 3개3개 중 3개3개 중 0개
인라인 태그를 사이에 둔 중국어 텍스트 사이의 공백없음0, 5, 4모든 실행에서 6개
<html>의 langzh-CN (요청되지 않음)zh-Hanszh-Hans (스크립트에 의해 설정됨)

아홉 번의 실행 모두 동일한 58개의 태그를 동일한 순서로 유지했고, 모든 href, src, id, class, 페이지 내 링크 대상, 주석, 이스케이프된 &lt;library&gt;, 그리고 translate="no" 안의 제품 이름도 그대로 유지했습니다. 단순한 페이지의 마크업을 모델이 망가뜨릴까 걱정했다면, 그런 일은 일어나지 않았습니다. 문제는 모두 무엇을 번역할지에 있었습니다.

1. <pre> 내부의 샘플 출력이 번역됨

샘플의 코드 블록은 프로그램 출력 한 줄로 끝납니다:

<pre><code>shelfmark export --format epub --covers --out ~/Books/export
# Export finished: 128 books</code></pre>

전체 파일 및 지시된 실행 여섯 번 모두에서 모델은 해당 줄에 # 导出完成:128 本书를 반환했으며, 여기에는 <pre> 내부의 내용을 변경하지 말라고 프롬프트에 명시된 세 번의 실행도 포함됩니다. 명령 자체는 전혀 건드리지 않았습니다. 모델은 그 줄을 주석으로 간주했고, 튜토리얼에서는 주석을 번역하는 것이 합리적인 선택입니다. 하지만 이 줄은 프로그램이 출력하는 내용이며, 이를 자신의 터미널과 비교하는 독자는 영어를 보게 됩니다. 번역하기 전에 샘플 출력과 코드 주석을 번역할지 결정하고 도구에 알리거나, <pre> 블록을 완전히 보호하세요.

전체 파일 실행에서는 <style> 블록의 CSS content: "Note: "도 번역한 반면, <style>을 그대로 두라는 지시를 받은 실행에서는 영어로 유지했습니다. 어느 쪽도 그 자체로는 틀리지 않습니다. 스타일시트를 번역하는 것은 CSS가 인라인인 페이지에서는 올바르고, 하나의 공유 스타일시트가 모든 언어를 제공하는 사이트에서는 잘못입니다. 어느 쪽이든 모델이 추측할 세부 사항이 아니라 누군가가 내려야 할 결정입니다.

2. 접근성 레이블이 건너뛰어짐

브레드크럼 탐색은 <nav aria-label="Breadcrumb">입니다. 화면 읽기 프로그램은 해당 레이블을 읽어 주지만, 눈으로 보는 독자는 결코 볼 수 없습니다. 전체 파일 및 지시된 실행 여섯 번 모두에서 이 레이블은 영어로 유지되었으며, 지시된 프롬프트에 번역할 속성으로 aria-label이 나열되었음에도 그랬습니다. 한 지시된 실행에서는 검색 버튼의 aria-label="Search"도 영어로 유지되었습니다.

세그먼트 방식은 매번 이를 번역했습니다. 스크립트가 모든 aria-label, alt, title, placeholder 값을 수집하여 각각을 명시적으로 요청했기 때문입니다. 이것이 모델이 알아채기를 바라기보다 텍스트 속성을 의도적으로 추출해야 한다는 가장 강력한 근거입니다.

3. 조각 단위 번역은 태그는 지켰지만 문장을 망가뜨렸다

세그먼트 방식은 구조적으로 마크업에 안전합니다. 모델이 태그를 전혀 보지 못하므로 태그를 손상시킬 수 없습니다. 세 번의 실행 모두에서 모든 구조 검사를 통과했습니다. 하지만 이 테스트에서 독자에게 잘못된 지시를 준 유일한 번역이기도 했습니다.

영어 문장은 "Type the path to the <library> folder after --out."입니다. 인라인 <code>이 이 문장을 세 조각으로 나눕니다: "Type the path to the", "folder after", 그리고 마침표입니다. 중국어는 이 단어들의 순서가 다르기 때문에 번역자는 두 코드 요소를 이동해야 합니다. 세그먼트 방식은 이를 이동할 수 없습니다. 세 번의 실행 모두 지시를 반대로 바꿨습니다. 실행 2는 다음과 같이 출력했습니다(실행 1과 3은 之后 대신 后를 사용했으며 의미는 같습니다):

在 <code>&lt;library&gt;</code> 之后输入 <code>--out</code>文件夹的路径。

이는 "<library> 뒤에 --out 폴더의 경로를 입력하세요"로 읽히며, 원래 지시와 반대입니다. 전체 파일 및 지시된 실행 여섯 번 모두 코드 요소를 이동하여 올바르게 처리했습니다:

在 <code>--out</code> 后面输入 <code>&lt;library&gt;</code> 文件夹的路径。

같은 분할 방식 때문에 태그와 중국어 텍스트가 만나는 곳에 불필요한 공백이 남았습니다. 예를 들어 前往 <b>设置</b> 및 <b>书库</b> ,와 같이 세그먼트 실행 세 번 모두에서 실행당 여섯 군데에 나타났습니다. 브라우저에서는 중국어 문장 부호 앞을 포함해 눈에 보이는 간격으로 표시됩니다. 전체 파일 실행에서는 전혀 없었고, 지시된 실행 세 번 중 두 번에서는 네다섯 군데 있었습니다.

구조 검사기는 뒤바뀐 문장을 잡아낼 수 없습니다. 모든 태그가 원래 있던 자리에 정확히 있기 때문입니다. 우리 검사기는 인라인 코드의 순서가 바뀌면 보고하는데, 이는 많은 언어에서 정상적인 현상이므로 그 경고만으로는 옳고 그름을 판단할 수 없습니다. 이 테스트에서 검사기는 올바른 번역 여섯 개 모두에서 경고를 냈고, 잘못된 번역 세 개에서는 조용했습니다. 잘못된 번역이 영어 어순을 유지했기 때문입니다. 문장을 읽는 사람만이 판별할 수 있습니다. 해결책은 인라인 태그를 포함한 완전한 문장을 번역기에 보내는 것이며, 이는 전체 파일 실행이 한 방식이고, 그 후에 구조를 검사하는 것입니다.

더 작은 차이점

  • lang: 전체 파일 실행은 스스로 zh-CN으로 변경했습니다. 지시된 실행은 요청대로 zh-Hans를 사용했습니다. 둘 다 유효한 언어 태그입니다. zh-Hans는 문자 체계(간체 중국어)를, zh-CN은 지역을 나타냅니다. W3C의 언어 선언 지침은 <html> 요소에 언어를 선언하고 Content-Language 메타 태그로 선언하지 말 것을 권장합니다.
  • JSON-LD: 전체 파일 및 지시된 실행은 name과 description 값을 번역하고 JSON을 유효하게 유지했습니다. 우리 세그먼트 스크립트는 <script> 콘텐츠를 건너뛰었기 때문에 구조화 데이터가 영어로 남았습니다. 이는 스크립트를 무시하는 도구도 마찬가지로 동작합니다.
  • 문자 참조: 전체 파일 및 지시된 실행은 &rsaquo;, &nbsp;, &copy;, 그리고 이모지 참조를 작성된 그대로 유지했습니다. 그 여섯 번의 실행 중 다섯 번은 범위 2&ndash;5를 중국어 단어(2 到 5 또는 2 至 5)로 다시 썼습니다. 한 지시된 실행은 프롬프트에서 참조를 유지하라고 했음에도 참조 대신 일반 대시를 썼습니다. 우리 세그먼트 스크립트는 모두 디코딩하고 문자를 다시 썼는데, 표시는 동일합니다. 중요한 것 하나인 <code> 내부의 &lt;library&gt;는 아홉 번의 실행 모두에서 이스케이프된 상태로 유지되었습니다.

번역 전: 파일 준비

  1. 변경되면 안 되는 것을 표시하세요. 제품 이름, 모델 번호, 산문 속 코드 같은 문자열, 그리고 그대로 유지해야 하는 모든 것에 translate="no"를 붙이세요. 이는 HTML의 일부이며 비용이 들지 않고, 전체 파일을 보낸 여섯 번의 실행 모두에서 모델이 이를 존중했습니다. 우리 스크립트처럼 이를 읽는 세그먼트 도구는 해당 요소를 완전히 건너뜁니다. 번역하지 말아야 할 목록 안내에서 목록에 무엇을 올릴지 결정하는 방법을 다룹니다.
  2. 코드 정책을 결정하세요. 코드 주석을 번역할까요? 샘플 출력은? 스타일시트를 언어 간에 공유할까요? 답을 변경되면 안 되는 이름과 함께 번역 용어집에 적고, 번역하는 사람이나 도구에 제공하세요.
  3. ID는 원문 언어로 유지하세요. 페이지 내 링크는 id 값을 가리킵니다. 누군가 id를 번역하면 그에 대한 모든 링크가 깨집니다. 우리 실행에서는 아무도 그렇게 하지 않았지만, "친절하게" 앵커를 현지화하는 번역자는 그렇게 할 것입니다.
  4. 숨은 텍스트를 수집하세요. 단어를 담는 속성들, 즉 alt, title, placeholder, aria-label, 버튼의 value, 메타 태그, JSON-LD를 나열하여 검사되고 잊히지 않도록 하세요.
  5. JavaScript 문자열을 제자리에서 번역하지 마세요. 스크립트로 설정된 메시지(예: 경고나 코드에서 생성된 버튼 레이블)는 코드 내에서 별도의 현지화 단계가 필요합니다. 전체 파일 또는 지시된 실행 여섯 번 중 어느 것도 우리 스크립트 블록을 변경하지 않았는데, 이는 올바르지만 그 문자열들은 영어로 남는다는 뜻입니다.

번역 후: 검사하기

  1. 구조 검사를 실행하세요.

    python3 check_html_translation.py source.html translated.html --lang zh-Hans --keep Shelfmark
    

    태그와 그 순서, 독자에게 보이는 텍스트가 아닌 모든 속성, <pre>, <script>, <style>의 내용, 인라인 코드, JSON-LD 키, translate="no" 텍스트, 주석, 페이지 내 링크를 비교합니다. 여전히 영어인 텍스트 속성, 예상과 다른 lang 값, 번역되지 않은 것처럼 보이는 가시 텍스트, 순서가 바뀐 인라인 코드, 인라인 태그를 사이에 둔 중국어·일본어·한국어 텍스트 사이의 공백에 대해 경고합니다. 번역 품질은 판단하지 않습니다.

  2. 인라인 태그가 포함된 모든 문장을 읽으세요. 우리 테스트에서 의미가 깨진 곳이 바로 여기입니다. 검사기의 "인라인 코드 순서 변경" 경고에 의존하여 이 문장들을 찾지 마세요. 우리 테스트에서는 원래 순서를 유지한 번역이 잘못된 번역이었습니다.

  3. 브라우저에서 페이지를 여세요. 페이지 내 링크를 클릭하고, 가능하면 스크린 리더로 폼을 탭 이동하며, 굵은 텍스트와 링크된 텍스트 주변의 간격을 확인하세요.

  4. 오른쪽에서 왼쪽으로 쓰는 언어의 경우, <html> 요소에 dir="rtl"도 설정하세요. RTL 도서 번역 안내에서 그 밖에 달라지는 점을 다룹니다.

  5. XHTML의 경우, 예를 들어 EPUB 내부의 장 파일은 파일이 여전히 잘 구성된 XML인지도 확인하세요. 예: xmllint --noout chapter.xhtml. 브라우저는 닫히지 않은 태그를 용서하지만 전자책 리더기는 그렇지 않을 수 있습니다. EPUB 번역 QA 체크리스트에서 전자책의 나머지 부분을 다룹니다.

BookTranslator가 적합한 곳

BookTranslator의 업로드 페이지는 책과 문서 외에도 .html, .htm, .xhtml 파일을 지원하므로, 저장된 페이지나 정적 도움말 문서를 통째로 업로드할 수 있습니다. 이 글을 위해 해당 샘플을 BookTranslator로 실행해 보지는 않았고, 인라인 코드 순서, CSS 콘텐츠, JSON-LD를 어떻게 처리하는지도 테스트하지 않았으므로, 다른 도구와 마찬가지로 출력 결과를 확인하시기 바랍니다. 결제 전에 가격을 확인할 수 있습니다.

무엇을 사용하든, 번역 전에 준비 단계를 수행하고 번역 후에 검사를 하세요. 모든 태그가 그대로인 페이지라도 독자에게 잘못된 내용을 입력하라고 안내할 수 있습니다. HTML이 사실상 마크다운으로 작성된 문서라면, 마크다운 번역 가이드에서 원본 형식을 다룹니다.

테스트 파일 받기

이 테스트의 모든 자료는 자유롭게 재사용할 수 있습니다:

관련 글