最近,我在继续整理 A Tour of Go 多语言翻译项目的开发博客。
为了方便不同语言的读者阅读,我通常会先发布中文文章,再使用 SlyTranslate 将文章翻译成英文。
除了标题和正文,文章的标签也需要同步翻译。
我的操作流程比较固定:
- 发布中文文章,并设置相应标签。
- 执行批量脚本,为尚未翻译的中文标签创建英文版本。
- 检查并完善英文标签的名称与别名。
- 使用 SlyTranslate 翻译文章。
- 检查英文草稿,确认没有问题后发布。
按照这个流程,只要中文文章有 8 个标签,而且这 8 个标签都已经建立英文对应关系,英文文章就应该关联相应的 8 个英文标签。
但实际情况并不是这样。
这次遇到的问题很有意思:原来就存在的英文标签能够正常继承,刚刚创建的英文标签却会遗漏。
更奇怪的是,数据库中的标签和翻译关系明明都是正确的。
一、第一次检查:英文文章少了两个标签
2026 年 10 月 20 日,我在处理一篇新的开发博客时,再次发现了标签遗漏问题。
当时的中文文章 ID 为 27889,一共设置了 8 个标签。
其中,6 个标签已经有对应的英文版本,另外 2 个是新增加的。
我按照平时的流程执行标签同步脚本。
同步完成后,两个新标签也都成功创建了英文版本:
| 中文标签 | 英文标签 | 对应关系 |
|---|---|---|
| 本地内容覆盖 | Local Content Override | 46228 → 46232 |
| 站点架构 | Site Architecture | 46230 → 46234 |
随后,我手动修改了新英文标签的名称和别名,并再次检查中英文标签的对应关系。
检查结果表明,8 个中文标签全部都有正确的英文对应关系。
到这里,我原本认为标签准备工作已经完成,可以开始翻译文章了。
但英文草稿生成后,问题再次出现。
英文草稿 ID 为 27892,实际只有 6 个标签。
恰好缺少刚刚创建的两个英文标签:
- Local Content Override
- Site Architecture
其他 6 个原来就存在的标签全部正常。
这一次,我特别注意了检查时机。
在打开英文文章编辑器之前,我就已经通过数据库和接口检查,确认英文草稿只有 6 个标签。
因此,可以排除一种可能:不是因为打开编辑器、保存文章或者页面显示异常,才导致两个标签丢失。
遗漏发生在翻译生成英文草稿的过程中,或者与这个过程直接相关的标签关联环节。
二、数据库明明正确,为什么程序却读不到?
接下来,我开始检查 Polylang 保存的标签翻译关系。
Polylang 是 WordPress 中负责多语言内容关联的插件。
例如,中文标签“站点架构”与英文标签“Site Architecture”,并不是单纯通过名称对应,而是由 Polylang 保存它们之间的翻译关系。
检查结果显示,这两个新标签的翻译关系在数据库中都存在。
而且关系没有写错。
这就出现了一个需要解释的矛盾:
数据库知道中文标签对应哪个英文标签,但 SlyTranslate 在迁移标签时,似乎没有获取到这些对应关系。
之前排查时,我还发现过另一个现象。
英文标签已经改成英文名称,但通过某些 WordPress 接口读取,得到的仍然是修改前的中文名称。
直接查询数据库,结果却是正确的英文名称。
这让我开始怀疑缓存。
不过,怀疑并不意味着已经找到原因。
如果只因为接口数据和数据库不一致,就直接清理整个 Redis 缓存,虽然可能暂时恢复正常,却无法解释问题究竟出在哪里。
而且,清理缓存只是消除当前的旧数据,并不能保证下一次创建新标签时不会再次发生。
所以,我决定继续调查具体的缓存读取过程。
三、真正的突破:命令行和网站读到了不同的缓存
进一步检查 WordPress、Polylang 和 W3 Total Cache 的实际代码后,发现了一个非常关键的细节。
服务器命令行和网站请求使用的缓存键并不完全相同。
我的标签批量同步脚本通过 PHP 命令行执行。
而文章翻译请求是在 WordPress 网站环境中执行的。
网站安装的 W3 Total Cache 会将请求使用的主机名纳入对象缓存键。
这里的主机名,可以简单理解为请求对应的网站域名。
正常访问网站时,请求携带网站域名。
但原来的标签批处理脚本直接加载 WordPress,没有在加载前设置对应的主机名。
于是,两种执行环境可能使用不同的缓存位置。
这意味着:
命令行刚刚创建了英文标签,并更新了它所使用的缓存。
但网站请求使用的另一份缓存,可能仍然保留着英文标签创建之前的旧数据。
问题并不在于数据库没有保存成功,而在于网站读取到了旧缓存。
这次调查没有只停留在推测阶段。
通过只读方式检查 Redis 中实际保存的对象后,找到了对应的证据:
| 翻译组 | 数据库中的对应关系 | 网站旧缓存中的对应关系 |
|---|---|---|
| 46229 | 中文 46228、英文 46232 | 只有中文 46228 |
| 46231 | 中文 46230、英文 46234 | 只有中文 46230 |
这两条缓存记录恰好对应遗漏的两个新英文标签。
也就是说,数据库中的翻译组已经包含中英文两个成员,但网站旧缓存中的翻译组仍然只有中文成员。
接下来,就需要看看 SlyTranslate 遇到这种情况时会怎么处理。
四、为什么只遗漏新标签,而且没有报错?
检查 SlyTranslate 的代码后,发现标签迁移流程大致是这样的:
首先,读取中文文章关联的全部标签。
然后,逐个调用 Polylang 的接口,查询每个中文标签对应的英文标签。
如果查询成功,就把英文标签加入待关联列表。
如果查询不到,插件会根据配置决定是否自动创建英文标签。
而我的网站并没有启用翻译文章时自动创建标签的功能。
原因也很简单:我已经有专门的标签同步脚本,希望先准备好英文标签,再进行文章翻译。
因此,当 Polylang 因旧缓存返回“找不到英文对应标签”时,SlyTranslate 会直接跳过这个标签。
不会停止整个翻译过程。
也不会提醒我哪些标签被遗漏。
最终,插件只把成功找到的 6 个英文标签关联到草稿,然后返回成功。
这就解释了此前观察到的现象:
- 已存在较久的英文标签,缓存中的关系也是完整的,所以能够正常继承。
- 刚刚创建的英文标签,网站旧缓存中还没有对应关系,所以被跳过。
- 整篇文章仍然翻译成功,但标签数量少了两个。
这条触发链已经通过实际缓存内容和离线测试进行了验证。
不过,这里仍然需要区分证据与推断。
我们没有捕获到发生故障的那一次翻译请求内部的逐标签日志,因此不能说已经百分之百还原了当时每一次函数调用。
更准确的结论是:发现了与故障现象直接匹配的旧缓存,并且可以利用这些真实缓存数据复现“翻译成功,但遗漏两个新标签”的结果。
对我来说,这已经比单纯猜测 Redis 缓存异常有价值多了。
五、最初考虑修复缓存同步,后来改变了方向
找到缓存命名空间不一致的问题后,最初考虑的是修复标签批处理脚本。
方法并不复杂。
在加载 WordPress 之前,为命令行设置与网站请求一致的主机名。
这样,命令行和网站就可以使用相同的缓存命名空间。
同时,再修改 SlyTranslate,让它在发现标签对应关系不完整时明确报错,而不是继续跳过。
这个方案可以解决一部分问题,也比较符合正常的缓存维护思路。
但继续考虑后,我觉得还有一个更简单的方向。
既然问题发生在翻译文章时读取标签对应关系,那么能不能只让这个环节读取最新数据?
我的需求其实很明确:
我不要求整个 WordPress 网站都绕过缓存。
也不需要重新设计 Redis 的缓存管理机制。
更没有必要为此修改 Polylang 或 W3 Total Cache 的内部代码。
我只希望翻译文章时,能够准确找到每个中文标签对应的英文标签。
因此,我倾向于把修复限制在 SlyTranslate 中。
这也符合我最近处理项目性能和架构问题时逐渐形成的原则:
先解决当前真正影响使用的问题。
如果一个问题能够通过局部修改解决,就不要急着把它扩大成整个系统的架构改造。
六、最终只修改了 SlyTranslate 的一个文件
最终选择的方案,只修改:
PolylangAdapter.php
主要做了三件事。
1. 读取源文章标签时绕过查询结果缓存
让插件获取中文文章标签时,不再依赖可能已经过期的查询结果。
不过,单纯关闭这一层缓存还不够。
因为 Polylang 查询标签对应关系时,内部仍然可能读取另一层对象缓存。
这正是本次故障的关键。
2. 查询标签对应关系时,直接读取数据库
新增一个专门用于标签迁移的内部函数:
get_uncached_tag_translation()
它通过参数化的只读数据库查询,获取 Polylang 保存的中英文标签对应关系。
这样,即使 Redis 里仍然保存着旧翻译组,也不会影响这一步获取最新映射。
需要说明的是,这个方案使用了当前 Polylang 版本的内部数据保存格式。
它不是 Polylang 官方公开接口提供的通用功能。
因此,未来升级 Polylang 时,需要重新确认这部分代码是否仍然适用。
我认为这个代价可以接受。
毕竟,依赖集中在一个小范围的函数中,比为了修复一个标签迁移问题而改造全站缓存机制更容易控制。
3. 标签写入之后,再检查一遍
过去的实现有一个明显不足。
调用 WordPress 的标签关联接口后,并没有确认所有标签是否真的关联成功。
现在增加了完整性检查。
例如,预期应该关联 8 个英文标签,那么在执行完标签关联以及相关同步操作后,需要重新读取实际标签列表。
不仅比较数量,还要比较具体的标签 ID。
只有预期集合与实际集合完全一致,才能认为标签迁移成功。
如果缺少标签、出现多余标签,或者数据库查询出错,就明确返回错误。
这样,即使以后遇到其他原因导致部分标签写入失败,也不会再轻易出现“文章翻译成功,但标签悄悄少了几个”的情况。
整个方案没有清理 Redis 缓存,也没有引入自动重新翻译机制。
同时,它采用了更严格的策略:翻译前必须准备好全部英文标签对应关系。如果对应关系不完整,就报告错误,而不是跳过标签继续完成草稿。
七、部署之前,先验证真实故障能否复现
这次没有直接把修改后的 PHP 文件上传服务器。
先使用之前保存的真实缓存数据进行离线测试。
测试重点不是简单确认函数能够正常运行,而是确认原来的故障能否被准确复现,以及修改后的程序能否避免相同结果。
其中最重要的对照是:
| 测试情况 | 原版 SlyTranslate | 修改后的版本 |
|---|---|---|
| 使用实际旧缓存数据 | 返回成功,仅关联 6 个标签 | 正确关联全部 8 个标签 |
| 标签对应关系不完整 | 可能直接跳过 | 明确报错 |
| 标签只写入一部分 | 可能继续返回成功 | 检查失败 |
| 后续操作移除了某个标签 | 缺少最终核验 | 能够发现遗漏 |
最终,23 项离线检查全部通过。
PHP 语法检查也通过。
当然,离线测试仍然不能代替真正的网站环境。
所以我决定先部署修复,再用下一篇正常发布的博客验证。
这样既不用专门制造测试文章,也不需要对已经翻译好的文章重复调用翻译模型。
八、生产部署过程也遇到了两个小问题
代码修复完成后,还需要将文件部署到生产服务器,并提交至 GitHub。
部署时,我要求先核对生产原文件的哈希值,确认与本地分析的版本完全一致。
随后备份原文件,再替换为修复版本。
整个过程只涉及 SlyTranslate 的一个 PHP 文件。
但第一次执行部署命令时,出现了一个错误:
php: 未找到命令
原因并不是服务器没有安装 PHP,而是 PHP 程序没有加入当前命令行的默认搜索路径。
服务器实际使用的 PHP 路径是:
/usr/local/php/bin/php
改用这个路径后,PHP 语法检查通过,生产文件也成功替换。
接着,在提交 GitHub 时又遇到一个小问题。
因为仓库中保存了一份代码补丁文件,Git 的空白检查把补丁中的部分上下文缩进识别为格式问题。
但这些内容属于补丁文件本身,并不是实际 PHP 代码的错误。
因此,我没有为了消除提示而随意修改补丁内容。
最后改为分别检查真实代码的格式,以及补丁能否正确应用。
检查通过后,成功提交并推送到 GitHub。
本次提交:
36a9621:修复 SlyTranslate 翻译时新增英文标签遗漏
至此,代码修复和生产部署全部完成。
但我还没有把这个问题直接判定为彻底解决。
因为还有最后一步:使用真实的新文章验证。
九、下一篇真实博客验证通过
修复部署完成后,我继续按照平时的流程处理下一篇开发博客。
没有重新设计测试流程,也没有专门创建测试文章。
在正常完成中文文章发布、标签准备和英文翻译之后,我检查了新生成的英文文章。
这次英文文章 ID 为 27896。
文章关联了完整的 8 个英文标签:
- code block
- Code Comments
- Completeness Audit
- Go
- Go Documentation
- localization
- Multilingual Translation
- Translation Completeness
我先在 WordPress 后台确认了标签列表。

随后,我又打开了英文文章的正式前台页面。
页面底部同样显示完整的 8 个标签。

至此,修复后的正常翻译流程已经通过了一次真实文章验证。
这比单纯看到 PHP 语法检查通过,或者离线测试全部成功,更能说明修改确实解决了当前使用中遇到的问题。
不过,我也没有因此把整个 WordPress 缓存体系判定为完全没有问题。
本次修改解决的是 SlyTranslate 在迁移文章标签时读取旧翻译映射的问题。
其他地方如果仍然依赖不同缓存命名空间,未来可能还有独立的缓存同步问题需要处理。
但那些问题不属于这次必须完成的修复范围。
十、这次排查带来的几个认识
回顾整个过程,我觉得有三点值得记录。
第一,数据库中的数据正确,不代表程序实际读取到的数据也正确。
最初,我已经确认新增英文标签存在,而且 Polylang 的中英文对应关系也正确。
如果只检查数据库,很容易认为标签同步环节已经完全正常。
但程序真正使用的数据,可能来自缓存。
当缓存与数据库不一致时,就会出现数据库正确、程序行为却错误的情况。
第二,不能因为数量看起来正常,就忽略具体内容。
这次新增标签遗漏还有一个比较明显的规律:
旧标签正常,新标签缺失。
这个规律最终帮助我们把调查范围缩小到新翻译关系的缓存问题,而不是继续怀疑整个标签关联流程。
后续的完整性检查,也不再只比较标签数量,而是比较具体的标签集合。
第三,修复问题不一定需要改造整个相关系统。
最初发现命令行与网站缓存不同步时,很容易把目标扩大成修复整个缓存体系。
但我的实际需求只是保证文章翻译时能获取正确的标签对应关系。
最后虽然单文件方案增加的代码行数更多,却只影响一个明确的业务环节。
对我来说,这样的改动范围更容易理解,也更方便将来维护。
最后
这次问题看起来只是英文文章少了两个标签。
但从最初确认数据库关系,到发现缓存命名空间差异,再到修改插件、完成部署和真实文章验证,中间涉及了 WordPress、Polylang、Redis、W3 Total Cache 以及 SlyTranslate 的多个环节。
最终,我没有选择全站清理缓存,也没有重新翻译已经生成的文章。
而是让 SlyTranslate 在迁移标签时读取最新的翻译关系,并在写入后检查结果是否完整。
现在,下一篇真实博客已经验证通过。
这次修复也已经提交到 GitHub,方便以后查看修改内容及维护。
对我来说,最重要的收获不是多写了几十行 PHP 代码,而是进一步确认了一种排查思路:
先找到程序实际读错了哪一层数据,再决定应该修复哪一个环节。
能够用局部修改解决的问题,就没有必要急着扩大成全站范围的改造。

