xref: /expo/docs/pages/guides/localization.mdx (revision da751fd5)
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