Setup
इस पेज पर
This guide walks you through installing @better-i18n/expo and setting up i18next in your Expo or React Native app.
Installation #
npm install @better-i18n/expo i18next react-i18nextRecommended Optional Dependencies #
For offline caching and device locale detection:
npx expo install expo-localization- expo-localization - Enables device locale detection via
useDeviceLocale
For persistent storage, install one of:
# Fastest — MMKV (recommended)
npx expo install react-native-mmkv
# Most common — AsyncStorage
npx expo install @react-native-async-storage/async-storageimport i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import { initBetterI18n } from '@better-i18n/expo'; // [!code highlight]
i18n.use(initReactI18next); // [!code highlight]
// Start at module level — the promise is cached, so multiple imports won't re-run init
export const i18nReady = initBetterI18n({ // [!code highlight]
projectId: 'your-org/your-project', // [!code highlight]
i18n,
defaultLocale: 'en',
debug: __DEV__,
});
export default i18n;Offline-First (Recommended)
Pass a storageAdapter to enable offline caching and locale persistence. Translations are cached locally and the user's language choice survives app restarts — no "English flash".
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import { MMKV } from 'react-native-mmkv';
import { initBetterI18n, storageAdapter } from '@better-i18n/expo'; // [!code highlight]
const mmkv = new MMKV({ id: 'app' });
i18n.use(initReactI18next);
export const i18nReady = initBetterI18n({
projectId: 'your-org/your-project',
i18n,
storage: storageAdapter(mmkv, { localeKey: '@app:locale' }), // [!code highlight]
defaultLocale: 'en',
debug: __DEV__,
});
export default i18n;AsyncStorage
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { initBetterI18n, storageAdapter } from '@better-i18n/expo'; // [!code highlight]
i18n.use(initReactI18next);
export const i18nReady = initBetterI18n({
projectId: 'your-org/your-project',
i18n,
storage: storageAdapter(AsyncStorage, { localeKey: '@app:locale' }), // [!code highlight]
defaultLocale: 'en',
debug: __DEV__,
});
export default i18n;Initialize in App Entry #
Await i18nReady before rendering your app. Use SplashScreen to keep the native splash visible during init — the user sees no loading indicator at all.
Expo Router
import { useEffect, useState } from 'react';
import * as SplashScreen from 'expo-splash-screen';
import { Stack } from 'expo-router';
import { i18nReady } from '~/lib/i18n'; // [!code ++]
// Hold the splash screen until the app is ready
SplashScreen.preventAutoHideAsync(); // [!code ++]
export default function RootLayout() {
const [ready, setReady] = useState(false);
useEffect(() => { // [!code ++]
i18nReady.then(() => { // [!code ++]
setReady(true); // [!code ++]
SplashScreen.hideAsync(); // [!code ++]
}); // [!code ++]
}, []); // [!code ++]
if (!ready) return null; // [!code ++]
return <Stack />;
}Expo (No Router)
import { useEffect, useState } from 'react';
import * as SplashScreen from 'expo-splash-screen';
import { i18nReady } from './src/i18n'; // [!code ++]
import { HomeScreen } from './src/screens/HomeScreen';
// Hold the splash screen until the app is ready
SplashScreen.preventAutoHideAsync(); // [!code ++]
export default function App() {
const [ready, setReady] = useState(false);
useEffect(() => { // [!code ++]
i18nReady.then(() => { // [!code ++]
setReady(true); // [!code ++]
SplashScreen.hideAsync(); // [!code ++]
}); // [!code ++]
}, []); // [!code ++]
// SplashScreen is visible, so the user sees nothing during init
if (!ready) return null; // [!code ++]
return <HomeScreen />;
}Bare React Native
import { registerRootComponent } from 'expo';
import { i18nReady } from './src/i18n';
// The App component is only mounted after i18n is ready — no loading state needed
i18nReady.then(() => {
const { App } = require('./src/App');
registerRootComponent(App);
});Use Translations #
Use the standard react-i18next hooks — no changes to your components:
import { useTranslation } from 'react-i18next';
import { Text, View } from 'react-native';
export function HomeScreen() {
const { t } = useTranslation();
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text style={{ fontSize: 24 }}>{t('welcome')}</Text>
<Text>{t('description')}</Text>
</View>
);
}Language Picker Example
Use i18n.changeLanguage() to switch languages at runtime. Translations are pre-loaded before the switch — no loading spinners or English flash.
import { useTranslation } from 'react-i18next';
import { FlatList, Pressable, Text } from 'react-native';
import { getLanguages } from '@better-i18n/expo'; // [!code highlight]
export function LanguagePicker() {
const { i18n } = useTranslation();
return (
<FlatList
data={getLanguages()} // [!code highlight]
keyExtractor={(item) => item.code}
renderItem={({ item }) => (
<Pressable onPress={() => i18n.changeLanguage(item.code)}>
<Text>{item.nativeName ?? item.name}</Text>
</Pressable>
)}
/>
);
}Next Steps #
- Offline & Caching — Understand the caching strategy and offline support.
- Dynamic CFBundleLocalizations — Auto-sync iOS locale support with your CDN languages — no manual app.config updates.
- API Reference — Full API documentation for all exports.
Better I18N