Skip to content

feat: add setLocaleIdentifier for runtime locale changes#162

Open
funCapital wants to merge 1 commit intosuperwall:mainfrom
funCapital:feat/set-locale-identifier
Open

feat: add setLocaleIdentifier for runtime locale changes#162
funCapital wants to merge 1 commit intosuperwall:mainfrom
funCapital:feat/set-locale-identifier

Conversation

@funCapital
Copy link

@funCapital funCapital commented Feb 7, 2026

Summary

  • Expose setLocaleIdentifier as a runtime-settable property across all layers (iOS, Android, TS, Zustand store, compat)
  • Allows changing the paywall language at runtime without reconfiguring the SDK
  • Follows the same pattern as setLogLevel

Problem

Currently, localeIdentifier can only be set via SuperwallOptions at configure time. Apps that support runtime language switching (e.g., via i18next or user preferences) have no way to update the paywall locale after configure() has been called. Since Superwall.configure ignores subsequent calls, the only option was to restart the app.

The native SDKs (both iOS and Android) already support setting localeIdentifier at runtime via Superwall.shared.localeIdentifier, but the Expo wrapper doesn't expose it.

Changes

Layer File Change
iOS SuperwallExpoModule.swift Function("setLocaleIdentifier") setting Superwall.shared.localeIdentifier
Android SuperwallExpoModule.kt Function("setLocaleIdentifier") setting Superwall.instance.localeIdentifier
TS Module SuperwallExpoModule.ts Type declaration for setLocaleIdentifier
Zustand Store useSuperwall.ts Store type + implementation
Compat compat/index.ts Compat wrapper method

Usage

const { setLocaleIdentifier } = useSuperwall()

// Set to Spanish
setLocaleIdentifier("es")

// Reset to device locale
setLocaleIdentifier(null)

Test plan

  • Tested on iOS simulator with Expo — paywalls correctly switch to Spanish after calling setLocaleIdentifier("es")
  • Android testing

Greptile Overview

Greptile Summary

This PR exposes a runtime setLocaleIdentifier API through the Expo wrapper so apps can change paywall language without re-calling configure(). It adds a new JS-exposed function in both native modules (iOS sets Superwall.shared.localeIdentifier, Android sets Superwall.instance.localeIdentifier), wires the method into the TS native module typing, and surfaces it through both the Zustand hook store and the compat class wrapper.

One correctness issue: the newly-added JS wrapper methods in the compat layer and Zustand store are declared async but don’t await/return the underlying native call, which can cause synchronous native errors (e.g., module unavailable) to be silently swallowed rather than propagated to callers.

Confidence Score: 4/5

  • Generally safe to merge after fixing the async wrapper error propagation issue.
  • Native changes are straightforward property assignments and TS typings align, but the JS wrappers’ async methods currently don’t await/return the native call, which can hide failures and make runtime issues hard to diagnose.
  • src/compat/index.ts, src/useSuperwall.ts

Important Files Changed

Filename Overview
android/src/main/java/expo/modules/superwallexpo/SuperwallExpoModule.kt Adds a new JS-exposed setLocaleIdentifier function that assigns Superwall.instance.localeIdentifier.
ios/SuperwallExpoModule.swift Adds setLocaleIdentifier function to set Superwall.shared.localeIdentifier from JS.
src/SuperwallExpoModule.ts Extends the native module type definition with `setLocaleIdentifier(localeIdentifier: string
src/compat/index.ts Adds compat wrapper setLocaleIdentifier, but it doesn’t await/return the native call (can swallow synchronous native errors).
src/useSuperwall.ts Adds Zustand store action setLocaleIdentifier, but it doesn’t await/return the native call (can swallow synchronous native errors).

Sequence Diagram

sequenceDiagram
  participant App as App (JS)
  participant Store as useSuperwall (Zustand)
  participant Compat as compat/Superwall
  participant Expo as SuperwallExpoModule (TS)
  participant IOS as iOS SuperwallExpoModule.swift
  participant Droid as Android SuperwallExpoModule.kt
  participant SDK as Native Superwall SDK

  alt Hooks SDK path
    App->>Store: "setLocaleIdentifier('es')"
    Store->>Expo: "setLocaleIdentifier('es')"
    Expo-->>IOS: "Function(setLocaleIdentifier)"
    IOS->>SDK: "localeIdentifier = 'es'"
    Expo-->>Droid: "Function(setLocaleIdentifier)"
    Droid->>SDK: "localeIdentifier = 'es'"
  else Compat wrapper path
    App->>Compat: "setLocaleIdentifier('es')"
    Compat->>Expo: "setLocaleIdentifier('es')"
    Expo-->>IOS: "Function(setLocaleIdentifier)"
    IOS->>SDK: "localeIdentifier = 'es'"
    Expo-->>Droid: "Function(setLocaleIdentifier)"
    Droid->>SDK: "localeIdentifier = 'es'"
  end
Loading

(2/5) Greptile learns from your feedback when you react with thumbs up/down!

Context used:

  • Context from dashboard - CLAUDE.md (source)

Expose Superwall.shared.localeIdentifier as a runtime-settable property
across iOS, Android, TypeScript module, Zustand store, and compat layer.

This allows changing the paywall language at runtime without needing to
reconfigure the SDK. Previously, localeIdentifier could only be set via
SuperwallOptions at configure time, making dynamic language switching
impossible.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

5 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +730 to +732
async setLocaleIdentifier(localeIdentifier: string | null): Promise<void> {
SuperwallExpoModule.setLocaleIdentifier(localeIdentifier)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Swallowed native errors

setLocaleIdentifier is async but doesn't await/return the underlying SuperwallExpoModule.setLocaleIdentifier(...) call. If the native module throws synchronously (e.g., module unavailable), this method will still resolve successfully and hide the failure. Consider return await SuperwallExpoModule.setLocaleIdentifier(localeIdentifier) for parity with other wrappers here.

Also appears in src/useSuperwall.ts:332-334.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/compat/index.ts
Line: 730:732

Comment:
**Swallowed native errors**

`setLocaleIdentifier` is `async` but doesn't `await`/`return` the underlying `SuperwallExpoModule.setLocaleIdentifier(...)` call. If the native module throws synchronously (e.g., module unavailable), this method will still resolve successfully and hide the failure. Consider `return await SuperwallExpoModule.setLocaleIdentifier(localeIdentifier)` for parity with other wrappers here.

Also appears in `src/useSuperwall.ts:332-334`.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +332 to +334
setLocaleIdentifier: async (localeIdentifier) => {
SuperwallExpoModule.setLocaleIdentifier(localeIdentifier)
},
Copy link
Contributor

Choose a reason for hiding this comment

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

Async call not awaited

This store action is async but doesn't await/return SuperwallExpoModule.setLocaleIdentifier(...). If the native module call throws synchronously, the returned Promise resolves and the error is swallowed. Returning/awaiting the call will preserve failures for callers.

(Compat layer has the same issue in src/compat/index.ts:730-732.)

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/useSuperwall.ts
Line: 332:334

Comment:
**Async call not awaited**

This store action is `async` but doesn't `await`/`return` `SuperwallExpoModule.setLocaleIdentifier(...)`. If the native module call throws synchronously, the returned Promise resolves and the error is swallowed. Returning/awaiting the call will preserve failures for callers.

(Compat layer has the same issue in `src/compat/index.ts:730-732`.)

How can I resolve this? If you propose a fix, please make it concise.

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.

1 participant