Internationalization (i18n) of Expo apps is an important feature for serving global users. Expo supports multiple internationalization solutions, enabling developers to easily implement multi-language support.
Internationalization Library Selection:
- i18next
The most popular internationalization library, powerful and easy to use.
Installation:
bashnpm install i18next react-i18next expo-localization
Configure i18next:
typescript// i18n.ts import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; import { getLocales } from 'expo-localization'; const resources = { en: { translation: { welcome: 'Welcome', login: 'Login', logout: 'Logout', 'hello.name': 'Hello, {{name}}!', }, }, zh: { translation: { welcome: '欢迎', login: '登录', logout: '退出', 'hello.name': '你好,{{name}}!', }, }, }; i18n .use(initReactI18next) .init({ resources, lng: getLocales()[0]?.languageCode || 'en', fallbackLng: 'en', interpolation: { escapeValue: false, }, }); export default i18n;
Use i18next:
typescriptimport { useTranslation } from 'react-i18next'; function WelcomeScreen() { const { t, i18n } = useTranslation(); const changeLanguage = (lang: string) => { i18n.changeLanguage(lang); }; return ( <View> <Text>{t('welcome')}</Text> <Text>{t('hello.name', { name: 'John' })}</Text> <Button title="English" onPress={() => changeLanguage('en')} /> <Button title="中文" onPress={() => changeLanguage('zh')} /> </View> ); }
- expo-localization
Expo's official localization library for getting device language settings.
Use expo-localization:
typescriptimport * as Localization from 'expo-localization'; function getDeviceLanguage() { const locale = Localization.locale; const languageCode = Localization.locale.split('-')[0]; console.log('Locale:', locale); console.log('Language:', languageCode); return languageCode; }
Get Localization Info:
typescriptfunction getLocalizationInfo() { const locale = Localization.locale; const timezone = Localization.timezone; const isoCurrencyCodes = Localization.isoCurrencyCodes; return { locale, timezone, currency: isoCurrencyCodes[0], }; }
- React Native Localization
Lightweight internationalization solution.
Installation:
bashnpm install react-native-localize
Configuration:
typescriptimport * as RNLocalize from 'react-native-localize'; const translations = { en: require('./en.json'), zh: require('./zh.json'), }; const fallback = { languageTag: 'en', isRTL: false }; const { languageTag } = RNLocalize.findBestAvailableLanguage(Object.keys(translations)) || fallback; i18n.init({ resources: { [languageTag]: translations[languageTag], }, lng: languageTag, fallbackLng: 'en', });
Multi-language Resource Management:
- JSON File Structure
json// locales/en.json { "common": { "ok": "OK", "cancel": "Cancel", "save": "Save" }, "auth": { "login": "Login", "logout": "Logout", "register": "Register", "forgotPassword": "Forgot Password?" }, "home": { "title": "Home", "welcome": "Welcome back!", "recentActivity": "Recent Activity" } }
json// locales/zh.json { "common": { "ok": "确定", "cancel": "取消", "save": "保存" }, "auth": { "login": "登录", "logout": "退出", "register": "注册", "forgotPassword": "忘记密码?" }, "home": { "title": "首页", "welcome": "欢迎回来!", "recentActivity": "最近活动" } }
- Namespace Organization
typescript// Use namespaces i18n.init({ resources: { en: { common: require('./locales/en/common.json'), auth: require('./locales/en/auth.json'), home: require('./locales/en/home.json'), }, zh: { common: require('./locales/zh/common.json'), auth: require('./locales/zh/auth.json'), home: require('./locales/zh/home.json'), }, }, }); // Use namespace const { t } = useTranslation('common'); <Text>{t('ok')}</Text> // Cross namespace <Text>{t('auth:login')}</Text>
Date and Time Localization:
typescriptimport { format } from 'date-fns'; import { zhCN, enUS } from 'date-fns/locale'; function formatDate(date: Date, locale: string) { const localeMap = { en: enUS, zh: zhCN, }; return format(date, 'PPP', { locale: localeMap[locale] || enUS, }); } function formatDateTime(date: Date, locale: string) { return format(date, 'PPPppp', { locale: locale === 'zh' ? zhCN : enUS, }); }
Number and Currency Localization:
typescriptfunction formatCurrency(amount: number, locale: string) { return new Intl.NumberFormat(locale, { style: 'currency', currency: locale === 'zh' ? 'CNY' : 'USD', }).format(amount); } function formatNumber(number: number, locale: string) { return new Intl.NumberFormat(locale).format(number); } function formatPercent(value: number, locale: string) { return new Intl.NumberFormat(locale, { style: 'percent', minimumFractionDigits: 2, }).format(value); }
RTL (Right-to-Left) Support:
typescriptimport { I18nManager } from 'react-native'; function setupRTL(locale: string) { const isRTL = locale === 'ar' || locale === 'he'; if (I18nManager.isRTL !== isRTL) { I18nManager.allowRTL(isRTL); I18nManager.forceRTL(isRTL); // Need to restart app Updates.reloadAsync(); } }
Dynamic Language Switching:
typescriptfunction LanguageSwitcher() { const { i18n } = useTranslation(); const [currentLang, setCurrentLang] = useState(i18n.language); const changeLanguage = async (lang: string) => { await i18n.changeLanguage(lang); setCurrentLang(lang); // Save user preference await AsyncStorage.setItem('userLanguage', lang); // If RTL support needed setupRTL(lang); }; const languages = [ { code: 'en', name: 'English' }, { code: 'zh', name: '中文' }, { code: 'es', name: 'Español' }, ]; return ( <View> {languages.map((lang) => ( <Button key={lang.code} title={lang.name} onPress={() => changeLanguage(lang.code)} disabled={currentLang === lang.code} /> ))} </View> ); }
Best Practices:
-
Resource File Organization
- Organize translation files by feature modules
- Use namespaces to avoid conflicts
- Keep translation file structure consistent
-
Translation Quality
- Use professional translation tools
- Consider cultural differences and habits
- Regularly review and update translations
-
Performance Optimization
- Load language packages on demand
- Cache translation results
- Avoid frequent language switching
-
User Experience
- Auto-detect device language
- Provide language switching options
- Save user language preferences
-
Test Coverage
- Test display in all languages
- Check for text overflow issues
- Verify RTL layouts
Common Issues:
-
Missing Translations
- Set fallback language
- Use translation management tools
- Regularly check translation completeness
-
Text Overflow
- Use flex layout
- Provide text truncation options
- Adjust layout for different languages
-
Dynamic Loading
- Use code splitting
- Lazy load language packages
- Preload common languages
Through comprehensive internationalization implementation, Expo apps can better serve global users and improve user experience and market competitiveness.