@symbiote-native/localization wraps
expo-localization — the device’s
locale list and preferred calendar settings — so every SymbioteNative adapter can reach it, not
just React. Unlike network’s single useNetworkState, this package
ships two independent getters, getLocales/getCalendars, each synchronous (a direct
JSI-bridged native call, no await) and each with its own native change listener and its own
reactive hook per adapter (useLocales, useCalendars) — mirroring
battery’s shape of several distinct hooks in one package rather than
one combined hook.
expo-localization and expo-modules-core come along as regular dependencies, pinned to exact
versions — never install either yourself, and never add the expo meta-package to your project
(it bundles its own Metro/Babel pipeline, which conflicts with this project’s own).
No platform permission string is needed — locale and calendar settings are read-only system state
with no runtime permission prompt on either platform.
Both runes return a boxed getter object rather than a bare value — Svelte 5 reactivity does
not survive being returned as a raw value from a plain function, so read .current exactly
like unwrapping Vue’s Ref via .value.
List of the user’s locales, in the order the user defines in their device settings. Guaranteed to contain at least 1 element
getCalendars(): Calendar[]
List of the user’s preferred calendars. For now always returns a single element, but may return a user preference list on some platforms in the future. Guaranteed to contain at least 1 element
React returns this as a plain array, recomputed via useMemo whenever the locale-change listener fires. Vue returns Ref<Locale[]>. Angular’s LocalesService.connect() returns Signal<Locale[]>. Svelte returns { readonly current: Locale[] }, a boxed getter read as .current
React returns this as a plain array, recomputed via useMemo whenever the calendar-change listener fires. Vue returns Ref<Calendar[]>. Angular’s CalendarsService.connect() returns Signal<Calendar[]>. Svelte returns { readonly current: Calendar[] }, a boxed getter read as .current
An IETF BCP 47 language tag without the region code, e.g. 'en', 'es', 'pl'
languageScriptCode
string | null
An ISO 15924 4-letter script code, e.g. 'Latn', 'Hans', 'Hebr'. May be null on Android and web
regionCode
string | null
The device’s region, from the Region setting under Language & Region on iOS, Region settings on Android, and parsed from locale on web (can be null there), e.g. 'US'
languageRegionCode
string | null
The region code for the preferred language — the same value as regionCode for a non-region-specific language, or the language’s own region for a region-specific one ('en-CA' → 'CA'). Prefer regionCode for internationalization purposes
currencyCode
string | null
Currency code for the locale, e.g. 'USD', 'EUR', 'PLN'. On iOS this is the Region setting’s currency, not necessarily the current locale’s; on Android it’s specific to the locale in the list; null on web (look up by region instead)
currencySymbol
string | null
Currency symbol for the currency in currencyCode, e.g. '$', '€', 'zł'
languageCurrencyCode
string | null
Currency code for the current locale in the list rather than the device region (iOS), or equal to currencyCode (Android); null on web. Prefer currencyCode for internationalization purposes
languageCurrencySymbol
string | null
Currency symbol for the currency in languageCurrencyCode. Prefer currencySymbol for internationalization purposes
decimalSeparator
string | null
Decimal separator used for formatting numbers with fractional parts, e.g. '.', ','
digitGroupingSeparator
string | null
Digit grouping separator used for formatting large numbers, e.g. '.', ','
textDirection
'ltr' | 'rtl'
Text direction for the locale
measurementSystem
'metric' | 'us' | 'uk' | null
The measurement system used in the locale. null on web, since the user’s chosen measurement system isn’t exposed there and inferring it from locale is unreliable — ask for user preference if possible
temperatureUnit
'celsius' | 'fahrenheit' | null
The temperature unit used in the locale. null if the region code is unknown
The calendar identifier, one of the Unicode calendar types. Limited to Android’s own available calendar types there; iOS maps its own identifiers to the closest Unicode type and never reports 'dangi' or 'islamic-rgsa' (not implemented on iOS)
uses24hourClock
boolean | null
true when the current device settings use 24-hour time format. Can be null on browsers that don’t support the hourCycle property in Intl
firstWeekday
Weekday | null
The first day of the week — for most calendars Sunday is 1 and Saturday is 7. Can be null on browsers that don’t support the weekInfo property in Intl
timeZone
string | null
Time zone for the calendar, e.g. 'America/Los_Angeles', 'Europe/Warsaw', 'GMT+1'. Can be null on web
@symbiote-native/localization ships zero React/Vue/Angular logic in expo-localization itself
— its types and functions are hand-ported, verbatim, into this package’s own core/, resolving
the native module through expo-modules-core’s requireNativeModule rather than the expo
meta-package this project never installs. One native module fans out two independent event
streams (locale settings vs. calendar settings) through the same addListener, keyed by event
name — the same trick battery’s native module uses for its own
multiple event names on one addListener:
packages/localization/src/
├── core/ types.ts — Locale, Weekday, CalendarIdentifier, Calendar, hand-ported
│ verbatim from Localization.types.ts. native-module.ts resolves the
│ native module through expo-modules-core's requireNativeModule and
Two independent getters, each with its own native change listener and its own reactive hook per
adapter — mirroring @symbiote-native/battery’s shape of shipping several distinct hooks in one
package, not one combined hook. Each hook/composable/rune/service seeds its return value from the
matching synchronous get*() call (no initial “loading” state needed — the native call is sync,
not async) and recomputes it whenever the matching listener fires. The native code itself is
never vendored or copied — expo-modules-autolinking resolves it straight out of node_modules
(see the native setup guide).