How to get the device locale in React Native
AppleLocale returns undefined since iOS 13, and the fallback chain everyone copied hides the bug. react-native-get-device-locale asks the OS for the locale.

AppleLocale returns undefined since iOS 13, and the fallback chain everyone copied hides the bug. react-native-get-device-locale asks the OS for the locale.

Animalert ships in several languages. The first time someone opens the app, there is no stored preference to read, no account, no setting. The app has one question to answer before it draws anything: what language does this person actually use?
For years, that answer was a single line of JavaScript. Then the line stopped working, and the workaround the whole ecosystem adopted turned out to be worse than the original bug.
If you learned React Native localization from a tutorial or a Stack Overflow answer, you learned this: read NativeModules.SettingsManager.settings.AppleLocale on iOS, and NativeModules.I18nManager.localeIdentifier on Android.
It was never a documented public API. It was a settings blob exposed by a native module, and reading a value out of it happened to work.
In September 2019 the field simply stopped being there. Two issues opened on the React Native repository within three days of each other: AppleLocale not working in iOS 13 and settings doesn't have AppleLocale field on iOS 13.
Both were closed years ago. Both are still what you find when you search the problem today, which tells you something about how many apps quietly inherited it.
The workaround that circulated is a cascade: try AppleLocale, and if it is undefined fall back to the first entry of AppleLanguages, and if that fails too default to a hardcoded English.
That looks defensive. In practice it means three sources of truth returning three different formats: AppleLocale gives you en_US, the languages list gives you en-US, and the hardcoded default gives you en. Add the Android branch and a single app is parsing locales four ways.
The real cost is not the parsing. A cascade like this never fails loudly. It returns a plausible wrong answer, so an app that should have opened in French opens in English, nobody files a ticket, people just leave.
Locale resolution built on a fallback chain keeps running long after it has become wrong. Test it by actually setting a device to a language you do not develop in, because no error will ever tell you.
React Native made the New Architecture the default in 0.76, where TurboModules replace the old NativeModules system entirely, and the legacy bridge was disabled outright in 0.82.
None of that broke locale reading on purpose. It just removed any remaining reason to believe that poking at a legacy settings object was a stable place to stand.
The fix is unglamorous. Rather than reading a settings blob that happens to contain a locale, ask the platform for the locale directly, through the API whose job that is.
On iOS that is NSLocale.currentLocale.localeIdentifier: one line, always present, no version condition. Android has its exact equivalent. Wrapping the pair in a TurboModule gives one async function returning a string in en_US form, with an optional fallback if resolution ever fails.
That is all react-native-get-device-locale does: npm install react-native-get-device-locale, one method, zero dependencies, and it runs on the New Architecture as well as the old one because the native side compiles both paths.
In Animalert, react-native-get-device-locale decides the interface language on first launch, before any preference exists. There is a second, less obvious consumer: the resolved locale is written to a shared preferences key that the iOS and Android home-screen widgets read, so the widget on someone's home screen speaks the same language as the app that put it there.
That detail is a good argument for keeping locale resolution in one small, predictable place. When four code paths each guess the language, the widget is where the disagreement becomes visible.
One format, en_US, and one value. It does not return every preferred language in order, and it knows nothing about timezone, calendar, currency or number formatting.
If the device locale is all you are after, react-native-get-device-locale is the right call in the large majority of cases: one method, zero dependencies, nothing to configure, and you are not pulling in a whole localization library to read one string.
If you need the rest, react-native-localize is the mature, widely used library for the job, and it is the honest recommendation. The choice is about scope rather than quality.
The lesson is not about a particular field name. It is that the platform almost always exposes the thing you want through an API meant for it, and the shortcut that reads it out of a convenient nearby object is the one that breaks two major versions later, silently.
The source is on GitHub, issues and pull requests are open, and the README lays out both native paths and the APIs this module replaces.
react-native-get-device-locale calls the platform locale API directly and returns a string such as en_US or fr_FR, with zero dependencies and support for both the New Architecture and the old one. For timezone, currency, number formatting or language negotiation, react-native-localize covers far more ground.
The field stopped being present as of iOS 13, and it was never a documented public API in the first place. Anything reading it should move to a platform locale API instead of adding a fallback.
Through a native module that calls the platform locale API, NSLocale.currentLocale.localeIdentifier on iOS and its Android counterpart, rather than reading a value out of the legacy settings object.
Yes, provided the module is a TurboModule. The older approach was built on the legacy NativeModules system that the New Architecture replaces, which is a second reason to move away from it.
react-native-get-device-locale returns the underscore form, such as en_US or fr_FR. This matters more than it looks, because the various fallback sources return hyphenated and short forms too, and code that assumes one shape breaks on the others.