You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[0.87] Pick the I18nUtil RTL-detection fix (wrong direction + main-thread locale scan on every Android cold start) #1438
The result is identical to I18nUtil.kt on main. The change is a single private function plus the test bookkeeping.
The bug.react/react-native#53417 (in 0.82 through 0.87.1) changed isDevicePreferredLanguageRTL to Locale.getAvailableLocales()[0]. react/react-native#57635 describes the correctness side: that entry is always af, so the device language never makes isRTL true. 0.87-stable still has it, and 0.87.1 is latest on npm.
A cost that has not been reported: it adds a main-thread scan to every Android cold start.ReactSurfaceImpl's constructor calls I18nUtil.isRTL() inside ReactActivity.onCreate. With the defaults (forceRTL false, allowRTL true), that reaches Locale.getAvailableLocales(), which builds every ICU locale on the device.
Measured on a production release build (RN 0.87.1, Hermes V1, R8, profile-compiled) on an OPPO CPH1937 (Android 11, arm64):
Profile. simpleperf on a profileable build: ReactHostImpl.createSurface -> ReactSurfaceImpl.<init> -> I18nUtil.isRTL -> Locale.getAvailableLocales -> ICU.localesFromStrings / localeFromIcuLocaleId on the main thread.
atrace, ActivityOnCreate median. 82.7 ms on RN 0.81 (which used Locale.getDefault()) vs 119.9 ms on RN 0.87.1 (n=10 / 12). Every other startup phase is within +-5 ms.
Bare-process probe (app_process) on the same phone.getAvailableLocales() returns 755 locales with af first; the first call costs 15.9-17.4 ms, later calls ~0 ms. Inside the app during startup the measured cost is larger (JIT compiling the ICU parsers runs concurrently in every trace).
Control. When isRTL short-circuits (allowRTL(false) persisted, or forceRTL(true)), the ICU work disappears from the traces (24/24 before, 0/12 after) and cold start drops: paired medians 641 -> 617 ms and 655 -> 625 ms (n=15 each).
The app-side workaround is to always persist allowRTL / forceRTL explicitly so isRTL never reaches the device-locale branch. But that is not the default, so apps on 0.87 that never call those APIs (most LTR apps) pay it on every cold start, and fresh installs always pay it once.
The fix is small, already on main and in 0.88, and swaps an O(all locales) lookup for a direct read of the configuration's first locale - so low risk for a patch release.
Activity