Expo应用的国际化(i18n)是面向全球用户的重要功能。Expo支持多种国际化解决方案,使开发者能够轻松实现多语言支持。
国际化库选择:
- i18next
最流行的国际化库,功能强大且易于使用。
安装:
bashnpm install i18next react-i18next expo-localization
配置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;
使用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官方的本地化库,用于获取设备语言设置。
使用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; }
获取本地化信息:
typescriptfunction getLocalizationInfo() { const locale = Localization.locale; const timezone = Localization.timezone; const isoCurrencyCodes = Localization.isoCurrencyCodes; return { locale, timezone, currency: isoCurrencyCodes[0], }; }
- React Native Localization
轻量级的国际化解决方案。
安装:
bashnpm install react-native-localize
配置:
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', });
多语言资源管理:
- JSON文件结构
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": "最近活动" } }
- 命名空间组织
typescript// 使用命名空间 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'), }, }, }); // 使用命名空间 const { t } = useTranslation('common'); <Text>{t('ok')}</Text> // 跨命名空间 <Text>{t('auth:login')}</Text>
日期和时间本地化:
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, }); }
数字和货币本地化:
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(从右到左)支持:
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); // 需要重启应用 Updates.reloadAsync(); } }
动态语言切换:
typescriptfunction LanguageSwitcher() { const { i18n } = useTranslation(); const [currentLang, setCurrentLang] = useState(i18n.language); const changeLanguage = async (lang: string) => { await i18n.changeLanguage(lang); setCurrentLang(lang); // 保存用户偏好 await AsyncStorage.setItem('userLanguage', lang); // 如果需要RTL支持 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> ); }
最佳实践:
-
资源文件组织
- 按功能模块组织翻译文件
- 使用命名空间避免冲突
- 保持翻译文件结构一致
-
翻译质量
- 使用专业的翻译工具
- 考虑文化差异和习惯
- 定期审查和更新翻译
-
性能优化
- 按需加载语言包
- 缓存翻译结果
- 避免频繁的语言切换
-
用户体验
- 自动检测设备语言
- 提供语言切换选项
- 保存用户语言偏好
-
测试覆盖
- 测试所有语言的显示
- 检查文本溢出问题
- 验证RTL布局
常见问题:
-
翻译缺失
- 设置fallback语言
- 使用翻译管理工具
- 定期检查翻译完整性
-
文本溢出
- 使用flex布局
- 提供文本截断选项
- 为不同语言调整布局
-
动态加载
- 使用代码分割
- 懒加载语言包
- 预加载常用语言
通过完善的国际化实现,Expo应用可以更好地服务全球用户,提升用户体验和市场竞争力。