内容本地化:新帖子的推送通知在翻译存储之前就已发送

摘要

在启用内容本地化和 AI 翻译的情况下,新帖子的推送通知几乎总是携带作者的原始文本,即使接收者阅读的是另一种语言,且翻译在几秒后才存储。推送在帖子创建时入队,并在翻译任务完成之前仅查找一次翻译。

今天已宣布启用翻译后的电子邮件功能,详见此回复(#44240)。电子邮件不受此时序问题影响,而推送通知受影响,因此一旦该更改部署,同一帖子的电子邮件和推送通知将以不同的语言到达同一成员。

成员看到的内容

界面语言为英语的成员会收到关于一个用法语撰写的新主题的推送通知(在浏览器和移动端上)。通知正文是法语。打开通知后,帖子显示为英语。

测量

站点运行在 release/2026.7 (2026.7.3) 上,包含捆绑的 discourse-ai 插件,支持的语言区域为 en、fr、de 和 it,翻译使用 gemini-3.8-flash,语言检测使用 gemini-3.5-flash-lite。

对于 11 个已翻译的帖子,第一个翻译在帖子创建后 2 到 3 秒存储,三个翻译中的最后一个在 4 到 7 秒后存储。在观察到的案例中,到接收者语言的翻译在帖子后 2 秒存储,而推送已经携带原始文本发出。

可以在任何站点通过以下 SQL 读取延迟:

SELECT p.id,
       p.locale AS post_locale,
       pl.locale AS translation_locale,
       ROUND(EXTRACT(EPOCH FROM (pl.created_at - p.created_at))) AS seconds_after_post
FROM posts p
JOIN post_localizations pl ON pl.post_id = p.id
WHERE p.created_at > NOW() - INTERVAL '2 days'
ORDER BY p.id, pl.created_at

原因

  1. 在 :post_created 事件中,discourse-ai 入队 Jobs::DetectTranslatePost(plugins/discourse-ai/lib/translation/entry_point.rb)。该任务通过一次模型调用检测语言,然后依次翻译为每个其他支持的语言区域。
  2. PostJobsEnqueuer 在同一时刻入队 :post_alert。PostAlerter.push_notification 立即入队 Jobs::DeliverPushNotification。仅当接收者在 push_notification_time_window_mins(默认 1)内被看到时,才会延迟,且仅延迟该窗口的剩余时间。
  3. DeliverPushNotification#localize_content! 在任务运行时查找一次 TopicLocalization 和 PostLocalization 以匹配 user.effective_locale。如果未找到,则发送原始标题和摘要。没有任何等待或重试机制。
  4. 通知电子邮件通过 NotificationEmailer 发送,延迟为 email_time_window_mins(默认 10),因此在构建电子邮件时翻译已经存在。

app/jobs/regular/deliver_push_notification.rb 在 v2026.7.3 和 main 分支的 b5548f76 (2026-10-05) 上是相同的。

请求

  1. 在新帖子的翻译存储之前保留推送。 一个选项:当启用内容本地化且帖子尚未检测到语言区域,或者在预期存在的情况下没有接收者语言区域的本地化时,在短暂延迟后重新入队一次 DeliverPushNotification,然后发送任何可用内容。几秒钟即可覆盖上述测量的情况。
  2. 使用共享规则选择内容。 推送任务通过 effective_locale 进行自己的查找。#44240 中的电子邮件更改通过 ContentLocalization.show_translated_post? 并比较 post_version。对推送使用相同的路径将使其尊重 automatically_translate 和 understood_languages,跳过过时的翻译,并与电子邮件和主题视图保持一致。
  3. 对实时警报应用相同逻辑。 在 PostAlerter.create_notification_alert 中发布到 /notification-alert/:user_id 的负载携带原始标题和摘要。修饰符 post_alerter_live_notification_payload 存在,但核心代码并未本地化该负载。

相关

2 个赞