Content localization: push notifications for new posts are sent before the translation is stored

Summary

With content localization and AI translation enabled, the push notification for a new post almost always carries the author’s original text, even when the recipient reads another language and a translation is stored a few seconds later. The push is queued when the post is created and looks for a translation once, before the translation job has finished.

Translated emails were announced today in this reply (#44240). Emails are not exposed to this timing and push is, so once that change is deployed the email and the push for the same post will reach the same member in different languages.

What a member sees

A member whose interface language is English receives a push notification, in the browser and on mobile, for a new topic written in French. The notification body is in French. Opening the notification shows the post in English.

Measurement

Site on release/2026.7 (2026.7.3) with the bundled discourse-ai plugin, supported locales en, fr, de and it, translation on gemini-3.8-flash and language detection on gemini-3.5-flash-lite.

For 11 translated posts the first translation was stored 2 to 3 seconds after the post was created, and the last of the three 4 to 7 seconds after. In the observed case the translation into the recipient’s language was stored 2 seconds after the post, and the push had already gone out with the original text.

The delay can be read on any site with:

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

Cause

  1. On :post_created, discourse-ai enqueues Jobs::DetectTranslatePost (plugins/discourse-ai/lib/translation/entry_point.rb). The job detects the language with one model call and then translates into each other supported locale in turn.
  2. PostJobsEnqueuer enqueues :post_alert at the same moment. PostAlerter.push_notification enqueues Jobs::DeliverPushNotification immediately. It is delayed only when the recipient was seen within push_notification_time_window_mins (default 1), and then only by the remainder of that window.
  3. DeliverPushNotification#localize_content! looks up TopicLocalization and PostLocalization for user.effective_locale once, when the job runs. Finding none, it sends the original title and excerpt. Nothing waits or retries.
  4. Notification emails go through NotificationEmailer with a delay of email_time_window_mins (default 10), so the translation exists by the time the email is built.

app/jobs/regular/deliver_push_notification.rb is identical at v2026.7.3 and on main at b5548f76 (2026-10-05).

Request

  1. Hold the push for a new post until its translation is stored. One option: when content localization is enabled and the post has no detected locale yet, or no localization for the recipient’s locale while one is expected, re-enqueue DeliverPushNotification once after a short delay and then send whatever is available. A few seconds would cover the cases measured above.
  2. Select the content with the shared rule. The push job does its own lookup by effective_locale. The email change in #44240 goes through ContentLocalization.show_translated_post? and compares post_version. Using the same path for push would make it respect automatically_translate and understood_languages, skip stale translations, and stay consistent with emails and the topic view.
  3. Apply the same to live alerts. The payload published to /notification-alert/:user_id in PostAlerter.create_notification_alert carries the original title and excerpt. The modifier post_alerter_live_notification_payload exists, but core does not localize the payload.

Related

2 Likes