The wild west of polyglot docs sites
本文为原文前 6,000 字符的节选翻译,完整内容请查看原文。
The wild west of polyglot docs sites
多语言文档网站的蛮荒西部
2026 Oct 08 Given a project that provides libraries in N different programming languages, the docs site for that project often needs to interact with N or N+1 different documentation generators. This is because each programming language has its own API reference generator. While the libraries themselves may be loosely coupled or decoupled from each other, the docs site often needs tighter coupling in various ways: all pages should use the same fonts and colors, the in-site search UX should be consistent and comprehensive, etc. There doesn’t seem to be an established term for this kind of docs site, where you’re attempting to wrangle the outputs from disparate docs generators into one cohesive whole. Let’s call it a polyglot docs site for now. Maybe someone will come up with a better name in the future. Short story long, polyglot docs sites feel like a sparsely explored frontier of technical writing, full of rattlesnakes and tumbleweeds. And perhaps a little gold, too.
2026年10月8日,对于一个提供N种不同编程语言库的项目来说,其文档网站通常需要与N或N+1个不同的文档生成器进行交互。这是因为每种编程语言都有其专属的API参考生成器。虽然库本身可能是松散耦合或完全解耦的,但文档网站往往需要在多个方面保持更紧密的耦合:所有页面应使用相同的字体和颜色,站内搜索的用户体验应保持一致且全面,等等。目前似乎还没有一个公认的术语来描述这种试图将不同文档生成器的输出整合为一个整体的网站。我们暂且称之为“多语言文档网站”(polyglot docs site)。也许未来会有人想出更好的名字。长话短说,多语言文档网站感觉像是技术写作中一个鲜有人探索的边疆,充满了响尾蛇和风滚草,当然,或许也藏着一点金子。
Strategies# In terms of top-down strategy I can only think of 2 ways to structure a polyglot docs site. I think both strategies are valid and viable but they both currently have a lot of major issues.
策略# 就自上而下的策略而言,我只能想到两种构建多语言文档网站的方法。我认为这两种策略都是有效且可行的,但它们目前都存在许多重大问题。
Transformation# The first strategy is to parse each API reference generator’s output and transform it into markup that plays nicely with your main docs generator. For example, in my first job I ingested Doxygen HTML as input, used XSLT (!!) to transform it into simpler HTML fragments, and then used the raw directive to pull the HTML fragments into my Sphinx site. The main drawback of the transformation approach boils down to losing out on the expertise of the API reference generators: Tools like Doxygen, rustdoc, javadoc, etc. understand the details of their respective languages much better than I do. With a custom transformation that “simplifies” the output, there’s a risk that I’m stripping out information that users actually need. I.e. Chesterton’s fence. These tools have put a lot of thought into the UX of API references. For example, given a structured search query like vec -> usize, rustdoc’s search engine will only return functions that take in a vec as an arg and returns usize. Another drawback of transformation is that it goes against the grain of the ecosystem. Rust programmers are familiar with the rustdoc UI. Even if I could theoretically create an API reference that’s superior in every way, I’m still asking my users to figure out a new and different UI that they won’t encounter anywhere else.
转换# 第一种策略是解析每个API参考生成器的输出,并将其转换为能与主文档生成器良好兼容的标记语言。例如,在我的第一份工作中,我将Doxygen生成的HTML作为输入,使用XSLT(!!)将其转换为更简单的HTML片段,然后使用raw指令将这些片段拉取到我的Sphinx网站中。转换方法的主要缺点在于失去了API参考生成器原有的专业性:像Doxygen、rustdoc、javadoc等工具对各自语言细节的理解远胜于我。通过“简化”输出的自定义转换,存在剥离用户实际所需信息的风险,即“切斯特顿栅栏”(Chesterton’s fence)效应。这些工具在API参考的用户体验上投入了大量思考。例如,给定一个像“vec -> usize”这样的结构化搜索查询,rustdoc的搜索引擎只会返回那些以vec为参数并返回usize的函数。转换方法的另一个缺点是它违背了生态系统的习惯。Rust程序员熟悉rustdoc的界面。即使我理论上能创建一个在各方面都更优越的API参考,我仍然是在要求用户去学习一个他们在其他任何地方都遇不到的全新界面。
Case study: Breathe# Another example of the transformation approach is Breathe. You first run a Doxygen XML build, and then make that available as an input to the Sphinx build. In your reStructuredText you insert a directive like .. doxygenclass:: pw::Foo to indicate the place where the API reference for pw::Foo should go. Breathe parses the info from the Doxygen XML and transforms it into API reference content that Sphinx understands. This was the foundation of C/C++ API reference content on pigweed.dev from 2022 to 2024. We moved to the approach described in the next section for a few reasons: One issue was slowness. I don’t remember the exact numbers but Breathe was a significant bottleneck in our docs build. We went from something like 90秒 with Breathe to 60秒 without it. Another issue was too much glue code leading to silent failures. In addition to marking up your headers with Doxygen comments, you have to remember to pull the content into Sphinx via a directive like .. doxygenclass:: pw::Foo. On quite a few occasions I saw SWEs make an honest effort to document their code, but the documentation never actually got published, because they had forgotten the doxygenclass step. The last issue was too much flexibility. Some docs contributors would order their doxygenclass directives alphabetically on a single page. Others would take a thematic approach. E.g. in the middle of a guide on how to foo the bar, they would insert the API reference for pw::Foo. I respect and appreciate the work of the Breathe maintainers and don’t like singling them out. Breathe just happens to be in the unfortunate position of being the transformation tool that I have a lot of firsthand experience with and clearly demonstrates general issues of the transformation approach that any transformation tool will need to reckon with.
案例研究:Breathe# 转换方法的另一个例子是Breathe。你首先运行Doxygen XML构建,然后将其作为输入提供给Sphinx构建。在reStructuredText中,你插入类似 .. doxygenclass:: pw::Foo 的指令,以指定 pw::Foo 的API参考应放置的位置。Breathe解析来自Doxygen XML的信息,并将其转换为Sphinx能理解的API参考内容。这是2022年至2024年间pigweed.dev上C/C++ API参考内容的基础。我们出于几个原因转向了下一节描述的方法:一个问题是速度慢。我不记得确切的数字,但Breathe是我们文档构建中的一个重大瓶颈。使用Breathe时构建大约需要90秒,而不使用它则缩短至60秒。另一个问题是过多的胶水代码导致了静默失败。除了用Doxygen注释标记头文件外,你还必须记得通过类似 .. doxygenclass:: pw::Foo 的指令将内容拉取到Sphinx中。我曾多次看到软件工程师(SWE)认真地为代码编写文档,但文档却从未真正发布,因为他们忘记了doxygenclass这一步。最后一个问题是灵活性过高。一些文档贡献者会在单个页面上按字母顺序排列doxygenclass指令,而另一些人则采用主题式方法。例如,在关于如何“foo the bar”的指南中间,他们会插入 pw::Foo 的API参考。我尊重并感谢Breathe维护者的工作,也不想专门针对他们。Breathe只是恰好处于一个不幸的位置,作为我拥有丰富第一手经验的转换工具,它清楚地展示了任何转换工具都需要面对的通用问题。
Turducken# The second strategy is to defer to the expertise of the API reference generators and publish their output as-is. This is what pigweed.dev does. Under-the-hood, pigweed.dev is 3 separate docs sites cobbled together. We generate our C/C++ API reference with Doxygen, our Rust API reference with rustdoc, and everything else with Sphinx. Architecture-wise, it’s a turducken. A chicken stuffed into a duck stuffed into a turkey. Well, in the case of pigweed.dev it’s more like a chicken (Doxygen) and a duck (rustdoc) stuffed side-by-side into a turkey (Sphinx). The obvious problem is that the subsites all look different from each other: A Doxygen-generated page# A rustdoc-generated page# A Sphinx-generated page# With an obscene amount of !important flags I can make them look more similar to each other. And I plan on doing that. But no amount of CSS hacking can solve the following more insidious problems: It’s hard to navigate from one subsite to another. E.g. from a rustdoc-generated page there is no way to get back to the pigweed.dev homepage. The search UX is fragmented and incomplete. E.g. when using the in-site search on a page generated by Sphinx, the search results do not include any content from rustdoc-generated pages. It’s hard to link from one subsite to another. You end up relying on hardcoded manual relative paths, which are brittle. So, we’ve got 3 completely separate subsites that have no awareness of each other’s existence, and we somehow need to make them feel more connected. On pigweed.dev we made some progress on this front by introducing a universal header
“Turducken”(填馅火鸡)# 第二种策略是尊重API参考生成器的专业性,直接发布它们的原始输出。这就是pigweed.dev所采用的方法。在底层,pigweed.dev实际上是由三个独立的文档网站拼凑而成的。我们用Doxygen生成C/C++ API参考,用rustdoc生成Rust API参考,其余部分则使用Sphinx。从架构上讲,这就像是一个“Turducken”(一种将鸡塞进鸭子,再将鸭子塞进火鸡的菜肴)。好吧,就pigweed.dev而言,它更像是把一只鸡(Doxygen)和一只鸭子(rustdoc)并排塞进了一只火鸡(Sphinx)里。显而易见的问题是,这些子网站看起来各不相同:一个Doxygen生成的页面# 一个rustdoc生成的页面# 一个Sphinx生成的页面# 通过使用大量的 !important 标志,我可以让它们看起来更相似,我也计划这样做。但无论怎么进行CSS黑客攻击,都无法解决以下更隐蔽的问题:从一个子网站导航到另一个子网站很困难。例如,从rustdoc生成的页面无法返回到pigweed.dev的主页。搜索体验是碎片化且不完整的。例如,当在Sphinx生成的页面上使用站内搜索时,搜索结果不包含任何来自rustdoc生成页面的内容。在子网站之间建立链接也很困难。你最终不得不依赖硬编码的手动相对路径,而这些路径非常脆弱。因此,我们拥有三个完全独立、互不感知的子网站,我们必须设法让它们感觉联系更紧密。在pigweed.dev上,我们通过引入一个通用页眉,在这方面取得了一些进展。