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

要翻译 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-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; - 一条在段落前插入“Note:”一词的 CSS 规则;
- 一个指向
#choose-format的页内链接、一个mailto:链接、一个搜索表单和一个 JSON-LD 块。
然后我们让一个本地模型以三种方式将其翻译成简体中文:
- 整个文件。 粘贴整个文件并要求返回翻译后的文件。
- 带指令。 同上,外加八条规则:保留每个标签和属性名,不更改
href、src、id、class及类似值,翻译列出的文本属性和元标签,除 JSON-LD 的name和description外不动<code>、<pre>、<script>和<style>,遵守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/3 | 3/3 |
translate="no" 文本保留,页内链接仍然有效,<library> 仍被转义 | 3/3 | 3/3 | 3/3 |
<pre> 内的示例输出行已翻译 | 3/3 | 3/3 | 0/3(按设计跳过) |
CSS content: "Note: " 已翻译 | 3/3 | 0/3 | 0/3(按设计跳过) |
JSON-LD name 和 description 已翻译 | 3/3 | 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-Language元标签。- 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、元标签和 JSON-LD,以便检查而不遗忘。 - 不要就地翻译 JavaScript 字符串。 由脚本设置的消息,例如在代码中创建的警报或按钮标签,需要在代码中单独进行本地化步骤。六次整文件或指令运行都没有更改我们的脚本块,这是正确的,但这意味着那些字符串保持英文。
翻译之后:检查
-
运行结构检查。
python3 check_html_translation.py source.html translated.html --lang zh-Hans --keep Shelfmark它比较标签及其顺序、每个非面向读者文本的属性、
<pre>、<script>和<style>的内容、内联代码、JSON-LD 键、translate="no"文本、注释和页内链接。它会警告仍为英文的文本属性、与你预期不同的lang值、看起来未翻译的可见文本、顺序变化的内联代码以及跨内联标签的中日韩文本之间的空格。它不评判翻译质量。 -
阅读每个包含内联标签的句子。 这是我们的测试中意义出错的地方。不要依赖检查器的“内联代码重新排序”警告来查找这些句子:在我们的测试中,错误的译文正是那些保持原始顺序的。
-
在浏览器中打开页面。 点击页内链接,如果可能,用屏幕阅读器通过 Tab 键浏览表单,并查找粗体和链接文本周围的间隙。
-
对于从右到左的语言, 还要在
<html>元素上设置dir="rtl"。我们的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。





