Skip to content

feat(surveys): re-translate displayed survey on language change#686

Open
bs1180 wants to merge 4 commits into
mainfrom
posthog-code/survey-live-language-update
Open

feat(surveys): re-translate displayed survey on language change#686
bs1180 wants to merge 4 commits into
mainfrom
posthog-code/survey-live-language-update

Conversation

@bs1180

@bs1180 bs1180 commented Jun 30, 2026

Copy link
Copy Markdown

💡 Motivation and Context

Once a translated survey was displayed, later changes to the user's language (e.g. via setPersonProperties / identify with a language property) had no effect — the survey kept the language it was first rendered with. The translation was resolved only at display time.

This makes a survey already on screen follow the user's language: when the person properties used for flags change and a matching translation exists, the survey re-translates in place, keeping the current question and any progress.

Closes #66998

💚 How did you test it?

Added unit tests (run on the iOS simulator, where survey code compiles):

  • Changing the language person property while a survey is active pushes a re-translated survey to the delegate and updates the tracked survey language.
  • Re-resolving to the same language is a no-op (no churn of $survey_language or re-renders).
  • No active survey ⇒ no update pushed.
  • SurveyDisplayController.updateSurvey preserves the current question index and completion state, and ignores updates for a different / absent survey.

I was unable to run make test / the iOS build in this environment (no Xcode available), so CI should be relied on for the full build + simulator test run.

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm changeset to generate a changeset file

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Implemented from a Slack thread request, logged as #66998. Built with the PostHog Slack app (Claude Code). Key decisions:

  • Trigger: chose an internal NotificationCenter signal posted from PostHogRemoteConfig.setPersonPropertiesForFlags (the single chokepoint all person-property updates flow through) rather than sniffing the captured event stream — the $set/$identify ordering relative to the flag-property update is inconsistent, so the notification is the order-correct, robust hook.
  • UI update: added an optional updateSurvey(_:) to the delegate and made SurveySheet observe the display controller (instead of the snapshot passed by .sheet(item:)), so an in-place content swap with the same survey id renders live while preserving SwiftUI @State (current question, typed answers).
  • Guardrails: only commit + render when the matched language actually changes; the whole path is a no-op when no survey is displayed.

One known limitation worth a reviewer's eye: for choice questions, selection is keyed by the option's string value, so a mid-survey language switch can drop an in-progress selection on those question types (labels change). Text/rating answers are preserved.


Created with PostHog from a Slack thread

Once a survey was on screen, later updates to the user's `language` person
property had no effect — the translation was resolved only at display time. Now
the SDK re-resolves the survey language whenever the person properties used for
flags change and updates the on-screen survey in place, preserving the current
question and progress.

- Post an internal notification when person properties for flags change.
- Survey integration observes it, re-resolves translations for the active
  survey, and pushes an in-place update only when the matched language actually
  changed (so `$survey_language` and renders aren't churned needlessly).
- Add optional `updateSurvey(_:)` to `PostHogSurveysDelegate`; the default
  delegate updates the live survey via the display controller. The sheet now
  observes the controller so re-translations render without restarting.

Closes #66998

Generated-By: PostHog Code
Task-Id: 50affc3a-7798-47a6-94fe-c86b2b971dac
@bs1180 bs1180 self-assigned this Jun 30, 2026
@greptile-apps

greptile-apps Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Comments Outside Diff (1)

  1. PostHog/Surveys/PostHogSurveysDefaultDelegate.swift, line 120-123 (link)

    P1 Translation update dropped when survey has a display delay

    When updateSurvey is called while a survey is still within its surveyPopupDelaySeconds window, it correctly updates self.pendingSurvey to the re-translated copy. However, the DispatchWorkItem closure captures the original survey local variable at the time it is created, so when the delay fires it always calls displayController.showSurvey(survey) with the pre-update content. The work item should use self.pendingSurvey — the most-recent version — at the point it executes.

Reviews (1): Last reviewed commit: "feat(surveys): re-translate displayed su..." | Re-trigger Greptile

Comment thread PostHog/Surveys/SurveyDisplayController.swift
@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

posthog-ios Compliance Report

Date: 2026-07-06 12:23:58 UTC
Duration: 211112ms

✅ All Tests Passed!

45/45 tests passed


Capture Tests

29/29 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields 2874ms
Format Validation.Event Has Uuid 2787ms
Format Validation.Event Has Lib Properties 2700ms
Format Validation.Distinct Id Is String 2752ms
Format Validation.Token Is Present 2749ms
Format Validation.Custom Properties Preserved 340ms
Format Validation.Event Has Timestamp 2666ms
Retry Behavior.Retries On 503 11347ms
Retry Behavior.Does Not Retry On 400 4729ms
Retry Behavior.Does Not Retry On 401 2318ms
Retry Behavior.Respects Retry After Header 7757ms
Retry Behavior.Implements Backoff 21357ms
Retry Behavior.Retries On 500 9171ms
Retry Behavior.Retries On 502 9183ms
Retry Behavior.Retries On 504 9286ms
Retry Behavior.Max Retries Respected 21742ms
Deduplication.Generates Unique Uuids 482ms
Deduplication.Preserves Uuid On Retry 9274ms
Deduplication.Preserves Uuid And Timestamp On Retry 10258ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry 7704ms
Deduplication.No Duplicate Events In Batch 2880ms
Deduplication.Different Events Have Different Uuids 2832ms
Compression.Sends Gzip When Enabled 2655ms
Batch Format.Uses Proper Batch Structure 2797ms
Batch Format.Flush With No Events Sends Nothing 224ms
Batch Format.Multiple Events Batched Together 2823ms
Error Handling.Does Not Retry On 403 2383ms
Error Handling.Does Not Retry On 413 2348ms
Error Handling.Retries On 408 5237ms

Feature_Flags Tests

16/16 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id 2752ms
Request Payload.Flags Request Uses V2 Query Param 2788ms
Request Payload.Flags Request Hits Flags Path Not Decide 2665ms
Request Payload.Flags Request Omits Authorization Header 2653ms
Request Payload.Token In Flags Body Matches Init 2844ms
Request Payload.Groups Round Trip 2681ms
Request Payload.Groups Default To Empty Object 2764ms
Request Payload.Person Properties Distinct Id Auto Populated When Caller Omits It 2703ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False 2730ms
Request Payload.Disable Geoip Omitted Defaults To False 2805ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key 2777ms
Request Lifecycle.No Flags Request On Init Alone 114ms
Request Lifecycle.No Flags Request On Normal Capture 2931ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests 5625ms
Request Lifecycle.Mock Response Value Is Returned To Caller 2920ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event 3049ms

bs1180 added 2 commits June 30, 2026 14:22
- Keep PostHogSurveyIntegration.swift under the 1200-line SwiftLint limit by
  moving self-contained helper types (survey state, condition matching, rating
  buckets) into a new PostHogSurveyMatching.swift, registered in the Xcode
  project.
- Use the non-availability-gated `_surveysConfig` backing in tests; the public
  `surveysConfig` accessor is iOS 15+ only and broke the simulator build.
- Add the new optional `updateSurvey(_:)` delegate method to the public API
  snapshot.

Generated-By: PostHog Code
Task-Id: 50affc3a-7798-47a6-94fe-c86b2b971dac
Address Greptile review on #686:

- When a survey is within its display-delay window and its language updates,
  `updateSurvey` refreshes `pendingSurvey`, but the scheduled work item showed
  the captured (pre-update) copy. Read the latest `pendingSurvey` at execution
  time so the re-translated content is shown.
- Distinguish the "no survey displayed" vs "different survey" cases in the
  display controller's update log to make debugging clearer.

Generated-By: PostHog Code
Task-Id: 50affc3a-7798-47a6-94fe-c86b2b971dac
@turnipdabeets turnipdabeets requested a review from a team July 2, 2026 20:54
@turnipdabeets

Copy link
Copy Markdown
Contributor

@lucasheriques I know you worked a bit on this, mind giving this a review?

@turnipdabeets turnipdabeets marked this pull request as ready for review July 2, 2026 20:56
@turnipdabeets turnipdabeets requested a review from a team as a code owner July 2, 2026 20:56
@greptile-apps

greptile-apps Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Reviews (2): Last reviewed commit: "fix(surveys): apply latest translation w..." | Re-trigger Greptile

@turnipdabeets

Copy link
Copy Markdown
Contributor

Thanks @bs1180 instead of a CHANGELOG we actually need a changes, please see RELEASING.md

@ioannisj

ioannisj commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

@bs1180 friendly ping on this one

Follow the repo's Changesets release flow: add a minor changeset describing the live survey re-translation feature and revert the direct CHANGELOG.md edit (the release workflow regenerates CHANGELOG.md from changesets).

Generated-By: PostHog Code
Task-Id: 50affc3a-7798-47a6-94fe-c86b2b971dac
@bs1180

bs1180 commented Jul 6, 2026

Copy link
Copy Markdown
Author

Hey @turnipdabeets and @ioannisj, sorry for the delay. This PR is the product of letting PostHog AI loose on a customer complaint :D I'm not an IOS dev so not sure if this implementation makes sense and I've done literally zero QA. Is that OK with you? Won't be offended this just gets rejected.

@ioannisj

ioannisj commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

@bs1180 thank you, I can do the QA part and see also how this will affect Flutter surveys as well. we'll probably need to land a similar feature for posthog-android as well I imagine? To get some more context though, do you have the original link to the customer complaint? I personally don't think that on-the-fly language changes are the norm so I'm not completely sure we should be supporting this

@ioannisj

ioannisj commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Would like to know @turnipdabeets thoughts as well on this, maybe it's just me :)

@bs1180

bs1180 commented Jul 7, 2026

Copy link
Copy Markdown
Author

Yup totally understood - PostHog/posthog#66998 is the original feature request, and this is the customer Slack thread

@turnipdabeets

Copy link
Copy Markdown
Contributor

Would like to know @turnipdabeets thoughts as well on this, maybe it's just me :)

I think we need on-device QA, if you want to take that @ioannisj. Maybe we need to check out the sheet dismissal animation too. Not sure if we need @PostHog/team-surveys on par before making this change? And it makes sense to do this for the other mobile if it's needed, android flutter and RN.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants