Working with Different Languages in an Expo App

Imagine you've made an app with lots of love and care, but when people from different places try it, they can't understand it.

That's where localization comes in! It's like giving your app a language makeover so everyone can enjoy it, no matter where they're from.

In this blog, we're diving into how you can make your Expo app speak different languages and connect with people around the world.

I'll keep things simple and show you step-by-step how to make your app feel like home for everyone who uses it. Let's get started on this exciting journey of making your app truly global!

This article assumes that you've bootstrapped an Expo project, as it doesn't cover the process of bootstrapping."

Step 1

npx expo install expo-localization i18next react-i18next expo-updates

i18next is a JavaScript-based internationalization framework that offers flexibility in translation methods. It provides the freedom to choose your preferred approach. For React applications, you can utilize the i18next package along with react-i18next, a powerful React hook designed for integrating i18next seamlessly.

On the other hand, expo-localization grants access to the device's locale information, facilitating localization within Expo apps. Additionally, expo-updates offers an API for checking, downloading, and applying updates to the app.

Step 2

Add expo-localization as part of your app.json plugin property:

{
  "expo": {
    //...otherProps
    // If you want to support RTL languages, set the supportsRTL to true
    // This will ensure that the  direction is set to RTL for languages like Arabic and Hebrew
    "extra": {
      "supportsRTL": true
    },
    "plugins": ["expo-localization"]
  }
}

Step 3

create a new folder called locales at the root of your project and add 2 files en.json and es.json:

en for English
es for Spanish

{
  "greeting": "Hello World",
  "objective": "This is a simple example of how to use i18next with Expo",
  "changeLanguage": "Swap Language"
}
{
  "greeting": "Hola Mundo",
  "objective": "Este es un ejemplo simple de cómo usar i18next con Expo",
  "changeLanguage": "Cambiar idioma"
}

Step 4

create a new file called i18n.js or i18n.ts as the case maybe if you're on typescript at the root of your project and add the following codes:

import i18next from "i18next";
import * as Localization from "expo-localization";
import { initReactI18next } from "react-i18next";

//import the locales for the different languages
import en from "./locales/en.json";
import es from "./locales/es.json";

export const languageResources = {
  en: { translation: en },
  es: { translation: es },
};

i18next.use(initReactI18next).init({
  compatibilityJSON: "v3",

  /**
   * Get the locale used by the device
   */
  lng: Localization.locale,

  /**
   * Set the fallback language in case the device locale is not available
   */
  fallbackLng: "en",

  /**
   * Set the resources to be used by i18next
   * The resources are the translations for the different languages
   */
  resources: languageResources,
});

export default i18next;

Step 5

import useTranslation from react-i18next in the component you want to use the translations and use it as shown below.

Now, it's also important to import the configuration file (i18n.ts or i18n.js) into the starting point of your app. This could be your App.tsx file or your _layout.ts file if you're using Expo router.

import "./i18n"; 
import { View, Text, Button, Platform, I18nManager } from "react-native";
import * as Updates from "expo-updates";
import { useTranslation } from "react-i18next";
import i18next from "i18next";


const App = () => {
  const { t } = useTranslation();

  function changeLanguage() {
    //Get the current language
    const lang = i18next.language;

    //Change the language to the opposite of the current language
    i18next.changeLanguage(lang === "en" ? "es" : "en");

    //If the platform is web, break out of the function
    if (Platform.OS === "web") return;

    //If the language is Arabic or Hebrew, set the direction to RTL
    const shouldBeRTL = ["ar"].includes(lang);

    //Dynamically change the direction of the app based on the language
    if (shouldBeRTL !== I18nManager.isRTL) {
      I18nManager.allowRTL(shouldBeRTL);
      I18nManager.forceRTL(shouldBeRTL);

      //Forcefully reload the app to apply the changes
      Updates.reloadAsync();
    }
  }

  return (
    <View>
      <Text>{t("greeting")}</Text>
      <Text>{t("objective")}</Text>
      <Button title={t("changeLanguage")} onPress={changeLanguage} />
    </View>
  );
};

Now It is important to set a default text alignment for the text component to ensure that the text is displayed correctly for languages that are written from right to left.

import { Text } from "react-native";

const MobileText: React.FC<TextProps> = ({ style, ...rest }) => {
  return <Text style={[{ textAlign: "left" }, style]} {...rest} />;
};

export default MobileText;

The next step is to swap every Text component with the MobileText component except for the Text component with alignItems:"center" to maintain the centre alignment.

The example above should be rewritten as shown below with the MobileText component:

<View>
  <MobileText>{t("greeting")}</MobileText>
  <MobileText>{t("objective")}</MobileText>
  <Button title={t("changeLanguage")} onPress={changeLanguage} />
</View>

Having done all these, you should be able to switch between the different languages in your app.

Step 6

This step is optional if you are using JavaScript and not Typescript. If you're a TypeScript user, you might want to add some type of intelligence to your locale typing.

Make sure your tsconfig compilerOptions have the strictflag or the strictNullChecksset to true.

If your project spans multiple i18next instances with different translation resources, you probably can't use type-safe translations.

To ensure type safety for translations, we create an i18next.d.ts file, preferably in a @types folder at the root of the project. in this file, we import the translation resources of our reference language.

import en from "../locales/en.json";
import es from "../locales/es.json";

declare module 'i18next' {
  interface CustomTypeOptions {
    defaultNS: 'en';
    resources: {
      en: typeof en;
      es: typeof es;
    };
  }
}

In wrapping up, remember that by applying the steps we've covered in this post, you can make your Expo app more inclusive and engaging for people worldwide. Take the time to give it a try! Thanks for reading, and stay tuned for more insights in our future posts!