Перезапись ссылок на партнерские программы (и любой хук post_process_cooked) пропускается для локализованного/переведенного содержимого сообщений

Резюме

Когда включена локализация контента, партнерский тег Amazon плагина (и, в более общем смысле, любой плагин, который переписывает готовый HTML через событие post_process_cooked) применяется к исходному готовому HTML поста, но не к его переведенному готовому HTML PostLocalization. Поэтому читатели, просматривающие пост в переведенной локали, видят «сырую» ссылку без тега, что приводит к потере партнерского вознаграждения при каждом просмотре переведенной версии.

Корень проблемы заключается в том, что переведенный контент обрабатывается вторым, отдельным процессором (LocalizedCookedPostProcessor), который никогда не вызывает событие post_process_cooked.

Окружение

  • Discourse: ядро main @ 978e9b2e5dc (2026-07-15); также подтверждено на ветке 2026.7.0 @ 7c06c1528 (2026-07-10). Никаких существенных изменений между ними нет.
  • Плагин: discourse-affiliate (поставляется в каталоге plugins/ ядра), с установленным валидным тегом affiliate_amazon_de (или любым другим affiliate_amazon_*).
  • Настройки: content_localization_enabled = true, content_localization_supported_locales включает хотя бы одну локаль, отличную от исходной.
  • Область: серверная часть (обработка поста). Независимо от устройства/ОС/браузера; воспроизводится в Safe Mode и без тем, поскольку происходит в конвейере обработки, а не на стороне клиента.

Шаги для воспроизведения

  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. Просмотрите пост в его исходной локали и проверьте атрибут href якоря Amazon.
  5. Сгенерируйте/отобразите переведенную версию того же поста (через переключатель языка или путем создания перевода) и проверьте атрибут href того же якоря.

Фактический результат

  • Исходный готовый HTML: …/dp/B0C3WGSSWC?tag=mytag-21 — партнерский тег присутствует. :white_check_mark:
  • Готовый HTML PostLocalization (переведенный): …/dp/B0C3WGSSWC — нет tag, вообще нет переписывания ссылок. :cross_mark:

(Подтверждено напрямую в БД: для затронутого поста поле posts.cooked содержит URL с тегом, тогда как каждая строка post_localizations.cooked для этого поста содержит «голый» URL.)

Ожидаемый результат

Переведенный готовый HTML должен получать то же самое переписывание ссылок, что и исходный, чтобы партнерский тег (и любые другие преобразования post_process_cooked) присутствовал в каждой локали, доступной для чтения.

Корень проблемы

Плагин affiliate переписывает ссылки только внутри события 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 в пути локализации — поэтому ни один плагин, зависящий от этого события, не участвует в обработке переведенного контента. Это общая проблема: сегодня она затрагивает партнерские теги, но в равной степени влияет на любой плагин, который изменяет готовый HTML через post_process_cooked (декораторы ссылок, переписывание значков/упоминаний и т.д.).

Предлагаемое решение

Заставьте локализацию участвовать в том же переписывании ссылок/DOM, что и исходная обработка. Любой из вариантов решает проблему:

  1. Генерация хукаемого события из локализационного процессора — вызовите post_process_cooked (или выделенное post_process_localized_cooked) внутри LocalizedCookedPostProcessor#post_process, чтобы существующие плагины работали с переводами без изменений в каждом плагине. (Если предпочтительнее новое событие для избежания побочных эффектов двойной обработки, плагины могут подключиться к нему.)
  2. Запуск переписывания affiliate в пути локализации, например, повторно используйте AffiliateProcessor.apply для @doc.css("a[href]") в LocalizedCookedPostProcessor#post_process после прохода onebox/изображений.

Вариант 1 является более общим решением, поскольку та же проблема затрагивает каждого потребителя post_process_cooked, а не только affiliate.

Дополнительные примечания

  • Сайтам, у которых уже есть затронутые переводы, потребуется повторная обработка существующих строк PostLocalization после исправления (например, повторная постановка в очередь Jobs::ProcessLocalizedCooked с флагом recook: true), чтобы добавить теги в уже переведенные посты.
  • Воспроизводится стабильно (не прерывисто) каждый раз, когда пост со ссылкой, подходящей для партнерской программы, отображается в переведенной локали.