本地化/翻译后的帖子内容会跳过附属链接重写(以及任何 post_process_cooked 钩子)

摘要

当启用内容本地化时,联盟插件的 Amazon 标签(以及更广泛地说,任何通过 post_process_cooked 事件重写已处理 HTML 的插件)会应用于帖子的原始已处理 HTML,但不会应用于其翻译后的 PostLocalization 已处理 HTML。因此,以翻译语言查看帖子的读者会看到未经处理的原始链接,这意味着每次翻译内容的展示都会损失联盟佣金。

根本原因是翻译内容是通过第二个独立的处理器(LocalizedCookedPostProcessor)进行烘焙的,该处理器从未触发 post_process_cooked 事件。

环境

  • Discourse: 核心 main 分支 @ 978e9b2e5dc(2026-07-15);在 2026.7.0 分支 @ 7c06c1528(2026-07-10)上也已确认。两者之间无相关变更。
  • 插件: discourse-affiliate(包含在核心的 plugins/ 目录中),并设置了有效的 Amazon 标签(如 affiliate_amazon_de 或任何 affiliate_amazon_*)。
  • 设置: content_localization_enabled = truecontent_localization_supported_locales 至少包含一个不同于帖子原始语言的语言环境。
  • 范围: 服务器端(帖子烘焙)。与设备/操作系统/浏览器无关;在安全模式下且无主题时也能复现,因为它发生在烹饪管道中,而非前端。

复现步骤

  1. 启用 content_localization_enabled 并添加第二个支持的语言环境(例如,原始帖子为 de,翻译为 en)。
  2. 配置 discourse-affiliate:为相关市场设置 Amazon 标签(例如 affiliate_amazon_de = mytag-21)。
  3. 创建一个原始内容包含 Amazon 产品链接的帖子,例如 https://www.amazon.de/-/en/…/dp/B0C3WGSSWC
  4. 原始语言环境查看该帖子,并检查 Amazon 锚点的 href
  5. 生成/提供同一帖子的翻译版本(通过语言切换器,或等待翻译创建),并检查相同锚点的 href

实际结果

  • 原始已处理内容: …/dp/B0C3WGSSWC?tag=mytag-21 — 包含联盟标签。:white_check_mark:
  • PostLocalization 已处理内容(翻译): …/dp/B0C3WGSSWC — 无 tag,完全未进行联盟重写。:cross_mark:

(直接在数据库中确认:对于受影响的帖子,posts.cooked 包含带标签的 URL,而该帖子的每个 post_localizations.cooked 行都包含裸 URL。)

预期结果

翻译后的已处理 HTML 应接收与原始内容相同的链接重写,以便联盟标签(以及任何其他 post_process_cooked 转换)出现在读者可查看的每种语言环境中。

根本原因

联盟插件仅在 post_process_cooked 事件内部重写链接:

# plugins/discourse-affiliate/plugin.rb
on(:post_process_cooked) do |doc, post|
  doc.css("a[href]").each { |a| a["href"] = AffiliateProcessor.apply(a["href"]) }
  true
end

该事件仅从原始语言烹饪的一个位置触发:

# lib/cooked_post_processor.rb:50
DiscourseEvent.trigger(:post_process_cooked, @doc, @post)

然而,翻译内容是由不同的处理器烘焙的,由 PostLocalizationCreator/PostLocalizationUpdater 入队为 Jobs::ProcessLocalizedCooked

# lib/localized_cooked_post_processor.rb
def post_process
  post_process_oneboxes
  post_process_images
  @post_localization.link_post_uploads(fragments: @doc)
end

LocalizedCookedPostProcessor#post_process 从未触发 post_process_cooked,并且在本地化烘焙路径中没有任何地方存在 DiscourseEvent.trigger — 因此任何依赖该事件的插件都不参与翻译内容。这是一个普遍存在的缺陷:目前影响联盟标记,但同样影响任何通过 post_process_cooked 修改已处理 HTML 的插件(链接装饰器、徽章/提及重写等)。

建议的修复方案

让本地化烘焙参与与原始烹饪相同的链接/DOM 重写。以下任一选项均可解决此问题:

  1. 从本地化处理器发出可挂钩的事件 — 在 LocalizedCookedPostProcessor#post_process 内部触发 post_process_cooked(或专用的 post_process_localized_cooked),以便现有插件无需针对每个插件进行修改即可在翻译上工作。(如果为了双重处理副作用而首选新事件,插件可以选择加入。)
  2. 在本地化路径中运行联盟重写,例如在 LocalizedCookedPostProcessor#post_process 中,在 onebox/图片处理之后,对 @doc.css("a[href]") 重用 AffiliateProcessor.apply

选项 1 是更通用的修复方案,因为同样的缺陷影响每一个 post_process_cooked 消费者,而不仅仅是联盟插件。

补充说明

  • 已经拥有受影响翻译的网站需要在修复后重新烘焙现有的 PostLocalization 行(例如,重新入队 Jobs::ProcessLocalizedCooked 并设置 recook: true),以便将标签回填到已翻译的帖子中。
  • 只要以翻译语言环境提供包含联盟资格链接的帖子,该问题就会一致复现(非间歇性)。