Affiliate link rewriting (and any post_process_cooked hook) is skipped for localized/translated post content

Summary

When Content Localization is enabled, the affiliate plugin’s Amazon tag (and, more generally, any plugin that rewrites cooked HTML via the post_process_cooked event) is applied to a post’s original cooked HTML but not to its translated PostLocalization cooked HTML. Readers who view a post in a translated locale therefore see the raw, untagged link, which means affiliate commission is lost on every translated impression.

The root cause is that translated content is baked through a second, separate processor (LocalizedCookedPostProcessor) that never triggers the post_process_cooked event.

Environment

  • Discourse: core main @ 978e9b2e5dc (2026-07-15); also confirmed on the 2026.7.0 line @ 7c06c1528 (2026-07-10). No relevant change between them.
  • Plugin: discourse-affiliate (shipped in core’s plugins/ directory), with affiliate_amazon_de (or any affiliate_amazon_*) set to a valid tag.
  • Settings: content_localization_enabled = true, content_localization_supported_locales includes at least one locale other than the post’s original.
  • Scope: server-side (post baking). Independent of device/OS/browser; reproduces in Safe Mode and with no theme, since it happens in the cook pipeline, not the front end.

Steps to reproduce

  1. Enable content_localization_enabled and add a second supported locale (e.g. original posts in de, translation to en).
  2. Configure discourse-affiliate: set the Amazon tag for the relevant marketplace (e.g. affiliate_amazon_de = mytag-21).
  3. Create a post whose raw contains an Amazon product link, e.g. https://www.amazon.de/-/en/…/dp/B0C3WGSSWC.
  4. View the post in its original locale and inspect the Amazon anchor’s href.
  5. Generate/serve the translated version of the same post (via the language switcher, or by letting the translation be created) and inspect the same anchor’s href.

Actual result

  • Original cooked: …/dp/B0C3WGSSWC?tag=mytag-21 — affiliate tag present. :white_check_mark:
  • PostLocalization cooked (translated): …/dp/B0C3WGSSWC — no tag, no affiliate rewriting at all. :cross_mark:

(Confirmed directly in the DB: for an affected post, posts.cooked carries the tagged URL while every post_localizations.cooked row for that post carries the bare URL.)

Expected result

The translated cooked HTML should receive the same link rewriting as the original, so the affiliate tag (and any other post_process_cooked transformation) is present in every locale a reader can view.

Root cause

The affiliate plugin rewrites links only inside the post_process_cooked event:

# 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

That event is triggered from exactly one place, the original-language cook:

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

Translated content, however, is baked by a different processor, enqueued from PostLocalizationCreator/PostLocalizationUpdater as 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 never triggers post_process_cooked, and there is no DiscourseEvent.trigger anywhere in the localization bake path — so no plugin relying on that event participates in translated content. This is a general gap: it affects affiliate tagging today, but equally any plugin that mutates cooked HTML through post_process_cooked (link decorators, badge/mention rewrites, etc.).

Suggested fix

Have the localized bake participate in the same link/DOM rewriting as the original cook. Either option resolves it:

  1. Emit a hookable event from the localized processor — trigger post_process_cooked (or a dedicated post_process_localized_cooked) inside LocalizedCookedPostProcessor#post_process, so existing plugins work on translations with no per-plugin change. (If a new event is preferred to avoid double-processing side effects, plugins can opt in.)
  2. Run the affiliate rewrite in the localized path, e.g. reuse AffiliateProcessor.apply over @doc.css("a[href]") in LocalizedCookedPostProcessor#post_process after the onebox/image passes.

Option 1 is the more general fix, since the same gap affects every post_process_cooked consumer, not just affiliate.

Additional notes

  • A site that already has affected translations will need existing PostLocalization rows re-baked after the fix (e.g. re-enqueue Jobs::ProcessLocalizedCooked with recook: true) to backfill tags into already-translated posts.
  • Reproduces consistently (not intermittent) whenever a post with an affiliate-eligible link is served in a translated locale.