Skip to content

Fix layout direction override (PWENG-39)#6723

Merged
alexrepty merged 9 commits into
mainfrom
alex/fix-layout-direction-override
May 5, 2026
Merged

Fix layout direction override (PWENG-39)#6723
alexrepty merged 9 commits into
mainfrom
alex/fix-layout-direction-override

Conversation

@alexrepty

@alexrepty alexrepty commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Checklist

  • If applicable, unit tests
  • If applicable, create follow-up issues for purchases-android and hybrids

Motivation

When Purchases.shared.overridePreferredUILocale(_:) is set to an RTL locale (e.g. Hebrew or Arabic) while the device system locale is LTR (e.g. English), the paywall renders translated strings correctly but the layout direction stays LTR. Close buttons, package cards, and text alignment all remain left-anchored, producing a broken RTL experience.

Resolves PWENG-39

Description

SwiftUI's layout engine derives text direction and HStack alignment from the \.locale environment value. When that key is not explicitly set, SwiftUI inherits it from the host app's environment, which reflects the system locale — not the locale override. Because overridePreferredUILocale was only threaded through the content-selection path (string bundle lookup), the environment was never updated and layout stayed LTR.

Fix: Inject \.locale and \.layoutDirection into the SwiftUI environment at the point where the resolved locale is already available, in both the V1 (LoadedOfferingPaywallView) and V2 (PaywallsV2View) render paths. A Locale.swiftUILayoutDirection helper maps Locale.characterDirection(forLanguage:) to SwiftUI.LayoutDirection.

Testing

Added LocaleLayoutDirectionTests unit tests covering RTL languages (Hebrew, Arabic, Farsi, Urdu) and LTR languages (English, French, Japanese), including region-qualified identifiers and the empty-string edge case.
Expanded PaywallViewLocalizationTests with Hebrew (he-IL) and Arabic (ar) snapshot regression tests. Each test uses a locale-specific offering with native translations (just AI-translated, since it won't actually be facing end users) and renders with localeOverride set, so a future regression (RTL strings but LTR layout) would produce a diff in the reference PNGs.


Note

Medium Risk
Changes SwiftUI environment injection for locale/layoutDirection in both V1 and V2 paywall render paths, which can affect layout across all paywalls and locales. Risk is mitigated by added unit and snapshot coverage for RTL and locale-resolution edge cases.

Overview
Fixes RTL rendering when overridePreferredUILocale is set by explicitly injecting the resolved content Locale and matching LayoutDirection into the SwiftUI environment for both V1 (LoadedOfferingPaywallView) and V2 (PaywallsV2View) paywalls.

Adds Locale.swiftUILayoutDirection plus a shared Locale.selectPreferredLocale helper (used by V2 locale resolution), and expands test coverage with new RTL/LTR unit tests and Hebrew/Arabic snapshot regressions (plus a small snapshot-recording helper in TestCase).

Reviewed by Cursor Bugbot for commit 5e6b9bc. Bugbot is set up for automated code reviews on this repo. Configure here.

@alexrepty alexrepty requested review from a team as code owners April 30, 2026 15:03
@alexrepty alexrepty force-pushed the alex/fix-layout-direction-override branch from 5724f7b to 0f85cf1 Compare April 30, 2026 15:04
Comment on lines +176 to +177
.environment(\.locale, resolvedLocale)
.environment(\.layoutDirection, resolvedLocale.swiftUILayoutDirection)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the actual meat of the change. Setting the environment variables will correctly apply the locale and layout direction.

@emerge-tools

emerge-tools Bot commented Apr 30, 2026

Copy link
Copy Markdown

📸 Snapshot Test

3 added, 264 unchanged

Name Added Removed Modified Renamed Unchanged Errored Approval
RevenueCat
com.revenuecat.PaywallsTester
3 0 0 0 264 0 ✅ Approved

🛸 Powered by Emerge Tools

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b0fa02f. Configure here.

Comment thread RevenueCatUI/Templates/V2/PaywallsV2View.swift Outdated
Comment thread RevenueCatUI/PaywallView.swift
@alexrepty alexrepty added the pr:fix A bug fix label May 5, 2026
@alexrepty alexrepty merged commit 22cb25b into main May 5, 2026
17 of 19 checks passed
@alexrepty alexrepty deleted the alex/fix-layout-direction-override branch May 5, 2026 09:05
matteinn pushed a commit to matteinn/purchases-android that referenced this pull request May 5, 2026
…enueCat#3425)

<!-- Thank you for contributing to Purchases! Before pressing the
"Create Pull Request" button, please provide the following: -->

### Checklist
- [x] If applicable, unit tests
- [x] If applicable, create follow-up issues for `purchases-ios` and
hybrids [PR](RevenueCat/purchases-ios#6723)

### Motivation
Fixes paywall layout direction when `preferredUILocaleOverride` resolves
to an RTL locale while the device/system locale is LTR.
Previously, Android paywalls could use the overridden locale for
localized strings while still inheriting the system Compose
`LocalLayoutDirection`, so RTL languages like Hebrew and Arabic could
render translated text but keep LTR layout behavior.

Resolves:  PWENG-39

### Changes
- Adds a locale-to-Compose-layout-direction helper backed by Android
`Configuration.layoutDirection`.
- Applies the resolved locale layout direction to legacy paywalls.
- Applies the resolved locale layout direction to V2/components
paywalls.
- Adds unit coverage for RTL and LTR locale direction resolution.


[Screen_recording_20260501_134422.webm](https://github.com/user-attachments/assets/b19c2be7-9bdd-4daa-8954-e119e8f8d01d)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes paywall composition locals to force `LocalLayoutDirection`
based on the resolved/overridden locale, which could subtly affect
layout/alignment across both legacy and components paywalls. Risk is
limited to UI rendering (no purchase/auth logic), but may introduce
visual regressions in LTR/RTL edge cases.
> 
> **Overview**
> Fixes paywall rendering when a `preferredUILocaleOverride` resolves to
an RTL language by deriving a Compose `LayoutDirection` from the
resolved locale and providing it via `LocalLayoutDirection`.
> 
> Applies this override in both legacy template paywalls
(`InternalPaywall`) and V2/components paywalls
(`LoadedPaywallComponents`), and adds a new `Locale.toLayoutDirection()`
helper with Android instrumentation tests covering RTL
(Hebrew/Arabic/Farsi) and LTR locales.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
8fc3352. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants