In today’s globally connected world, offering multilingual support in your mobile app isn’t just a nice-to-have feature—it’s essential. By adding multiple language options, you increase your app’s reach, improve user satisfaction, and show cultural sensitivity. If you’re building with React Native, the good news is that implementing multilingual support is straightforward with the right tools.
In this blog, we’ll walk you through the process of implementing multilingual support in a React Native app.
Install the package using npm or yarn:
Using npm:
npm install react-native-localization
npm install react-redux
npm install redux
npm install redux-persist
npm install redux-saga
npm install @react-native-async-storage/async-storage
npm install react-native-modal
Using yarn:
yarn add react-native-localization
yarn add react-redux
yarn add redux
yarn add redux-persist
yarn add redux-saga
yarn add @react-native-async-storage/async-storage
yarn add react-native-modal
iOS Setup
Reinstall pod with
cd ios && pod install && cd ..
Example:
(1) You have implemented a modular system by defining language resources in the following structure:

/constants/lang/index.tsx
import LocalizedStrings from 'react-native-localization';
import EN from "./en";
import AR from './ar';
let strings = new LocalizedStrings({
EN,
AR
})
export default strings;
/constants/lang/ar.tsx
export default {
HOME: "الرئيسية",
PROFILE: "الملف الشخصي",
LOGIN: "تسجيل الدخول",
}
/constants/lang/en.tsx
export default {
HOME: "Home",
PROFILE: "Profile",
LOGIN: "Login",
}
/constants/langData/langData.tsx
export const langData: any = [
{
id: 1,
title: "English",
code: 'EN'
},
{
id: 2,
title: "Arabic",
code: 'AR'
},
]
(2) Create a lang modal and LeftTextRightImage
/components/LangModalCmp.tsx
import strings from 'app/constants/lang';
import { langData } from 'app/constants/langData/langData';
import React, { useEffect } from 'react';
import { View, Text, StyleSheet, SafeAreaView, I18nManager } from 'react-native';
import Modal from "react-native-modal";
import { useDispatch, useSelector } from 'react-redux';
import RNRestart from "react-native-restart";
import * as appData from "../store/actions/appData";
import { responsiveFontSize, responsiveHeight } from 'react-native-responsive-dimensions';
import LeftTextRightImage from './LeftTextRightImage';
const LangModalCmp = ({ setIsVisible, isVisible }: any) => {
const lang = useSelector((state: any) => state?.categoriDataReducer?.lang)
const dispatch = useDispatch();
const onPressLang = (lan: any) => {
setIsVisible(false)
if (lan == 'AR' && !langData == lan) {
strings.setLanguage(lan)
dispatch(appData.requestSelectedLangues(lan));
setTimeout(() => {
RNRestart.restart();
}, 400);
} else if (langData !== lan) {
strings.setLanguage(lan)
dispatch(appData.requestSelectedLangues(lan));
setTimeout(() => {
RNRestart.restart();
}, 400);
}
}
return (
<SafeAreaView>
<Modal
isVisible={isVisible}
style={{ justifyContent: 'flex-end', margin: 0, }}
onBackdropPress={() => setIsVisible(false)}
>
<View style={{
backgroundColor: 'white',
minHeight: responsiveHeight(6),
borderTopLeftRadius: 10,
borderTopRightRadius: 10,
padding: responsiveHeight(2)
}}>
<Text style={{
color: 'black',
fontWeight: 'bold',
fontSize: responsiveFontSize(2),
textTransform: 'capitalize',
marginBottom: responsiveHeight(1.5)
}}>{strings.CHOOSE_LANGUAGE}</Text>
{langData?.map((val: any, i: any) => {
return (
<LeftTextRightImage
key={String(i)}
text={val.name}
isSelected={lang == val?.isoCode}
onPress={() => onPressLang(val.isoCode)}
/>
)
})}
</View>
</Modal>
</SafeAreaView>
);
};
export default LangModalCmp;
/components/LeftTextRightImage.tsx
import AppStyles from 'app/config/styles';
import React, { Component } from 'react';
import { View, Text, StyleSheet, Image, TouchableOpacity } from 'react-native';
import { responsiveFontSize, responsiveHeight } from 'react-native-responsive-dimensions';
const LeftTextRightImage = ({
onPress = () => { },
isSelected,
text = '',
image = isSelected ? require('../assets/check.png') : require('../assets/unchecked.png'),
}: any) => {
return (
<TouchableOpacity
activeOpacity={0.7}
style={styles.horizontalView}
onPress={onPress}
>
<Text style={{
...styles.langTextStyle,
color: isSelected ? AppStyles().color.BTN_BG_COLOR : 'gray'
}}>{text}</Text>
<Image style={{ tintColor: isSelected ? AppStyles().color.BTN_BG_COLOR : 'gray',
height: responsiveHeight(3), width: responsiveHeight(3) }} source={image} />
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
langTextStyle: {
color: 'black',
fontSize: responsiveFontSize(1.8),
textTransform: 'capitalize',
},
horizontalView: {
flexDirection: "row",
justifyContent: 'space-between',
alignItems: 'center',
marginVertical: responsiveHeight(0.8),
}
});
export default LeftTextRightImage;
(3) Create a Redux to store the selected language
/store/index.tsx
import { createStore, compose, applyMiddleware } from 'redux';
import { persistStore, persistCombineReducers } from 'redux-persist';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { createLogger } from 'redux-logger';
import createSagaMiddleware from 'redux-saga';
import rootReducers from '../store/reducers';
import sagas from '../store/sagas';
const config = {
key: 'root',
storage: AsyncStorage,
blacklist: ['loadingReducer'],
debug: true,
};
const middleware = [];
const sagaMiddleware = createSagaMiddleware();
middleware.push(sagaMiddleware);
if (__DEV__) {
middleware.push(createLogger());
}
const reducers = persistCombineReducers(config, rootReducers);
const enhancers = [applyMiddleware(...middleware)];
const persistConfig: any = { enhancers };
export const store = createStore(reducers, undefined, compose(...enhancers));
const persistor = persistStore(store, persistConfig, () => {});
const configureStore = () => {
return { persistor, store };
};
sagaMiddleware.run(sagas);
export default configureStore;
/store/actions/types.tsx
export const LANGUAGE = 'LANGUAGE'
/store/actions/index.tsx
import * as selectCategoryActions from './appData';
export const ActionCreators = Object.assign(
{},
selectCategoryActions,
);
/store/actions/appData.tsx
import * as types from './types';
export function requestSelectedLangues(lang: any) {
return {
type: types.LANGUAGE,
lang,
};
}
/store/reducers/appDataReducer.tsx
import * as types from 'app/store/actions/types'
import createReducer from 'app/lib/createReducer';
const initialState: any = {
lang: 'EN',
};
export const categoriDataReducer = createReducer(initialState, {
[types.LANGUAGE](state: any, action: any) {
return {
...state,
lang: action.lang,
};
},
});
/store/reducers/index.tsx
import * as categoriDataReducer from './appDataReducer'
export default Object.assign(categoriDataReducer);
lib/createReducer.tsx
export default function createReducer(initialState: any, handlers: any) {
return function reducer(state = initialState, action: any) {
if (handlers.hasOwnProperty(action.type)) {
return handlers[action.type](state, action);
} else {
return state;
}
};
}
(4) App.tsx
import LangModalCmp from 'app/components/LangModalCmp';
import React, { useState } from 'react';
import { SafeAreaView, ScrollView, View, Pressable, Text, StyleSheet } from 'react-native';
const APP = () => {
const [isVisible, setIsVisible] = useState(false)
return (
<SafeAreaView style={styles.container}>
<LangModalCmp setIsVisible={setIsVisible}
isVisible={isVisible} />
<Pressable style={styles.loginbtn}>
<Text style={styles.text}>{strings.LOGIN}</Text>
</Pressable>
<Pressable onPress={() => setIsVisible(!isVisible)}
style={styles.langBtn}>
<Text style={styles.text}>Choose your
Language</Text>
</Pressable>
</SafeAreaView>
);
};
export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "white",
justifyContent: 'center',
alignItems: 'center'
},
loginbtn: {
height: responsiveHeight(5),
width: responsiveWidth(50),
backgroundColor: 'black',
borderRadius: 10,
justifyContent: 'center',
alignItems: 'center'
},
langBtn: {
height: responsiveHeight(5),
width: responsiveWidth(50),
marginVertical: responsiveHeight(1),
backgroundColor: 'black',
borderRadius: 10,
justifyContent: 'center',
alignItems: 'center'
},
text: {
color: 'white',
fontSize: responsiveFontSize(1.8)
}
})
(5) index.tsx
import React, { useEffect, useState } from 'react';
import { Provider, useDispatch, useSelector } from
'React-redux';
import configureStore from './store';
import strings from './constants/lang';
import * as appData from "./store/actions/appData";
const { persistor, store } = configureStore();
const index = () => {
useEffect(() => {
initiateLang()
}, []);
const dispatch = useDispatch()
const lang = useSelector((state: any) => state?.categoriDataReducer?.lang)
const initiateLang = async () => {
try {
if (!!lang) {
strings.setLanguage(lang)
dispatch(appData.requestSelectedLangues(lang));
}
} catch (error) {
console.log("no data found")
}
}
return (
<Provider store={store}>
<PersistGate loading={<ActivityIndicator />} persistor={persistor}>
<App/>
</PersistGate>
</Provider>
)}
export default index;
Output:

Conclusion:
Adding multilingual support to your React Native app can significantly improve user experience and expand your reach.
Share it with your React Native Developer friends.