没有不值得去解决的问题,也没有不值得去学习的技术!

SlyTranslate 翻译报错:修复 Paragraph Run 无法识别带属性的问题

图1:SlyTranslate 使用 GLM-5.2 翻译文章时出现 Paragraph Run 错误

作者:

WordPress AI 翻译工程实战

图1:SlyTranslate 使用 GLM-5.2 翻译文章时出现 Paragraph Run 错误

(1) SlyTranslate 翻译报错:修复 Paragraph Run 无法识别带属性的问题

WordPress 经典编辑器代码块无损迁移:批量转换 SyntaxHighlighter 短代码为 Gutenberg 区块

(2) WordPress 经典编辑器代码块无损迁移:批量转换 SyntaxHighlighter 短代码为 Gutenberg 区块

点击翻译按钮后,遇到一个弹窗“神秘”闪退问题及解决方法

(3) 点击翻译按钮后,遇到一个弹窗“神秘”闪退问题及解决方法

图5:DeepL API 注册页面,国家或地区列表中没有中国大陆

(4) 中国大陆 WordPress 博客想提升英文翻译质量,为什么这么麻烦?一次 Polylang + AutoPoly + DeepL API 排查复盘

图5:DeepSeek 的评分结果截图

(5) Chrome Built-in AI、Yandex Translate、ChatGPT Plus 翻译质量对比:我为什么决定重点英文文章改用 ChatGPT Plus

[截图 2:浏览器 Network 中直接请求 translate.yandex.net]

(6) 从 AutoPoly 到 Yandex 网页版:一次 WordPress 英文自动翻译质量验证

【图1:AutoPoly Free 与 Pro 功能对比截图】

(7) AutoPoly Pro + OpenAI API 能否自动化 WordPress 英文翻译?一次从技术到支付的可行性分析

【图2:点击“立即试用”后,ChatGPT 输入框中出现“代理模式”】

(8) ChatGPT Agent 能否替代 ChatGPT Plus 完成 WordPress 英文重译?

【图 7,SlyTranslate 显示翻译完成并创建文章 19426】

(9) SlyTranslate + 智谱 GLM-5.2 长文翻译排障:从 600 秒超时到完整生成英文 WordPress 文章

【图 5,54 个代码块精确匹配、差异数量为 0】

(10) WordPress SlyTranslate + GLM-5.2 长文单次翻译实战:排查 invalid_translation_runaway_output 假失败

【图 2,SlyTranslate 的 Additional Instructions 保持为空】

(11) SlyTranslate + GLM-5.2 翻译质量继续优化:从关闭随机采样、内置提示词到深度思考与模型 A/B 测试

【图6,终端中的完整测试结果】

(12) 在 GLM 5.2 之后测试 DeepSeek:一次面向 WordPress 长文翻译的模型对比与取舍

【图 4,“英文博客翻译质量优化”项目中已经整理完成的会话列表】

(13) 为英文博客翻译质量优化建立独立 ChatGPT 项目:整理历史会话并统一后续评测标准

【图3:Gemini 3.5 Flash 首次成功调用结果】

(14) 暂缓 OpenAI 后,我在本地测试了 Gemini:与 GLM 5.2 的 WordPress 技术文章翻译对比

图3:清理英文 Host 下的对象与术语关系缓存后,英文文章重新显示 SEO Tools、Yoast SEO 和 Blog SEO Log

(15) 为 SlyTranslate 的 GLM 5.2 整篇翻译补上 Plaintext 智能处理,并修复 Code Block Pro 区块失效

图5:英文文章发布后,前台特色图片、分类、标签和 Series 顺序均正常

(16) SlyTranslate + GLM-5.2 翻译 WordPress 长文章后,分类、标签、Series 与特色图片同步问题排查

图1:Codex 输出 Token 校验诊断结果

(17) SlyTranslate 全文翻译报错排查:完善 GLM 5.2 的段落与占位符约束

图1:创建 wordpress-ai-translation-pipeline 私有 GitHub 仓库

(18) 将 WordPress AI 翻译优化项目提交到 GitHub:一次阶段性的收尾

图1:文章列表中因摘要为空而重复出现两个 Post Views

(19) 从空摘要、历史格式混杂到重新翻译:我如何规划 WordPress AI 翻译工程的下一阶段

图4:最终批处理输出 42/42 完成、待处理 0、异常状态 0。

(20) 从只读审计到 42/42 完成:用 GLM 与 SlyTranslate 安全补全 WordPress 历史文章摘要

图3:GitHub 已正确识别仓库的 MIT License。

(21) 将 WordPress AI 翻译优化仓库从私有切换为公共:从敏感信息审计到最小改动发布

图1:仓库显示所有历史批次已经完成,并允许创建下一批。

(22) 每天批量处理 20 篇 WordPress 历史文章:代码块迁移、摘要补全与英文覆盖翻译完整 SOP

图 2:复杂 Classic 一次转换成多个 Gutenberg 区块

(23) 每天批量处理 20 篇 Mixed WordPress 历史文章:Classic 转 Gutenberg、SyntaxHighlighter 迁移、摘要补全与英文覆盖翻译完整 SOP

GLM-5.2 整篇翻译再次出现 Protected Token 校验失败:从尾部 Token 丢失到 Plaintext 结构重排的完整排查记录

(24) GLM-5.2 整篇翻译再次出现 Protected Token 校验失败:从尾部 Token 丢失到 Plaintext 结构重排的完整排查记录

图 1:历史文章 4652 在 GLM-5.2 整篇翻译时返回 HTTP 400 安全检测错误

(25) GLM-5.2 整篇翻译持续返回 HTTP 400:从安全检测拦截到 Plaintext 载荷缩减的完整排查与修复

图 1:历史文章批处理返回 HTTP 400,并提示内容安全检查未通过。

(26) GLM-5.2 整篇翻译返回 400:一次内容安全误拦截的逐步定位与恢复记录

图 5:文章最终已经 completed,但原来的 excerpt_generation_failed、HTTP 400 和人工完成记录同时保留。

(27) WordPress 历史文章自动化遇到 GLM 1301:从 HTTP 400 到 ChatGPT 人工兜底完成

图 1:index 814 的 INLINE Token 换序及中英文 Gutenberg 对照

(28) WordPress AI 翻译排错实战:一次合法的行内代码换序,为什么会阻断尾部结构自动修复?

图 1:完整文章首次整篇翻译失败,SlyTranslate 返回受保护标记校验错误

(29) WordPress 超长 Gutenberg 文章整篇 AI 翻译踩坑:从 1000+ 受保护标记到完整翻译成功

【图1:历史文章迁移最终状态,67 个固定批次、1281 篇文章全部完成】

(30) WordPress 历史文章迁移终于收尾:全量审计 1438 篇中文文章,并停用 SyntaxHighlighter Evolved

最近继续使用 SlyTranslate + GLM-5.2 翻译 WordPress 中文博客时,遇到了一个之前偶尔出现过的问题。

中文文章已经正常保存,在 Gutenberg 编辑器里也看不出什么异常,但点击 SlyTranslate 的“Translate now”以后,翻译直接失败:

Plaintext
A paragraph run did not contain a top-level paragraph.
图1:SlyTranslate 使用 GLM-5.2 翻译文章时出现 Paragraph Run 错误
图1:SlyTranslate 使用 GLM-5.2 翻译文章时出现 Paragraph Run 错误

以前我也遇到过类似情况。当时曾经通过一个比较奇怪的方法绕过去:在中文文章末尾增加一个空段落,保存以后再删除这个空段落,然后重新保存。

那一次重新翻译就成功了。

所以这次最开始我也重复尝试了几次,但已经没有任何作用。

既然文章内容本身看不出异常,继续反复修改 Gutenberg 区块也没有太大意义,于是决定直接查找错误到底来自哪里。

一、先定位错误来自哪里

我在 WordPress 生产目录中搜索完整的报错文本:

Bash
grep -RniF \
  --include='*.php' \
  --include='*.js' \
  'A paragraph run did not contain a top-level paragraph.' \
  wp-content/plugins \
  wp-content/mu-plugins

结果只有一处:

Plaintext
wp-content/mu-plugins/swq-glm52-translation-tuning.php:2193:
return new WP_Error( 'swq_full_article_paragraph_run_empty', 'A paragraph run did not contain a top-level paragraph.' );
图2:通过 grep 定位 Paragraph Run 错误来自自定义 MU Plugin
图2:通过 grep 定位 Paragraph Run 错误来自自定义 MU Plugin

这一步把排查范围缩小了很多。

错误并不是 SlyTranslate 核心插件直接产生的,而是来自我此前为 GLM-5.2 整篇 Gutenberg 翻译增加的 MU Plugin:

swq-glm52-translation-tuning.php

因此,问题更可能出现在我自己实现的 Paragraph Run 解析与恢复逻辑。

二、Paragraph Run 的作用

为了让长篇 Gutenberg 文章能够整篇交给 AI 翻译,同时尽量保护 WordPress 区块结构,我之前增加了一套 Paragraph Run 机制。

简单来说,连续的普通顶层段落可以组成一个 Paragraph Run。

例如:

HTML
<p>第一段。</p>
<p>第二段。</p>
<p>第三段。</p>

这些连续段落可以作为一个整体交给模型翻译。

这里并不要求翻译前后的 paragraph 数量完全一致。

自然语言翻译过程中,模型可能认为两个很短的中文段落合并成一个英文段落更加自然,也可能把一个很长的中文段落拆分成两个英文段落。

因此 Paragraph Run 本身允许:

3 个 paragraph → 2 个 paragraph

也允许:

1 个 paragraph → 2 个 paragraph

真正需要严格保持的是 Paragraph Run 外面的 Gutenberg 结构,例如标题、列表、引用、图片、代码块、表格以及其他受保护区块。

这些结构不能因为翻译而被删除、跨越或者随意重排。

三、真正的问题出在 <p> 解析

继续检查错误附近的代码以后,很快发现了真正值得怀疑的位置:

PHP
preg_match_all( '~<p>(.*?)</p>~su', $html, $matches, PREG_OFFSET_CAPTURE );

if ( empty( $matches[0] ) ) {
    return new WP_Error(
        'swq_full_article_paragraph_run_empty',
        'A paragraph run did not contain a top-level paragraph.'
    );
}

原来的正则只认识严格的:

HTML
<p>Text</p>

但是不能识别:

HTML
<p class="model">Text</p>

或者:

HTML
<p style="color:red">Text</p>

从 HTML 本身来看,这些仍然是 <p> 段落。

但是对于原来的 Paragraph Run parser 来说,只要 opening tag 上出现 attribute,就无法匹配。

如果一个 Paragraph Run 中没有匹配到任何符合 ~<p>(.*?)</p>~ 的内容,就会返回:

swq_full_article_paragraph_run_empty

最后在 SlyTranslate 中显示:

A paragraph run did not contain a top-level paragraph.

所以这个错误提示其实并不是说程序经过完整 DOM 分析以后确认“没有顶层 paragraph”。

真正发生的是:

模型返回的 paragraph wrapper 不再严格等于裸 <p>,而旧 parser 又只认识裸 <p>

四、问题来自模型返回的 wrapper 漂移

源文章本身并不是这次问题的重点。

当前 Paragraph Run 逻辑只会把符合条件的普通 Gutenberg paragraph 放入 run;带特殊 Gutenberg attributes 的 paragraph 仍然走更严格的结构保护路径。

真正可能发生变化的是 AI 返回结果。

例如发送给模型的是:

HTML
<p>第一段。</p>
<p>第二段。</p>

正常情况下应该返回:

HTML
<p>First paragraph.</p>
<p>Second paragraph.</p>

但模型偶尔可能自行增加 HTML attribute:

HTML
<p class="something">First paragraph.</p>
<p>Second paragraph.</p>

对于浏览器来说,第一个仍然是完全可以理解的 <p>

但对原来的正则:

Plaintext
~<p>(.*?)</p>~

来说,它已经不是符合要求的 paragraph。

这也解释了为什么文章在 Gutenberg 编辑器中肉眼看不出问题。

真正发生变化的地方并不是中文源文章,而是翻译模型返回的 HTML。

五、最终只做一个很小的兼容修复

由于现有翻译流程已经运行得比较稳定,所以这次没有重新设计 Paragraph Run,也没有改变它原来的 merge/split 行为。

最终只修改两个文件:

swq-glm52-translation-tuning.php

swq-paragraph-run-test.php

整个提交只有:

Plaintext
2 files changed, 12 insertions(+), 6 deletions(-)

真正的核心修改,是把原来的:

PHP
preg_match_all( '~<p>(.*?)</p>~su', $html, $matches, PREG_OFFSET_CAPTURE );

调整为:

PHP
preg_match_all( '~<p(?:\s+[^<>]*)?>(.*?)</p\s*>~isu', $html, $matches, PREG_OFFSET_CAPTURE );

这样除了原来的:

HTML
<p>...</p>

也能够识别模型偶发返回的:

HTML
<p class="model">...</p>

或者:

HTML
<p style="color:red">...</p>

不过,仅仅能够识别还不够。

模型自己生成的 paragraph attributes 并不应该因此进入最终的 Gutenberg 文章。

所以恢复时不再直接使用模型返回的完整 wrapper,而是只取其中的 inner HTML:

PHP
$paragraphs[] = '<p>' . $inner . '</p>';

例如模型返回:

HTML
<p class="model">English A.</p>

本地最终只会保留:

HTML
<p>English A.</p>

然后重新生成标准 Gutenberg paragraph:

HTML
<!-- wp:paragraph -->
<p>English A.</p>
<!-- /wp:paragraph -->

也就是说,这个修复的处理原则是:

可以容忍模型偶尔给 <p> wrapper 增加 attribute,但不会信任和保存这些 attribute。

图3:最终 Git diff 只扩展 <p> wrapper 解析,并在恢复时丢弃模型生成的 attributes
图3:最终 Git diff 只扩展 <p> wrapper 解析,并在恢复时丢弃模型生成的 attributes

六、原有结构保护没有放宽

支持 <p class><p style>,并不意味着 Paragraph Run 开始接受任意 HTML。

原来的结构保护规则仍然存在。

例如 Gutenberg comments、heading、list、image、blockquote、pre、nested paragraph、非 paragraph 根节点内容等仍然会被拒绝。

空 paragraph 和只有标点符号的 paragraph 也仍然无法通过验证。

原有的 protected token 和 inline HTML balance 等检查也没有因为这次修改而放宽。

因此,这次并不是把 Paragraph Run 的验证规则整体变宽。

真正放宽的只有一个很具体的兼容边界:

原来只能识别裸 <p>,现在可以识别模型偶发返回的带 attributes <p>,然后在恢复时重新归一化成裸 <p>

七、确认原来的 merge/split 能力没有受到影响

代码修改以后重新执行 Paragraph Run 测试:

Bash
php swq-paragraph-run-test.php

全部测试通过。

其中我比较关注的是原有段落重组能力:

Plaintext
PASS: post 25425 pattern: consecutive source paragraphs may merge without paragraph STRUCT tokens
PASS: merged run restores one standard Gutenberg paragraph
PASS: one source paragraph may split into two target paragraphs
PASS: split run restores two standard Gutenberg paragraphs
PASS: three source paragraphs may become two target paragraphs

说明原来的 Paragraph Run 行为没有因为这个 Bug 修复发生变化。

这次新增的两类情况同样通过:

Plaintext
PASS: model class paragraph wrapper is accepted as a non-structural wrapper
PASS: model class paragraph wrapper attributes are discarded during standard restoration
PASS: model style paragraph wrapper is accepted as a non-structural wrapper
PASS: model style paragraph wrapper attributes are discarded during standard restoration

最终:

Plaintext
ALL TESTS PASSED

与此同时,原来的各种非法情况仍然继续被拒绝。

这比单纯让当前这一篇文章翻译成功更加重要,因为一个局部 Bug 修复不应该破坏已经长期正常工作的其他行为。

八、用原来失败的文章进行生产验证

修改完成以后,我先备份生产环境原来的 swq-glm52-translation-tuning.php,再将修复后的版本部署到 WordPress。

部署时使用 SHA-256 核对文件,并再次执行 PHP 语法检查。

完成以后,没有再修改那篇出错的中文文章。

没有增加空段落,也没有删除空段落,没有调整 Gutenberg 区块,更没有通过重新保存文章尝试改变它的 serialization。

我直接在原文章上再次点击:

Translate now

这一次翻译成功。

这个结果也基本完成了整个问题的验证闭环:

同一篇文章、同样的中文正文,只修改 Paragraph Run parser 后,原来的错误消失。

因此,可以排除之前通过增删空段落偶然成功所带来的干扰。

这次真正需要修复的是模型输出与本地 parser 之间的兼容问题。

九、提交修复

生产环境真实翻译成功以后,才正式提交代码:

Plaintext
3e2d43c
fix: 修复 paragraph run 带属性 p 标签解析

最终修改规模仍然只有:

Plaintext
2 files changed, 12 insertions(+), 6 deletions(-)

对于一个已经运行比较稳定的自动翻译流程,我更希望看到这样的修改规模。

找到明确 Bug,然后只补上这个兼容边界,而不是为了一个低概率异常重新调整整个翻译协议。

十、总结

这次最开始看到:

Plaintext
A paragraph run did not contain a top-level paragraph.

很容易怀疑是 Gutenberg 中文文章本身的结构存在问题。

尤其是以前通过“增加一个空段落、删除以后重新保存”曾经偶尔解决过类似问题,更容易把排查方向带到文章 serialization 上。

但最终真正的问题非常小:

模型偶发返回:

HTML
<p class="...">...</p>

而旧 Paragraph Run parser 只认识:

HTML
<p>...</p>

于是一个实际上仍然合法的 paragraph,被误判成了“没有 top-level paragraph”。

最终的解决方式也比较简单:

扩大 <p> wrapper 的识别范围,但只提取 inner HTML;模型自行添加的 attributes 不进入最终 Gutenberg 内容。

这样既解决了当前翻译失败,也没有改变 Paragraph Run 原来允许 merge/split 的行为,更没有放宽其他 Gutenberg 结构保护。

对于已经经过大量文章验证、整体运行比较稳定的翻译流程,我现在越来越倾向于这种处理方式:

定位具体问题,做最小修复,用回归测试保护原有行为,再拿真实失败文章完成最终验证。

这一次,问题解决了,原来的翻译能力也完整保留下来。

WordPress 经典编辑器代码块无损迁移:批量转换 SyntaxHighlighter 短代码为 Gutenberg 区块

需要长期技术维护或远程问题排查?

我是拥有 15+ 年经验的 PHP / Go 后端工程师,长期关注已有系统维护、Bug 修复、性能优化、服务器排查、WordPress 网站维护和小功能迭代。

如果你的项目遇到以下情况,可以先从一次小问题排查开始合作:

  • ✅ PHP / Laravel / Yii2 老项目无人维护
  • ✅ Go / Gin 后端接口需要排查或优化
  • ✅ WordPress 网站访问慢、报错或插件冲突
  • ✅ Nginx / MySQL / Redis / Linux 服务器异常
  • ✅ CDN / Cloudflare / DNS / HTTPS 配置问题
  • ✅ 需要长期远程技术支持或兼职维护

更多介绍请查看:关于我 & 合作

微信:13980074657
邮箱:shuijingwanwq@gmail.com
Telegram:@shuijingwan
GitHub:https://github.com/shuijingwan