書譯
書譯

如何翻譯 HTML 檔案而不破壞標籤、屬性與連結

HTML 檔案中哪些內容該翻譯、哪些該保留,以及如何檢查。附範例頁面、免費檢查工具,以及 9 次本機 LLM 測試。

BookTranslator

BookTranslator Team

21 分鐘閱讀

要翻譯 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-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" 元素中,文字和屬性值「在頁面本地化時應保持原樣」。

我們測試了什麼

我們撰寫了 sample-source.html,這是一個虛構閱讀應用程式 Shelfmark 的說明中心頁面,與我們在 Markdown 翻譯測試中使用的是同一個應用程式。它包含了上表中每一列的一個範例,外加幾個陷阱:

  • 一個被行內標籤分割的句子:「在匯出之前,備份你的筆記。匯出會複製你的書籍,但不會從 Shelfmark 刪除任何內容」,其中產品名稱位於 translate="no" 內;
  • 一個包含兩個行內 <code> 元素且順序至關重要的句子:「在 --out 之後輸入 <library> 資料夾的路徑」;
  • 一個 <pre><code> 區塊,包含一個命令和一行範例輸出:# Export finished: 128 books;
  • 一條在段落前插入「注意:」一詞的 CSS 規則;
  • 一個指向 #choose-format 的頁內連結、一個 mailto: 連結、一個搜尋表單,以及一個 JSON-LD 區塊。

然後我們要求一個本地模型以三種方式將其翻譯成簡體中文:

  1. 整個檔案。 貼上整個檔案並要求返回翻譯後的檔案。
  2. 指令式。 相同,外加八條規則:保留每個標籤和屬性名稱,不變更 href、src、id、class 及類似值,翻譯列出的文字屬性和 meta 標籤,保留 <code>、<pre>、<script> 和 <style> 不動,但 JSON-LD 的 name 和 description 除外,尊重 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,運行於配備 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" 文字保留,頁內連結仍可運作,&lt;library&gt; 仍保持轉義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/30/33/3
含有兩個行內 <code> 元素的句子表達了正確的意思3/33/30/3
行內標籤前後的中文文字間距無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 meta 標籤。
  • 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、meta 標籤和 JSON-LD,以便檢查而不被遺忘。
  5. 不要就地翻譯 JavaScript 字串。 由腳本設定的訊息,例如 alert 或在程式碼中建立的按鈕標籤,需要在程式碼中進行單獨的本地化步驟。六次整檔或指令式執行都沒有更改我們的腳本區塊,這是正確的,但這意味著那些字串保持英文。

翻譯之後:檢查

  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"。我們的從右到左書籍翻譯指南涵蓋了其他變化。

  5. 對於 XHTML, 例如 EPUB 內的章節檔案,還要確認檔案仍是格式良好的 XML,例如使用 xmllint --noout chapter.xhtml。瀏覽器會原諒未閉合的標籤;電子閱讀器可能不會。我們的 EPUB 翻譯 QA 檢查清單涵蓋了電子書的其他部分。

BookTranslator 的適用場景

BookTranslator 的上傳頁面 除了書籍和文件外,也接受 .html、.htm 和 .xhtml 檔案,因此您可以將儲存的網頁或靜態說明文章作為完整檔案上傳。我們並未為本文將此範例交由 BookTranslator 處理,也未測試它如何處理行內程式碼順序、CSS 內容或 JSON-LD,因此請以檢查任何其他工具輸出的相同方式來檢查其結果。您會在付款前看到價格。

無論您使用哪種工具,請在翻譯前完成準備步驟,並在翻譯後進行檢查。即使所有標籤都完好無損,頁面仍可能指示讀者輸入錯誤的內容。如果您的 HTML 實際上是使用 Markdown 撰寫的文件,我們的 Markdown 翻譯指南 則涵蓋了原始格式。

取得測試檔案

本次測試的所有內容均可免費重複使用:

相關文章