# Localization in React Native

# Introduction

In today's connected world, it's crucial for mobile apps to provide proper support for users who speak different languages.

In this article, we are going to build a fully-functioning React Native app that supports multiple languages, allowing users to even switch between different languages based on their preferences.

As a bonus, we’ll also build a custom hook to deal with localized dates.

## Final code

If you want to jump directly into the code, here's the Snack

%[https://snack.expo.dev/@maurogarcia0209/localization-demo] 

## What is localization?

Localization refers to the process of adapting an application to meet the specific language, cultural, and regional requirements of different target audiences. The process of implementing localization usually starts with translating the text of the UI, but it can go beyond and consider other preferences like date and time formats, currency symbols, measurement units, and more.

## Benefits of implementing localization

By implementing localization, mobile apps can reach a broader audience and enhance user engagement. It allows users to interact with the app in their native language, making it more accessible, intuitive, and enjoyable for them

# Creating a new React Native project

Let’s start by creating an empty react native using Expo. I’ll be using the `expo-template-blank-typescript` to avoid dealing with TypeScript configuration.

```bash
npx create-expo-app -t expo-template-blank-typescript
```

Once the project is ready, `cd` into your project and run the following command in the root to add the required dependencies

```bash
npx expo install expo-localization i18next react-i18next
```

By using the `npx expo install` command, we make sure that we get the right version of each package for the Expo SDK version we are using. At the time I’m writing this article, the latest SDK version is 48.

We’ll use `expo-localization` to get the user’s preferred locale, `i18next` to simplify the process of translating and displaying text in different languages, and `react-i18next` as an extension of the `i18next` library that includes a list of convenient hooks and components we can use while translating a react app.

---

# Setting up localization

## Creating the required files

To setup localization in our Expo app, let’s start by creating a `i18n.ts` file at the root of the project.

```bash
# Create an empty file
touch i18n.ts
```

> Feel free to choose whatever name you want for the file.

We also need to create a `translate` folder where we’ll add a separate `json` file for each supported language. In this article, I’ll add support for English and Spanish.

```typescript
mkdir translate
touch translate/es.json
touch translate/en.json
```

## Adding localization keys for each supported language

In the new `json` files, make sure to initialize both `json` files to avoid annoying errors when setting up localizations later in the article.

`translate/en.json`

```json
{
  greetings: "Hello world"
}
```

`translate/es.json`

```json
{
  greetings: "Hola mundo"
}
```

## Configuring the i18n instance

Next, let’s add the following code in the new `i18n.ts` file to initialize `i18next`.

```typescript
//i18n.ts

import i18n from "i18next"
import { initReactI18next } from "react-i18next"
import es from "./translate/es.json"
import en from "./translate/en.json"
import * as Localization from "expo-localization";

const getLangCode = () => {
  const code = Localization.getLocales().shift();
  if (!code) return "en";
  return code.languageCode;
};

i18n.use(initReactI18next)
	.init({
        compatibilityJSON: "v3",
		lng: getLangCode(),
        defaultNS: "translation",
		interpolation: {
            // We disable this because it's not required, given that react already scapes the text
		    escapeValue: false
	    },
        // Here you can add all your supported languages
		resources: {
			es: es,
			en: en
		}
	})

export default i18n
```

Here are a few things to mention about this block of code:

* We use `initReactI18next` to initialize and setup `i18next` on our expo app.
    
* We retrieve the current device locales using `expo-localization` and extract the language code from the first locale.
    
* We are using the `compatibilityJSON` setting to make sure the default behavior is to use a JSON parser that supports advanced features such as nested objects and pluralization.
    
* We are disabling text escaping in the interpolation to rely on React’s built-in escaping.
    
* We are defining the resource files for each supported language.
    
* We set “translation” as the default namespace
    
    * If you want to learn more about namespaces in i18next, checkout [this article](https://www.i18next.com/principles/namespaces)
        
* Finally, we export the `i18n` instance to use it later.
    

## Wrapping our app with the I18nextProvider

Thanks to `react-i18next`, we can wrap our entire app using the `I18nextProvider`. Here are a few benefits to mention:

1. **Centralized Configuration:** The `I18nextProvider` allows you to centralize the configuration of i18next in a single place. By wrapping the app with the provider, you can set up and configure i18next once and have it accessible throughout your entire application.
    
2. **Context Propagation:** The `I18nextProvider` utilizes React's Context API to provide the i18next instance and translations to all nested components. This eliminates the need to pass down the i18next instance manually through props to every component that requires access to translations.
    
3. **Efficient Updates:** The `I18nextProvider` manages the `i18next` instance and ensures that updates to language and translations propagate efficiently to the components that depend on them. It optimizes the rendering process by selectively re-rendering only the affected components when language changes occur.
    
4. **Simplified Usage:** Wrapping the app with the `I18nextProvider` simplifies the usage of `i18next` within components. It eliminates the need to manually import and initialize `i18next` in every component that requires translations, as the provider takes care of this automatically.
    
5. **Future-Proofing:** Using the `I18nextProvider` ensures that your application is prepared for any future updates or changes to the i18next library. It provides a standardized and recommended way to integrate i18next with React, ensuring compatibility and ease of maintenance.
    

Let’s update the `App.tsx` file to include the provider:

```diff
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
+ import i18n from "./i18n";
+ import { I18nextProvider } from "react-i18next";

export default function App() {
  return (
+   <I18nextProvider i18n={i18n}>
      <View style={styles.container}>
        <Text>Open up App.tsx to start working on your app!</Text>
        <StatusBar style="auto" />
      </View>
+   </I18nextProvider>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});
```

---

# Using localization keys within a React component

With everything in place, it’s time to use our localization keys inside a React component.

1. First, we need to create a new folder and an empty file for the card component:
    
    ```bash
    mkdir components
    touch components/Card.tsx
    ```
    
2. Add the following code to the `Card` component
    
    ```typescript
    // components/Card.tsx
    
    import React from "react";
    import { View, Text, StyleSheet } from "react-native";
    
    type CardProps = {
      title: string;
      content: string;
    };
    
    const Card: React.FC<CardProps> = ({ title, content }) => {
      return (
        <View style={styles.container}>
          <Text style={styles.title}>{title}</Text>
          <Text style={styles.content}>{content}</Text>
        </View>
      );
    };
    
    const styles = StyleSheet.create({
      container: {
        backgroundColor: "#fff",
        borderRadius: 8,
        padding: 16,
        marginBottom: 16,
        shadowColor: "#000",
        shadowOffset: { width: 0, height: 2 },
        shadowOpacity: 0.2,
        shadowRadius: 2,
        elevation: 2,
      },
      title: {
        fontSize: 18,
        fontWeight: "bold",
        marginBottom: 8,
      },
      content: {
        fontSize: 16,
        lineHeight: 22,
      },
    });
    
    export default Card;
    ```
    
3. Now, let’s update the `App.tsx` to add a few instances of the `Card` component:
    

```diff
import { StatusBar } from 'expo-status-bar';
- import { StyleSheet, Text, View } from 'react-native';
+ import { StyleSheet, SafeAreaView, View } from 'react-native';
import i18n from "./i18n";
- import { I18nextProvider } from "react-i18next";
+ import { I18nextProvider, useTranslation } from "react-i18next";

export default function App() {
  return (
    <I18nextProvider i18n={i18n}>
-     <View style={styles.container}>
-       <Text>Open up App.tsx to start working on your app!</Text>
-       <StatusBar style="auto" />
-     </View>
+     <Home />
+     <StatusBar style="auto" />
    </I18nextProvider>
  );
}

+ const Home = () => {
+   const { t } = useTranslation("cards");
+   return (
+     <SafeAreaView style={{ flex: 1 }}>
+       <View style={styles.container}>
+         <Card title={t("cardTitle")} content={t("cardDescription")} />
+         <Card
+           title={t("anotherCardTitle")}
+           content={t("anotherCardDescription")}
+         />
+       </View>
+     </SafeAreaView>
+   );
+ };

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});
```

1. Finally, let’s update our resource files to include all the required localization keys:
    
    `translate/en.json`
    
    ```json
    {
      "cards": {
        "cardTitle": "Card Title",
        "cardDescription": "Card Description",
        "anotherCardTitle": "Another Card Title",
        "anotherCardDescription": "Another Card Description"
      }
    }
    ```
    
    `translate/es.json`
    
    ```json
    {
      "cards": {
        "cardTitle": "Titulo",
        "cardDescription": "Descripción",
        "anotherCardTitle": "Otro Titulo",
        "anotherCardDescription": "Otra Descripción"
      }
    }
    ```
    

Run `npm start` and you should see all the card content localized based on your preferences.

# Switching between languages

To wrap up this article, let’s add a new component that will list all the supported languages, allowing the user to switch between them.

1. Create a new file for the component by executing the following command:
    
    ```bash
    touch components/LangSwitcher.tsx
    ```
    
2. Update the new empty file with the following code
    
    ```typescript
    // components/LangSwitcher.tsx
    
    import { useTranslation } from "react-i18next";
    import { TouchableOpacity, Text, View, StyleSheet } from "react-native";
    
    const LangSwitcher = () => {
      const { i18n, t } = useTranslation("supportedLanguages");
      const supportedLanguages = ["en", "es"];
    
      const changeLanguage = (lng: string) => {
        i18n.changeLanguage(lng);
      };
    
      return (
        <View style={styles.container}>
          <Text style={styles.title}>Choose your language</Text>
          {supportedLanguages.map((lng) => (
            <TouchableOpacity
              onPress={() => changeLanguage(lng)}
              style={styles.item}
            >
              <Text style={styles.itemText}>{t(lng)}</Text>
            </TouchableOpacity>
          ))}
        </View>
      );
    };
    
    // improve styles
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        backgroundColor: "#fff",
        alignItems: "center",
        justifyContent: "center",
        padding: 16,
      },
      title: {
        marginBottom: 16,
        fontSize: 24,
        fontWeight: "bold",
      },
      item: {
        width: "100%",
        padding: 16,
        borderBottomWidth: 1,
        borderBottomColor: "#ccc",
      },
      itemText: {
        fontSize: 16,
        textAlign: "center",
      },
    });
    
    export default LangSwitcher;
    ```
    
3. Update the resource files, so all the supported languages in the switcher are localized 😉
    
    `translate/en.json`
    

```diff
{
  "cards": {
    "cardTitle": "Card Title",
    "cardDescription": "Card Description",
    "anotherCardTitle": "Another Card Title",
    "anotherCardDescription": "Another Card Description"
  },
+ "supportedLanguages": {
+   "en": "English",
+   "es": "Spanish"
+ }
}
```

`translate/es.json`

```diff
{
  "cards": {
    "cardTitle": "Titulo",
    "cardDescription": "Descripción",
    "anotherCardTitle": "Otro Titulo",
    "anotherCardDescription": "Otra Descripción"
  },
+ "supportedLanguages": {
+   "en": "Inglés",
+   "es": "Español"
+ }
}
```

1. Finally, we need to update the `App.tsx` file to add the new component
    

```diff
...
+ import LangSwitcher from "./components/LangSwitcher";

...

const Home = () => {
  const { t } = useTranslation("cards");
  return (
    <SafeAreaView style={{ flex: 1 }}>
      <View style={styles.container}>
        ...
+       <LangSwitcher></LangSwitcher>
      </View>
    </SafeAreaView>
  );
};
```

# Translating app metadata (iOS - Optional)

To further improve your multi-language support, you can also provide localized string for your app metadata. This includes things like:

* Your app display name
    
* System dialogs (every time you request permissions like location, camera, etc).
    

1. Update your `app.json` file to set `CFBundleAllowMixedLocalizations` to `true` like this:
    

```diff
{
  "expo": {
+   "ios": {
+     "infoPlist": {
+       "CFBundleAllowMixedLocalizations": true
+     }
+   },
  }
}
```

1. You also need to provide a `locales` object, where you’ll define the path to a `json` file for each supported language.
    

```diff
{
  "expo": {
    "ios": {
      "infoPlist": {
        "CFBundleAllowMixedLocalizations": true
      }
    },
+   "locales": {
+     "en": "./metadata/en.json",
+     "es": "./metadata/es.json",
+   }
  }
}
```

1. Create the new folder and required files with the following command
    
    ```diff
    mkdir metadata
    touch metadata/en.json
    touch metadata/es.json
    ```
    
2. Update each `json` file to include your localized metadata. Here’s an example: `metadata/en.json`
    
    ```json
    {
      "CFBundleDisplayName": "My cooking app",
      "NSCameraUsageDescription": "The app need to use the camera so you can take photos of your dishes",
      "NSLocationWhenInUseUsageDescription": "The app needs access to your location to share the address of your favorite restorants"
    }
    ```
    
    `metadata/es.json`
    
    ```json
    {
      "CFBundleDisplayName": "Mi aplicación de cocina",
      "NSCameraUsageDescription": "La aplicación necesita usar la cámara para que puedas tomar fotos de tus platos",
      "NSLocationWhenInUseUsageDescription": "La aplicación necesita acceso a tu ubicación para compartir la dirección de tus restaurantes favoritos"
    }
    ```
    

# Bonus: Adding a localized date

Just for fun, let’s combine `expo-localization` with the built-in method `toLocaleString()` to localize our dates.

Update the `Card` component to include an additional prop called `date`

```diff
import React from "react";
import { View, Text, StyleSheet } from "react-native";
+ import * as Localization from "expo-localization";

type CardProps = {
  title: string;
  content: string;
+ date: Date;
};

const Card: React.FC<CardProps> = ({ title, content, date }) => {

+ // Get the user's preferred locale
+ const locale = Localization.getLocales().shift();
+ const languageCode = locale?.languageCode || "en";

+ const localizedDate = date.toLocaleString(languageCode, {
+   dateStyle: "long",
+   timeStyle: "long",
+ });

  return (
    <View style={styles.container}>
      <Text style={styles.title}>{title}</Text>
      <Text style={styles.content}>{content}</Text>
+     <Text style={styles.content}>{localizedDate}</Text>
    </View>
  );
};
```

Now, you should be able to see a localized date. You can even go a step further and add a convenient hook to encapsulate this logic, so you don’t need to add all this boilerplate every time you want to display a localized date.

Besides, the code provided above has a catch: If the user changes the language of the app in runtime, the localized string won’t update.

Let’s fix that problem with the `useLocalizedDate` hook

1. First, we need to create a new folder and an empty file for the new hook.
    
    ```bash
    mkdir hooks
    touch hooks/useLocalizedDate.tsx
    ```
    
2. Now, add the following code for the hook
    
    ```typescript
    // hooks/useLocalizedDate.tsx
    
    import * as Localization from "expo-localization";
    import { useEffect, useState } from "react";
    import { useTranslation } from "react-i18next";
    
    const useLocalizedDate = (
      date: Date,
      options: Intl.DateTimeFormatOptions = {}
    ) => {
      const { i18n } = useTranslation();
    
      const defaultOptions: Intl.DateTimeFormatOptions = {
        dateStyle: "long",
        timeStyle: "long",
      };
    
      const [localizedDate, setLocalizedDate] = useState<string>("");
    
      // When the component mounts, set the initial value of the localized date and add a listener for language changes
      useEffect(() => {
        setInitialValue();
        i18n.on("languageChanged", (lng) => {
          setLocalizedDate(date.toLocaleString(lng, defaultOptions));
        });
    
        return () => {
          i18n.off("languageChanged");
        };
      }, []);
    
      const setInitialValue = () => {
        // Merge the user's options with the default options
        const mergedOptions = { ...defaultOptions, ...options };
    
        // Get the user's preferred locale
        const locale = Localization.getLocales().shift();
        const languageCode = locale?.languageCode || "en";
        // Localize the date
        setLocalizedDate(date.toLocaleString(languageCode, mergedOptions));
      };
    
      return localizedDate as string;
    };
    
    export default useLocalizedDate;
    ```
    
    In this improved version, we are also listening to `i18next` events to update the localized dates when the language of the user changes.
    
3. Finally, we can update the card component to use the new hook like this:
    
    ```typescript
    // components/Card.tsx
    
    import React from "react";
    import { View, Text, StyleSheet } from "react-native";
    import useLocalizedDate from "../hooks/useLocalizedDate";
    
    type CardProps = {
      title: string;
      content: string;
      date: Date;
    };
    
    const Card: React.FC<CardProps> = ({ title, content, date }) => {
      const localizedDate = useLocalizedDate(date);
    
      return (
        <View style={styles.container}>
          <Text style={styles.title}>{title}</Text>
          <Text style={styles.content}>{content}</Text>
          <Text style={styles.content}>{localizedDate}</Text>
        </View>
      );
    };
    ```
    

# Final thoughts

Implementing localization in mobile apps is essential for reaching a broader audience and enhancing user engagement. Thanks to libraries like `expo-localization`, `i18next` and `react-i18next`, it’s easier than ever to implement localization.

By providing support for multiple languages, your app will be more accessible, intuitive, and enjoyable for your users.
