1--- 2title: Localization 3description: Learn about getting started and configuring localization in an Expo project using expo-localization. 4--- 5 6import { SnackInline } from '~/ui/components/Snippet'; 7import { Terminal } from '~/ui/components/Snippet'; 8import { Collapsible } from '~/ui/components/Collapsible'; 9 10If you want your app to be easy to use for users who speak different languages or come from different cultures, you should localize it. 11 12Localizing an app makes it adapt to the locale of the user's device. The app will show translations and currencies that the user knows and understands. Numbers, lists, and more will format in a way that the user is used to. 13 14## Getting the user's language 15 16Use the [`expo-localization`](../versions/latest/sdk/localization) module to get the user's current language. Install the package by running the following command: 17 18<Terminal cmd={['$ npx expo install expo-localization']} /> 19 20Then, you will be able to access localization methods and data in your app: 21 22```ts 23import { getLocales } from 'expo-localization'; 24 25const deviceLanguage = getLocales()[0].languageCode; 26``` 27 28The `getLocales` method returns the current locale based on the system settings of the device. 29 30On new Android and iOS versions, app language can be set per app, so you usually don't need to build a custom UI to allow users to change the current locale inside of your app. 31 32Sometimes, it makes sense to build UI to allow the user to set other localization preferences on a per-app basis. As a general rule, you should allow the user to change the following: 33 34- Localized units if your app makes at least a moderate use of them (such as metric/imperial measurements, currency, temperature, and more) 35- Other preferences if there's no API to get the default value on platforms you want to support (check [`expo-localization`](/versions/latest/sdk/localization) API documentation for details) 36 37## Translating an app 38 39Creating and managing translations quickly becomes a large task. You can handle translations manually, but it's best to use a library to handle this for you. 40 41Let's make the app support English and Japanese. To achieve this install the i18n package `i18n-js`: 42 43<Terminal cmd={['$ npx expo install i18n-js']} /> 44 45Then, configure the languages for your app: 46 47```tsx 48import { getLocales } from 'expo-localization'; 49import { I18n } from 'i18n-js'; 50 51// Set the key-value pairs for the different languages you want to support. 52const i18n = new I18n({ 53 en: { welcome: 'Hello' }, 54 ja: { welcome: 'こんにちは' }, 55}); 56 57// Set the locale once at the beginning of your app. 58i18n.locale = getLocales()[0].languageCode; 59 60console.log(i18n.t('welcome')); 61``` 62 63Now, you can use the `i18n.t` function to translate strings throughout your application. 64 65You can refrain from localizing text for certain things, for example, names. In this case, you can define them _once_ in your default language and reuse them with `i18n.enableFallback = true;`. 66 67On Android, when a user changes the device's language, the app will not reset. You can use the [`AppState`](https://reactnative.dev/docs/appstate#basic-usage) API to listen for changes to the app's state and call the `getLocales()` function each time the app's state changes. 68 69On iOS, when a user changes the device's language, the app will reset. This means you can set the language once without updating any of your React components to account for the language changes. 70 71### Complete example 72 73<SnackInline label="Localization" dependencies={['expo-localization', 'i18n-js']}> 74 75```tsx 76import { View, StyleSheet, Text } from 'react-native'; 77import * as Localization from 'expo-localization'; 78import { I18n } from 'i18n-js'; 79 80// Set the key-value pairs for the different languages you want to support. 81const translations = { 82 en: { welcome: 'Hello', name: 'Charlie' }, 83 ja: { welcome: 'こんにちは' }, 84}; 85const i18n = new I18n(translations); 86 87// Set the locale once at the beginning of your app. 88i18n.locale = Localization.locale; 89 90// When a value is missing from a language it'll fall back to another language with the key present. 91i18n.enableFallback = true; 92// To see the fallback mechanism uncomment the line below to force the app to use the Japanese language. 93// i18n.locale = 'ja'; 94 95export default function App() { 96 return ( 97 <View style={styles.container}> 98 <Text style={styles.text}> 99 {i18n.t('welcome')} {i18n.t('name')} 100 </Text> 101 <Text>Current locale: {i18n.locale}</Text> 102 <Text>Device locale: {Localization.getLocales()[0].languageCode}</Text> 103 </View> 104 ); 105} 106 107/* @hide const styles = StyleSheet.create({ ... }); */ 108const styles = StyleSheet.create({ 109 container: { 110 backgroundColor: '#fff', 111 alignItems: 'center', 112 justifyContent: 'center', 113 flex: 1, 114 }, 115 text: { 116 fontSize: 20, 117 marginBottom: 16, 118 }, 119}); 120/* @end */ 121``` 122 123</SnackInline> 124 125<Collapsible summary="Using other translation libraries"> 126 127While this guide uses `i18n-js` as an example, there are many other libraries that can help you with translations, and they each have different features: 128 129- Creating translations is a huge effort, and you'd ideally want to hire someone. It's also hard for them to edit translations directly in your code. Some translation libraries can integrate with translation management tools (essentially a web service that lets you outsource, auto-generate, or make it easier to create translations). 130 131- Some libraries make it easy to move the component structure around (not just words of your copy) based on translation strings. Imagine you want to localize a string that includes a `<Pressable>` link, and you want the link to be in a different order from the rest of the text, depending on the translation. 132 133Here are some libraries that can be a good option depending on your needs: 134 135- [`React i18next`](https://react.i18next.com/) 136 137 `React i18next` is a stable, well-maintained library based on `i18next`. 138 139- [`typesafe-i18n`](https://github.com/ivanhofer/typesafe-i18n) 140 141 `typesafe-i18n` compiles translations to save space in the production bundle. It also generates typescript types for your translations so that you can use them in your code and get autocomplete and type safety. 142 It requires the `Intl` API for some features, so it's only usable in projects with Hermes enabled. 143 144</Collapsible> 145 146### Translating app metadata 147 148If you plan on shipping your app to different countries or regions or want it to support various languages, you can provide [localized](/versions/latest/sdk/localization) strings for things like the display name and system dialogs. This is easily set up [in the app config](/workflow/configuration) file. First, set `ios.infoPlist.CFBundleAllowMixedLocalizations: true`, then provide a list of file paths to `locales`. 149 150```json app.json 151{ 152 "expo": { 153 "ios": { 154 "infoPlist": { 155 "CFBundleAllowMixedLocalizations": true 156 } 157 }, 158 "locales": { 159 "ja": "./languages/japanese.json" 160 } 161 } 162} 163``` 164 165The keys provided to `locales` should be the [language identifier](https://developer.apple.com/documentation/xcode/choosing-localization-regions-and-scripts), made up of a [2-letter language code](https://www.loc.gov/standards/iso639-2/php/code_list.php) of your desired language, with an optional region code (for example, `en-US` or `en-GB`), and the value should point to a JSON file that looks something like below: 166 167```json japanese.json 168{ 169 "CFBundleDisplayName": "こんにちは", 170 "NSContactsUsageDescription": "日本語のこれらの言葉" 171} 172``` 173 174Now, iOS knows to set the display name of your app to `こんにちは` whenever it's installed on a device with the language set to Japanese. 175 176## Enabling RTL Support 177 178Several regions around the world write text from right to left. If you want to localize your app, so it looks as expected in RTL languages, you need to make sure your app handles these layout and text direction changes accordingly. 179 180To enable RTL support in your application using SDK 48 or later, install the `expo-localization` package and add the following properties in app config: 181 182<Terminal cmd={['$ npx expo install expo-localization']} /> 183 184```json app.json 185{ 186 "expo": { 187 "extra": { 188 "supportsRTL": true 189 }, 190 "plugins": ["expo-localization"] 191 } 192} 193``` 194 195This enables RTL when your app is loaded in Expo Go, in Expo dev Client, and in applications built using EAS Build or `npx expo prebuild`. 196 197When an application starts, Expo checks if the current device locale should render in RTL layout to look correctly. For example, an app marked to support RTL in the app config file will render in RTL mode in Hebrew or Arabic locale. 198 199<Collapsible summary="Dynamically overriding RTL settings"> 200 201If you want to override the default RTL detection from your application code dynamically, you cannot use the static configuration in app config. Instead, apply these changes dynamically from your application code. 202 203This does not work in Expo Go, as Expo Go resets RTL preferences when opening the launcher or individual projects. 204 205<SnackInline 206label="Overriding RTL settings" 207dependencies={['expo-updates', 'expo-constants']} 208> 209 210```jsx 211import { Text, View, StyleSheet, I18nManager, Platform } from 'react-native'; 212import Constants from 'expo-constants'; 213 214import * as Updates from 'expo-updates'; 215 216export default function App() { 217 const shouldBeRTL = true; 218 219 if (shouldBeRTL !== I18nManager.isRTL && Platform.OS !== 'web') { 220 I18nManager.allowRTL(shouldBeRTL); 221 I18nManager.forceRTL(shouldBeRTL); 222 Updates.reloadAsync(); 223 } 224 225 return ( 226 <View style={styles.container}> 227 <Text style={styles.paragraph}>{I18nManager.isRTL ? ' RTL' : ' LTR'}</Text> 228 </View> 229 ); 230} 231 232const styles = StyleSheet.create({ 233 container: { 234 flex: 1, 235 justifyContent: 'center', 236 paddingTop: Constants.statusBarHeight, 237 padding: 8, 238 }, 239 paragraph: { 240 fontSize: 18, 241 fontWeight: 'bold', 242 textAlign: 'left', 243 width: '50%', 244 backgroundColor: 'pink', 245 }, 246}); 247``` 248 249</SnackInline> 250 251</Collapsible> 252 253## Making your app behave correctly on RTL locales 254 255### Layouts and views 256 257You don't need to manually adjust `<View>` styling properties based on locale. You can use properties like `justifyContext`, `alignItems`, and others. Their property values change behavior as required. 258 259- On LTR locales, `start` and `end` are the same as `left` and `right`. 260- On RTL locales, `start` and `end` are the same as `right` and `left`, respectively. 261 262> **info** For more details on how RTL works in React Native check out the React Native [blog article](https://reactnative.dev/blog/2016/08/19/right-to-left-support-for-react-native-apps) introducing RTL support. 263 264#### Web support 265 266Web support for RTL layouts requires no app config changes. 267 268Expo uses `react-native-web` for running Expo projects in the browser. 269To make `react-native-web` automatically adapt to locale direction, add a `dir` property to your root `<View>` component. 270 271```tsx App.tsx 272import { View } from 'react-native'; 273import { getLocales } from 'expo-localization'; 274// ... 275 276return <View dir={getLocales()[0].textDirection || 'ltr'}>//...</View>; 277``` 278 279> **warning** `textDirection` is not available on Firefox and older browser versions. [Detect it manually](https://stackoverflow.com/a/15726039) if needed. 280 281### Text alignment 282 283The React Native's `textDirection` property does not accept `start` or `end` values that you can use in flex properties. Instead, `left` effectively works as `start` (aligns to the left on LTR and to the right on RTL), and `right` works as `end`. 284 285However, the default unset value of `textDirection` property signifies the actual left (aligns to the left both on LTR and RTL). 286 287Because of this, each `<Text>` tag needs to have the `textDirection: left` or `textDirection: right` style set if you want it to be aligned correctly. 288 289It's best to define this style in your custom reusable `<Text>` component that you can then import everywhere you need to render text strings. 290 291```tsx MobileText.tsx 292import { Text as RNText, TextProps as RNTextProps } from 'react-native'; 293 294const MobileText = (props: RNTextProps) => { 295 return <RNText style={{ textAlign: 'left', ...props.style }} {...props} />; 296}; 297export default MobileText; 298``` 299 300#### Web support 301 302For each text tag, you need to add the `lang` property with the current locale identifier. 303It's best to define this style in a custom reusable component. 304 305```tsx WebText.tsx 306import { getLocales } from 'expo-localization'; 307 308const deviceLanguage = getLocales()[0].languageCode; 309 310const WebText = (props: RNTextProps) => { 311 return <RNText lang={deviceLanguage} {...props} />; 312}; 313 314export default WebText; 315``` 316 317You can then pick either the mobile or web Text component based on the current platform. 318 319```tsx Text.tsx 320const Text = Platform.OS === 'web' ? WebText : MobileText; 321export default Text; 322``` 323 324### Selecting assets based on locale direction 325 326If you need to use different icons for LTR/RTL or change styles based on this setting, you can use `I18nManager.isRTL` to get the current layout direction. 327 328## Locale settings and units 329 330Expo provides the `expo-localization` module to allow you to read the user's locale and other preferences. 331 332You can use synchronous `getLocales()` and `getCalendars()` methods to get the current locale settings of the user device. 333 334`getLocales()` returns a list of locales based on the order in which the user prefers them. There will always be at least one locale in the list. 335 336`getCalendars()` returns a list of calendars based on the order in which the user prefers them. There will always be at least one calendar on the list. 337 338```ts 339import { getLocales, getCalendars } from 'expo-localization'; 340 341const { 342 languageTag, 343 languageCode, 344 textDirection, 345 digitGroupingSeparator, 346 decimalSeparator, 347 measurementSystem, 348 currencyCode, 349 currencySymbol, 350 regionCode, 351} = getLocales()[0]; 352 353const { calendar, timeZone, uses24hourClock, firstWeekday } = getCalendars()[0]; 354``` 355 356<Collapsible summary="Limitations"> 357 There are a few limitations to keep in mind when relying on auto-detected locale preferences from 358 `expo-localization`: - There is yet to be a way to read temperature units from user preferences. 359 On Android, you can use a lookup table based on locale, but on iOS, the user can change it in 360 device preferences. - Some properties can be null when they are unavailable on the current 361 platform. 362</Collapsible> 363 364## Intl API 365 366If you're using Hermes in your app, you can use the [`Intl`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) API on all platforms. 367 368It provides a set of utilities you can use to format lists, dates, numbers, monetary amounts, units, plural forms, and more. 369 370If you pass `default` as the locale string, the `Intl` API will use the device's locale, so you don't need to rely on `expo-localization` to get the current locale (such as `"en-US"`). 371 372```ts 373new Intl.NumberFormat('default', { style: 'currency', currency: 'EUR' }).format(5.0); 374``` 375 376> You can use `Intl` APIs to format strings and values once you know what the user expects to see. 377> 378> `Intl` APIs do not provide information about the device or current locale, so you can't use the `Intl` APIs to get current locale units, currencies, or measurement systems. 379> 380> For this you need to use `expo-localization`, JS code on the web, or third-party or custom native code on Android and iOS. 381