●MAX — Rork Max generates native Swift apps across iPhone, iPad, Watch, TV, Vision Pro, and iMessage●NATIVE — It reaches AR/LiDAR scanning, Metal 3D games, widgets, Live Activities, and on-device Core ML●FUNDING — Rork raised $2.8M from a16z, with 743K monthly visits and 85% growth●PRICING — It's free to start, with paid plans beginning at $25 per month●FLOW — Describe your idea in plain English to get working code, a shareable test link, and iOS/Android builds●COMPARE — The original Rork builds cross-platform apps on Expo/React Native; choose the right tool per goal●MAX — Rork Max generates native Swift apps across iPhone, iPad, Watch, TV, Vision Pro, and iMessage●NATIVE — It reaches AR/LiDAR scanning, Metal 3D games, widgets, Live Activities, and on-device Core ML●FUNDING — Rork raised $2.8M from a16z, with 743K monthly visits and 85% growth●PRICING — It's free to start, with paid plans beginning at $25 per month●FLOW — Describe your idea in plain English to get working code, a shareable test link, and iOS/Android builds●COMPARE — The original Rork builds cross-platform apps on Expo/React Native; choose the right tool per goal
Your Arabic Users See an Unmirrored Layout — RTL in a Rork (Expo) App and the Reload Trap
You added Arabic to a Rork-generated Expo app, but the screen never flips and the back button stays on the wrong side. The cause is that I18nManager.forceRTL requires a relaunch. This walks through detecting direction with expo-localization, applying it reliably with Updates.reloadAsync, swapping to marginStart, and mirroring only the arrows — all with working code.
A message from an Arabic-speaking user — "the back button is on the left and hard to reach" — arrived a few days after I added Arabic metadata to one of my wallpaper apps. The copy was genuinely Arabic. But the layout still ran left to right, and the back arrow that belongs in the top-right corner was sitting in the top-left. I had translated the strings and forgotten the right-to-left layout flip entirely.
The frustrating part was that calling I18nManager.forceRTL(true) in code changed nothing on screen. The logs said it was applied, yet the layout stayed left-to-right. As an indie developer I spent the better part of an hour staring at code that looked correct. The cause was simpler: React Native's RTL setting is handed to the native layout engine, so it doesn't switch until the app is rebuilt.
Using a Rork-generated Expo app as the example, here's how to get past that relaunch wall — and how to stop "the strings translated but the layout broke" from happening structurally — alongside the pitfalls I actually hit in my own apps.
Mirroring is automatic, but enabling it is manual
The first thing worth knowing is that once RTL is active, arrangements like flexDirection: 'row' flip automatically. You don't rebuild every screen by hand. The trouble is the act of enabling it.
RTL on/off is baked into the native view hierarchy, so toggling it mid-session doesn't touch screens that are already mounted. I18nManager gives you two calls:
API
Role
When it applies
I18nManager.allowRTL(bool)
The base permission. If this is false, forceRTL has no effect
Next launch
I18nManager.forceRTL(bool)
Actually forces the RTL layout
Next launch (i.e. the app must be rebuilt)
"Next launch" is the catch for both. If you want the user to see the change right away, you have to trigger that rebuild yourself. In Expo, Updates.reloadAsync() from expo-updates is the most reliable way.
Read the device's text direction to set the launch orientation
Start by detecting whether the device is set to an RTL language such as Arabic or Hebrew. getLocales() from expo-localization returns a textDirection for each locale, so lean on that rather than maintaining your own list of RTL language codes — the OS won't miss edge cases the way a hand-rolled list does.
// lib/rtl.tsimport * as Localization from 'expo-localization';import { I18nManager } from 'react-native';import * as Updates from 'expo-updates';// Is the device's top-priority locale RTL?export function deviceWantsRTL(): boolean { const [primary] = Localization.getLocales(); return primary?.textDirection === 'rtl';}// Call once on launch; if the direction disagrees, rebuildexport async function syncLayoutDirection(): Promise<void> { const wantsRTL = deviceWantsRTL(); // Already in the desired direction? Do nothing (prevents an infinite reload) if (I18nManager.isRTL === wantsRTL) return; I18nManager.allowRTL(wantsRTL); I18nManager.forceRTL(wantsRTL); // Fast Refresh in dev sometimes ignores reload, so guard it if (!__DEV__) { await Updates.reloadAsync(); }}
The if (I18nManager.isRTL === wantsRTL) return; guard looks minor but matters. Without it you fall into "direction differs → reload → launches again → …" forever. I left it out the first time and watched the simulator restart itself in a loop.
Call it at the root, before any UI renders.
// app/_layout.tsx (Expo Router)import { useEffect, useState } from 'react';import { syncLayoutDirection } from '../lib/rtl';export default function RootLayout() { const [ready, setReady] = useState(false); useEffect(() => { syncLayoutDirection().finally(() => setReady(true)); }, []); if (!ready) return null; // stalls here if a reload is about to fire return <Stack /* ... */ />;}
Allow RTL on the app.json side as well. Baking allowRTL in at build time keeps the first launch stable.
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Understand why switching to Arabic doesn't mirror the layout — I18nManager.forceRTL needs a relaunch — and ship a Updates.reloadAsync flow that applies it reliably
✦Replace hard-coded marginLeft/right and position:left with marginStart/end logical properties so your full-screen viewer's close button stops landing on the wrong side
✦Mirror only the back arrows and chevrons with a transform while leaving the wallpaper thumbnails untouched — the 'mirror the UI, not the content' line
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
When a user switches language in-app, rebuild for real
If you offer an in-app language switcher separate from device settings, calling forceRTL on the spot won't change the look. To the user it reads as "I set it but nothing happened." Being honest and triggering a reload turned out to be the kindest option.
// lib/rtl.ts (continued)export async function applyLanguage(lang: string, isRTL: boolean): Promise<void> { await saveLanguagePreference(lang); // persist to AsyncStorage etc. if (I18nManager.isRTL !== isRTL) { I18nManager.allowRTL(isRTL); I18nManager.forceRTL(isRTL); // rebuild only when the direction actually changes await Updates.reloadAsync(); return; } // Switching between same-direction languages (English to French) needs no reload}
The key is to skip the reload when the direction doesn't change (English to French). Rebuilding every time makes the app feel heavy, so limit it to the I18nManager.isRTL !== isRTL case. Showing a quick "The app will restart to apply your language" line just before the switch keeps the sudden relaunch from startling people.
The real cause of broken layout is hard-coded left/right
While flexDirection: 'row' flips automatically, physical-direction values like marginLeft or left: do not. A button you wanted on the right that stays on the left in RTL is almost always this. The fix is moving to logical (start/end-based) properties.
Avoid (physical, won't flip)
Use (logical, flips automatically)
marginLeft / marginRight
marginStart / marginEnd
paddingLeft / paddingRight
paddingStart / paddingEnd
left / right (position)
start / end
textAlign: 'left'
textAlign: 'auto'
The one that hurt most was the close button on my full-screen wallpaper viewer. It was absolutely positioned in the top-right, but because I wrote right: 16, it stayed top-right under RTL too — putting "close" on the counterintuitive side for Arabic users. Changing it to end: 16 was enough to move it naturally: top-right in LTR, top-left in RTL.
textAlign: 'auto' decides alignment from the language of the text, so English aligns left and Arabic aligns right without you writing a branch.
Mirror only the arrows and chevrons
Here's a trap: the layout flips, but the icon artwork itself does not. A back arrow (←) means "go left/back" in LTR, but in RTL the direction of travel is reversed, so it should read as "→". That won't happen automatically, so explicitly mirror only the direction-dependent icons.
import { I18nManager } from 'react-native';function DirectionalIcon({ name, ...props }) { // flip only icons whose meaning depends on direction const mirror = I18nManager.isRTL ? { transform: [{ scaleX: -1 }] } : undefined; return <Icon name={name} style={mirror} {...props} />;}
Only mirror things where direction carries meaning: back/forward, send (paper plane), indent, ordered arrows. Symmetric or direction-neutral icons — checkmarks, gears, hearts, play buttons — must not be mirrored. Flip the play triangle and RTL users will read it as rewind. I once did this sloppily as "mirror every icon," flipped the settings gear inside out, and had to rebuild.
Don't flip the wallpaper itself — the "mirror the UI only" line
When adding RTL to a wallpaper or wellness app, the most important judgment is what to flip and what to leave alone. The UI chassis — navigation, text, buttons — should flip, but content the user is there to view (wallpaper images, photos, logos, QR codes) must never flip. A mirrored wallpaper is just a broken image, and a reversed logo is brand damage.
Conveniently, React Native doesn't flip the pixels of an image, so images are safe if you leave them be. The thing to watch is grid order. Laying thumbnails in a grid, RTL starts the order from the top-right. That's usually natural and you needn't force it back to LTR order. But in lists where order carries meaning ("New," "Popular"), check that numbers and labels still make sense with the first item on the right.
In some cases you want to pin direction for the content area alone. Give the parent an explicit direction.
// when the image preview must stay in an LTR coordinate system<View style={{ direction: 'ltr' }}> <ZoomableImage source={wallpaper} /> {/* keeps zoom scale and coordinate math from sign-flipping under RTL */}</View>
In my case, the pinch-zoom coordinate math sign-flipped under RTL and the image jumped in unexpected directions; pinning just the preview area to direction: 'ltr' fixed it. The whole UI is RTL, only the content's coordinate system is LTR — a two-layer structure.
Verify with testing and a staged rollout
You can check RTL on a device by switching it to Arabic, but flipping OS settings every time is tedious. During development, a force flag toggles it quickly.
// force RTL in dev only, to grab screenshotsif (__DEV__ && process.env.EXPO_PUBLIC_FORCE_RTL === '1') { I18nManager.allowRTL(true); I18nManager.forceRTL(true);}
Things to verify: the position of back/close buttons, the direction of inline list icons, absolutely positioned elements (floating buttons, badges, checkmarks), and wrapping when digits appear inside text. When prices or model numbers sit inside Arabic, only the digit run goes LTR — bidirectional text — and that's a common place for breakage.
For the production rollout, including whether the AdMob banner position shifted, it's safer to verify with a staged release rather than shipping to everyone at once. I run several wallpaper, wellness, and law-of-attraction apps, and for foundational changes like enabling RTL I start at 5% and widen while watching the crash rate. Layout flips tend to break on screens you didn't expect, so the behavior of those first few percent of users is the best evidence you'll get.
A next step
Start by grepping your own app for marginLeft, right:, and textAlign: 'left', and replacing them with logical properties. Even before you fully support RTL, that swap alone leaves LTR behavior unchanged and lays the groundwork for RTL later. Direction-following layout tends to make the LTR side of your code more honest, too.
Share
Thank You for Reading
Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.