当 mkdocs-static-i18n 遇上 Read the Docs 多版本:踩坑与解法
背景使用 MkDocs Material 主题 mkdocs-static-i18n 构建多语言文档站点时如果同时部署到 Read the Docs 并启用多版本支持会遇到语言切换链接路径错误的问题。本文记录了这一问题的排查过程和解决方案。技术栈mkdocs1.6.0 mkdocs-material9.5.24 mkdocs-static-i18n1.2.3问题描述现象在 Read the Docs 多版本模式下右上角的语言切换按钮和 README 中的中英文切换链接点击后返回 404。根因MkDocs 构建后的语言切换链接默认为绝对路径ahref/hreflangenEnglish/aahref/zh/hreflangzh中文/a在 Read the Docs 多版本模式Multiple versions without translations下站点部署在/version/子路径下如/latest/、/v2.0/。点击/zh/会跳转到site.readthedocs.io/zh/而正确的目标应该是site.readthedocs.io/latest/zh/。同样的问题也出现在 README 中的手动语言切换链接和自动语言跳转脚本中。解决方案1.site_url: 配置site_url:MkDocs 默认会根据site_url生成页面中的部分绝对路径如 sitemap、canonical link。如果不设置site_url或留空字符串MkDocs 在构建时使用相对路径不会硬编码域名或路径前缀。这对于 Read the Docs 多版本部署很重要site_url设置效果(空字符串)所有链接使用相对路径适配任意部署路径https://site.io/链接硬编码为根路径多版本下可能指向错误位置https://site.io/latest/只在 latest 版本正确其他版本如 v2.0路径错误设为空字符串后语言切换器、导航链接、CSS/JS 资源路径全部使用相对路径不受部署路径影响。2. 语言切换器使用相对路径在mkdocs.yml中通过extra.alternate配置语言切换按钮使用相对路径代替绝对路径extra:alternate:-name:Englishlink:.lang:en-name:中文link:zh/lang:zhmkdocs-static-i18n插件会根据当前页面的语言自动调整extra.alternate中的相对路径当前页面位置English 链接中文链接说明/latest/.→/latest/zh/→/latest/zh/英文页 → 中文进入子目录/latest/zh/..→/latest/./→/latest/zh/中文页 → 英文返回上级/v1.0/.→/v1.0/zh/→/v1.0/zh/不同版本自动适配相对路径不包含版本号或语言前缀因此在任意版本下都能正确解析。3. Hook 重写 README 中的语言链接项目根目录的 README 通过软链接docs/README.md → ../README.md作为文档首页。README 中包含手动的语言切换链接[中文文档](README.zh.md) | **English**MkDocs 构建后README.zh.md不再是有效的文件路径i18n 插件将其合并到/zh/路径下需要通过on_page_markdownhook 重写。docs/hooks/rewrite_paths.py完整代码 修复 README 中的相对路径使其在 mkdocs 中正常工作。 README.md 位于项目根目录使用 ./docs/imgs/... 等路径引用资源。 当通过软链接 docs/README.md → ../README.md 在 docs_dir: docs/ 下构建时 ./docs/ 前缀会导致路径解析到不存在的 docs/docs/ 目录需要去除该前缀。 同时将中英文切换链接README.zh.md / README.md重写为 mkdocs-static-i18n 插件生成的语言 URL确保链接在构建后的站点中正常跳转。 from__future__importannotationsimportrefromtypingimportAnydefon_page_markdown(markdown:str,page:Any,config:Any,files:Any)-str:# 仅对根目录的 README / index 页面生效ifpage.file.namenotin(README,index):returnmarkdown# 去除多余的 ./docs/ 前缀# 示例: ](./docs/imgs/foo.png) → ](imgs/foo.png)markdownre.sub(r\]\(\./docs/,](,markdown)# 将语言切换链接重写为相对路径兼容 Read the Docs 多版本 URL# 中文页 (/zh/): [English](README.md) → [English](../) 从 /zh/ 向上回到根# 英文页 (/): [中文文档](README.zh.md) → [中文文档](zh/) 从根进入 /zh/localegetattr(page.file,locale,None)iflocalezh:markdownre.sub(r\]\(README\.md\),](../),markdown)else:markdownre.sub(r\]\(README\.zh\.md\),](zh/),markdown)returnmarkdown关键: 英文页到中文的链接使用zh/不是../zh/。原因假设英文页 URL 为 /latest/ ../zh/ → 先到 /再到 /zh/ — ❌ 脱离了版本路径 zh/ → 直接到 /latest/zh/ — ✅ 正确4. 浏览器语言自动跳转脚本docs/js/lang-redirect.js实现了首次访问时根据浏览器语言自动跳转到中文版本的功能。docs/js/lang-redirect.js完整代码/** * 浏览器语言自动跳转脚本。 * * 首次访问时根据浏览器语言设置自动跳转到对应的语言版本。 * 跳转 URL 从 Material 主题的语言切换器获取使用相对路径兼容 Read the Docs 多版本。 * * 使用 sessionStorage 记录用户主动切换语言的行为防止自动跳转覆盖用户选择。 * 标签页关闭后记录清除下次访问重新按浏览器语言跳转。 */document.addEventListener(DOMContentLoaded,function(){varSTORAGE_KEYpyimgproc_lang_override;// 监听 Material 主题语言切换器的点击记录用户主动选择varswitcherdocument.querySelectorAll(.md-select__link[hreflang]);switcher.forEach(function(link){link.addEventListener(click,function(){sessionStorage.setItem(STORAGE_KEY,link.getAttribute(hreflang));});});// 已有用户主动选择的记录跳过自动跳转varoverridesessionStorage.getItem(STORAGE_KEY);if(override)return;// 检测当前页面是否在中文版本路径下匹配任意位置的 /zh/兼容多版本 URLvarpathwindow.location.pathname;varisZh/\/zh\//.test(path)||/\/zh$/.test(path);// 通过 referrer 检测用户从站内跨语言导航如从 /zh/ 点击到 /en/// 视为主动切换设置覆盖标记并跳过自动跳转varrefdocument.referrer;if(ref){try{varrefUrlnewURL(ref);if(refUrl.hostwindow.location.host){varrefIsZh/\/zh\//.test(refUrl.pathname)||/\/zh$/.test(refUrl.pathname);if(refIsZh!isZh){sessionStorage.setItem(STORAGE_KEY,isZh?zh:en);return;}}}catch(e){}}// 浏览器语言为中文但当前不在中文版本自动跳转到中文varbrowserLang(navigator.language||navigator.userLanguage||).toLowerCase();varwantZhbrowserLang.indexOf(zh)0;if(wantZh!isZh){varzhLinkdocument.querySelector(.md-select__link[hreflangzh]);if(zhLinkzhLink.href){window.location.replace(zhLink.href);}}});多版本兼容的关键设计路径检测不限位置:/\/zh\//.test(path)匹配/latest/zh/FAQ/等任意位置的/zh/跳转 URL 从 DOM 获取: 读取 Material 主题语言切换器的href由extra.alternate的相对路径在浏览器中解析为完整 URL自动包含版本前缀sessionStorage 防冲突: 用户点击语言切换器或从站内跨语言导航时记录覆盖标记阻止自动跳转选择sessionStorage而非localStorage标签页关闭后自动清除Read the Docs 网站配置以下操作均在 Read the Docs 后台完成地址为https://readthedocs.org/dashboard/project/。4.1 URL 前缀方案选择路径: Settings → General SettingsRead the Docs 的 URL 结构由两个维度决定语言Language和版本Versioning。选择Multiple versions without translations/version/filenameURL 只包含版本前缀不包含语言前缀site.readthedocs.io/latest/ → 英文首页 site.readthedocs.io/latest/zh/ → 中文首页 site.readthedocs.io/v2.0/ → 英文首页 (v2.0) site.readthedocs.io/v2.0/zh/ → 中文首页 (v2.0)如果选择了包含 translations 的方案如/language/version/filenameURL 会出现/en/latest/或/zh-cn/latest/前缀与 mkdocs-static-i18n 生成的/zh/子目录叠加产生混乱。4.2 项目语言设置路径: Settings → General Settings → Language将Language设为English。该设置决定项目的主语言影响 Read the Docs 搜索索引和界面文字。即使文档有中文版本主语言也应设为 English——中文版本由 mkdocs-static-i18n 在/zh/子目录下管理不需要 Read the Docs 层面感知。4.3 版本管理路径: VersionsRead the Docs 自动检测 Git 仓库中的分支和 tag创建对应的文档版本。操作说明激活版本勾选要构建的分支/tag默认只构建latest对应master/main分支默认版本Settings → Default version通常设为latest或stable隐藏旧版本取消勾选不需要的旧 tag减少构建资源占用4.4 不创建翻译项目路径: Settings → Translations不要创建 Translation 子项目。Read the Docs 的翻译机制是为每种语言创建独立的项目和仓库与mkdocs-static-i18n的单仓库、后缀命名模式冲突。如果已创建需要在此页面解除关联。4.5 构建配置确保仓库中存在.readthedocs.yamlversion:2build:os:ubuntu-22.04tools:python:3.9mkdocs:configuration:mkdocs.ymlpython:install:-requirements:requirements_docs.txtrequirements_docs.txt需包含mkdocs、mkdocs-material、mkdocs-static-i18n及其依赖。推荐使用pip-compile从.in文件生成锁定版本的.txt文件。4.6 Webhook 触发构建路径: Settings → Integrations默认情况下Read the Docs 会在仓库中添加 GitHub Webhookpush 到master/main后自动触发latest版本重建。创建新 tag 后如果该 tag 对应的版本已激活也会自动构建。如果 Webhook 未生效检查:GitHub 仓库 Settings → Webhooks 中是否有 readthedocs 的 hookRead the Docs 项目 Integrations 中是否显示 GitHub incoming webhookURL 结构对比Multiple versions without translations(/version/filename):页面URL英文首页 (latest)site.readthedocs.io/latest/中文首页 (latest)site.readthedocs.io/latest/zh/英文首页 (v2.0)site.readthedocs.io/v2.0/中文 Reference (v2.0)site.readthedocs.io/v2.0/zh/Reference/中文 FAQ (latest)site.readthedocs.io/latest/zh/FAQ/旧版本 404 问题在 i18n 配置之前发布的旧 tag如 v1.3.6其构建产物中不包含/zh/路径。访问site.readthedocs.io/v1.3.6/zh/会返回 404。Read the Docs 不会用新代码重建旧 tag——每个 tag 永远使用其对应的代码快照。处理方式:在 RTD Versions 页面取消激活不需要的旧版本后续发布的新 tag 自动包含 i18n 配置不需要额外操作完整配置参考mkdocs.ymlsite_name:Image Processing Librarysite_url:repo_url:https://github.com/skylerhu/py-img-processordocs_dir:docshooks:-docs/hooks/rewrite_paths.pytheme:name:materiallogo:imgs/logo.svgfavicon:imgs/favicon.icopalette:scheme:defaultfeatures:-navigation.tabs-toc.integrateextra:alternate:-name:Englishlink:.lang:en-name:中文link:zh/lang:zhnav:-Home:README.md-Reference:Reference.md-FAQ:FAQ.md-Changelog:CHANGELOG-1.x.md-Contributing:CONTRIBUTING.mdextra_css:-css/custom.cssextra_javascript:-js/lang-redirect.jsplugins:-search-i18n:docs_structure:suffixlanguages:-locale:endefault:truename:Englishbuild:true-locale:zhname:中文build:truesite_name:参数化图像处理库nav_translations:Home:首页Reference:图像处理参数FAQ:常见问题Changelog:更新日志Contributing:贡献者指南.readthedocs.yamlversion:2build:os:ubuntu-22.04tools:python:3.9mkdocs:configuration:mkdocs.ymlpython:install:-requirements:requirements_docs.txt总结mkdocs-static-i18n和 Read the Docs 各自管理语言和版本两者叠加时的核心矛盾是路径前缀冲突——插件生成的绝对路径不包含 Read the Docs 注入的版本前缀。解决思路只有一个全部使用相对路径。site_url: 让 MkDocs 不硬编码域名和路径extra.alternate用.和zh/替代/和/zh/Hook 中英文页用zh/不是../zh/中文页用../JS 跳转 URL 从 DOM 读取而非拼接配合 Read the Docs 后台选择 Multiple versions without translations、不创建翻译项目即可在任意版本/latest/、/v2.0/下正常使用多语言切换。

相关新闻

SpringBoot3+OAuth2 企业 SSO:不透明 Token、Redis 热缓存与授权码一次性消费

SpringBoot3+OAuth2 企业 SSO:不透明 Token、Redis 热缓存与授权码一次性消费

SpringBoot3OAuth2 企业 SSO:不透明 Token、Redis 热缓存与授权码一次性消费 🌐 文档地址:https://ruoyioffice.com 📦 源码1GitHub:https://github.com/yuqing2026/ruoyi-office 📦 源码2GitCode&#xff…

2026/8/23 23:06:29 阅读更多 →
网站被攻击怎么办?站长必备的应急响应指南

网站被攻击怎么办?站长必备的应急响应指南

网站被攻击怎么办?站长必备的应急响应指南导读 在数字化时代,网站已成为企业展示形象、开展业务的重要平台。然而,随着网络攻击手段的不断升级,网站安全威胁日益严峻。从DDoS攻击、SQL注入到XSS跨站脚本,各类安全漏洞可…

2026/8/23 23:06:29 阅读更多 →
关于文件系统和文件格式

关于文件系统和文件格式

关于文件系统和文件格式现在电脑启动都是uefigptgpt分区表记录了硬盘上每个分区的信息,比如每个分区从哪个扇区开始,每个分区分了多少个最前面的扇区来存储系统盘里面的启动程序,在启动程序后面的扇区分了多少个扇区存储文件格式数据&#xf…

2026/8/23 23:06:29 阅读更多 →

最新新闻

LLM Agent内存优化:证据条件化渐进执行框架解析与实践

LLM Agent内存优化:证据条件化渐进执行框架解析与实践

1. 项目概述:当内存足够时停止最近在折腾大语言模型智能体(LLM Agents)时,一个绕不开的痛点就是资源消耗,尤其是内存。你肯定也遇到过类似的情况:精心设计的Agent流程,在处理复杂任务时&#xf…

2026/8/24 3:33:11 阅读更多 →
AI智能体推理中的隐私合规挑战:CARE框架解决证据不一致问题

AI智能体推理中的隐私合规挑战:CARE框架解决证据不一致问题

1. 项目概述:当AI需要“自证清白”时,我们遇到了什么?最近在折腾一个挺有意思的AI项目,核心问题其实很普遍:我们想让大语言模型(LLM)去完成一些复杂的、需要多步推理的任务,比如分析…

2026/8/24 3:33:11 阅读更多 →
LLM Agent如何模拟权力不对称对话的社会认知效应?

LLM Agent如何模拟权力不对称对话的社会认知效应?

1. 从一次失败的“人机协作”实验说起去年,我参与了一个旨在探索大语言模型(LLM)在复杂商业谈判场景中应用潜力的项目。我们设计了一个模拟场景:一个经验丰富的资深经理(高权力方)与一个刚入职的年轻员工&a…

2026/8/24 3:33:11 阅读更多 →
redis--006

redis--006

1. 缓存穿透现象查询缓存、数据库都不存在的数据,大量恶意请求直接打向数据库,压垮 DB。例:传入不存在的商铺 id,Redis 查不到,每次都去访问数据库。解决方案缓存空值(缓存 null) 查询数据库不存…

2026/8/24 3:33:11 阅读更多 →
IOPaint完整指南:5分钟上手AI图像修复工具

IOPaint完整指南:5分钟上手AI图像修复工具

IOPaint完整指南:5分钟上手AI图像修复工具 【免费下载链接】IOPaint Image inpainting tool powered by SOTA AI Model. Remove any unwanted object, defect, people from your pictures or erase and replace(powered by stable diffusion) any thing on your pic…

2026/8/24 3:33:11 阅读更多 →
Vue 3组件定义方式全解析:从选项式到组合式API的实战演进

Vue 3组件定义方式全解析:从选项式到组合式API的实战演进

1. 从“选项”到“组合”:Vue 3组件定义方式的演进背景如果你是从Vue 2时代过来的开发者,或者正在学习Vue 3,那么“如何定义一个组件”这个问题,可能比你想象的要复杂和有趣得多。在Vue 2的世界里,答案几乎是唯一的&am…

2026/8/24 3:32:11 阅读更多 →

日新闻

前端内容安全与依赖审计实践

前端内容安全与依赖审计实践

前端内容安全与依赖审计实践 前端安全依赖分层防护。没有任何单一配置能替代输出编码、权限校验和依赖更新。 把不可信内容当作数据 默认使用框架的转义能力;确需渲染 HTML 时,先在服务端或可信的客户端库中进行白名单过滤。避免把用户输入直接赋给 inne…

2026/8/24 1:08:15 阅读更多 →
Windows登录密码存储机制全解析:从哈希算法到安全加固实战

Windows登录密码存储机制全解析:从哈希算法到安全加固实战

1. 项目概述:Windows登录密码的“黑匣子”每次你按下CtrlAltDel,输入密码,然后看到那个熟悉的桌面,这背后发生了一系列复杂而精密的操作。作为一名长期与Windows系统打交道的从业者,我经常被问到:“我的密码…

2026/8/24 1:08:15 阅读更多 →
AI面试系统安全挑战与解决方案

AI面试系统安全挑战与解决方案

1. 项目概述:AI面试系统的安全挑战去年参与某跨国企业AI面试系统部署时,遇到一个典型案例:候选人在视频面试中无意提到竞争对手产品名称,系统竟自动将该信息关联到企业知识库并生成竞品分析报告。这个看似"智能"的功能&…

2026/8/24 1:08:15 阅读更多 →

周新闻

[光学原理与应用-521]:对光的错误理解与纠偏

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/24 0:06:02 阅读更多 →
SIP通话转接原理与REFER方法实战解析

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/24 0:20:20 阅读更多 →
Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/24 0:14:11 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/23 18:47:06 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/23 12:10:44 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/22 3:22:48 阅读更多 →