多语言文档站点的混乱之地

Lobsters Hottest 工具

摘要

探讨多语言(polyglot)文档站点的构建难题与两大整合策略——转换(transform)与嵌入(embed),并以 Breathe 和 Doxygen/Sphinx 在 pigweed.dev 的实践为案例,分析各自的权衡与痛点。

<p><a href="https://lobste.rs/s/wyxufm/wild_west_polyglot_docs_sites">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/10/11 23:39

# 多语言文档站的蛮荒西部 来源:https://technicalwriting.dev/blog/2026/10/polyglot/index.html 2026年10月8日 假设一个项目提供了 N 种不同编程语言的库,那么该项目的文档站通常需要与 N 或 N+1 种不同的文档生成器打交道。这是因为每种编程语言都有自己的 API 参考文档生成器。虽然这些库本身可能是松散耦合的(https://en.wikipedia.org/wiki/Loose_coupling)或彼此解耦的,但文档站在很多方面往往需要更紧密的耦合:所有页面应使用统一的字体和颜色、站内搜索体验应保持一致且覆盖全面,等等。 目前似乎还没有一个公认的术语来描述这类文档站——即你试图将来自不同文档生成器的输出整合成一个有机的整体。我们姑且称之为**多语言(polyglot)**文档站(https://www.merriam-webster.com/dictionary/polyglot)吧。也许将来有人能想出一个更好的名字。 话说回来,多语言文档站就像是技术写作领域一片人迹罕至的边疆,到处是响尾蛇和风滚草。不过,也许还藏着那么一点金子。 ## 策略 从自上而下的策略来看,我只能想到 2 种构建多语言文档站的思路。我认为这两种策略都是有效可行的,但目前它们都存在不少重大问题。 ### 转换 第一种策略是解析每个 API 参考文档生成器的输出,并将其转换为能与你的主流文档生成器无缝配合的标记语言。举个例子,在我的第一份工作中,我将 Doxygen 生成的 HTML 作为输入,用 XSLT(!!!)将其转换为更简单的 HTML 片段,然后使用 `raw` 指令(https://docutils.sourceforge.io/docs/ref/rst/directives.html#raw)将这些 HTML 片段嵌入到我的 Sphinx 站点中。 转换方案的主要缺点在于,你会丧失 API 参考文档生成器的专业性: - Doxygen、rustdoc、javadoc 等工具对其各自语言细节的理解远胜于我。用自定义转换"简化"输出存在一个风险:我可能剥除了用户真正需要的信息——即所谓的"切斯特顿栅栏"(https://en.wiktionary.org/wiki/Chesterton%27s_fence)。 - 这些工具在 API 参考文档的用户体验方面倾注了大量心血。例如,给定一个结构化搜索查询 `vec -> usize`,rustdoc 的搜索引擎只会返回接收 `vec` 作为参数并返回 `usize` 的函数。 转换方案的另一个缺点是它与生态系统的习惯相悖。Rust 程序员早已熟悉 rustdoc 的 UI。即使我理论上能创建一个各方面都更优秀的 API 参考文档,我仍然是在要求用户去适应一个全新的、在其他任何地方都不会遇到的界面。 #### 案例研究:Breathe 转换方案的另一个例子是 Breathe(https://breathe.readthedocs.io/en/latest/index.html)。你先运行一次 Doxygen XML 构建,然后将其作为 Sphinx 构建的输入。在 reStructuredText 中,你插入类似 `.. doxygenclass:: pw::Foo` 的指令来指定 `pw::Foo` 的 API 参考文档应该放置的位置。Breathe 从 Doxygen XML 中解析信息,并将其转换为 Sphinx 能理解的 API 参考内容。从 2022 年到 2024 年,`pigweed.dev` 上的 C/C++ API 参考内容正是基于这种方式构建的。由于以下几个原因,我们转向了下一节描述的方案: - 一个问题是**速度慢**(https://github.com/breathe-doc/breathe/issues/439)。我不记得确切的数字了,但 Breathe 在我们的文档构建中是一个显著的瓶颈。启用 Breathe 时构建需要大约 90 秒,去掉之后只需 60 秒。 - 另一个问题是胶水代码太多,容易导致静默失败。除了用 Doxygen 注释标记你的头文件之外,你还必须记得通过 `.. doxygenclass:: pw::Foo` 这样的指令将内容拉入 Sphinx。我曾多次看到软件工程师认真努力地为自己的代码编写文档,但这些文档最终从未被发布出去,因为他们忘记了 `doxygenclass` 这一步。 - 最后一个问题是灵活性太高。有些文档贡献者会把他们的 `doxygenclass` 指令按字母顺序排列在同一个页面上。另一些人则采用主题式方法。例如,在讲解如何 foo the bar 的指南中间,插入 `pw::Foo` 的 API 参考文档。 我尊重并感谢 Breathe 维护者的工作,也不喜欢单独针对他们。只不过 Breathe 恰好是我有大量亲身使用经验的转换工具,并且它清晰地展示了任何转换工具都需要面对的转换方案的普遍问题。 ### Turducken(火鸡套鸭套鸡) 第二种策略是信任 API 参考文档生成器的专业性,原样发布它们的输出。这正是 `pigweed.dev` 的做法。在底层,`pigweed.dev`(https://pigweed.dev/)实际上是拼凑在一起的 3 个独立文档站。我们使用 Doxygen(https://www.doxygen.nl/)生成 C/C++ API 参考文档,使用 rustdoc(https://doc.rust-lang.org/rustdoc/)生成 Rust API 参考文档,其余内容则使用 Sphinx(https://www.sphinx-doc.org/)生成。 从架构上看,它就是一个 **turducken**(https://en.wikipedia.org/wiki/Turducken)——火鸡肚子里塞一只鸭子,鸭子肚子里再塞一只鸡。不过在 `pigweed.dev` 的情况下,更像是把一只鸡(Doxygen)和一只鸭子(rustdoc)并排塞进一只火鸡(Sphinx)里。 显而易见的问题是,各个子站彼此之间看起来完全不同: ../../../../_images/d1.png 一个 Doxygen 生成的页面 ../../../../_images/r1.png 一个 rustdoc 生成的页面 ../../../../_images/s1.png 一个 Sphinx 生成的页面 借助大量多到令人发指的 `!important`(https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/important)标志,我可以让它们看起来更加相似。我也打算这么做。但再多的 CSS 黑客手段也无法解决以下更棘手的问题: - 在不同子站之间导航很困难。例如,从 rustdoc 生成的页面无法返回 pigweed.dev 的主页。 - 搜索体验碎片化且不完整。例如,在 Sphinx 生成的页面上使用站内搜索时,搜索结果不会包含任何来自 rustdoc 生成页面的内容。 - 在不同子站之间建立链接很困难。你最终只能依赖硬编码的手动相对路径,这非常脆弱。 所以,我们有 3 个完全独立的子站,彼此之间完全不知道对方的存在,而我们却需要想办法让它们感觉更加连贯。在 `pigweed.dev` 上,我们通过引入全局页头和全面搜索在这方面取得了一些进展。 #### 全面搜索 在全局页头中,现在有一个一致的 UI 用于访问站内搜索。搜索以模态框的形式打开,输入时即时显示结果。最大的成果是搜索结果现在是全面的——即我们所有 Doxygen、rustdoc 和 Sphinx 的内容都已被索引,并呈现在搜索结果中: ../../../../_images/pf.png 第 1 条结果来自 Doxygen,第 2 条和第 3 条来自 Sphinx,第 4 条来自 rustdoc。 这里真正的功臣是 Pagefind(https://pagefind.app/)。这个库是我们所有站内搜索需求的一站式解决方案。我们只需将 Pagefind 指向输出目录,它就会基于我们构建的 HTML 创建搜索索引。它提供了允许列表和禁止列表 API,可以精细控制哪些内容被索引。在搜索 UI 方面,我们使用 Pagefind 的默认 Web Components 并做了一些定制。如果你想完全自定义 UX,它也提供了 JS API。Pagefind 在 Web Workers、WebAssembly 和索引分片按需加载方面似乎也做了很酷的事情,但我在官方文档中找不到对其内部原理的详细介绍。 ### 手动编写 好吧,我骗了你们。其实还有第 3 种方法。对于小型的、变化缓慢的 API 接口,手动编写所有文档是可行的。尤其是在 AI 智能体时代。我做过,效果还不错。 ## 未来的探索方向 我写这篇文章的主要动机,就是希望在文档站维护者、Doxygen 等文档生成器以及 Pagefind 等库的开发者之间引发讨论。以下是我们未来更好地驾驭和驯服多语言文档站的一些可能方向。 ### `--fragment` 模式 为了让 pigweed.dev 的 turducken 架构能够运作,我不得不大量修改和覆盖 Doxygen 和 rustdoc 的 CSS。它们提供的唯一 HTML 输出假定你想要一个完整的站点,而我真正需要的只是主体内容。如果能提供一个 `--fragment` 模式,只输出纯内容的 HTML 片段,而不是包含页头、搜索、导航栏等的完整站点,那将会帮上大忙。 ### 链接 有一个很棒的小型库 Doxylink(https://sphinxcontrib-doxylink.readthedocs.io/en/stable/),它让从 Sphinx 链接到 Doxygen 变得万无一失。如果你链接到一个不存在的符号,文档构建就会失败。我需要在各个方向上都有类似的能力:从 rustdoc 链接到 Sphinx、从 Doxygen 链接到 rustdoc,等等。

相似文章

用 Gleam、Org-Mode 和 Pandoc 写博客

Hacker News Top

作者介绍了使用 Emacs/Org-mode 写作、Pandoc 转换、Gleam + Blogatto + Lustre 生成 HTML、Nix 固定构建环境、d2 绘制图示的个人博客发布管线,强调用类型化 Gleam 代码替代模板语言来生成站点页面。

@amiaoapp: 天天啃外文文献、看国外技术博客,每次想翻译 PDF 段落,复制粘贴到网页翻译里全是一堆莫名其妙的断句和乱码换行?还得自己一行行手动删空格、删换行,效率低到让人原地抓狂?赶紧把这种折寿又折腾的“古法复制粘贴”连夜送进碎纸机! 推荐一款Git…

X AI KOLs Timeline

推荐一款开源的实时翻译工具CopyTranslator,支持跨平台,能自动处理PDF复制时的断行乱码,实现复制即翻译,提升外文文献阅读效率。

@0xQiYan: 兄弟们,平时有没有经常有各种格式转需要会员的情况,还在没有会员而烦恼? 发现一个各种格式转的开源项目,微软谷歌没做到的,一位哲学教授用业余时间搞定了。 Pandoc——文档转换神器,一条命令,几秒钟,50多种格式随便转。Word转PDF,…

X AI KOLs Timeline

介绍开源文档转换神器Pandoc,由哲学教授约翰·麦克法兰用业余时间开发,支持50多种格式互转,免费、开源、纯本地运行。

@shachepi: 天下苦沉浸式翻译久矣。 除了昨天的陪读蛙,KISS Translator 也是个顶级平替。 纯粹,完全开源。界面清爽。除了网页翻译,它同样自建接口支持非常全(Claude、Gemini 等各类AI都能接)。不想被商业插件割韭菜,用这种自接…

X AI KOLs Timeline

天下苦沉浸式翻译久矣。 除了昨天的陪读蛙,KISS Translator 也是个顶级平替。 纯粹,完全开源。界面清爽。除了网页翻译,它同样自建接口支持非常全(Claude、Gemini 等各类AI都能接)。不想被商业插件割韭菜,用这种自接 API 的最稳,加载也快,效果还好! 还没找到顺手平替的,再试试这个。 项目地址:https://github点com/fishjar/kiss-translator