Expo Integration

Learn how to integrate i18next with React Native Expo applications for internationalization.

Integrate i18next with your React Native Expo application to support multiple languages using Lengrise-managed translations, with full support for mobile-specific internationalization features.

Prerequisites

  • Expo project (SDK 48+)
  • Node.js (v16.0.0+)
  • Package manager (npm, yarn, or pnpm)
  • Translations downloaded from Lengrise

Pull Translation Files

First, you must pull your translation files from Lengrise API. These files will contain all the translated text for your application.

  1. Go to the Installation Guide and follow the steps to download your translations
  2. Place the downloaded JSON files in your project as shown in the project structure below
  3. Ensure that each language has its own JSON file named with the language code (e.g., en.json, es.json)

Integration Setup

After downloading your translation files, install the necessary packages:

javascript npm install i18next react-i18next react-native-localize

Project Structure

package.jsonadd
app.jsonadd
App.jsadd
babel.config.jsadd
srcadd
localizationadd
i18n.jsadd
LanguageContext.jsadd
LanguageProvider.jsadd
componentsadd
LanguageSwitcher.jsadd
screensadd
HomeScreen.jsadd
SettingsScreen.jsadd
assetsadd
localesadd
en.jsonadd
es.jsonadd

Configuration Steps

1. Set up i18n Configuration

Create a file at src/localization/i18n.js:

import i18next from "i18next";
import { initReactI18next } from "react-i18next";
import * as RNLocalize from "react-native-localize";
import { I18nManager } from "react-native";

// Import your translation JSON files
import en from "../../assets/locales/en.json";
import es from "../../assets/locales/es.json";

// Define the supported locales
export const LANGUAGES = {
  en: { translation: en, label: "English", isRTL: false },
  es: { translation: es, label: "EspaƱol", isRTL: false },
};

// Function to get the device language and use a supported one
export const getDeviceLanguage = () => {
  // Get device locales
  const deviceLocales = RNLocalize.getLocales();

  if (deviceLocales && deviceLocales.length > 0) {
    // Get the language tag (e.g., 'en-US' -> 'en')
    const languageTag = deviceLocales[0].languageTag.split("-")[0];

    // Check if we support this language
    if (Object.keys(LANGUAGES).includes(languageTag)) {
      return languageTag;
    }
  }

  // Default to English if not supported
  return "en";
};

// Set up i18next
i18next.use(initReactI18next).init({
  compatibilityJSON: "v3", // Required for Android devices
  resources: LANGUAGES,
  lng: getDeviceLanguage(),
  fallbackLng: "en",
  interpolation: {
    escapeValue: false, // Not needed for React
  },
  react: {
    useSuspense: false,
  },
});

// For RTL support if you have RTL languages
export const setI18nConfig = (language) => {
  const isRTL = LANGUAGES[language]?.isRTL || false;

  // Set the RTL status for the app
  I18nManager.forceRTL(isRTL);

  // Set the new language
  i18next.changeLanguage(language);

  return isRTL;
};

export default i18next;

2. Create a Language Context

Create a file at src/localization/LanguageContext.js:

import { createContext } from "react";

const LanguageContext = createContext({
  language: "en",
  setLanguage: () => {},
  languages: {},
  isRTL: false,
});

export default LanguageContext;

3. Create a Language Provider

Create a file at src/localization/LanguageProvider.js:

import React, { useState, useEffect } from "react";
import AsyncStorage from "@react-native-async-storage/async-storage";
import * as RNLocalize from "react-native-localize";
import { LANGUAGES, setI18nConfig, getDeviceLanguage } from "./i18n";
import LanguageContext from "./LanguageContext";

const LANGUAGE_STORAGE_KEY = "@app_language";

export const LanguageProvider = ({ children }) => {
  const [language, setLanguageState] = useState(getDeviceLanguage());
  const [isRTL, setIsRTL] = useState(false);

  // Load the saved language from storage on app start
  useEffect(() => {
    const loadSavedLanguage = async () => {
      try {
        const savedLanguage = await AsyncStorage.getItem(LANGUAGE_STORAGE_KEY);
        if (savedLanguage && Object.keys(LANGUAGES).includes(savedLanguage)) {
          const rtlStatus = setI18nConfig(savedLanguage);
          setLanguageState(savedLanguage);
          setIsRTL(rtlStatus);
        }
      } catch (error) {
        console.error("Failed to load language", error);
      }
    };

    loadSavedLanguage();

    // Add listener for device locale changes
    const localeChangeListener = RNLocalize.addEventListener("change", () => {
      const deviceLang = getDeviceLanguage();
      setLanguage(deviceLang);
    });

    // Clean up listener
    return () => {
      localeChangeListener.remove();
    };
  }, []);

  // Function to change the app language
  const setLanguage = async (newLanguage) => {
    try {
      if (Object.keys(LANGUAGES).includes(newLanguage)) {
        await AsyncStorage.setItem(LANGUAGE_STORAGE_KEY, newLanguage);
        const rtlStatus = setI18nConfig(newLanguage);
        setLanguageState(newLanguage);
        setIsRTL(rtlStatus);
      }
    } catch (error) {
      console.error("Failed to save language", error);
    }
  };

  return (
    <LanguageContext.Provider
      value={{
        language,
        setLanguage,
        languages: LANGUAGES,
        isRTL,
      }}
    >
      {children}
    </LanguageContext.Provider>
  );
};

export default LanguageProvider;

4. Create a Language Switcher Component

Create a file at src/components/LanguageSwitcher.js:

import React, { useContext } from "react";
import { View, Text, TouchableOpacity, StyleSheet } from "react-native";
import { useTranslation } from "react-i18next";
import LanguageContext from "../localization/LanguageContext";

const LanguageSwitcher = () => {
  const { t } = useTranslation();
  const { language, setLanguage, languages } = useContext(LanguageContext);

  return (
    <View style={styles.container}>
      <Text style={styles.label}>{t("settings.language")}</Text>
      <View style={styles.buttonContainer}>
        {Object.keys(languages).map((langCode) => (
          <TouchableOpacity
            key={langCode}
            style={[
              styles.button,
              language === langCode && styles.activeButton,
            ]}
            onPress={() => setLanguage(langCode)}
          >
            <Text
              style={[
                styles.buttonText,
                language === langCode && styles.activeButtonText,
              ]}
            >
              {languages[langCode].label}
            </Text>
          </TouchableOpacity>
        ))}
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    marginVertical: 16,
  },
  label: {
    fontSize: 16,
    fontWeight: "bold",
    marginBottom: 8,
  },
  buttonContainer: {
    flexDirection: "row",
    flexWrap: "wrap",
  },
  button: {
    paddingHorizontal: 16,
    paddingVertical: 8,
    marginRight: 8,
    marginBottom: 8,
    borderRadius: 4,
    backgroundColor: "#f0f0f0",
  },
  activeButton: {
    backgroundColor: "#0066cc",
  },
  buttonText: {
    color: "#333",
  },
  activeButtonText: {
    color: "#fff",
  },
});

export default LanguageSwitcher;

5. Update Your App Entry File

Edit your App.js file to include the language provider:

import React from "react";
import { StatusBar } from "expo-status-bar";
import { SafeAreaProvider } from "react-native-safe-area-context";
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";

// Import screens
import HomeScreen from "./src/screens/HomeScreen";
import SettingsScreen from "./src/screens/SettingsScreen";

// Import language provider
import "./src/localization/i18n"; // Initialize i18next
import LanguageProvider from "./src/localization/LanguageProvider";

const Stack = createStackNavigator();

export default function App() {
  return (
    <SafeAreaProvider>
      <LanguageProvider>
        <NavigationContainer>
          <Stack.Navigator initialRouteName="Home">
            <Stack.Screen name="Home" component={HomeScreen} />
            <Stack.Screen name="Settings" component={SettingsScreen} />
          </Stack.Navigator>
          <StatusBar style="auto" />
        </NavigationContainer>
      </LanguageProvider>
    </SafeAreaProvider>
  );
}

6. Create a Settings Screen

Create a file at src/screens/SettingsScreen.js:

import React from "react";
import { ScrollView, View, StyleSheet } from "react-native";
import { useTranslation } from "react-i18next";
import LanguageSwitcher from "../components/LanguageSwitcher";

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

  return (
    <ScrollView style={styles.container}>
      <View style={styles.section}>
        <LanguageSwitcher />
      </View>
      {/* Add other settings here */}
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
  },
  section: {
    padding: 16,
    borderBottomWidth: 1,
    borderBottomColor: "#eee",
  },
});

export default SettingsScreen;

7. Create Example Translation Files

Create a file at assets/locales/en.json:

{
  "home": {
    "welcome": "Welcome to our app",
    "description": "This is a multilingual React Native app"
  },
  "settings": {
    "title": "Settings",
    "language": "Select Language"
  },
  "features": {
    "title": "Features",
    "easy": "Easy to use",
    "fast": "Fast translation",
    "flexible": "Flexible integration"
  },
  "buttons": {
    "settings": "Settings",
    "back": "Back",
    "continue": "Continue"
  },
  "greeting": "Hello, {{name}}!"
}

Create a file at assets/locales/es.json:

{
  "home": {
    "welcome": "Bienvenido a nuestra aplicación",
    "description": "Esta es una aplicación React Native multilingüe"
  },
  "settings": {
    "title": "Configuración",
    "language": "Seleccionar Idioma"
  },
  "features": {
    "title": "CaracterĆ­sticas",
    "easy": "FƔcil de usar",
    "fast": "Traducción rÔpida",
    "flexible": "Integración flexible"
  },
  "buttons": {
    "settings": "Configuración",
    "back": "AtrƔs",
    "continue": "Continuar"
  },
  "greeting": "”Hola, {{name}}!"
}

Using Translations

In Function Components

Use the useTranslation hook in your functional components:

import React from "react";
import { View, Text, Button, StyleSheet } from "react-native";
import { useTranslation } from "react-i18next";

const HomeScreen = ({ navigation }) => {
  const { t } = useTranslation();

  return (
    <View style={styles.container}>
      <Text style={styles.title}>{t("home.welcome")}</Text>
      <Text style={styles.description}>{t("home.description")}</Text>

      <View style={styles.section}>
        <Text style={styles.sectionTitle}>{t("features.title")}</Text>
        <Text style={styles.feature}>• {t("features.easy")}</Text>
        <Text style={styles.feature}>• {t("features.fast")}</Text>
        <Text style={styles.feature}>• {t("features.flexible")}</Text>
      </View>

      <Text style={styles.greeting}>
        {t("greeting", { name: "React Native Developer" })}
      </Text>

      <Button
        title={t("buttons.settings")}
        onPress={() => navigation.navigate("Settings")}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center",
    padding: 20,
    backgroundColor: "#fff",
  },
  title: {
    fontSize: 24,
    fontWeight: "bold",
    marginBottom: 10,
  },
  description: {
    fontSize: 16,
    textAlign: "center",
    marginBottom: 20,
  },
  section: {
    width: "100%",
    marginBottom: 20,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: "bold",
    marginBottom: 10,
  },
  feature: {
    fontSize: 16,
    marginBottom: 5,
  },
  greeting: {
    fontSize: 18,
    marginVertical: 20,
  },
});

export default HomeScreen;

In Class Components

For class components, use the Higher Order Component (HOC) approach:

import React, { Component } from "react";
import { View, Text, StyleSheet } from "react-native";
import { withTranslation } from "react-i18next";

class ProfileScreen extends Component {
  render() {
    const { t } = this.props;

    return (
      <View style={styles.container}>
        <Text style={styles.title}>{t("profile.title")}</Text>
        {/* Rest of your component */}
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: "bold",
  },
});

export default withTranslation()(ProfileScreen);

Date and Number Formatting

For proper date and number formatting, you can use additional libraries:

import { format } from "date-fns";
import { enUS, es } from "date-fns/locale";

// Get the correct locale for date-fns
const getDateLocale = () => {
  const language = i18next.language || "en";

  switch (language) {
    case "es":
      return es;
    default:
      return enUS;
  }
};

// Format a date according to the current language
export const formatDate = (date, formatString = "PPP") => {
  return format(new Date(date), formatString, {
    locale: getDateLocale(),
  });
};

Handling Right-to-Left (RTL) Languages

If you plan to support RTL languages like Arabic or Hebrew:

import { View, Text, StyleSheet, I18nManager } from "react-native";
import { useContext } from "react";
import LanguageContext from "../localization/LanguageContext";

const RTLAwareComponent = () => {
  const { isRTL } = useContext(LanguageContext);

  return (
    <View
      style={[
        styles.container,
        { flexDirection: isRTL ? "row-reverse" : "row" },
      ]}
    >
      <Text>First Item</Text>
      <Text>Second Item</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    width: "100%",
    justifyContent: "space-between",
  },
});

Resources

For more detailed information, check out these resources: