如何翻譯 HTML 檔案而不破壞標籤、屬性與連結
HTML 檔案中哪些內容該翻譯、哪些該保留,以及如何檢查。附範例頁面、免費檢查工具,以及 9 次本機 LLM 測試。

要翻譯 HTML 檔案,請翻譯讀者會看到的文字,以及承載讀者可見詞彙的屬性,例如 alt、title、placeholder、aria-label,還有頁面的 meta description。標記本身請保持原樣:標籤名稱、href、src、id、class、指令碼、樣式,以及任何標記為 translate="no" 的內容。接著修改一個容易忘記的值,也就是 <html> 元素上的 lang 屬性,讓瀏覽器、螢幕閱讀器和搜尋引擎知道頁面現在使用的是哪種語言。
保持標籤完整,結果證明是簡單的部分。我們用本機模型將一個 59 行的 HTML 說明頁面翻譯成簡體中文九次,使用三種方法,並用指令碼檢查每一份輸出。每一次執行都保留了所有標籤、連結和 ID。出問題的地方更不明顯:模型在全部六次傳送整個檔案的執行中,翻譯了 <pre> 程式碼區塊內的一行;在全部六次中,它把導覽的 aria-label 留成英文;而設計上最能確保標記安全的方法——逐段翻譯文字片段——在它的全部三次執行中,都產生了告訴讀者做錯事的句子。
快速解答:哪些該翻譯、哪些該保留
| 檔案部分 | 我們範例中的例子 | 要翻譯嗎? |
|---|---|---|
| 可見文字 | 標題、段落、表格儲存格、連結文字、<label> | 是 |
<title> 與 meta description | <title>Export Your Library | Shelfmark Help</title> | 是。兩者都會出現在搜尋結果和瀏覽器分頁中。 |
og:title 和其他社交媒體 meta 標籤 | <meta property="og:title" content="Export Your Library"> | 是,僅限 content 值 |
alt, title, placeholder, aria-label | alt="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... --> | 選用。讀者永遠看不到它們。 |
| 字元參照 | <library>, , © | 保持轉義的內容轉義。< 不得變成真正的 <。 |
這份清單並非我們發明。HTML 標準中關於 translate 屬性的章節定義了「可翻譯屬性」:每個元素上的 title、圖片上的 alt、輸入框上的 placeholder、名稱已知可翻譯的 meta 元素上的 content,以及 lang,該章節指出必須將其變更為符合新語言。它還將 ARIA 的 aria-label 列為其他規範可以新增的屬性範例,並指出在 translate="no" 元素中,文字和屬性值「在頁面本地化時應保持原樣」。
我們測試了什麼
我們撰寫了 sample-source.html,這是一個虛構閱讀應用程式 Shelfmark 的說明中心頁面,與我們在 Markdown 翻譯測試中使用的是同一個應用程式。它包含了上表中每一列的一個範例,外加幾個陷阱:
- 一個被行內標籤分割的句子:「在匯出之前,備份你的筆記。匯出會複製你的書籍,但不會從 Shelfmark 刪除任何內容」,其中產品名稱位於
translate="no"內; - 一個包含兩個行內
<code>元素且順序至關重要的句子:「在--out之後輸入<library>資料夾的路徑」; - 一個
<pre><code>區塊,包含一個命令和一行範例輸出:# Export finished: 128 books; - 一條在段落前插入「注意:」一詞的 CSS 規則;
- 一個指向
#choose-format的頁內連結、一個mailto:連結、一個搜尋表單,以及一個 JSON-LD 區塊。
然後我們要求一個本地模型以三種方式將其翻譯成簡體中文:
- 整個檔案。 貼上整個檔案並要求返回翻譯後的檔案。
- 指令式。 相同,外加八條規則:保留每個標籤和屬性名稱,不變更
href、src、id、class及類似值,翻譯列出的文字屬性和 meta 標籤,保留<code>、<pre>、<script>和<style>不動,但 JSON-LD 的name和description除外,尊重translate="no",保留字元參照和註解,並將lang設為zh-Hans。 - 分段式。 從 HTML 中提取文字的工具所採用的方法:腳本將檔案分割成標籤和文字,跳過
<code>、<pre>、<script>、<style>和translate="no"元素,將剩餘的文字片段和文字屬性作為編號的 JSON 清單發送給模型,並將翻譯寫回未觸及的標記中。它還會自行設定lang="zh-Hans"。
模型是 LM Studio 中的 Qwen 3.6 27B (Q4_K_M),關閉思考功能,溫度設為 0.3,運行於配備 64 GB 記憶體的 Apple M4 Max 上。我們在 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 次全數通過 | 3 次全數通過 | 3 次全數通過 |
translate="no" 文字保留,頁內連結仍可運作,<library> 仍保持轉義 | 3 次全數通過 | 3 次全數通過 | 3 次全數通過 |
<pre> 內的範例輸出行已翻譯 | 3 次全數通過 | 3 次全數通過 | 0 次通過(設計上跳過) |
CSS content: "Note: " 已翻譯 | 3 次全數通過 | 0 次通過 | 0 次通過(設計上跳過) |
JSON-LD name 與 description 已翻譯 | 3 次全數通過 | 3 次全數通過 | 0/3(依設計略過) |
aria-label="Breadcrumb" 已翻譯 | 0/3 | 0/3 | 3/3 |
含有兩個行內 <code> 元素的句子表達了正確的意思 | 3/3 | 3/3 | 0/3 |
| 行內標籤前後的中文文字間距 | 無 | 0、5 和 4 | 每次執行皆為 6 |
<html> 上的 lang | zh-CN(未要求) | zh-Hans | zh-Hans(由指令碼設定) |
九次執行中的每一次都保留了相同的 58 個標籤,順序也相同,每一個 href、src、id 和 class、頁內連結目標、註解、轉義的 <library>,以及 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><library></code> 之后输入 <code>--out</code>文件夹的路径。
這段話讀作「在 <library> 之後,輸入 --out 資料夾的路徑」,與原指示相反。所有六次整檔執行和指令式執行都移動了程式碼元素並正確處理:
在 <code>--out</code> 后面输入 <code><library></code> 文件夹的路径。
同樣的分割在標籤與中文文字相接處留下了多餘空格,例如 前往 <b>设置</b> 和 <b>书库</b> ,,在所有三次分段執行中,每次執行出現六處。瀏覽器會將其顯示為可見間隙,包括中文標點前。整檔執行沒有這個問題;三次指令式執行中有兩次出現了四到五處。
結構檢查器無法捕捉顛倒的句子,因為每個標籤都恰好在原來的位置。我們的檢查器會在行內程式碼順序改變時發出警告,這對許多語言來說是正常的,但該警告無法分辨對錯。在此測試中,它對所有六個正確譯文都發出了警告,而對三個錯誤譯文保持沉默,因為錯誤的譯文保留了英文順序。只有閱讀句子的人才能分辨。解決方法是將完整句子連同行內標籤一起發送給譯者,這正是整檔執行所做的,然後再檢查結構。
較小的差異
lang: 整檔執行自行將其改為zh-CN。指令式執行則按要求使用了zh-Hans。兩者都是有效的語言標籤;zh-Hans標示文字系統(簡體中文),zh-CN標示地區。W3C 的語言宣告指南建議在<html>元素上宣告語言,而不是使用Content-Languagemeta 標籤。- JSON-LD: 整檔執行和指令式執行翻譯了
name和description的值,並保持了 JSON 的有效性。我們的分段腳本跳過了<script>內容,因此其結構化資料保持英文,忽略腳本的工具也會如此。 - 字元參照: 整檔執行和指令式執行保留了
›、 、©以及表情符號參照的原樣。這六次執行中有五次將範圍2–5改寫為中文詞語(2 到 5 或 2 至 5);一次指令式執行寫了普通破折號而非參照,儘管其提示要求保留參照。我們的分段腳本解碼了所有參照並寫回字元,顯示效果相同。最重要的<code>內的<library>在所有九次執行中都保持轉義。
翻譯之前:準備檔案
- 標記不可更改的內容。 在產品名稱、型號、散文中類似程式碼的字串,以及其他必須保持原樣的內容上加上
translate="no"。這是 HTML 的一部分,不花任何成本,而且在所有六次發送整檔的執行中,模型都遵守了這一點。讀取該屬性的分段工具(如我們的腳本)會完全跳過這些元素。我們的不翻譯指南涵蓋了如何決定哪些內容應列入該清單。 - 決定程式碼政策。 程式碼註解要翻譯嗎?範例輸出呢?樣式表是否跨語言共用?將答案連同不可更改的名稱寫下來,放入翻譯詞彙表,並交給負責翻譯的人或工具。
- 將 ID 保留在來源語言中。 頁內連結指向
id值。如果有人翻譯了id,所有指向它的連結都會失效。我們的執行中沒有發生這種情況,但「熱心」本地化錨點的譯者會這麼做。 - 收集隱藏文字。 列出包含文字的屬性:
alt、title、placeholder、aria-label、按鈕上的value、meta 標籤和 JSON-LD,以便檢查而不被遺忘。 - 不要就地翻譯 JavaScript 字串。 由腳本設定的訊息,例如 alert 或在程式碼中建立的按鈕標籤,需要在程式碼中進行單獨的本地化步驟。六次整檔或指令式執行都沒有更改我們的腳本區塊,這是正確的,但這意味著那些字串保持英文。
翻譯之後:檢查
-
執行結構檢查。
python3 check_html_translation.py source.html translated.html --lang zh-Hans --keep Shelfmark它比較標籤及其順序、每個非讀者面向文字的屬性、
<pre>、<script>和<style>的內容、行內程式碼、JSON-LD 鍵、translate="no"文字、註解和頁內連結。它會警告仍為英文的文字屬性、與預期不同的lang值、看起來未翻譯的可見文字、順序改變的行內程式碼,以及中日韓文字跨行內標籤的空格。它不評判翻譯品質。 -
閱讀每個包含行內標籤的句子。 這正是我們測試中語意出錯的地方。不要依賴檢查器的「行內程式碼重新排序」警告來找出這些句子:在我們的測試中,錯誤的譯文正是那些保留了原始順序的譯文。
-
在瀏覽器中開啟頁面。 點擊頁內連結,如果可能的話用螢幕閱讀器瀏覽表單,並尋找粗體和連結文字周圍的間隙。
-
對於從右到左的語言, 還要在
<html>元素上設定dir="rtl"。我們的從右到左書籍翻譯指南涵蓋了其他變化。 -
對於 XHTML, 例如 EPUB 內的章節檔案,還要確認檔案仍是格式良好的 XML,例如使用
xmllint --noout chapter.xhtml。瀏覽器會原諒未閉合的標籤;電子閱讀器可能不會。我們的 EPUB 翻譯 QA 檢查清單涵蓋了電子書的其他部分。
BookTranslator 的適用場景
BookTranslator 的上傳頁面 除了書籍和文件外,也接受 .html、.htm 和 .xhtml 檔案,因此您可以將儲存的網頁或靜態說明文章作為完整檔案上傳。我們並未為本文將此範例交由 BookTranslator 處理,也未測試它如何處理行內程式碼順序、CSS 內容或 JSON-LD,因此請以檢查任何其他工具輸出的相同方式來檢查其結果。您會在付款前看到價格。
無論您使用哪種工具,請在翻譯前完成準備步驟,並在翻譯後進行檢查。即使所有標籤都完好無損,頁面仍可能指示讀者輸入錯誤的內容。如果您的 HTML 實際上是使用 Markdown 撰寫的文件,我們的 Markdown 翻譯指南 則涵蓋了原始格式。
取得測試檔案
本次測試的所有內容均可免費重複使用:
- sample-source.html:59 行的英文範例;
- check_html_translation.py:結構檢查器(僅使用 Python 標準函式庫);
- run_llm_html_test.py:三種方法,適用於任何與 OpenAI 相容的本機伺服器;
- outputs 資料夾 中的九個輸出,以及 check-results.txt 和 results.csv。





