Hyaika Blog

Penguin is all you need

设计 技术

一篇打了三十年绳结的键盘 Bug——波兰字母 Ś 消失之谜

一篇打了三十年绳结的键盘 Bug——波兰字母 Ś 消失之谜

一篇打了三十年绳结的键盘 Bug——波兰字母 Ś 消失之谜

目录

  • 「我打不出 Ś」
  • 第一个意外:波兰语多了 9 个字母
  • 第二个意外:铁幕那边的键盘上,Alt 变成了临时工
  • 第三个意外:Ctrl+S 流进了肌肉记忆
  • 第四个意外:Windows 里藏着一个没人注意的等式
  • 当四个绳结拉到一起
  • 现场验证:在我的机器上重现 2015 年的 Bug
  • 最后:一段加在 if 条件里的历史

「我打不出 Ś」

2015 年初,有人在 Medium 提了一个 Bug 报告,语气很困惑:

「我刚用波兰语写了一篇文章。其他字母都能打出来,除了 Ś。按 Ś 的那个键,字母不出来。只有 Medium 上有这个问题。」

在 Medium 工作的波兰工程师 Marcin Wichary 看了一眼这个报告。

不是因为他有多擅长 Debug——而是因为他本身就是波兰人。他知道波兰语有 32 个字母,比英语多了 9 个带变音符号的字母。Ś 只是其中之一,随机性上说没有理由只有它出问题。

但当他开始追这个 Bug,发现了一条打了三十年结的绳子。

四个节点,来自四个完全不同的时代和领域:波兰语的 9 个变音字母、1980 年代计划经济下的键盘布局、Ctrl+S 的肌肉记忆、以及 Windows 内部一个几乎没人注意的映射。

单独看,每一个都很合理。连在一起,Ś 消失了。


第一个意外:波兰语多了 9 个字母

波兰语是继俄语之后使用人数第二多的斯拉夫语。和俄语用西里尔字母不同,波兰语用的是拉丁字母,但多了 9 个带变音符号的字母——Ą、Ć、Ę、Ł、Ń、Ó、Ś、Ź、Ż。

这些字母不是装饰品。没有 diacritics(变音符号),波兰语就没办法读了。

举一个作者在原文里用的例子:「ZŁY」(坏的)和 「ZLY」(呃,什么都没写对,但可能被误解为某个缩写)——看起来只是少了 L 上的一杠,但意思完全不同了。

所以打字机时期,波兰的解决方案是把 Ł 和 Ż 升级成独立键(不需要组合键就能打出来),其他 7 个则和数字键共享。用打字机打 Ź 或 Ć 的流程是:按字母 → 退格 → 覆盖叠印变音符号。这听起来像石器时代,但确实是当时全球通行的做法。

打字机时代还能凑合。真正的挑战来自 1980 年代。


第二个意外:铁幕那边的键盘上,Alt 变成了临时工

1980 年代波兰还在共产主义政权下。华约国家对西方科技产品的商业进口是被禁止的。

这意味着什么?普通人能买到的电脑,都是美国键盘布局的个人电脑走私进来或二手交易的——美国键盘上根本没有 Ł、Ś、Ć 的键位。

同时期法国、德国等西欧国家,从早期 PC 时代开始就有了定制键盘布局。

但在波兰,物理键盘上只有一个 Alt 键,其余什么都没有。

于是波兰人发明了一个方案:用 Alt 组合键输入波兰语中的变音字母。

具体来说,把 Alt 当作一个类似 Shift 的修饰键:Alt+A=Ą,Alt+E=Ę,Alt+C=Ć……等等。不需要改物理键盘,不需要贴纸,只需要一个软件层的映射。

波兰人给它起了个外号叫「程序员布局」——因为早期 PC 用户大多是程序员,他们需要完整的标点符号来写代码。相比之下,传统的「打字员布局」保留了所有标点符号完整,但意味着你需要买一台专门的波兰键盘。

结果是:程序员布局赢了。即使十年后波兰市场上有了正经的波兰键盘,也没人愿意切换到打字员布局。就像 QWERTY 在英语世界统治了打字机时代一样,Alt+L、Alt+C 的组合在波兰统治了后 PC 时代。

重要的事情来了:打出 Ś 的快捷键是 Alt+S。


第三个意外:Ctrl+S 流进了肌肉记忆

1980 到 1990 年代,软盘保存是又慢又磨损的苦差。磁片驱动器吱吱响,写入过程会吃掉所有 CPU 时间。你没办法一边写文档一边等它存完。

所以「手动保存」这个动作变成了程序员的自动反应。写过文稿到一半电脑死机的人都知道那种痛——Ctrl+S 在 1980 年代不是快捷键,是生存本能。

有人每段话按一次,有人一句话按一次,有人打字后焦虑症候群地隔几秒按一次。Ctrl+S 成了和「摸键盘」一样内化的动作。

到了 Web 编辑器时代(包括 Medium),这个习惯反过来咬人了。

在浏览器里按 Ctrl+S,默认行为是弹出「保存网页」的对话框。这对在线编辑器来说是多余的——内容早就自动保存了,这个对话框只会打断写作。

所以 Medium 加了一行代码来拦截 Ctrl+S:

if ((e.metaKey || e.ctrlKey) && e.keyCode === goog.events.KeyCodes.S) {
    this._editors.save();
    e.preventDefault();
}

逻辑很简单:如果 S 带着 Ctrl 或 Command 键按下,触发编辑器内保存,阻止浏览器的默认行为。

但这里埋了一个伏笔。注意这个条件判断只检查了 Ctrl 和 Command,没检查 Alt 的状态。


第四个意外:Windows 里藏着一个没人注意的等式

Windows 95 有一个很体贴但也很微妙的设计。

为了让用户只用键盘就能访问菜单栏,Windows 支持 Alt+[下划线字母] 快捷键(菜单加速键)。但同时,波兰人已经用 Alt 输入变音字符了。冲突怎么办?

解决方案:左 Alt 保留给 Windows 快捷键,右 Alt 用来输入变音字符。

但问题是:老旧键盘只有一个 Alt 键。为了让这两个场景在物理层兼容,微软在操作系统层面做了一个内部映射——右 Alt 被当作 Ctrl+Alt 处理。

也就是说,如果你用右 Alt+S 输入 Ś,操作系统看到的其实是 Ctrl+Alt+S

你现在应该看出来为什么这四个绳结会走到一起了。


当四个绳结拉到一起

现在重新看 Medium 那行代码:

if ((e.metaKey || e.ctrlKey) && e.keyCode === goog.events.KeyCodes.S) { ... e.preventDefault(); }

波兰人打 Ś → 按右 Alt+S → 操作系统翻译成 Ctrl+Alt+S → JavaScript 事件捕获到 Ctrl+S(额外多了一个 Alt) → e.preventDefault() 阻止了默认行为 → Ś 不见了。

一行代码,四个历史事件的交汇:

  1. 波兰语多了 9 个字母
  2. 电脑是美国键盘走私进来的,只能在 Alt 上想办法
  3. 离线编辑时代的肌记忆变成了 Ctrl+S 的条件反射
  4. Windows 为了兼容老键盘,把右 Alt 变成了 Ctrl+Alt

没有一个参与者是有恶意的。波兰人只是想让那 9 个字母有一个归宿。微软只是不想让只有一个 Alt 键的人没法工作。Medium 的工程师只是想消除一个无用的浏览器对话框。

但拼在一起,它们把波兰字母 Ś 变成了一个永远不会到达的光标。


现场验证:在我的机器上重现 2015 年的 Bug

这篇文章讲的是 2015 年的 Bug,而且修复已经过去 11 年了。但它背后的逻辑跨越了四个不同的历史层。

我在自己的系统上试了一下。我调出波兰语(程序员布局)键盘,打开一个纯文本编辑器,按右 Alt+S:

Ś

——出来了。2026 年的浏览器和操作系统已经没有这个 Bug 了。

但我不只是想验证「它修好了」。我想看这个 Bug 在今天是否还有理论可能性

起因是 Medium 的那行修复代码:

- if ((e.metaKey || e.ctrlKey) && e.keyCode === goog.events.KeyCodes.S) {
+ if ((e.metaKey || (e.ctrlKey && !e.altKey)) && e.keyCode === goog.events.KeyCodes.S) {

就是加了 !e.altKey。检查 Alt 是否被按下。简单到不可思议:一个 && !e.altKey,让四层历史同时解套。

但这告诉我一个更大的事实——同样模式的 Bug 今天仍然在到处发生。 任何一个拦截键盘事件的网页应用,如果只想着「Ctrl+S = 保存」,而没想过「Ctrl+S 也可以用什么组合方式出现」,都有潜在风险。

Signal、Figma、Google Docs——这些对键盘事件深度定制的应用,是新技术栈上的 Medium。它们的修改过的键盘拦截逻辑里,也可能藏着类似的"历史绳结"。

不是 Bug,是 多个没问题的东西放在一起之后变成了一个 Bug


最后:一段加在 if 条件里的历史

修复这件事的工程师 Marcin Wichary 在文章里说,他之所以能迅速定位这个 Bug,是因为他本人正好走过那三十年:

「我在我妈妈的打字机上学会了触摸打字,拥有过早期 PC 的美国键盘,用 Alt+S 打了不知道多少个 Ś——而 Ctrl+S 的肌肉记忆从 80 年代就刻在我手指上。」

他在 Medium 的代码库里加了一段注释。不是因为代码复杂——是为了让以后看到这行 !e.altKey 的人知道,他们不是在读一段代码,是在读一部微缩的计算机史。

波兰语 32 个字母中的那一个,花了三十年,穿过四个时代,撞到了一行 if 条件上。


「作家可以修改历史,但修 Bug 的人必须理解历史。」

—— Marcin Wichary, Medium 工程师

分享:

评论(0)

暂无评论,来写第一条吧~

发表评论