[Paywalls] Improve locale consistency#4158
Conversation
f6eeca4 to
d330125
Compare
| static func localized( | ||
| packageType: PackageType, | ||
| locale: Locale = .current | ||
| locale: Locale, |
There was a problem hiding this comment.
- Defaulting to
.currentis hard to traceback for decision making and debugging to getting rid of it
| switch paywall.validate() { | ||
| case let .success(template): | ||
| return (paywall, template, nil) | ||
| return (paywall, paywall.locale ?? locale, template, nil) |
There was a problem hiding this comment.
paywall.locale SHOULD always exist at this point but needed to do something like this to make it happy 🤷♂️
| Text("All subscriptions", bundle: .module) | ||
| Text("All subscriptions", bundle: bundle) |
There was a problem hiding this comment.
Things like this were the cause of not putting correct localizations and so needing to "add" the localizations to Xcode
| var locale: Locale? { | ||
| let singleTier = self.localizedConfiguration(for: Self.localesOrderedByPriority)?.0 | ||
| let multiTier = self.localizedConfigurationByTier(for: Self.localesOrderedByPriority)?.0 | ||
|
|
||
| return singleTier ?? multiTier | ||
| } |
There was a problem hiding this comment.
This is slightly gross but it gets the job done 🤷♂️
There was a problem hiding this comment.
One thing I don't love here is how all of these variables are computed 😬 If they are called multiple times, it reruns all of this logic again. I don't really feel comfortable changing all of that in this PR yet
There was a problem hiding this comment.
I guess the computed properties might support changing localizations on the fly. Not sure how often that happens or if it's even a use case worth supporting though.
| locale: locale, | ||
| // Or defaults to english | ||
| default: value(locale: Self.defaultLocale, default: nil) | ||
| // Or falls back to first localization or english |
There was a problem hiding this comment.
| // Or falls back to first localization or english | |
| // Or falls back to first localization, and if unavailable, english |
| introEligibility: TrialOrIntroEligibilityChecker? = nil, | ||
| purchaseHandler: PurchaseHandler | ||
| purchaseHandler: PurchaseHandler, | ||
| locale: Locale = .current |
There was a problem hiding this comment.
Based on your loom, is a default of .current safe?
There was a problem hiding this comment.
That's a fair point! I removed it 😊
| internal func localizedConfiguration(for preferredLocales: [Locale]) -> LocalizedConfiguration? { | ||
| internal func localizedConfiguration( | ||
| for preferredLocales: [Locale] | ||
| ) -> (Locale, LocalizedConfiguration)? { |
There was a problem hiding this comment.
Consider labeling the tuple parameters so that we can refer to them by name rather than order (ie instead of ?.0, ?.locale).
| ) -> (Locale, LocalizedConfiguration)? { | |
| ) -> (locale: Locale, localizedConfiguration: LocalizedConfiguration)? { |
| set { self._revision = newValue } | ||
| } | ||
|
|
||
| /// The default localue identifier for this paywall. |
There was a problem hiding this comment.
typo
| /// The default localue identifier for this paywall. | |
| /// The default locale identifier for this paywall. |
There was a problem hiding this comment.
I guess the computed properties might support changing localizations on the fly. Not sure how often that happens or if it's even a use case worth supporting though.
| introEligibility: Self.introEligibility, | ||
| purchaseHandler: Self.purchaseHandler | ||
| purchaseHandler: Self.purchaseHandler, | ||
| locale: Locale.current |
There was a problem hiding this comment.
Should we be defaulting to Locale.preferredLocales.first ?? Locale.current? (access restrictions aside.)
There was a problem hiding this comment.
Ah, probably! The loading view is all skeleton stuff anyway so there isn't actually any text anywhere but that does feel better 😇
tonidero
left a comment
There was a problem hiding this comment.
Not an expert, but this makes sense. Great fixes!
4349304 to
faa499e
Compare
|
@RCGitBot please test |
b755f25 to
dfc4342
Compare
|
@RCGitBot please test |
|
@RCGitBot please test |
**This is an automatic release.** ### New Features * Price rounding logic (#4132) via James Borthwick (@jamesrb1) ### Bugfixes * [Customer Center] Migrate to List style (#4190) via Cody Kerns (@codykerns) * [Paywalls] Improve locale consistency (#4158) via Josh Holtz (@joshdholtz) * Set Paywalls Tester deployment target to iOS 15 (#4196) via James Borthwick (@jamesrb1) * [Customer Center] Hide Contact Support button if URL can't be created (#4192) via Cesar de la Vega (@vegaro) * Fix the setting for SKIP_INSTALL in Xcode project (#4195) via Andy Boedo (@aboedo) * [Customer Center] Improving customer center buttons (#4165) via Cody Kerns (@codykerns) * Revert workaround for iOS 18 beta 5 SwiftUI crash (#4173) via Mark Villacampa (@MarkVillacampa) * [Paywalls] Make iOS version calculation lazy (#4163) via Mark Villacampa (@MarkVillacampa) * [Paywalls] Observe `PurchaseHandler` when owned externally (#4097) via James Borthwick (@jamesrb1) ### Other Changes * [Customer Center] Clean up colors in WrongPlatformView and NoSubscriptionsView (#4204) via Cesar de la Vega (@vegaro) * Fix failing `all-tests` and retry more flaky tests (#4188) via Josh Holtz (@joshdholtz) * Compatibility content unavailable improvements (#4197) via James Borthwick (@jamesrb1) * Create lane to enable customer center (#4191) via Cesar de la Vega (@vegaro) * XCFramework artifacts in CircleCI (#4189) via Andy Boedo (@aboedo) * [Customer Center] CustomerCenterViewModel checks whether the app is the latest version (#4169) via JayShortway (@JayShortway) * export RevenueCatUI xcframework (#4172) via Andy Boedo (@aboedo) * Corrects references from ManageSubscriptionsButtonStyle to ButtonsStyle. (#4186) via JayShortway (@JayShortway) * Speed up carthage installation tests (#4184) via Andy Boedo (@aboedo) * Customer center improvements (#4166) via James Borthwick (@jamesrb1) * replace `color(from colorInformation:)` global with extension (#4183) via Andy Boedo (@aboedo) * Fix tests in main (#4174) via Andy Boedo (@aboedo) * Enable customer center tests (#4171) via James Borthwick (@jamesrb1) * [Customer Center] Initial implementation (#3967) via Cesar de la Vega (@vegaro) --------- Co-authored-by: Josh Holtz <me@joshholtz.com>
⚠️ Waiting for #4188 to be merged first (because needs all the tests to pass) ## Motivation Fix cases where paywall was showing multiple languages at once and random languages. ## Description 👉 For a demo of the problem and solution, look in Slack in the paywalls or SDK channel for the latest Loom that I posted ### Flow of paywall localization logic (since it can be confusing) 1. [PaywallView](https://github.com/RevenueCat/purchases-ios/pull/4158/files#diff-ea75fe7280803e15b975d702a16c6851ffc2bbed01b7b196bf0fb80d75b4b332) starts with `Locale.current` but then calls `offering.validatedPaywall()` which will return the new `displayedLocale: Locale` variable 2. [PaywallData+Validation](https://github.com/RevenueCat/purchases-ios/pull/4158/files#diff-452790cf5287a44f75a4b2260254db2281a066faeea2b3e441a007ec60522b6b) returns `displayedLocale` if its a valid paywall, otherwise it return `Locale.current` for fallback paywall 3. [PaywallData+Localization](https://github.com/RevenueCat/purchases-ios/pull/4158/files#diff-e620baead94f7acad56d931c485d7691c4f909be3fe625e104528a2702cca2a0) has a lot of the new logic - New `locale: Locale` computer property that it determines the locale from the `localizedConfiguration` or `localizedConfigurationsByTier` - New ` defaultLocalizedConfiguration` that uses the developer set `defaultLocale` (from `PaywallData`) to find the default localization - Updated `localizedConfiguration<Value>()` to take a new `defaultLocalization` to put in its priority (and log it if its used). If for some reason that's not found, it will use the legacy `fallbackLocalization` (which is the first localization it finds or english) 4. After all of that, the `displayedLocale` is passed into the actually SwiftUI `LoadedOfferingPaywallView` to show this locale everywhere ### Problem 1 - Inconsistent languages displayed at once The user defined localizations and the pre-localized content in the SDK (like links in the footer and package duration names) could should different localizations. The choice of which locale was happening in two different spots. Since the SDK has all possible localizations that the dashboard offers, the locale SDK displays from the dashboard will be chosen for the pre-localized content. ### Problem 2 - Showing random languages if not supported We decided early on to not use a `default_locale` but that has hurt us. The solution to use was use `localizations.first` to show the first locale in the list and the intention that this was the first locale the user did in the paywall. That was not the case, however. The order of the localizations list was indeterministic so it should should a random language (even to the point where different random languages would show the the same user). We are now introducing a `default_locale` that the developer can set in the paywall. If the locale the user is requesting is not translated by the developer, it will use the `default_locale` ### Problem 3 - Footer not using correct localization from the bundle There were times where some parts of the footer itself (mainly the restore button) would be a different language than the terms of service and privacy policy buttons. This was due to a code issue where we were using `Bundle.module`. Instead, we needed to specify a localization bundle specifically that we wanted to use.
**This is an automatic release.** ### New Features * Price rounding logic (#4132) via James Borthwick (@jamesrb1) ### Bugfixes * [Customer Center] Migrate to List style (#4190) via Cody Kerns (@codykerns) * [Paywalls] Improve locale consistency (#4158) via Josh Holtz (@joshdholtz) * Set Paywalls Tester deployment target to iOS 15 (#4196) via James Borthwick (@jamesrb1) * [Customer Center] Hide Contact Support button if URL can't be created (#4192) via Cesar de la Vega (@vegaro) * Fix the setting for SKIP_INSTALL in Xcode project (#4195) via Andy Boedo (@aboedo) * [Customer Center] Improving customer center buttons (#4165) via Cody Kerns (@codykerns) * Revert workaround for iOS 18 beta 5 SwiftUI crash (#4173) via Mark Villacampa (@MarkVillacampa) * [Paywalls] Make iOS version calculation lazy (#4163) via Mark Villacampa (@MarkVillacampa) * [Paywalls] Observe `PurchaseHandler` when owned externally (#4097) via James Borthwick (@jamesrb1) ### Other Changes * [Customer Center] Clean up colors in WrongPlatformView and NoSubscriptionsView (#4204) via Cesar de la Vega (@vegaro) * Fix failing `all-tests` and retry more flaky tests (#4188) via Josh Holtz (@joshdholtz) * Compatibility content unavailable improvements (#4197) via James Borthwick (@jamesrb1) * Create lane to enable customer center (#4191) via Cesar de la Vega (@vegaro) * XCFramework artifacts in CircleCI (#4189) via Andy Boedo (@aboedo) * [Customer Center] CustomerCenterViewModel checks whether the app is the latest version (#4169) via JayShortway (@JayShortway) * export RevenueCatUI xcframework (#4172) via Andy Boedo (@aboedo) * Corrects references from ManageSubscriptionsButtonStyle to ButtonsStyle. (#4186) via JayShortway (@JayShortway) * Speed up carthage installation tests (#4184) via Andy Boedo (@aboedo) * Customer center improvements (#4166) via James Borthwick (@jamesrb1) * replace `color(from colorInformation:)` global with extension (#4183) via Andy Boedo (@aboedo) * Fix tests in main (#4174) via Andy Boedo (@aboedo) * Enable customer center tests (#4171) via James Borthwick (@jamesrb1) * [Customer Center] Initial implementation (#3967) via Cesar de la Vega (@vegaro) --------- Co-authored-by: Josh Holtz <me@joshholtz.com>
Motivation
Fix cases where paywall was showing multiple languages at once and random languages.
Description
👉 For a demo of the problem and solution, look in Slack in the paywalls or SDK channel for the latest Loom that I posted
Flow of paywall localization logic (since it can be confusing)
Locale.currentbut then callsoffering.validatedPaywall()which will return the newdisplayedLocale: LocalevariabledisplayedLocaleif its a valid paywall, otherwise it returnLocale.currentfor fallback paywalllocale: Localecomputer property that it determines the locale from thelocalizedConfigurationorlocalizedConfigurationsByTierdefaultLocalizedConfigurationthat uses the developer setdefaultLocale(fromPaywallData) to find the default localizationlocalizedConfiguration<Value>()to take a newdefaultLocalizationto put in its priority (and log it if its used). If for some reason that's not found, it will use the legacyfallbackLocalization(which is the first localization it finds or english)displayedLocaleis passed into the actually SwiftUILoadedOfferingPaywallViewto show this locale everywhereProblem 1 - Inconsistent languages displayed at once
The user defined localizations and the pre-localized content in the SDK (like links in the footer and package duration names) could should different localizations. The choice of which locale was happening in two different spots.
Since the SDK has all possible localizations that the dashboard offers, the locale SDK displays from the dashboard will be chosen for the pre-localized content.
Problem 2 - Showing random languages if not supported
We decided early on to not use a
default_localebut that has hurt us. The solution to use was uselocalizations.firstto show the first locale in the list and the intention that this was the first locale the user did in the paywall. That was not the case, however. The order of the localizations list was indeterministic so it should should a random language (even to the point where different random languages would show the the same user).We are now introducing a
default_localethat the developer can set in the paywall. If the locale the user is requesting is not translated by the developer, it will use thedefault_localeProblem 3 - Footer not using correct localization from the bundle
There were times where some parts of the footer itself (mainly the restore button) would be a different language than the terms of service and privacy policy buttons. This was due to a code issue where we were using
Bundle.module. Instead, we needed to specify a localization bundle specifically that we wanted to use.