feat(surveys): re-translate displayed survey on language change#686
feat(surveys): re-translate displayed survey on language change#686bs1180 wants to merge 20 commits into
Conversation
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
|
posthog-ios Compliance ReportDate: 2026-07-21 12:27:31 UTC ✅ All Tests Passed!45/45 tests passed Capture Tests✅ 29/29 tests passed View Details
Feature_Flags Tests✅ 16/16 tests passed View Details
|
- 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
|
@lucasheriques I know you worked a bit on this, mind giving this a review? |
|
Reviews (2): Last reviewed commit: "fix(surveys): apply latest translation w..." | Re-trigger Greptile |
|
Thanks @bs1180 instead of a CHANGELOG we actually need a changes, please see |
|
@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
|
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. |
|
@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 |
|
Would like to know @turnipdabeets thoughts as well on this, maybe it's just me :) |
|
Yup totally understood - PostHog/posthog#66998 is the original feature request, and this is the customer Slack thread |
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. |
ioannisj
left a comment
There was a problem hiding this comment.
LG. Tested this on iOS and Flutter and left some minor comments. We'll probably want to follow this up on Android and Flutter side
|
Hey @bs1180, want me to take this over if you've moved on to something else? |
|
Hey @ioannisj, sorry didn't see your latest comments. Yes please - as warned I'm not a mobile dev so my next steps would be to just keep prompting in Posthog Code :D Thank you! |
…ive-language-update
… instead of NotificationCenter
| // Commit only if the update targets a different language than what's currently shown | ||
| guard self.activeSurveyLanguage != translations.matchedKey else { return nil } | ||
| self.activeSurveyLanguage = translations.matchedKey | ||
| self.activeSurveyQuestionTranslations = translations.questions |
There was a problem hiding this comment.
[question] Once this commits mid-survey, survey sent stamps all $survey_questions (and $survey_language) with the final translations — answers given before the switch are reported with question text the user never saw, and survey shown vs sent disagree on the language. Acceptable for analytics, or should we freeze the language after the first response?
There was a problem hiding this comment.
Yeah, tbh I think this is a product call? It may make sense to track each answer with the $survey_language that matches the UI at that point. There's no other SDK doing mid-display translation changes, so I can't really reference a previous implementation. @PostHog/team-surveys what's your call here? Should $survey_language correctly reflect the language shown to the user for each question or should we fix it so that all survey events match (even though the user may have seen something else)
💡 Motivation and Context
Once a translated survey was displayed, later changes to the user's language (e.g. via
setPersonProperties/identifywith alanguageproperty) 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):
languageperson property while a survey is active pushes a re-translated survey to the delegate and updates the tracked survey language.$survey_languageor re-renders).SurveyDisplayController.updateSurveypreserves 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
If releasing new changes
pnpm changesetto 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:
NotificationCentersignal posted fromPostHogRemoteConfig.setPersonPropertiesForFlags(the single chokepoint all person-property updates flow through) rather than sniffing the captured event stream — the$set/$identifyordering relative to the flag-property update is inconsistent, so the notification is the order-correct, robust hook.updateSurvey(_:)to the delegate and madeSurveySheetobserve 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).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