书译
书译

如何翻译 HTML 文件而不破坏标签、属性或链接

HTML 文件中哪些内容需要翻译、哪些应保持原样,以及如何检查。包含示例页面、免费检查工具和 9 次本地 LLM 测试。

BookTranslator

BookTranslator Team

21 分钟阅读

要翻译 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" 元素中,文本和属性值“在页面本地化时应保持原样”。

我们测试了什么

我们编写了 sample-source.html,这是一个虚构阅读应用 Shelfmark 的帮助中心页面,该应用也用于我们的 Markdown 翻译测试。它包含了上表中每一行的一个示例,外加几个陷阱:

  • 一个被内联标签拆分的句子:“导出前,请备份你的笔记。导出会复制你的书籍,但不会从 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,运行在配备 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/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/30/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 元标签。
  • 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. 在浏览器中打开页面。 点击页内链接,如果可能,用屏幕阅读器通过 Tab 键浏览表单,并查找粗体和链接文本周围的间隙。

  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 实际上是使用 Markdown 编写的文档,我们的 Markdown 翻译指南 则涵盖了源格式。

获取测试文件

本次测试的所有内容均可免费重用:

相关文章