feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,2 @@
// @generated by expo-module-scripts
module.exports = require('expo-module-scripts/eslintrc.base.js');

View File

@@ -0,0 +1,258 @@
# Changelog
## Unpublished
### 🛠 Breaking changes
### 🎉 New features
### 🐛 Bug fixes
### 💡 Others
## 15.0.3 — 2024-05-06
### 🎉 New features
- Added a `forcesRTL` manifest flag for forcing RTL to be on. ([#28129](https://github.com/expo/expo/pull/28129) by [@aleqsio](https://github.com/aleqsio))
## 15.0.2 — 2024-05-01
_This version does not introduce any user-facing changes._
## 15.0.1 — 2024-04-23
_This version does not introduce any user-facing changes._
## 15.0.0 — 2024-04-18
### 🐛 Bug fixes
- [iOS] Add privacy manifest describing required reason API usage. ([#27770](https://github.com/expo/expo/pull/27770) by [@aleqsio](https://github.com/aleqsio))
- [Android] Fix es-419 locale returning empty list. ([#27250](https://github.com/expo/expo/pull/27250) by [@aleqsio](https://github.com/aleqsio))
### 💡 Others
- Removed deprecated backward compatible Gradle settings. ([#28083](https://github.com/expo/expo/pull/28083) by [@kudo](https://github.com/kudo))
## 14.8.3 - 2024-01-18
_This version does not introduce any user-facing changes._
## 14.8.2 - 2024-01-10
_This version does not introduce any user-facing changes._
## 14.8.1 - 2023-12-19
_This version does not introduce any user-facing changes._
## 14.8.0 — 2023-12-12
_This version does not introduce any user-facing changes._
## 14.7.0 — 2023-11-14
### 🛠 Breaking changes
- Bumped iOS deployment target to 13.4. ([#25063](https://github.com/expo/expo/pull/25063) by [@gabrieldonadel](https://github.com/gabrieldonadel))
- On `Android` bump `compileSdkVersion` and `targetSdkVersion` to `34`. ([#24708](https://github.com/expo/expo/pull/24708) by [@alanjhughes](https://github.com/alanjhughes))
### 🐛 Bug fixes
- [iOS] Fix expo-localization tvOS compile, add CI. ([#25082](https://github.com/expo/expo/pull/25082) by [@douglowder](https://github.com/douglowder))
### 💡 Others
- [iOS] Use newer, non-deprecated platform APIs in `getLocales()`. ([#24884](https://github.com/expo/expo/pull/24884) by [@aleqsio](https://github.com/aleqsio))
### ⚠️ Notices
- Deprecated `locale` constant. ([#25078](https://github.com/expo/expo/pull/25078) by [@aleqsio](https://github.com/aleqsio))
## 14.6.0 — 2023-10-17
### 🛠 Breaking changes
- Dropped support for Android SDK 21 and 22. ([#24201](https://github.com/expo/expo/pull/24201) by [@behenate](https://github.com/behenate))
### 🐛 Bug fixes
- [iOS] fix tvOS compilation. ([#24845](https://github.com/expo/expo/pull/24845) by [@douglowder](https://github.com/douglowder))
## 14.5.0 — 2023-09-04
### 🎉 New features
- Added support for React Native 0.73. ([#24018](https://github.com/expo/expo/pull/24018) by [@kudo](https://github.com/kudo))
- Added a `temperatureUnit` field, which contains the default temperature unit for the locale. ([#24059](https://github.com/expo/expo/pull/24059) by [@behenate](https://github.com/behenate))
### 💡 Others
- Change documentation to refer to a correct replacement method in a deprecated field. ([#23811](https://github.com/expo/expo/pull/23811) by [@aleqsio](https://github.com/aleqsio))
## 14.4.0 — 2023-08-02
_This version does not introduce any user-facing changes._
## 14.3.0 — 2023-06-21
### 🎉 New features
- Changing locale on Android no longer reloads the app if the `expo-localization` config plugin is added to app.json. ([#22763](https://github.com/expo/expo/pull/22763) by [@aleqsio](https://github.com/aleqsio))
- Added hooks to get current locale and calendar. ([#22763](https://github.com/expo/expo/pull/22763) by [@aleqsio](https://github.com/aleqsio))
- Measurement system now returns `uk` and `us` values on iOS 16 and higher. ([#22763](https://github.com/expo/expo/pull/22763) by [@aleqsio](https://github.com/aleqsio))
### 🐛 Bug fixes
- User settings for delimiters and other locale preferences now override default locale settings for each locale in the list. ([#22763](https://github.com/expo/expo/pull/22763) by [@aleqsio](https://github.com/aleqsio))
- Fixed Android build warnings for Gradle version 8. ([#22537](https://github.com/expo/expo/pull/22537), [#22609](https://github.com/expo/expo/pull/22609) by [@kudo](https://github.com/kudo))
## 14.2.0 — 2023-05-08
### 🐛 Bug fixes
- Fixed invalid timezone returned for `getCalendars` on Web. ([#22003](https://github.com/expo/expo/pull/22003) by [@aleqsio](https://github.com/aleqsio))
- Fixed errors thrown on Play Console pre-launch report. ([#22003](https://github.com/expo/expo/pull/22003) by [@aleqsio](https://github.com/aleqsio))
## 14.1.1 — 2023-02-09
_This version does not introduce any user-facing changes._
## 14.1.0 — 2023-02-03
### 💡 Others
- On Android bump `compileSdkVersion` and `targetSdkVersion` to `33`. ([#20721](https://github.com/expo/expo/pull/20721) by [@lukmccall](https://github.com/lukmccall))
## 14.0.0 — 2022-10-25
### 🛠 Breaking changes
- Bumped iOS deployment target to 13.0 and deprecated support for iOS 12. ([#18873](https://github.com/expo/expo/pull/18873) by [@tsapeta](https://github.com/tsapeta))
### 🎉 New features
- Added two new synchronous functions: `getLocales` and `getCalendars`. ([#19019](https://github.com/expo/expo/pull/19019) by [@aleqsio](https://github.com/aleqsio))
- Added a `supportsRTL` manifest flag for enabling RTL on suitable locales. ([#19634](https://github.com/expo/expo/pull/19634) by [@aleqsio](https://github.com/aleqsio))
### 🐛 Bug fixes
- Fixed build error for setting `compileSdkVersion` to 33. ([#19518](https://github.com/expo/expo/pull/19518) by [@kudo](https://github.com/kudo))
### ⚠️ Notices
- Deprecated existing constants API while keeping backwards compatibility. ([#19019](https://github.com/expo/expo/pull/19019) by [@aleqsio](https://github.com/aleqsio))
## 13.1.0 — 2022-07-07
### 🎉 New features
- Native module on Android is now written in Kotlin using the new API. ([#17775](https://github.com/expo/expo/pull/17775) by [@barthap](https://github.com/barthap))
### 💡 Others
- Migrated Expo modules definitions to the new naming convention. ([#17193](https://github.com/expo/expo/pull/17193) by [@tsapeta](https://github.com/tsapeta))
## 13.0.0 — 2022-04-18
### 🛠 Breaking changes
- Guess the device language on iOS rather than using the app-specific language. ([#15807](https://github.com/expo/expo/pull/15807) by [@EvanBacon](https://github.com/EvanBacon))
### 🎉 New features
- Use JSI host object instead of the bridge module for communication between JavaScript and native code. ([#16972](https://github.com/expo/expo/pull/16972) by [@tsapeta](https://github.com/tsapeta))
### 🐛 Bug fixes
- Exception in HostObject::get for prop 'NativeUnimoduleProxy': java.lang.NullPointerException ([#16316](https://github.com/expo/expo/pull/16316) by [@nomi9995](https://github.com/nomi9995))
### ⚠️ Notices
- On Android bump `compileSdkVersion` to `31`, `targetSdkVersion` to `31` and `Java` version to `11`. ([#16941](https://github.com/expo/expo/pull/16941) by [@bbarthec](https://github.com/bbarthec))
## 12.0.1 - 2022-02-01
### 🐛 Bug fixes
- Fix `Plugin with id 'maven' not found` build error from Android Gradle 7. ([#16080](https://github.com/expo/expo/pull/16080) by [@kudo](https://github.com/kudo))
## 12.0.0 — 2021-12-03
### 🎉 New features
- Native module on iOS is now written in Swift using the new API. ([#15266](https://github.com/expo/expo/pull/15266) by [@tsapeta](https://github.com/tsapeta))
### 💡 Others
- Rewrite module to Kotlin. ([#14588](https://github.com/expo/expo/pull/14588) by [@mstach60161](https://github.com/mstach60161))
## 11.0.0 — 2021-09-28
### 🛠 Breaking changes
- Dropped support for iOS 11.0 ([#14383](https://github.com/expo/expo/pull/14383) by [@cruzach](https://github.com/cruzach))
### 🐛 Bug fixes
- Fix building errors from use_frameworks! in Podfile. ([#14523](https://github.com/expo/expo/pull/14523) by [@kudo](https://github.com/kudo))
## 10.2.0 — 2021-06-16
### 🐛 Bug fixes
- Enable kotlin in all modules. ([#12716](https://github.com/expo/expo/pull/12716) by [@wschurman](https://github.com/wschurman))
### 💡 Others
- Build Android code using Java 8 to fix Android instrumented test build error. ([#12939](https://github.com/expo/expo/pull/12939) by [@kudo](https://github.com/kudo))
## 10.1.0 — 2021-03-10
### 🎉 New features
- Updated Android build configuration to target Android 11 (added support for Android SDK 30). ([#11647](https://github.com/expo/expo/pull/11647) by [@bbarthec](https://github.com/bbarthec))
- Add `currency`, `isMetric`, `decimalSeparator`, `digitGroupingSeparator` properties. ([#11663](https://github.com/expo/expo/pull/11663) by [@IjzerenHein](https://github.com/IjzerenHein))
- Add support for `region` property for Android. ([#11663](https://github.com/expo/expo/pull/11663) by [@IjzerenHein](https://github.com/IjzerenHein))
### 🐛 Bug fixes
- Fix invalid `region` property on Web when locale contains script or variant fields. ([#11663](https://github.com/expo/expo/pull/11663) by [@IjzerenHein](https://github.com/IjzerenHein))
- Remove peerDependencies and unimodulePeerDependencies from Expo modules. ([#11980](https://github.com/expo/expo/pull/11980) by [@brentvatne](https://github.com/brentvatne))
## 10.0.0 — 2021-01-15
### 🛠 Breaking changes
- Dropped support for iOS 10.0 ([#11344](https://github.com/expo/expo/pull/11344) by [@tsapeta](https://github.com/tsapeta))
## 9.1.0 — 2020-11-17
_This version does not introduce any user-facing changes._
## 9.0.0 — 2020-08-18
### 🛠 Breaking changes
- `Localization.region` changed from `undefined | string` to `null | string` on web to match iOS. ([#8824](https://github.com/expo/expo/pull/8824) by [@EvanBacon](https://github.com/EvanBacon))
### 🎉 New features
- Added doc blocks for everything. ([#8824](https://github.com/expo/expo/pull/8824) by [@EvanBacon](https://github.com/EvanBacon))
- Added support for SSR environments. ([#8824](https://github.com/expo/expo/pull/8824) by [@EvanBacon](https://github.com/EvanBacon))
- `Localization.isRTL` defaults to `false` in node environments. ([#8824](https://github.com/expo/expo/pull/8824) by [@EvanBacon](https://github.com/EvanBacon))
### 🐛 Bug fixes
- `Localization.region` now returns `null` when a partial `locale` is defined by the browser on web. ([#8824](https://github.com/expo/expo/pull/8824) by [@EvanBacon](https://github.com/EvanBacon))
## 8.2.1 — 2020-05-29
_This version does not introduce any user-facing changes._
## 8.2.0 — 2020-05-27
### 🐛 Bug fixes
- Fixed `Localization.locale` throwing an exception on the iOS simulator. ([#8193](https://github.com/expo/expo/pull/8193) by [@lukmccall](https://github.com/lukmccall))

View File

@@ -0,0 +1,41 @@
<p>
<a href="https://docs.expo.dev/versions/latest/sdk/localization/">
<img
src="../../.github/resources/expo-localization.svg"
alt="expo-localization"
height="64" />
</a>
</p>
Provides an interface for native user localization information.
# API documentation
- [Documentation for the main branch](https://github.com/expo/expo/blob/main/docs/pages/versions/unversioned/sdk/localization.mdx)
- [Documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/localization/)
# Installation in managed Expo projects
For [managed](https://docs.expo.dev/archive/managed-vs-bare/) Expo projects, please follow the installation instructions in the [API documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/localization/).
# Installation in bare React Native projects
For bare React Native projects, you must ensure that you have [installed and configured the `expo` package](https://docs.expo.dev/bare/installing-expo-modules/) before continuing.
### Add the package to your npm dependencies
```
npx expo install expo-localization
```
### Configure for iOS
Run `npx pod-install` after installing the npm package.
### Configure for Android
No additional set up necessary.
# Contributing
Contributions are very welcome! Please refer to guidelines described in the [contributing guide](https://github.com/expo/expo#contributing).

View File

@@ -0,0 +1,23 @@
apply plugin: 'com.android.library'
group = 'host.exp.exponent'
version = '15.0.3'
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
apply from: expoModulesCorePlugin
applyKotlinExpoModulesCorePlugin()
useCoreDependencies()
useDefaultAndroidSdkVersions()
useExpoPublishing()
android {
namespace "expo.modules.localization"
defaultConfig {
versionCode 22
versionName "15.0.3"
}
}
dependencies {
implementation "androidx.core:core-ktx:1.6.0"
}

View File

@@ -0,0 +1,3 @@
<manifest>
</manifest>

View File

@@ -0,0 +1,238 @@
package expo.modules.localization
import android.content.Context
import android.icu.util.LocaleData
import android.icu.util.ULocale
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.os.Bundle
import android.text.TextUtils
import android.text.TextUtils.getLayoutDirectionFromLocale
import android.text.format.DateFormat
import android.util.LayoutDirection
import android.util.Log
import android.view.View
import androidx.core.os.LocaleListCompat
import androidx.core.os.bundleOf
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import java.text.DecimalFormatSymbols
import java.util.*
// must be kept in sync with https://github.com/facebook/react-native/blob/main/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nUtil.java
private const val SHARED_PREFS_NAME = "com.facebook.react.modules.i18nmanager.I18nUtil"
private const val KEY_FOR_PREFS_ALLOWRTL = "RCTI18nUtil_allowRTL"
private const val KEY_FOR_PREFS_FORCERTL = "RCTI18nUtil_forceRTL"
private const val LOCALE_SETTINGS_CHANGED = "onLocaleSettingsChanged"
private const val CALENDAR_SETTINGS_CHANGED = "onCalendarSettingsChanged"
class LocalizationModule : Module() {
private var observer: () -> Unit = {}
override fun definition() = ModuleDefinition {
Name("ExpoLocalization")
Constants {
bundledConstants.toShallowMap()
}
AsyncFunction<Bundle>("getLocalizationAsync") {
return@AsyncFunction bundledConstants
}
Function("getLocales") {
return@Function getPreferredLocales()
}
Function("getCalendars") {
return@Function getCalendars()
}
Events(LOCALE_SETTINGS_CHANGED, CALENDAR_SETTINGS_CHANGED)
OnCreate {
appContext.reactContext?.let {
setRTLFromStringResources(it)
}
observer = {
this@LocalizationModule.sendEvent(LOCALE_SETTINGS_CHANGED)
this@LocalizationModule.sendEvent(CALENDAR_SETTINGS_CHANGED)
}
Notifier.registerObserver(observer)
}
OnDestroy {
Notifier.deregisterObserver(observer)
}
}
private fun setRTLFromStringResources(context: Context) {
// These keys are used by React Native here: https://github.com/facebook/react-native/blob/main/React/Modules/RCTI18nUtil.m
// We set them before React loads to ensure it gets rendered correctly the first time the app is opened.
val supportsRTL = appContext.reactContext?.getString(R.string.ExpoLocalization_supportsRTL)
val forcesRTL = appContext.reactContext?.getString(R.string.ExpoLocalization_forcesRTL)
if (forcesRTL == "true") {
context
.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE)
.edit()
.also {
it.putBoolean(KEY_FOR_PREFS_ALLOWRTL, true)
it.putBoolean(KEY_FOR_PREFS_FORCERTL, true)
it.apply()
}
} else {
if (supportsRTL == "true" || supportsRTL == "false") {
context
.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE)
.edit()
.also {
it.putBoolean(KEY_FOR_PREFS_ALLOWRTL, supportsRTL == "true")
if (forcesRTL == "false") {
it.putBoolean(KEY_FOR_PREFS_FORCERTL, false)
}
it.apply()
}
}
}
}
// TODO: Bacon: add set language
private val bundledConstants: Bundle
get() {
val locale = Locale.getDefault()
val localeNames = getLocaleNames(locales)
val isRTL = TextUtils.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL
val region = getRegionCode(locale)
val symbols = DecimalFormatSymbols(locale)
return bundleOf(
"currency" to getCurrencyCode(locale),
"decimalSeparator" to symbols.decimalSeparator.toString(),
"digitGroupingSeparator" to symbols.groupingSeparator.toString(),
"isoCurrencyCodes" to ISOCurrencyCodes,
"isMetric" to !USES_IMPERIAL.contains(region),
"isRTL" to isRTL,
// TODO: (barthap) this can throw IndexOutOfBounds exception - handle this properly
"locale" to localeNames[0],
"locales" to localeNames,
"region" to region,
"timezone" to TimeZone.getDefault().id
)
}
private val locales: List<Locale>
get() {
val context = appContext.reactContext ?: return emptyList()
val configuration = context.resources.configuration
return if (VERSION.SDK_INT > VERSION_CODES.N) {
val locales = ArrayList<Locale>()
for (i in 0 until configuration.locales.size()) {
locales.add(configuration.locales[i])
}
locales
} else {
@Suppress("DEPRECATION")
listOf(configuration.locale)
}
}
private fun getMeasurementSystem(locale: Locale): String? {
return if (VERSION.SDK_INT >= VERSION_CODES.P) {
when (LocaleData.getMeasurementSystem(ULocale.forLocale(locale))) {
LocaleData.MeasurementSystem.SI -> "metric"
LocaleData.MeasurementSystem.UK -> "uk"
LocaleData.MeasurementSystem.US -> "us"
else -> "metric"
}
} else {
if (getRegionCode(locale).equals("uk")) {
"uk"
} else if (USES_IMPERIAL.contains(getRegionCode(locale))) {
"us"
} else {
"metric"
}
}
}
private fun getCurrencyProperties(locale: Locale): Map<String, Any?> {
return try {
mapOf(
"currencyCode" to Currency.getInstance(locale).currencyCode,
// currency symbol can be localized to display locale (1st on the list) or to the locale for the currency (as done here).
"currencySymbol" to Currency.getInstance(locale).getSymbol(locale)
)
} catch (e: Exception) {
mapOf(
"currencyCode" to null,
"currencySymbol" to null
)
}
}
private fun getPreferredLocales(): List<Map<String, Any?>> {
val locales = mutableListOf<Map<String, Any?>>()
val localeList: LocaleListCompat = LocaleListCompat.getDefault()
for (i in 0 until localeList.size()) {
try {
val locale: Locale = localeList.get(i) ?: continue
val decimalFormat = DecimalFormatSymbols.getInstance(locale)
locales.add(
mapOf(
"languageTag" to locale.toLanguageTag(),
"regionCode" to getRegionCode(locale),
"textDirection" to if (getLayoutDirectionFromLocale(locale) == LayoutDirection.RTL) "rtl" else "ltr",
"languageCode" to locale.language,
// the following two properties should be deprecated once Intl makes it way to RN, instead use toLocaleString
"decimalSeparator" to decimalFormat.decimalSeparator.toString(),
"digitGroupingSeparator" to decimalFormat.groupingSeparator.toString(),
"measurementSystem" to getMeasurementSystem(locale),
"temperatureUnit" to getTemperatureUnit(locale)
) + getCurrencyProperties(locale)
)
} catch (e: Exception) {
// warn about the problematic locale
// we don't append the problematic locale to the list
Log.w("expo-localization", "Failed to get locale for index $i", e)
}
}
return locales
}
private fun uses24HourClock(): Boolean {
if (appContext.reactContext == null) return false
return DateFormat.is24HourFormat(appContext.reactContext)
}
private fun getCalendarType(): String {
return if (VERSION.SDK_INT >= VERSION_CODES.O) {
Calendar.getInstance().calendarType.toString()
} else {
"gregory"
}
}
private fun getCalendars(): List<Map<String, Any?>> {
return listOf(
mapOf(
"calendar" to getCalendarType(),
"uses24hourClock" to uses24HourClock(), // we ideally would use hourCycle (one of h12, h23, h11, h24) instead, but not sure how to get it on android and ios
"firstWeekday" to Calendar.getInstance().firstDayOfWeek,
"timeZone" to Calendar.getInstance().timeZone.id
)
)
}
}
/**
* Creates a shallow [Map] from the [Bundle]. Does not traverse nested arrays and bundles.
*/
private fun Bundle.toShallowMap(): Map<String, Any?> {
val map = HashMap<String, Any?>()
for (key in this.keySet()) {
map[key] = this[key]
}
return map
}

View File

@@ -0,0 +1,35 @@
package expo.modules.localization
import android.content.Context
import android.content.res.Configuration
import expo.modules.core.interfaces.ApplicationLifecycleListener
import expo.modules.core.interfaces.Package
object Notifier {
private val observers = mutableListOf<() -> Unit>()
fun registerObserver(observer: () -> Unit) {
observers.add(observer)
}
fun deregisterObserver(observer: () -> Unit) {
observers.remove(observer)
}
fun onConfigurationChanged() {
// Notify all observers
observers.forEach { it() }
}
}
// TODO: Move to new listener API once it's available
class LocalizationPackage : Package {
override fun createApplicationLifecycleListeners(context: Context?): List<ApplicationLifecycleListener> {
return listOf(object : ApplicationLifecycleListener {
override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
Notifier.onConfigurationChanged()
}
})
}
}

View File

@@ -0,0 +1,49 @@
package expo.modules.localization
import android.text.TextUtils
import java.util.*
val USES_IMPERIAL = listOf("US", "LR", "MM")
// https://wisevoter.com/country-rankings/countries-that-use-fahrenheit/
val USES_FAHRENHEIT = listOf("AG", "BZ", "VG", "FM", "MH", "MS", "KN", "BS", "CY", "TC", "US", "LR", "PW", "KY")
val ISOCurrencyCodes: Array<String> by lazy {
Currency.getAvailableCurrencies().map { it.currencyCode as String }.toTypedArray()
}
fun getLocaleNames(locales: List<Locale>) = locales.map { it.toLanguageTag() }.toTypedArray()
fun getCountryCode(locale: Locale): String? {
return runCatching {
val country = locale.country
if (TextUtils.isEmpty(country)) null else country
}.getOrNull()
}
fun getSystemProperty(key: String): String {
return runCatching {
val systemProperties = Class.forName("android.os.SystemProperties")
val get = systemProperties.getMethod("get", String::class.java)
get.invoke(systemProperties, key) as String
}.getOrNull() ?: ""
}
fun getCurrencyCode(locale: Locale): String? {
return runCatching {
val currency = Currency.getInstance(locale)
currency?.currencyCode
}.getOrNull()
}
fun getRegionCode(locale: Locale): String? {
val miuiRegion = getSystemProperty("ro.miui.region")
return miuiRegion.ifEmpty {
getCountryCode(locale)
}
}
fun getTemperatureUnit(locale: Locale): String? {
val countryCode = getRegionCode(locale) ?: return null
return if (USES_FAHRENHEIT.contains(countryCode)) "fahrenheit" else "celsius"
}

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ExpoLocalization_supportsRTL" translatable="false">unset</string>
<string name="ExpoLocalization_forcesRTL" translatable="false">unset</string>
</resources>

View File

@@ -0,0 +1 @@
module.exports = require('./plugin/build/withExpoLocalization');

View File

@@ -0,0 +1,22 @@
import { Subscription } from 'expo-modules-core';
import { Localization, Calendar, Locale } from './Localization.types';
export declare function addLocaleListener(listener: (event: any) => void): Subscription;
export declare function addCalendarListener(listener: (event: any) => void): Subscription;
export declare function removeSubscription(subscription: Subscription): void;
declare const _default: {
readonly currency: string | null;
readonly decimalSeparator: string;
readonly digitGroupingSeparator: string;
readonly isRTL: boolean;
readonly isMetric: boolean;
readonly locale: string;
readonly locales: string[];
readonly timezone: string;
readonly isoCurrencyCodes: string[];
readonly region: string | null;
getLocales(): Locale[];
getCalendars(): Calendar[];
getLocalizationAsync(): Promise<Omit<Localization, 'getCalendars' | 'getLocales'>>;
};
export default _default;
//# sourceMappingURL=ExpoLocalization.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoLocalization.d.ts","sourceRoot":"","sources":["../src/ExpoLocalization.ts"],"names":[],"mappings":"AACA,OAAO,EAAY,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAG3D,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAsB,MAAM,sBAAsB,CAAC;AAoC1F,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,CAAC,KAAK,KAAA,KAAK,IAAI,GAAG,YAAY,CAKzE;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,CAAC,KAAK,KAAA,KAAK,IAAI,GAAG,YAAY,CAK3E;AAED,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,YAAY,QAE5D;;;;;;;;;;;;kBAsEe,MAAM,EAAE;oBAgCN,QAAQ,EAAE;4BAcI,QAAQ,KAAK,YAAY,EAAE,cAAc,GAAG,YAAY,CAAC,CAAC;;AAlH1F,wBA4IE"}

View File

@@ -0,0 +1,166 @@
/* eslint-env browser */
import { Platform } from 'expo-modules-core';
import * as rtlDetect from 'rtl-detect';
const getNavigatorLocales = () => {
return Platform.isDOMAvailable ? navigator.languages || [navigator.language] : [];
};
const WEB_LANGUAGE_CHANGE_EVENT = 'languagechange';
// https://wisevoter.com/country-rankings/countries-that-use-fahrenheit/
const USES_FAHRENHEIT = [
'AG',
'BZ',
'VG',
'FM',
'MH',
'MS',
'KN',
'BS',
'CY',
'TC',
'US',
'LR',
'PW',
'KY',
];
export function addLocaleListener(listener) {
addEventListener(WEB_LANGUAGE_CHANGE_EVENT, listener);
return {
remove: () => removeEventListener(WEB_LANGUAGE_CHANGE_EVENT, listener),
};
}
export function addCalendarListener(listener) {
addEventListener(WEB_LANGUAGE_CHANGE_EVENT, listener);
return {
remove: () => removeEventListener(WEB_LANGUAGE_CHANGE_EVENT, listener),
};
}
export function removeSubscription(subscription) {
subscription.remove();
}
export default {
get currency() {
// TODO: Add support
return null;
},
get decimalSeparator() {
return (1.1).toLocaleString().substring(1, 2);
},
get digitGroupingSeparator() {
const value = (1000).toLocaleString();
return value.length === 5 ? value.substring(1, 2) : '';
},
get isRTL() {
return rtlDetect.isRtlLang(this.locale) ?? false;
},
get isMetric() {
const { region } = this;
switch (region) {
case 'US': // USA
case 'LR': // Liberia
case 'MM': // Myanmar
return false;
}
return true;
},
get locale() {
if (!Platform.isDOMAvailable) {
return '';
}
const locale = navigator.language ||
navigator['systemLanguage'] ||
navigator['browserLanguage'] ||
navigator['userLanguage'] ||
this.locales[0];
return locale;
},
get locales() {
if (!Platform.isDOMAvailable) {
return [];
}
const { languages = [] } = navigator;
return Array.from(languages);
},
get timezone() {
const defaultTimeZone = 'Etc/UTC';
if (typeof Intl === 'undefined') {
return defaultTimeZone;
}
return Intl.DateTimeFormat().resolvedOptions().timeZone || defaultTimeZone;
},
get isoCurrencyCodes() {
// TODO(Bacon): Add this - very low priority
return [];
},
get region() {
// There is no way to obtain the current region, as is possible on native.
// Instead, use the country-code from the locale when possible (e.g. "en-US").
const { locale } = this;
const [, ...suffixes] = typeof locale === 'string' ? locale.split('-') : [];
for (const suffix of suffixes) {
if (suffix.length === 2) {
return suffix.toUpperCase();
}
}
return null;
},
getLocales() {
const locales = getNavigatorLocales();
return locales?.map((languageTag) => {
// TextInfo is an experimental API that is not available in all browsers.
// We might want to consider using a locale lookup table instead.
const locale = typeof Intl !== 'undefined'
? new Intl.Locale(languageTag)
: { region: null, textInfo: null, language: null };
const { region, textInfo, language } = locale;
// Properties added only for compatibility with native, use `toLocaleString` instead.
const digitGroupingSeparator = Array.from((10000).toLocaleString(languageTag)).filter((c) => c > '9' || c < '0')[0] ||
null; // using 1e5 instead of 1e4 since for some locales (like pl-PL) 1e4 does not use digit grouping
const decimalSeparator = (1.1).toLocaleString(languageTag).substring(1, 2);
const temperatureUnit = region ? regionToTemperatureUnit(region) : null;
return {
languageTag,
languageCode: language || languageTag.split('-')[0] || 'en',
textDirection: textInfo?.direction || null,
digitGroupingSeparator,
decimalSeparator,
measurementSystem: null,
currencyCode: null,
currencySymbol: null,
regionCode: region || null,
temperatureUnit,
};
});
},
getCalendars() {
const locale = ((typeof Intl !== 'undefined'
? Intl.DateTimeFormat().resolvedOptions()
: null) ?? null);
return [
{
calendar: (locale?.calendar || locale?.calendars?.[0]) || null,
timeZone: locale?.timeZone || locale?.timeZones?.[0] || null,
uses24hourClock: (locale?.hourCycle || locale?.hourCycles?.[0])?.startsWith('h2') ?? null,
firstWeekday: locale?.weekInfo?.firstDay || null,
},
];
},
async getLocalizationAsync() {
const { currency, decimalSeparator, digitGroupingSeparator, isoCurrencyCodes, isMetric, isRTL, locale, locales, region, timezone, } = this;
return {
currency,
decimalSeparator,
digitGroupingSeparator,
isoCurrencyCodes,
isMetric,
isRTL,
locale,
locales,
region,
timezone,
};
},
};
function regionToTemperatureUnit(region) {
return USES_FAHRENHEIT.includes(region) ? 'fahrenheit' : 'celsius';
}
//# sourceMappingURL=ExpoLocalization.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,7 @@
import { Subscription } from 'expo-modules-core';
declare const ExpoLocalizationModule: any;
export declare function addLocaleListener(listener: (event: any) => void): Subscription;
export declare function addCalendarListener(listener: (event: any) => void): Subscription;
export declare function removeSubscription(subscription: Subscription): void;
export default ExpoLocalizationModule;
//# sourceMappingURL=ExpoLocalization.native.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoLocalization.native.d.ts","sourceRoot":"","sources":["../src/ExpoLocalization.native.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,YAAY,EAAuB,MAAM,mBAAmB,CAAC;AAEpF,QAAA,MAAM,sBAAsB,KAA0C,CAAC;AAGvE,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,CAAC,KAAK,KAAA,KAAK,IAAI,GAAG,YAAY,CAEzE;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,CAAC,KAAK,KAAA,KAAK,IAAI,GAAG,YAAY,CAE3E;AAED,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,YAAY,QAE5D;AAED,eAAe,sBAAsB,CAAC"}

View File

@@ -0,0 +1,14 @@
import { EventEmitter, requireNativeModule } from 'expo-modules-core';
const ExpoLocalizationModule = requireNativeModule('ExpoLocalization');
const emitter = new EventEmitter(ExpoLocalizationModule);
export function addLocaleListener(listener) {
return emitter.addListener('onLocaleSettingsChanged', listener);
}
export function addCalendarListener(listener) {
return emitter.addListener('onCalendarSettingsChanged', listener);
}
export function removeSubscription(subscription) {
return emitter.removeSubscription(subscription);
}
export default ExpoLocalizationModule;
//# sourceMappingURL=ExpoLocalization.native.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoLocalization.native.js","sourceRoot":"","sources":["../src/ExpoLocalization.native.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAgB,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAEpF,MAAM,sBAAsB,GAAG,mBAAmB,CAAC,kBAAkB,CAAC,CAAC;AACvE,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,sBAAsB,CAAC,CAAC;AAEzD,MAAM,UAAU,iBAAiB,CAAC,QAAyB;IACzD,OAAO,OAAO,CAAC,WAAW,CAAC,yBAAyB,EAAE,QAAQ,CAAC,CAAC;AAClE,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,QAAyB;IAC3D,OAAO,OAAO,CAAC,WAAW,CAAC,2BAA2B,EAAE,QAAQ,CAAC,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,YAA0B;IAC3D,OAAO,OAAO,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAC;AAClD,CAAC;AAED,eAAe,sBAAsB,CAAC","sourcesContent":["import { EventEmitter, Subscription, requireNativeModule } from 'expo-modules-core';\n\nconst ExpoLocalizationModule = requireNativeModule('ExpoLocalization');\nconst emitter = new EventEmitter(ExpoLocalizationModule);\n\nexport function addLocaleListener(listener: (event) => void): Subscription {\n return emitter.addListener('onLocaleSettingsChanged', listener);\n}\n\nexport function addCalendarListener(listener: (event) => void): Subscription {\n return emitter.addListener('onCalendarSettingsChanged', listener);\n}\n\nexport function removeSubscription(subscription: Subscription) {\n return emitter.removeSubscription(subscription);\n}\n\nexport default ExpoLocalizationModule;\n"]}

View File

@@ -0,0 +1,188 @@
import { Localization } from './Localization.types';
export * from './Localization.types';
/**
* @hidden
* @deprecated Use Localization.getLocales() instead.
* Three-character ISO 4217 currency code. Returns `null` on web.
*
* @example
* `'USD'`, `'EUR'`, `'CNY'`, `null`
*/
export declare const currency: string | null;
/**
* @hidden
* @deprecated Use Localization.getLocales() instead.
* Decimal separator used for formatting numbers.
*
* @example
* `','`, `'.'`
*/
export declare const decimalSeparator: string;
/**
* @hidden
* @deprecated Use Localization.getLocales() instead.
* Digit grouping separator used when formatting numbers larger than 1000.
*
* @example
* `'.'`, `''`, `','`
*/
export declare const digitGroupingSeparator: string;
/**
* @hidden
* @deprecated Use Localization.getLocales() instead.
* A list of all the supported language ISO codes.
*/
export declare const isoCurrencyCodes: string[];
/**
* @hidden
* @deprecated Use Localization.getLocales() instead.
* Boolean value that indicates whether the system uses the metric system.
* On Android and web, this is inferred from the current region.
*/
export declare const isMetric: boolean;
/**
* @hidden
* @deprecated Use Localization.getLocales() instead.
* Returns if the system's language is written from Right-to-Left.
* This can be used to build features like [bidirectional icons](https://material.io/design/usability/bidirectionality.html).
*
* Returns `false` in Server Side Rendering (SSR) environments.
*/
export declare const isRTL: boolean;
/**
* @deprecated Use [`Localization.getLocales()`](#localizationgetlocales) instead.
* An [IETF BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag),
* consisting of a two-character language code and optional script, region and variant codes.
*
* @example
* `'en'`, `'en-US'`, `'zh-Hans'`, `'zh-Hans-CN'`, `'en-emodeng'`
*/
export declare const locale: string;
/**
* @hidden
* @deprecated Use Localization.getLocales() instead.
* List of all the native languages provided by the user settings.
* These are returned in the order the user defines in their device settings.
*
* @example
* `['en', 'en-US', 'zh-Hans', 'zh-Hans-CN', 'en-emodeng']`
*/
export declare const locales: string[];
/**
* @hidden
* @deprecated Use Localization.getCalendars() instead.
* The current time zone in display format.
* On Web time zone is calculated with Intl.DateTimeFormat().resolvedOptions().timeZone. For a
* better estimation you could use the moment-timezone package but it will add significant bloat to
* your website's bundle size.
*
* @example
* `'America/Los_Angeles'`
*/
export declare const timezone: string;
/**
* @hidden
* @deprecated Use Localization.getLocales() instead.
* The region code for your device that comes from the Region setting under Language & Region on iOS.
* This value is always available on iOS, but might return `null` on Android or web.
*
* @example
* `'US'`, `'NZ'`, `null`
*/
export declare const region: string | null;
/**
* List of user's locales, returned as an array of objects of type `Locale`.
* Guaranteed to contain at least 1 element.
* These are returned in the order the user defines in their device settings.
* On the web currency and measurements systems are not provided, instead returned as null.
* If needed, you can infer them from the current region using a lookup table.
* @example
* ```js
* [{
* "languageTag": "pl-PL",
* "languageCode": "pl",
* "textDirection": "ltr",
* "digitGroupingSeparator": " ",
* "decimalSeparator": ",",
* "measurementSystem": "metric",
* "currencyCode": "PLN",
* "currencySymbol": "zł",
* "regionCode": "PL",
* "temperatureUnit": "celsius"
* }]
* ```
*/
export declare const getLocales: () => import("./Localization.types").Locale[];
/**
* List of user's preferred calendars, returned as an array of objects of type `Calendar`.
* Guaranteed to contain at least 1 element.
* For now always returns a single element, but it's likely to return a user preference list on some platforms in the future.
* @example
* ```js
* [{
* "calendar": "gregory",
* "timeZone": "Europe/Warsaw",
* "uses24hourClock": true,
* "firstWeekday": 1
* }]
* ```
*/
export declare const getCalendars: () => import("./Localization.types").Calendar[];
/**
* A hook providing a list of user's locales, returned as an array of objects of type `Locale`.
* Guaranteed to contain at least 1 element.
* These are returned in the order the user defines in their device settings.
* On the web currency and measurements systems are not provided, instead returned as null.
* If needed, you can infer them from the current region using a lookup table.
* If the OS settings change, the hook will rerender with a new list of locales.
* @example
* ```js
* [{
* "languageTag": "pl-PL",
* "languageCode": "pl",
* "textDirection": "ltr",
* "digitGroupingSeparator": " ",
* "decimalSeparator": ",",
* "measurementSystem": "metric",
* "currencyCode": "PLN",
* "currencySymbol": "zł",
* "regionCode": "PL",
* "temperatureUnit": "celsius"
* }]
* ```
*/
export declare function useLocales(): import("./Localization.types").Locale[];
/**
* A hook providing a list of user's preferred calendars, returned as an array of objects of type `Calendar`.
* Guaranteed to contain at least 1 element.
* For now always returns a single element, but it's likely to return a user preference list on some platforms in the future.
* If the OS settings change, the hook will rerender with a new list of calendars.
* @example
* ```js
* [{
* "calendar": "gregory",
* "timeZone": "Europe/Warsaw",
* "uses24hourClock": true,
* "firstWeekday": 1
* }]
* ```
*/
export declare function useCalendars(): import("./Localization.types").Calendar[];
/**
* @hidden
* Get the latest native values from the device. Locale can be changed on some Android devices
* without resetting the app.
* > On iOS, changing the locale will cause the device to reset meaning the constants will always be
* correct.
*
* @example
* ```ts
* // When the app returns from the background on Android...
*
* const { locale } = await Localization.getLocalizationAsync();
* ```
* @deprecated
* Use Localization.getLocales() or Localization.getCalendars() instead.
*/
export declare function getLocalizationAsync(): Promise<Localization>;
//# sourceMappingURL=Localization.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"Localization.d.ts","sourceRoot":"","sources":["../src/Localization.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,cAAc,sBAAsB,CAAC;AAGrC;;;;;;;GAOG;AACH,eAAO,MAAM,QAAQ,eAA4B,CAAC;AAGlD;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,QAAoC,CAAC;AAGlE;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,QAA0C,CAAC;AAG9E;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,UAAoC,CAAC;AAGlE;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,SAA4B,CAAC;AAGlD;;;;;;;GAOG;AACH,eAAO,MAAM,KAAK,SAAyB,CAAC;AAG5C;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM,QAA0B,CAAC;AAG9C;;;;;;;;GAQG;AACH,eAAO,MAAM,OAAO,UAA2B,CAAC;AAGhD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,QAA4B,CAAC;AAGlD;;;;;;;;GAQG;AACH,eAAO,MAAM,MAAM,eAA0B,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,UAAU,+CAA8B,CAAC;AAEtD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,YAAY,iDAAgC,CAAC;AAE1D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,UAAU,4CAUzB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,8CAU3B;AAGD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,YAAY,CAAC,CAElE"}

View File

@@ -0,0 +1,222 @@
import { useEffect, useReducer, useMemo } from 'react';
import ExpoLocalization, { addCalendarListener, addLocaleListener, removeSubscription, } from './ExpoLocalization';
export * from './Localization.types';
// @needsAudit
/**
* @hidden
* @deprecated Use Localization.getLocales() instead.
* Three-character ISO 4217 currency code. Returns `null` on web.
*
* @example
* `'USD'`, `'EUR'`, `'CNY'`, `null`
*/
export const currency = ExpoLocalization.currency;
// @needsAudit
/**
* @hidden
* @deprecated Use Localization.getLocales() instead.
* Decimal separator used for formatting numbers.
*
* @example
* `','`, `'.'`
*/
export const decimalSeparator = ExpoLocalization.decimalSeparator;
// @needsAudit
/**
* @hidden
* @deprecated Use Localization.getLocales() instead.
* Digit grouping separator used when formatting numbers larger than 1000.
*
* @example
* `'.'`, `''`, `','`
*/
export const digitGroupingSeparator = ExpoLocalization.digitGroupingSeparator;
// @needsAudit
/**
* @hidden
* @deprecated Use Localization.getLocales() instead.
* A list of all the supported language ISO codes.
*/
export const isoCurrencyCodes = ExpoLocalization.isoCurrencyCodes;
// @needsAudit
/**
* @hidden
* @deprecated Use Localization.getLocales() instead.
* Boolean value that indicates whether the system uses the metric system.
* On Android and web, this is inferred from the current region.
*/
export const isMetric = ExpoLocalization.isMetric;
// @needsAudit
/**
* @hidden
* @deprecated Use Localization.getLocales() instead.
* Returns if the system's language is written from Right-to-Left.
* This can be used to build features like [bidirectional icons](https://material.io/design/usability/bidirectionality.html).
*
* Returns `false` in Server Side Rendering (SSR) environments.
*/
export const isRTL = ExpoLocalization.isRTL;
// @needsAudit
/**
* @deprecated Use [`Localization.getLocales()`](#localizationgetlocales) instead.
* An [IETF BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag),
* consisting of a two-character language code and optional script, region and variant codes.
*
* @example
* `'en'`, `'en-US'`, `'zh-Hans'`, `'zh-Hans-CN'`, `'en-emodeng'`
*/
export const locale = ExpoLocalization.locale;
// @needsAudit
/**
* @hidden
* @deprecated Use Localization.getLocales() instead.
* List of all the native languages provided by the user settings.
* These are returned in the order the user defines in their device settings.
*
* @example
* `['en', 'en-US', 'zh-Hans', 'zh-Hans-CN', 'en-emodeng']`
*/
export const locales = ExpoLocalization.locales;
// @needsAudit
/**
* @hidden
* @deprecated Use Localization.getCalendars() instead.
* The current time zone in display format.
* On Web time zone is calculated with Intl.DateTimeFormat().resolvedOptions().timeZone. For a
* better estimation you could use the moment-timezone package but it will add significant bloat to
* your website's bundle size.
*
* @example
* `'America/Los_Angeles'`
*/
export const timezone = ExpoLocalization.timezone;
// @needsAudit
/**
* @hidden
* @deprecated Use Localization.getLocales() instead.
* The region code for your device that comes from the Region setting under Language & Region on iOS.
* This value is always available on iOS, but might return `null` on Android or web.
*
* @example
* `'US'`, `'NZ'`, `null`
*/
export const region = ExpoLocalization.region;
/**
* List of user's locales, returned as an array of objects of type `Locale`.
* Guaranteed to contain at least 1 element.
* These are returned in the order the user defines in their device settings.
* On the web currency and measurements systems are not provided, instead returned as null.
* If needed, you can infer them from the current region using a lookup table.
* @example
* ```js
* [{
* "languageTag": "pl-PL",
* "languageCode": "pl",
* "textDirection": "ltr",
* "digitGroupingSeparator": " ",
* "decimalSeparator": ",",
* "measurementSystem": "metric",
* "currencyCode": "PLN",
* "currencySymbol": "zł",
* "regionCode": "PL",
* "temperatureUnit": "celsius"
* }]
* ```
*/
export const getLocales = ExpoLocalization.getLocales;
/**
* List of user's preferred calendars, returned as an array of objects of type `Calendar`.
* Guaranteed to contain at least 1 element.
* For now always returns a single element, but it's likely to return a user preference list on some platforms in the future.
* @example
* ```js
* [{
* "calendar": "gregory",
* "timeZone": "Europe/Warsaw",
* "uses24hourClock": true,
* "firstWeekday": 1
* }]
* ```
*/
export const getCalendars = ExpoLocalization.getCalendars;
/**
* A hook providing a list of user's locales, returned as an array of objects of type `Locale`.
* Guaranteed to contain at least 1 element.
* These are returned in the order the user defines in their device settings.
* On the web currency and measurements systems are not provided, instead returned as null.
* If needed, you can infer them from the current region using a lookup table.
* If the OS settings change, the hook will rerender with a new list of locales.
* @example
* ```js
* [{
* "languageTag": "pl-PL",
* "languageCode": "pl",
* "textDirection": "ltr",
* "digitGroupingSeparator": " ",
* "decimalSeparator": ",",
* "measurementSystem": "metric",
* "currencyCode": "PLN",
* "currencySymbol": "zł",
* "regionCode": "PL",
* "temperatureUnit": "celsius"
* }]
* ```
*/
export function useLocales() {
const [key, invalidate] = useReducer((k) => k + 1, 0);
const locales = useMemo(() => getLocales(), [key]);
useEffect(() => {
const subscription = addLocaleListener(invalidate);
return () => {
removeSubscription(subscription);
};
}, []);
return locales;
}
/**
* A hook providing a list of user's preferred calendars, returned as an array of objects of type `Calendar`.
* Guaranteed to contain at least 1 element.
* For now always returns a single element, but it's likely to return a user preference list on some platforms in the future.
* If the OS settings change, the hook will rerender with a new list of calendars.
* @example
* ```js
* [{
* "calendar": "gregory",
* "timeZone": "Europe/Warsaw",
* "uses24hourClock": true,
* "firstWeekday": 1
* }]
* ```
*/
export function useCalendars() {
const [key, invalidate] = useReducer((k) => k + 1, 0);
const calendars = useMemo(() => getCalendars(), [key]);
useEffect(() => {
const subscription = addCalendarListener(invalidate);
return () => {
removeSubscription(subscription);
};
}, []);
return calendars;
}
// @needsAudit
/**
* @hidden
* Get the latest native values from the device. Locale can be changed on some Android devices
* without resetting the app.
* > On iOS, changing the locale will cause the device to reset meaning the constants will always be
* correct.
*
* @example
* ```ts
* // When the app returns from the background on Android...
*
* const { locale } = await Localization.getLocalizationAsync();
* ```
* @deprecated
* Use Localization.getLocales() or Localization.getCalendars() instead.
*/
export async function getLocalizationAsync() {
return await ExpoLocalization.getLocalizationAsync();
}
//# sourceMappingURL=Localization.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,210 @@
export type Localization = {
/**
* Three-character ISO 4217 currency code. Returns `null` on web.
* @example
* `'USD'`, `'EUR'`, `'CNY'`, `null`
*/
currency: string | null;
/**
* Decimal separator used for formatting numbers.
* @example
* `','`, `'.'`
*/
decimalSeparator: string;
/**
* Digit grouping separator used when formatting numbers larger than 1000.
* @example
* `'.'`, `''`, `','`
*/
digitGroupingSeparator: string;
/**
* A list of all the supported language ISO codes.
*/
isoCurrencyCodes: string[];
/**
* Boolean value that indicates whether the system uses the metric system.
* On Android and web, this is inferred from the current region.
*/
isMetric: boolean;
/**
* Returns if the system's language is written from Right-to-Left.
* This can be used to build features like [bidirectional icons](https://material.io/design/usability/bidirectionality.html).
*
* Returns `false` in Server Side Rendering (SSR) environments.
*/
isRTL: boolean;
/**
* An [IETF BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag),
* consisting of a two-character language code and optional script, region and variant codes.
* @example
* `'en'`, `'en-US'`, `'zh-Hans'`, `'zh-Hans-CN'`, `'en-emodeng'`
*/
locale: string;
/**
* List of all the native languages provided by the user settings.
* These are returned in the order that the user defined in the device settings.
* @example
* `['en', 'en-US', 'zh-Hans', 'zh-Hans-CN', 'en-emodeng']`
*/
locales: string[];
/**
* The region code for your device that comes from the Region setting under Language & Region on iOS.
* This value is always available on iOS, but might return `null` on Android or web.
* @example
* `'US'`, `'NZ'`, `null`
*/
region: string | null;
/**
* The current time zone in display format.
* On Web time zone is calculated with Intl.DateTimeFormat().resolvedOptions().timeZone. For a
* better estimation you could use the moment-timezone package but it will add significant bloat to
* your website's bundle size.
* @example
* `'America/Los_Angeles'`
*/
timezone: string;
};
export type Locale = {
/**
* An [IETF BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag) with a region code.
* @example
* `'en-US'`, `'es-419'`, `'pl-PL'`.
*/
languageTag: string;
/**
* An [IETF BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag) without the region code.
* @example
* `'en'`, `'es'`, `'pl'`.
*/
languageCode: string | null;
/**
* The region code for your device that comes from the Region setting under Language & Region on iOS, Region settings on Android and is parsed from locale on Web (can be `null` on Web).
*/
regionCode: string | null;
/**
* Currency code for the locale.
* Is `null` on Web, use a table lookup based on region instead.
* @example
* `'USD'`, `'EUR'`, `'PLN'`.
*/
currencyCode: string | null;
/**
* Currency symbol for the locale.
* Is `null` on Web, use a table lookup based on region (if available) instead.
* @example
* `'$'`, `'€'`, `'zł'`.
*/
currencySymbol: string | null;
/**
* Decimal separator used for formatting numbers with fractional parts.
* @example
* `'.'`, `','`.
*/
decimalSeparator: string | null;
/**
* Digit grouping separator used for formatting large numbers.
* @example
* `'.'`, `','`.
*/
digitGroupingSeparator: string | null;
/**
* Text direction for the locale. One of: `'ltr'`, `'rtl'`, but can also be `null` on some browsers without support for the [textInfo](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/textInfo) property in [Intl](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) API.
*/
textDirection: 'ltr' | 'rtl' | null;
/**
* The measurement system used in the locale.
* Is `null` on Web, as user chosen measurement system is not exposed on the web and using locale to determine measurement systems is unreliable.
* Ask for user preferences if possible.
*/
measurementSystem: `metric` | `us` | `uk` | null;
/**
* The temperature unit used in the locale.
* Returns `null` if the region code is unknown.
*/
temperatureUnit: 'celsius' | 'fahrenheit' | null;
};
/**
* An enum mapping days of the week in Gregorian calendar to their index as returned by the `firstWeekday` property.
*/
export declare enum Weekday {
SUNDAY = 1,
MONDAY = 2,
TUESDAY = 3,
WEDNESDAY = 4,
THURSDAY = 5,
FRIDAY = 6,
SATURDAY = 7
}
/**
* The calendar identifier, one of [Unicode calendar types](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar).
* Gregorian calendar is aliased and can be referred to as both `CalendarIdentifier.GREGORIAN` and `CalendarIdentifier.GREGORY`.
*/
export declare enum CalendarIdentifier {
/** Thai Buddhist calendar */
BUDDHIST = "buddhist",
/** Traditional Chinese calendar */
CHINESE = "chinese",
/** Coptic calendar */
COPTIC = "coptic",
/** Traditional Korean calendar */
DANGI = "dangi",
/** Ethiopic calendar, Amete Alem (epoch approx. 5493 B.C.E) */
ETHIOAA = "ethioaa",
/** Ethiopic calendar, Amete Mihret (epoch approx, 8 C.E.) */
ETHIOPIC = "ethiopic",
/** Gregorian calendar */
GREGORY = "gregory",
/** Gregorian calendar (alias) */
GREGORIAN = "gregory",
/** Traditional Hebrew calendar */
HEBREW = "hebrew",
/** Indian calendar */
INDIAN = "indian",
/** Islamic calendar */
ISLAMIC = "islamic",
/** Islamic calendar, tabular (intercalary years [2,5,7,10,13,16,18,21,24,26,29] - civil epoch) */
ISLAMIC_CIVIL = "islamic-civil",
/** Islamic calendar, Saudi Arabia sighting */
ISLAMIC_RGSA = "islamic-rgsa",
/**Islamic calendar, tabular (intercalary years [2,5,7,10,13,16,18,21,24,26,29] - astronomical epoch) */
ISLAMIC_TBLA = "islamic-tbla",
/** Islamic calendar, Umm al-Qura */
ISLAMIC_UMALQURA = "islamic-umalqura",
/** ISO calendar (Gregorian calendar using the ISO 8601 calendar week rules) */
ISO8601 = "iso8601",
/** Japanese imperial calendar */
JAPANESE = "japanese",
/** Persian calendar */
PERSIAN = "persian",
/** Civil (algorithmic) Arabic calendar */
ROC = "roc"
}
export type Calendar = {
/**
* The calendar identifier, one of [Unicode calendar types](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar).
*
* On Android is limited to one of device's [available calendar types](https://developer.android.com/reference/java/util/Calendar#getAvailableCalendarTypes()).
*
* On iOS uses [calendar identifiers](https://developer.apple.com/documentation/foundation/calendar/identifier), but maps them to the corresponding Unicode types, will also never contain `'dangi'` or `'islamic-rgsa'` due to it not being implemented on iOS.
*/
calendar: CalendarIdentifier | null;
/**
* True when current device settings use 24-hour time format.
* Can be null on some browsers that don't support the [hourCycle](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/hourCycle) property in [Intl](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) API.
*/
uses24hourClock: boolean | null;
/**
* The first day of the week. For most calendars Sunday is numbered `1`, with Saturday being number `7`.
* Can be null on some browsers that don't support the [weekInfo](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/weekInfo) property in [Intl](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) API.
* @example
* `1`, `7`.
*/
firstWeekday: Weekday | null;
/**
* Time zone for the calendar. Can be `null` on Web.
* @example
* `'America/Los_Angeles'`, `'Europe/Warsaw'`, `'GMT+1'`.
*/
timeZone: string | null;
};
//# sourceMappingURL=Localization.types.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"Localization.types.d.ts","sourceRoot":"","sources":["../src/Localization.types.ts"],"names":[],"mappings":"AACA,MAAM,MAAM,YAAY,GAAG;IACzB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,sBAAsB,EAAE,MAAM,CAAC;IAC/B;;OAEG;IACH,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B;;;OAGG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;;OAKG;IACH,KAAK,EAAE,OAAO,CAAC;IACf;;;;;OAKG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB;;;;;OAKG;IACH,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;;;;;;;OAOG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG;IACnB;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B;;OAEG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;;OAKG;IACH,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B;;;;;OAKG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC;;;;OAIG;IACH,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC;;OAEG;IACH,aAAa,EAAE,KAAK,GAAG,KAAK,GAAG,IAAI,CAAC;IACpC;;;;OAIG;IACH,iBAAiB,EAAE,QAAQ,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IACjD;;;OAGG;IACH,eAAe,EAAE,SAAS,GAAG,YAAY,GAAG,IAAI,CAAC;CAClD,CAAC;AAEF;;GAEG;AACH,oBAAY,OAAO;IACjB,MAAM,IAAI;IACV,MAAM,IAAI;IACV,OAAO,IAAI;IACX,SAAS,IAAI;IACb,QAAQ,IAAI;IACZ,MAAM,IAAI;IACV,QAAQ,IAAI;CACb;AAED;;;GAGG;AACH,oBAAY,kBAAkB;IAC5B,6BAA6B;IAC7B,QAAQ,aAAa;IACrB,mCAAmC;IACnC,OAAO,YAAY;IACnB,sBAAsB;IACtB,MAAM,WAAW;IACjB,kCAAkC;IAClC,KAAK,UAAU;IACf,+DAA+D;IAC/D,OAAO,YAAY;IACnB,6DAA6D;IAC7D,QAAQ,aAAa;IACrB,yBAAyB;IACzB,OAAO,YAAY;IACnB,iCAAiC;IACjC,SAAS,YAAY;IACrB,kCAAkC;IAClC,MAAM,WAAW;IACjB,sBAAsB;IACtB,MAAM,WAAW;IACjB,uBAAuB;IACvB,OAAO,YAAY;IACnB,kGAAkG;IAClG,aAAa,kBAAkB;IAC/B,8CAA8C;IAC9C,YAAY,iBAAiB;IAC7B,wGAAwG;IACxG,YAAY,iBAAiB;IAC7B,oCAAoC;IACpC,gBAAgB,qBAAqB;IACrC,+EAA+E;IAC/E,OAAO,YAAY;IACnB,iCAAiC;IACjC,QAAQ,aAAa;IACrB,uBAAuB;IACvB,OAAO,YAAY;IACnB,0CAA0C;IAC1C,GAAG,QAAQ;CACZ;AAED,MAAM,MAAM,QAAQ,GAAG;IACrB;;;;;;OAMG;IACH,QAAQ,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACpC;;;OAGG;IACH,eAAe,EAAE,OAAO,GAAG,IAAI,CAAC;IAChC;;;;;OAKG;IACH,YAAY,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,CAAC"}

View File

@@ -0,0 +1,59 @@
/**
* An enum mapping days of the week in Gregorian calendar to their index as returned by the `firstWeekday` property.
*/
export var Weekday;
(function (Weekday) {
Weekday[Weekday["SUNDAY"] = 1] = "SUNDAY";
Weekday[Weekday["MONDAY"] = 2] = "MONDAY";
Weekday[Weekday["TUESDAY"] = 3] = "TUESDAY";
Weekday[Weekday["WEDNESDAY"] = 4] = "WEDNESDAY";
Weekday[Weekday["THURSDAY"] = 5] = "THURSDAY";
Weekday[Weekday["FRIDAY"] = 6] = "FRIDAY";
Weekday[Weekday["SATURDAY"] = 7] = "SATURDAY";
})(Weekday || (Weekday = {}));
/**
* The calendar identifier, one of [Unicode calendar types](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar).
* Gregorian calendar is aliased and can be referred to as both `CalendarIdentifier.GREGORIAN` and `CalendarIdentifier.GREGORY`.
*/
export var CalendarIdentifier;
(function (CalendarIdentifier) {
/** Thai Buddhist calendar */
CalendarIdentifier["BUDDHIST"] = "buddhist";
/** Traditional Chinese calendar */
CalendarIdentifier["CHINESE"] = "chinese";
/** Coptic calendar */
CalendarIdentifier["COPTIC"] = "coptic";
/** Traditional Korean calendar */
CalendarIdentifier["DANGI"] = "dangi";
/** Ethiopic calendar, Amete Alem (epoch approx. 5493 B.C.E) */
CalendarIdentifier["ETHIOAA"] = "ethioaa";
/** Ethiopic calendar, Amete Mihret (epoch approx, 8 C.E.) */
CalendarIdentifier["ETHIOPIC"] = "ethiopic";
/** Gregorian calendar */
CalendarIdentifier["GREGORY"] = "gregory";
/** Gregorian calendar (alias) */
CalendarIdentifier["GREGORIAN"] = "gregory";
/** Traditional Hebrew calendar */
CalendarIdentifier["HEBREW"] = "hebrew";
/** Indian calendar */
CalendarIdentifier["INDIAN"] = "indian";
/** Islamic calendar */
CalendarIdentifier["ISLAMIC"] = "islamic";
/** Islamic calendar, tabular (intercalary years [2,5,7,10,13,16,18,21,24,26,29] - civil epoch) */
CalendarIdentifier["ISLAMIC_CIVIL"] = "islamic-civil";
/** Islamic calendar, Saudi Arabia sighting */
CalendarIdentifier["ISLAMIC_RGSA"] = "islamic-rgsa";
/**Islamic calendar, tabular (intercalary years [2,5,7,10,13,16,18,21,24,26,29] - astronomical epoch) */
CalendarIdentifier["ISLAMIC_TBLA"] = "islamic-tbla";
/** Islamic calendar, Umm al-Qura */
CalendarIdentifier["ISLAMIC_UMALQURA"] = "islamic-umalqura";
/** ISO calendar (Gregorian calendar using the ISO 8601 calendar week rules) */
CalendarIdentifier["ISO8601"] = "iso8601";
/** Japanese imperial calendar */
CalendarIdentifier["JAPANESE"] = "japanese";
/** Persian calendar */
CalendarIdentifier["PERSIAN"] = "persian";
/** Civil (algorithmic) Arabic calendar */
CalendarIdentifier["ROC"] = "roc";
})(CalendarIdentifier || (CalendarIdentifier = {}));
//# sourceMappingURL=Localization.types.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,9 @@
{
"platforms": ["apple", "android"],
"apple": {
"modules": ["LocalizationModule"]
},
"android": {
"modules": ["expo.modules.localization.LocalizationModule"]
}
}

View File

@@ -0,0 +1,34 @@
require 'json'
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
Pod::Spec.new do |s|
s.name = 'ExpoLocalization'
s.version = package['version']
s.summary = package['description']
s.description = package['description']
s.license = package['license']
s.author = package['author']
s.homepage = package['homepage']
s.platforms = { :ios => '13.4', :tvos => '13.4' }
s.swift_version = '5.4'
s.source = { git: 'https://github.com/expo/expo.git' }
s.static_framework = true
s.dependency 'ExpoModulesCore'
# Swift/Objective-C compatibility
s.pod_target_xcconfig = {
'DEFINES_MODULE' => 'YES',
'SWIFT_COMPILATION_MODE' => 'wholemodule'
}
s.resource_bundles = {'ExpoLocalization_privacy' => ['PrivacyInfo.xcprivacy']}
if !$ExpoUseSources&.include?(package['name']) && ENV['EXPO_USE_SOURCE'].to_i == 0 && File.exist?("#{s.name}.xcframework") && Gem::Version.new(Pod::VERSION) >= Gem::Version.new('1.10.0')
s.source_files = "**/*.h"
s.vendored_frameworks = "#{s.name}.xcframework"
else
s.source_files = "**/*.{h,m,swift}"
end
end

View File

@@ -0,0 +1,238 @@
// Copyright 2021-present 650 Industries. All rights reserved.
import Foundation
import ExpoModulesCore
let LOCALE_SETTINGS_CHANGED = "onLocaleSettingsChanged"
let CALENDAR_SETTINGS_CHANGED = "onCalendarSettingsChanged"
public class LocalizationModule: Module {
public func definition() -> ModuleDefinition {
Name("ExpoLocalization")
Constants {
return Self.getCurrentLocalization()
}
AsyncFunction("getLocalizationAsync") {
return Self.getCurrentLocalization()
}
Function("getLocales") {
return Self.getLocales()
}
Function("getCalendars") {
return Self.getCalendars()
}
OnCreate {
if let forceRTL = Bundle.main.object(forInfoDictionaryKey: "ExpoLocalization_forcesRTL") as? Bool {
self.setRTLPreferences(true, forceRTL)
} else {
if let enableRTL = Bundle.main.object(forInfoDictionaryKey: "ExpoLocalization_supportsRTL") as? Bool {
self.setRTLPreferences(enableRTL, false)
}
}
}
Events(LOCALE_SETTINGS_CHANGED, CALENDAR_SETTINGS_CHANGED)
OnStartObserving {
NotificationCenter.default.addObserver(
self,
selector: #selector(LocalizationModule.localeChanged),
name: NSLocale.currentLocaleDidChangeNotification, // swiftlint:disable:this legacy_objc_type
object: nil
)
}
OnStopObserving {
NotificationCenter.default.removeObserver(
self,
name: NSLocale.currentLocaleDidChangeNotification, // swiftlint:disable:this legacy_objc_type
object: nil
)
}
}
func isRTLPreferredForCurrentLocale() -> Bool {
// swiftlint:disable:next legacy_objc_type
return NSLocale.characterDirection(forLanguage: NSLocale.preferredLanguages.first ?? "en-US") == NSLocale.LanguageDirection.rightToLeft
}
func setRTLPreferences(_ supportsRTL: Bool, _ forceRTL: Bool) {
// These keys are used by React Native here: https://github.com/facebook/react-native/blob/main/React/Modules/RCTI18nUtil.m
// We set them before React loads to ensure it gets rendered correctly the first time the app is opened.
// On iOS we need to set both forceRTL and allowRTL so apps don't have to include localization strings.
// Uses required reason API based on the following reason: CA92.1
if forceRTL {
UserDefaults.standard.set(true, forKey: "RCTI18nUtil_allowRTL")
UserDefaults.standard.set(true, forKey: "RCTI18nUtil_forceRTL")
} else {
UserDefaults.standard.set(supportsRTL, forKey: "RCTI18nUtil_allowRTL")
UserDefaults.standard.set(supportsRTL ? isRTLPreferredForCurrentLocale() : false, forKey: "RCTI18nUtil_forceRTL")
}
UserDefaults.standard.synchronize()
}
// If the application isn't manually localized for the device language then the
// native `Locale.current` will fallback on using English US
// [cite](https://stackoverflow.com/questions/48136456/locale-current-reporting-wrong-language-on-device).
// This method will attempt to return the locale that the device is using regardless of the app,
// providing better parity across platforms.
static func getLocale() -> Locale {
guard let preferredIdentifier = Locale.preferredLanguages.first else {
return Locale.current
}
return Locale(identifier: preferredIdentifier)
}
/**
Maps ios unique identifiers to [BCP 47 calendar types]
(https://github.com/unicode-org/cldr/blob/main/common/bcp47/calendar.xml)
*/
static func getUnicodeCalendarIdentifier(calendar: Calendar) -> String {
switch calendar.identifier {
case .buddhist:
return "buddhist"
case .chinese:
return "chinese"
case .coptic:
return "coptic"
case .ethiopicAmeteAlem:
return "ethioaa"
case .ethiopicAmeteMihret:
return "ethiopic"
case .gregorian:
return "gregory"
case .hebrew:
return "hebrew"
case .indian:
return "indian"
case .islamic:
return "islamic"
case .islamicCivil:
return "islamic-civil"
case .islamicTabular:
return "islamic-tbla"
case .islamicUmmAlQura:
return "islamic-umalqura"
case .japanese:
return "japanese"
case .persian:
return "persian"
case .republicOfChina:
return "roc"
case .iso8601:
return "iso8601"
}
}
static func getMeasurementSystemForLocale(_ locale: Locale) -> String {
if #available(iOS 16, tvOS 16, *) {
let measurementSystems = [
Locale.MeasurementSystem.us: "us",
Locale.MeasurementSystem.uk: "uk",
Locale.MeasurementSystem.metric: "metric"
]
return measurementSystems[locale.measurementSystem] ?? "metric"
}
return locale.usesMetricSystem ? "metric" : "us"
}
static func getLocales() -> [[String: Any?]] {
let userSettingsLocale = Locale.current
return (Locale.preferredLanguages.isEmpty ? [Locale.current.identifier] : Locale.preferredLanguages)
.map { languageTag -> [String: Any?] in
let languageLocale = Locale.init(identifier: languageTag)
if #available(iOS 16, tvOS 16, *) {
return [
"languageTag": languageTag,
"languageCode": languageLocale.language.languageCode?.identifier,
"regionCode": languageLocale.region?.identifier,
"textDirection": languageLocale.language.characterDirection == .rightToLeft ? "rtl" : "ltr",
"decimalSeparator": userSettingsLocale.decimalSeparator,
"digitGroupingSeparator": userSettingsLocale.groupingSeparator,
"measurementSystem": getMeasurementSystemForLocale(userSettingsLocale),
"currencyCode": languageLocale.currencyCode,
"currencySymbol": languageLocale.currencySymbol,
"temperatureUnit": getTemperatureUnit()
]
}
return [
"languageTag": languageTag,
"languageCode": languageLocale.languageCode,
"regionCode": languageLocale.regionCode,
"textDirection": Locale.characterDirection(forLanguage: languageTag) == .rightToLeft ? "rtl" : "ltr",
"decimalSeparator": userSettingsLocale.decimalSeparator,
"digitGroupingSeparator": userSettingsLocale.groupingSeparator,
"measurementSystem": getMeasurementSystemForLocale(userSettingsLocale),
"currencyCode": languageLocale.currencyCode,
"currencySymbol": languageLocale.currencySymbol,
"temperatureUnit": getTemperatureUnit()
]
}
}
@objc
private func localeChanged() {
// we send both events since on iOS it means both calendar and locale needs an update
sendEvent(LOCALE_SETTINGS_CHANGED)
sendEvent(CALENDAR_SETTINGS_CHANGED)
}
static func getTemperatureUnit() -> String? {
let formatter = MeasurementFormatter()
formatter.locale = Locale.current
let temperature = Measurement(value: 0, unit: UnitTemperature.celsius)
let formatted = formatter.string(from: temperature)
guard let unitCharacter = formatted.last else {
return nil
}
return unitCharacter == "F" ? "fahrenheit" : "celsius"
}
// https://stackoverflow.com/a/28183182
static func uses24HourClock() -> Bool {
let dateFormat = DateFormatter.dateFormat(fromTemplate: "j", options: 0, locale: Locale.current)!
return dateFormat.firstIndex(of: "a") == nil
}
static func getCalendars() -> [[String: Any?]] {
var calendar = Locale.current.calendar
return [
[
"calendar": getUnicodeCalendarIdentifier(calendar: calendar),
"timeZone": "\(calendar.timeZone.identifier)",
"uses24hourClock": uses24HourClock(),
"firstWeekday": calendar.firstWeekday
]
]
}
static func getCurrentLocalization() -> [String: Any?] {
let locale = getLocale()
let languageCode = locale.languageCode ?? "en"
var languageIds = Locale.preferredLanguages
if languageIds.isEmpty {
languageIds.append("en-US")
}
return [
"currency": locale.currencyCode ?? "USD",
"decimalSeparator": locale.decimalSeparator ?? ".",
"digitGroupingSeparator": locale.groupingSeparator ?? ",",
"isoCurrencyCodes": Locale.isoCurrencyCodes,
"isMetric": locale.usesMetricSystem,
"isRTL": Locale.characterDirection(forLanguage: languageCode) == .rightToLeft,
"locale": languageIds.first,
"locales": languageIds,
"region": locale.regionCode ?? "US",
"timezone": TimeZone.current.identifier
]
}
}

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,51 @@
{
"name": "expo-localization",
"version": "15.0.3",
"description": "Provides an interface for native user localization information.",
"main": "build/Localization.js",
"types": "build/Localization.d.ts",
"sideEffects": false,
"scripts": {
"build": "expo-module build",
"clean": "expo-module clean",
"lint": "expo-module lint",
"test": "expo-module test",
"prepare": "expo-module prepare",
"prepublishOnly": "expo-module prepublishOnly",
"expo-module": "expo-module"
},
"keywords": [
"react-native",
"expo",
"localization",
"locales",
"l10n"
],
"repository": {
"type": "git",
"url": "https://github.com/expo/expo.git",
"directory": "packages/expo-localization"
},
"bugs": {
"url": "https://github.com/expo/expo/issues"
},
"contributors": [
"Evan Bacon <bacon@expo.io> (https://github.com/evanbacon)"
],
"author": "650 Industries, Inc.",
"license": "MIT",
"homepage": "https://docs.expo.dev/versions/latest/sdk/localization/",
"jest": {
"preset": "expo-module-scripts"
},
"dependencies": {
"rtl-detect": "^1.0.2"
},
"devDependencies": {
"expo-module-scripts": "^3.0.0"
},
"peerDependencies": {
"expo": "*"
},
"gitHead": "7995f30a8d2af4836f3aef9fe322e9403fc23d34"
}

View File

@@ -0,0 +1,8 @@
import { ExpoConfig } from '@expo/config-types';
type ConfigPluginProps = {
supportsRTL?: boolean;
forcesRTL?: boolean;
allowDynamicLocaleChangesAndroid?: boolean;
};
declare function withExpoLocalization(config: ExpoConfig, data?: ConfigPluginProps): ExpoConfig;
export default withExpoLocalization;

View File

@@ -0,0 +1,63 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const Manifest_1 = require("@expo/config-plugins/build/android/Manifest");
const config_plugins_1 = require("expo/config-plugins");
function withExpoLocalizationIos(config, data) {
const mergedConfig = { ...config.extra, ...data };
if (mergedConfig?.supportsRTL == null && mergedConfig?.forcesRTL == null)
return config;
if (!config.ios)
config.ios = {};
if (!config.ios.infoPlist)
config.ios.infoPlist = {};
if (mergedConfig?.supportsRTL != null) {
config.ios.infoPlist.ExpoLocalization_supportsRTL = mergedConfig?.supportsRTL;
}
if (mergedConfig?.forcesRTL != null) {
config.ios.infoPlist.ExpoLocalization_forcesRTL = mergedConfig?.forcesRTL;
}
return config;
}
function withExpoLocalizationAndroid(config, data) {
if (data.allowDynamicLocaleChangesAndroid) {
config = (0, config_plugins_1.withAndroidManifest)(config, (config) => {
const mainActivity = (0, Manifest_1.getMainActivityOrThrow)(config.modResults);
if (!mainActivity.$['android:configChanges']?.includes('locale')) {
mainActivity.$['android:configChanges'] += '|locale';
}
if (!mainActivity.$['android:configChanges']?.includes('layoutDirection')) {
mainActivity.$['android:configChanges'] += '|layoutDirection';
}
return config;
});
}
return (0, config_plugins_1.withStringsXml)(config, (config) => {
const mergedConfig = { ...config.extra, ...data };
if (mergedConfig?.supportsRTL != null) {
config.modResults = config_plugins_1.AndroidConfig.Strings.setStringItem([
{
$: { name: 'ExpoLocalization_supportsRTL', translatable: 'false' },
_: String(mergedConfig?.supportsRTL ?? 'unset'),
},
], config.modResults);
}
if (mergedConfig?.forcesRTL != null) {
config.modResults = config_plugins_1.AndroidConfig.Strings.setStringItem([
{
$: { name: 'ExpoLocalization_forcesRTL', translatable: 'false' },
_: String(mergedConfig?.forcesRTL ?? 'unset'),
},
], config.modResults);
}
return config;
});
}
function withExpoLocalization(config, data = {
allowDynamicLocaleChangesAndroid: true,
}) {
return (0, config_plugins_1.withPlugins)(config, [
[withExpoLocalizationIos, data],
[withExpoLocalizationAndroid, data],
]);
}
exports.default = withExpoLocalization;

View File

@@ -0,0 +1,83 @@
import { getMainActivityOrThrow } from '@expo/config-plugins/build/android/Manifest';
import { ExpoConfig } from '@expo/config-types';
import {
AndroidConfig,
withAndroidManifest,
withPlugins,
withStringsXml,
} from 'expo/config-plugins';
type ConfigPluginProps = {
supportsRTL?: boolean;
forcesRTL?: boolean;
allowDynamicLocaleChangesAndroid?: boolean;
};
function withExpoLocalizationIos(config: ExpoConfig, data: ConfigPluginProps) {
const mergedConfig = { ...config.extra, ...data };
if (mergedConfig?.supportsRTL == null && mergedConfig?.forcesRTL == null) return config;
if (!config.ios) config.ios = {};
if (!config.ios.infoPlist) config.ios.infoPlist = {};
if (mergedConfig?.supportsRTL != null) {
config.ios.infoPlist.ExpoLocalization_supportsRTL = mergedConfig?.supportsRTL;
}
if (mergedConfig?.forcesRTL != null) {
config.ios.infoPlist.ExpoLocalization_forcesRTL = mergedConfig?.forcesRTL;
}
return config;
}
function withExpoLocalizationAndroid(config: ExpoConfig, data: ConfigPluginProps) {
if (data.allowDynamicLocaleChangesAndroid) {
config = withAndroidManifest(config, (config) => {
const mainActivity = getMainActivityOrThrow(config.modResults);
if (!mainActivity.$['android:configChanges']?.includes('locale')) {
mainActivity.$['android:configChanges'] += '|locale';
}
if (!mainActivity.$['android:configChanges']?.includes('layoutDirection')) {
mainActivity.$['android:configChanges'] += '|layoutDirection';
}
return config;
});
}
return withStringsXml(config, (config) => {
const mergedConfig = { ...config.extra, ...data };
if (mergedConfig?.supportsRTL != null) {
config.modResults = AndroidConfig.Strings.setStringItem(
[
{
$: { name: 'ExpoLocalization_supportsRTL', translatable: 'false' },
_: String(mergedConfig?.supportsRTL ?? 'unset'),
},
],
config.modResults
);
}
if (mergedConfig?.forcesRTL != null) {
config.modResults = AndroidConfig.Strings.setStringItem(
[
{
$: { name: 'ExpoLocalization_forcesRTL', translatable: 'false' },
_: String(mergedConfig?.forcesRTL ?? 'unset'),
},
],
config.modResults
);
}
return config;
});
}
function withExpoLocalization(
config: ExpoConfig,
data: ConfigPluginProps = {
allowDynamicLocaleChangesAndroid: true,
}
) {
return withPlugins(config, [
[withExpoLocalizationIos, data],
[withExpoLocalizationAndroid, data],
]);
}
export default withExpoLocalization;

View File

@@ -0,0 +1,9 @@
{
"extends": "expo-module-scripts/tsconfig.plugin",
"compilerOptions": {
"outDir": "build",
"rootDir": "src"
},
"include": ["./src"],
"exclude": ["**/__mocks__/*", "**/__tests__/*"]
}

View File

@@ -0,0 +1,18 @@
import { EventEmitter, Subscription, requireNativeModule } from 'expo-modules-core';
const ExpoLocalizationModule = requireNativeModule('ExpoLocalization');
const emitter = new EventEmitter(ExpoLocalizationModule);
export function addLocaleListener(listener: (event) => void): Subscription {
return emitter.addListener('onLocaleSettingsChanged', listener);
}
export function addCalendarListener(listener: (event) => void): Subscription {
return emitter.addListener('onCalendarSettingsChanged', listener);
}
export function removeSubscription(subscription: Subscription) {
return emitter.removeSubscription(subscription);
}
export default ExpoLocalizationModule;

View File

@@ -0,0 +1,203 @@
/* eslint-env browser */
import { Platform, Subscription } from 'expo-modules-core';
import * as rtlDetect from 'rtl-detect';
import { Localization, Calendar, Locale, CalendarIdentifier } from './Localization.types';
const getNavigatorLocales = () => {
return Platform.isDOMAvailable ? navigator.languages || [navigator.language] : [];
};
type ExtendedLocale = Intl.Locale &
// typescript definitions for navigator language don't include some modern Intl properties
Partial<{
textInfo: { direction: 'ltr' | 'rtl' };
timeZones: string[];
weekInfo: { firstDay: number };
hourCycles: string[];
timeZone: string;
calendars: string[];
}>;
const WEB_LANGUAGE_CHANGE_EVENT = 'languagechange';
// https://wisevoter.com/country-rankings/countries-that-use-fahrenheit/
const USES_FAHRENHEIT = [
'AG',
'BZ',
'VG',
'FM',
'MH',
'MS',
'KN',
'BS',
'CY',
'TC',
'US',
'LR',
'PW',
'KY',
];
export function addLocaleListener(listener: (event) => void): Subscription {
addEventListener(WEB_LANGUAGE_CHANGE_EVENT, listener);
return {
remove: () => removeEventListener(WEB_LANGUAGE_CHANGE_EVENT, listener),
};
}
export function addCalendarListener(listener: (event) => void): Subscription {
addEventListener(WEB_LANGUAGE_CHANGE_EVENT, listener);
return {
remove: () => removeEventListener(WEB_LANGUAGE_CHANGE_EVENT, listener),
};
}
export function removeSubscription(subscription: Subscription) {
subscription.remove();
}
export default {
get currency(): string | null {
// TODO: Add support
return null;
},
get decimalSeparator(): string {
return (1.1).toLocaleString().substring(1, 2);
},
get digitGroupingSeparator(): string {
const value = (1000).toLocaleString();
return value.length === 5 ? value.substring(1, 2) : '';
},
get isRTL(): boolean {
return rtlDetect.isRtlLang(this.locale) ?? false;
},
get isMetric(): boolean {
const { region } = this;
switch (region) {
case 'US': // USA
case 'LR': // Liberia
case 'MM': // Myanmar
return false;
}
return true;
},
get locale(): string {
if (!Platform.isDOMAvailable) {
return '';
}
const locale =
navigator.language ||
navigator['systemLanguage'] ||
navigator['browserLanguage'] ||
navigator['userLanguage'] ||
this.locales[0];
return locale;
},
get locales(): string[] {
if (!Platform.isDOMAvailable) {
return [];
}
const { languages = [] } = navigator;
return Array.from(languages);
},
get timezone(): string {
const defaultTimeZone = 'Etc/UTC';
if (typeof Intl === 'undefined') {
return defaultTimeZone;
}
return Intl.DateTimeFormat().resolvedOptions().timeZone || defaultTimeZone;
},
get isoCurrencyCodes(): string[] {
// TODO(Bacon): Add this - very low priority
return [];
},
get region(): string | null {
// There is no way to obtain the current region, as is possible on native.
// Instead, use the country-code from the locale when possible (e.g. "en-US").
const { locale } = this;
const [, ...suffixes] = typeof locale === 'string' ? locale.split('-') : [];
for (const suffix of suffixes) {
if (suffix.length === 2) {
return suffix.toUpperCase();
}
}
return null;
},
getLocales(): Locale[] {
const locales = getNavigatorLocales();
return locales?.map((languageTag) => {
// TextInfo is an experimental API that is not available in all browsers.
// We might want to consider using a locale lookup table instead.
const locale =
typeof Intl !== 'undefined'
? (new Intl.Locale(languageTag) as unknown as ExtendedLocale)
: { region: null, textInfo: null, language: null };
const { region, textInfo, language } = locale;
// Properties added only for compatibility with native, use `toLocaleString` instead.
const digitGroupingSeparator =
Array.from((10000).toLocaleString(languageTag)).filter((c) => c > '9' || c < '0')[0] ||
null; // using 1e5 instead of 1e4 since for some locales (like pl-PL) 1e4 does not use digit grouping
const decimalSeparator = (1.1).toLocaleString(languageTag).substring(1, 2);
const temperatureUnit = region ? regionToTemperatureUnit(region) : null;
return {
languageTag,
languageCode: language || languageTag.split('-')[0] || 'en',
textDirection: (textInfo?.direction as 'ltr' | 'rtl') || null,
digitGroupingSeparator,
decimalSeparator,
measurementSystem: null,
currencyCode: null,
currencySymbol: null,
regionCode: region || null,
temperatureUnit,
};
});
},
getCalendars(): Calendar[] {
const locale = ((typeof Intl !== 'undefined'
? Intl.DateTimeFormat().resolvedOptions()
: null) ?? null) as unknown as null | ExtendedLocale;
return [
{
calendar: ((locale?.calendar || locale?.calendars?.[0]) as CalendarIdentifier) || null,
timeZone: locale?.timeZone || locale?.timeZones?.[0] || null,
uses24hourClock: (locale?.hourCycle || locale?.hourCycles?.[0])?.startsWith('h2') ?? null, //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/hourCycle
firstWeekday: locale?.weekInfo?.firstDay || null,
},
];
},
async getLocalizationAsync(): Promise<Omit<Localization, 'getCalendars' | 'getLocales'>> {
const {
currency,
decimalSeparator,
digitGroupingSeparator,
isoCurrencyCodes,
isMetric,
isRTL,
locale,
locales,
region,
timezone,
} = this;
return {
currency,
decimalSeparator,
digitGroupingSeparator,
isoCurrencyCodes,
isMetric,
isRTL,
locale,
locales,
region,
timezone,
};
},
};
function regionToTemperatureUnit(region: string) {
return USES_FAHRENHEIT.includes(region) ? 'fahrenheit' : 'celsius';
}

View File

@@ -0,0 +1,242 @@
import { useEffect, useReducer, useMemo } from 'react';
import ExpoLocalization, {
addCalendarListener,
addLocaleListener,
removeSubscription,
} from './ExpoLocalization';
import { Localization } from './Localization.types';
export * from './Localization.types';
// @needsAudit
/**
* @hidden
* @deprecated Use Localization.getLocales() instead.
* Three-character ISO 4217 currency code. Returns `null` on web.
*
* @example
* `'USD'`, `'EUR'`, `'CNY'`, `null`
*/
export const currency = ExpoLocalization.currency;
// @needsAudit
/**
* @hidden
* @deprecated Use Localization.getLocales() instead.
* Decimal separator used for formatting numbers.
*
* @example
* `','`, `'.'`
*/
export const decimalSeparator = ExpoLocalization.decimalSeparator;
// @needsAudit
/**
* @hidden
* @deprecated Use Localization.getLocales() instead.
* Digit grouping separator used when formatting numbers larger than 1000.
*
* @example
* `'.'`, `''`, `','`
*/
export const digitGroupingSeparator = ExpoLocalization.digitGroupingSeparator;
// @needsAudit
/**
* @hidden
* @deprecated Use Localization.getLocales() instead.
* A list of all the supported language ISO codes.
*/
export const isoCurrencyCodes = ExpoLocalization.isoCurrencyCodes;
// @needsAudit
/**
* @hidden
* @deprecated Use Localization.getLocales() instead.
* Boolean value that indicates whether the system uses the metric system.
* On Android and web, this is inferred from the current region.
*/
export const isMetric = ExpoLocalization.isMetric;
// @needsAudit
/**
* @hidden
* @deprecated Use Localization.getLocales() instead.
* Returns if the system's language is written from Right-to-Left.
* This can be used to build features like [bidirectional icons](https://material.io/design/usability/bidirectionality.html).
*
* Returns `false` in Server Side Rendering (SSR) environments.
*/
export const isRTL = ExpoLocalization.isRTL;
// @needsAudit
/**
* @deprecated Use [`Localization.getLocales()`](#localizationgetlocales) instead.
* An [IETF BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag),
* consisting of a two-character language code and optional script, region and variant codes.
*
* @example
* `'en'`, `'en-US'`, `'zh-Hans'`, `'zh-Hans-CN'`, `'en-emodeng'`
*/
export const locale = ExpoLocalization.locale;
// @needsAudit
/**
* @hidden
* @deprecated Use Localization.getLocales() instead.
* List of all the native languages provided by the user settings.
* These are returned in the order the user defines in their device settings.
*
* @example
* `['en', 'en-US', 'zh-Hans', 'zh-Hans-CN', 'en-emodeng']`
*/
export const locales = ExpoLocalization.locales;
// @needsAudit
/**
* @hidden
* @deprecated Use Localization.getCalendars() instead.
* The current time zone in display format.
* On Web time zone is calculated with Intl.DateTimeFormat().resolvedOptions().timeZone. For a
* better estimation you could use the moment-timezone package but it will add significant bloat to
* your website's bundle size.
*
* @example
* `'America/Los_Angeles'`
*/
export const timezone = ExpoLocalization.timezone;
// @needsAudit
/**
* @hidden
* @deprecated Use Localization.getLocales() instead.
* The region code for your device that comes from the Region setting under Language & Region on iOS.
* This value is always available on iOS, but might return `null` on Android or web.
*
* @example
* `'US'`, `'NZ'`, `null`
*/
export const region = ExpoLocalization.region;
/**
* List of user's locales, returned as an array of objects of type `Locale`.
* Guaranteed to contain at least 1 element.
* These are returned in the order the user defines in their device settings.
* On the web currency and measurements systems are not provided, instead returned as null.
* If needed, you can infer them from the current region using a lookup table.
* @example
* ```js
* [{
* "languageTag": "pl-PL",
* "languageCode": "pl",
* "textDirection": "ltr",
* "digitGroupingSeparator": " ",
* "decimalSeparator": ",",
* "measurementSystem": "metric",
* "currencyCode": "PLN",
* "currencySymbol": "zł",
* "regionCode": "PL",
* "temperatureUnit": "celsius"
* }]
* ```
*/
export const getLocales = ExpoLocalization.getLocales;
/**
* List of user's preferred calendars, returned as an array of objects of type `Calendar`.
* Guaranteed to contain at least 1 element.
* For now always returns a single element, but it's likely to return a user preference list on some platforms in the future.
* @example
* ```js
* [{
* "calendar": "gregory",
* "timeZone": "Europe/Warsaw",
* "uses24hourClock": true,
* "firstWeekday": 1
* }]
* ```
*/
export const getCalendars = ExpoLocalization.getCalendars;
/**
* A hook providing a list of user's locales, returned as an array of objects of type `Locale`.
* Guaranteed to contain at least 1 element.
* These are returned in the order the user defines in their device settings.
* On the web currency and measurements systems are not provided, instead returned as null.
* If needed, you can infer them from the current region using a lookup table.
* If the OS settings change, the hook will rerender with a new list of locales.
* @example
* ```js
* [{
* "languageTag": "pl-PL",
* "languageCode": "pl",
* "textDirection": "ltr",
* "digitGroupingSeparator": " ",
* "decimalSeparator": ",",
* "measurementSystem": "metric",
* "currencyCode": "PLN",
* "currencySymbol": "zł",
* "regionCode": "PL",
* "temperatureUnit": "celsius"
* }]
* ```
*/
export function useLocales() {
const [key, invalidate] = useReducer((k) => k + 1, 0);
const locales = useMemo(() => getLocales(), [key]);
useEffect(() => {
const subscription = addLocaleListener(invalidate);
return () => {
removeSubscription(subscription);
};
}, []);
return locales;
}
/**
* A hook providing a list of user's preferred calendars, returned as an array of objects of type `Calendar`.
* Guaranteed to contain at least 1 element.
* For now always returns a single element, but it's likely to return a user preference list on some platforms in the future.
* If the OS settings change, the hook will rerender with a new list of calendars.
* @example
* ```js
* [{
* "calendar": "gregory",
* "timeZone": "Europe/Warsaw",
* "uses24hourClock": true,
* "firstWeekday": 1
* }]
* ```
*/
export function useCalendars() {
const [key, invalidate] = useReducer((k) => k + 1, 0);
const calendars = useMemo(() => getCalendars(), [key]);
useEffect(() => {
const subscription = addCalendarListener(invalidate);
return () => {
removeSubscription(subscription);
};
}, []);
return calendars;
}
// @needsAudit
/**
* @hidden
* Get the latest native values from the device. Locale can be changed on some Android devices
* without resetting the app.
* > On iOS, changing the locale will cause the device to reset meaning the constants will always be
* correct.
*
* @example
* ```ts
* // When the app returns from the background on Android...
*
* const { locale } = await Localization.getLocalizationAsync();
* ```
* @deprecated
* Use Localization.getLocales() or Localization.getCalendars() instead.
*/
export async function getLocalizationAsync(): Promise<Localization> {
return await ExpoLocalization.getLocalizationAsync();
}

View File

@@ -0,0 +1,214 @@
// @needsAudit
export type Localization = {
/**
* Three-character ISO 4217 currency code. Returns `null` on web.
* @example
* `'USD'`, `'EUR'`, `'CNY'`, `null`
*/
currency: string | null;
/**
* Decimal separator used for formatting numbers.
* @example
* `','`, `'.'`
*/
decimalSeparator: string;
/**
* Digit grouping separator used when formatting numbers larger than 1000.
* @example
* `'.'`, `''`, `','`
*/
digitGroupingSeparator: string;
/**
* A list of all the supported language ISO codes.
*/
isoCurrencyCodes: string[];
/**
* Boolean value that indicates whether the system uses the metric system.
* On Android and web, this is inferred from the current region.
*/
isMetric: boolean;
/**
* Returns if the system's language is written from Right-to-Left.
* This can be used to build features like [bidirectional icons](https://material.io/design/usability/bidirectionality.html).
*
* Returns `false` in Server Side Rendering (SSR) environments.
*/
isRTL: boolean;
/**
* An [IETF BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag),
* consisting of a two-character language code and optional script, region and variant codes.
* @example
* `'en'`, `'en-US'`, `'zh-Hans'`, `'zh-Hans-CN'`, `'en-emodeng'`
*/
locale: string;
/**
* List of all the native languages provided by the user settings.
* These are returned in the order that the user defined in the device settings.
* @example
* `['en', 'en-US', 'zh-Hans', 'zh-Hans-CN', 'en-emodeng']`
*/
locales: string[];
/**
* The region code for your device that comes from the Region setting under Language & Region on iOS.
* This value is always available on iOS, but might return `null` on Android or web.
* @example
* `'US'`, `'NZ'`, `null`
*/
region: string | null;
/**
* The current time zone in display format.
* On Web time zone is calculated with Intl.DateTimeFormat().resolvedOptions().timeZone. For a
* better estimation you could use the moment-timezone package but it will add significant bloat to
* your website's bundle size.
* @example
* `'America/Los_Angeles'`
*/
timezone: string;
};
export type Locale = {
/**
* An [IETF BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag) with a region code.
* @example
* `'en-US'`, `'es-419'`, `'pl-PL'`.
*/
languageTag: string;
/**
* An [IETF BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag) without the region code.
* @example
* `'en'`, `'es'`, `'pl'`.
*/
languageCode: string | null;
/**
* The region code for your device that comes from the Region setting under Language & Region on iOS, Region settings on Android and is parsed from locale on Web (can be `null` on Web).
*/
regionCode: string | null;
/**
* Currency code for the locale.
* Is `null` on Web, use a table lookup based on region instead.
* @example
* `'USD'`, `'EUR'`, `'PLN'`.
*/
currencyCode: string | null;
/**
* Currency symbol for the locale.
* Is `null` on Web, use a table lookup based on region (if available) instead.
* @example
* `'$'`, `'€'`, `'zł'`.
*/
currencySymbol: string | null;
/**
* Decimal separator used for formatting numbers with fractional parts.
* @example
* `'.'`, `','`.
*/
decimalSeparator: string | null;
/**
* Digit grouping separator used for formatting large numbers.
* @example
* `'.'`, `','`.
*/
digitGroupingSeparator: string | null;
/**
* Text direction for the locale. One of: `'ltr'`, `'rtl'`, but can also be `null` on some browsers without support for the [textInfo](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/textInfo) property in [Intl](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) API.
*/
textDirection: 'ltr' | 'rtl' | null;
/**
* The measurement system used in the locale.
* Is `null` on Web, as user chosen measurement system is not exposed on the web and using locale to determine measurement systems is unreliable.
* Ask for user preferences if possible.
*/
measurementSystem: `metric` | `us` | `uk` | null;
/**
* The temperature unit used in the locale.
* Returns `null` if the region code is unknown.
*/
temperatureUnit: 'celsius' | 'fahrenheit' | null;
};
/**
* An enum mapping days of the week in Gregorian calendar to their index as returned by the `firstWeekday` property.
*/
export enum Weekday {
SUNDAY = 1,
MONDAY = 2,
TUESDAY = 3,
WEDNESDAY = 4,
THURSDAY = 5,
FRIDAY = 6,
SATURDAY = 7,
}
/**
* The calendar identifier, one of [Unicode calendar types](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar).
* Gregorian calendar is aliased and can be referred to as both `CalendarIdentifier.GREGORIAN` and `CalendarIdentifier.GREGORY`.
*/
export enum CalendarIdentifier {
/** Thai Buddhist calendar */
BUDDHIST = 'buddhist',
/** Traditional Chinese calendar */
CHINESE = 'chinese',
/** Coptic calendar */
COPTIC = 'coptic',
/** Traditional Korean calendar */
DANGI = 'dangi',
/** Ethiopic calendar, Amete Alem (epoch approx. 5493 B.C.E) */
ETHIOAA = 'ethioaa',
/** Ethiopic calendar, Amete Mihret (epoch approx, 8 C.E.) */
ETHIOPIC = 'ethiopic',
/** Gregorian calendar */
GREGORY = 'gregory',
/** Gregorian calendar (alias) */
GREGORIAN = 'gregory',
/** Traditional Hebrew calendar */
HEBREW = 'hebrew',
/** Indian calendar */
INDIAN = 'indian',
/** Islamic calendar */
ISLAMIC = 'islamic',
/** Islamic calendar, tabular (intercalary years [2,5,7,10,13,16,18,21,24,26,29] - civil epoch) */
ISLAMIC_CIVIL = 'islamic-civil',
/** Islamic calendar, Saudi Arabia sighting */
ISLAMIC_RGSA = 'islamic-rgsa',
/**Islamic calendar, tabular (intercalary years [2,5,7,10,13,16,18,21,24,26,29] - astronomical epoch) */
ISLAMIC_TBLA = 'islamic-tbla',
/** Islamic calendar, Umm al-Qura */
ISLAMIC_UMALQURA = 'islamic-umalqura',
/** ISO calendar (Gregorian calendar using the ISO 8601 calendar week rules) */
ISO8601 = 'iso8601',
/** Japanese imperial calendar */
JAPANESE = 'japanese',
/** Persian calendar */
PERSIAN = 'persian',
/** Civil (algorithmic) Arabic calendar */
ROC = 'roc',
}
export type Calendar = {
/**
* The calendar identifier, one of [Unicode calendar types](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar).
*
* On Android is limited to one of device's [available calendar types](https://developer.android.com/reference/java/util/Calendar#getAvailableCalendarTypes()).
*
* On iOS uses [calendar identifiers](https://developer.apple.com/documentation/foundation/calendar/identifier), but maps them to the corresponding Unicode types, will also never contain `'dangi'` or `'islamic-rgsa'` due to it not being implemented on iOS.
*/
calendar: CalendarIdentifier | null;
/**
* True when current device settings use 24-hour time format.
* Can be null on some browsers that don't support the [hourCycle](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/hourCycle) property in [Intl](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) API.
*/
uses24hourClock: boolean | null;
/**
* The first day of the week. For most calendars Sunday is numbered `1`, with Saturday being number `7`.
* Can be null on some browsers that don't support the [weekInfo](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/weekInfo) property in [Intl](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) API.
* @example
* `1`, `7`.
*/
firstWeekday: Weekday | null;
/**
* Time zone for the calendar. Can be `null` on Web.
* @example
* `'America/Los_Angeles'`, `'Europe/Warsaw'`, `'GMT+1'`.
*/
timeZone: string | null;
};

View File

@@ -0,0 +1,9 @@
// @generated by expo-module-scripts
{
"extends": "expo-module-scripts/tsconfig.base",
"compilerOptions": {
"outDir": "./build"
},
"include": ["./src"],
"exclude": ["**/__mocks__/*", "**/__tests__/*"]
}