Boloop 阅读示意图(AI 生成):语言切换需要保持 URL、正文与控件状态一致。

一次语言切换故障,现象很简单:从英文切到简体成功,紧接着再选繁体,下拉框已经显示繁体,URL、标题和正文却仍然停在简体。

如果只检查下拉框的值,这次操作会看起来已经成功。真正暴露问题的是几个本来应该同步变化的地方,给出了不同的答案。

这是 Boloop 在 2026 年 10 月 12 日的早期私有预览中记录到的现象。它给我们的提醒是,服务端页面已经显示出来,与前端交互已经准备好,是两件需要分别处理的事。

先把不一致的几个状态保留下来

当时的实际浏览器记录来自已登录的 Edge 会话。英文切简体可以完成;在新显示的页面上立即选繁体时,控件值变了,其他内容没有跟上。

还有几个观察同样关键:页脚的普通语言链接可以工作;刷新页面或触发主题更新后,显示会恢复一致;稍后重试页头语言选择,或者直接打开语言链接,也可以正常进入相应页面。

这些现象使“这个语言完全没有内容”和“所有语言路由都坏了”变得不太符合观察。但它们仍然不足以精确证明哪一条脚本在什么时刻失效。原故障没有保留下完整的浏览器网络追踪,不能后来补写一个确定的加载时间。

更合理的解释是:原生选择框在 React 安装事件处理之前,已经接受了用户输入。它能改变自己的显示值,却没有完成应用所需的语言跳转。我们把这个解释作为有观察支持的判断,继续用受控实验检查。

修复的是交互准备状态

针对这段窗口,候选实现让服务端 HTML 中的语言控件保持禁用,并标记忙碌。React 的首次渲染保持相同状态,挂载完成后再允许操作。

这样,尚未准备好的控件不会表现成一个可以正常使用的入口。等待期间,用户仍能通过页脚的真实语言链接导航。链接不是 JavaScript 事件的装饰外壳,而是保留了实际 URL。

前端准备好以后,普通语言切换在同一文档中更新 React 状态与浏览器历史,保留当前资源的深层路径,并同步语言元数据。对修改键点击、无 JavaScript 和直接导航,普通链接仍有自己的作用。

这几项改变共同解决的是可见页面与可用交互之间的衔接。它们并没有要求改动托管站点的访问范围、认证规则或资源路径。

另一个错误,要作为另一条原因处理

排查过程中还单独复现了浏览器存储抛出 SecurityError 的情况。原来的存储读取可能使启动中断,写入失败也可能打断语言导航。

于是我们把本地存储当成可选能力:读取失败使用默认值,写入失败也不阻止用户继续操作。主题和语言的使用不应该以“这个浏览器一定允许写存储”为前提。

但这不是原来那次 Edge 故障已经确认的根因。两种问题都值得修,证据却不同。把它们合成一句“因为 localStorage 出错导致切换失败”,会丢掉原始观察,也让后来的回归测试不知道应该覆盖什么。

故意放慢加载,才能看见那段空隙

受控浏览器回归给 JavaScript 响应增加了 15 秒延迟。这个延迟来自测试环境,用于扩大观察窗口,不是对原来用户网络速度的测量,也没有进入正式 Worker。

等待脚本时,服务端内容已经可读,语言控件保持禁用;页脚链接仍可打开另一种语言。脚本完成后,控件启用,再连续切换英文、简体、繁体,检查的不只是选项,而是整组状态:

| 检查位置 | 需要保持一致的内容 | |---|---| | 地址栏 | 当前语言前缀与原资源路径 | | 页面正文 | 对应语言的标题与内容 | | 文档元数据 | 文档语言和页面标题 | | 控件 | 当前语言的选中状态 | | 浏览器历史 | 后退、前进后的语言与内容 |

那轮独立 Chromium 回归中,连续九次语言选择保持一致,深层 Skill、软件和文章页面也覆盖了切换、后退、前进与刷新。构建和相关自动检查通过,但这些仍是当时受控环境的证据,不能代替原私有托管预览上的同条件复验。

后来的控件可以变,这条约束仍然保留

Boloop 后来把公开页面的原生下拉框替换为统一的自定义菜单。新的外观解决了另一类问题:系统弹出的列表与网站主题不一致。语言控制仍然需要保留 hydration 阶段的禁用与忙碌行为。

因此,这次故障并不是一份“原生 select 都不可靠”的结论。它更适合变成一个长期检查:页面出现时,哪些操作已经可以安全执行,哪些还在等待客户端接管?

如果你也遇到“控件变了,页面没变”,可以先同时记录地址、正文、元数据和控件状态,再比较立即操作与稍后操作、直接链接与脚本导航。必要时在隔离预览中放慢资源加载。

这些对照能让一个偶发的界面抱怨,变成可解释、可回归的问题。至于最终原因,则应留给实际观察来决定。