5月28日 05:25

Expo 应用如何实现国际化?i18next 配置与 RTL 处理

Expo 国际化用 i18next + expo-localization,不要选 react-native-localize——它在 Expo Go 里直接报错,必须 eject 才能用。i18next 管翻译引擎(资源加载、变量插值、复数、语言切换),expo-localization 读设备语言和时区,两个配合才是正解。

核心流程:getLocales() 拿设备语言 → i18next 加载翻译资源 → useTranslation()t() 渲染文本。切换语言调 i18n.changeLanguage(),AsyncStorage 持久化偏好。i18next 的 init 必须在根组件渲染前执行——入口文件顶部 import 配置即可,否则子组件拿不到翻译,这是新手最常见的坑。

追问

i18next 和 expo-localization 分工是什么?能只用一个吗?

不能替代。expo-localization 是只读工具——告诉你设备语言是 zh-Hans、时区是 Asia/Shanghai,不碰翻译。i18next 才是翻译引擎:翻译资源管理、{{变量}} 插值、单复数(one item / {{count}} items)、命名空间拆分、运行时语言切换全归它管。一个读信息,一个做翻译,职责不重叠。

react-native-localize 比 expo-localization 好在哪?为什么不推荐?

react-native-localize 能拿更多信息:日历类型、温度单位、24 小时制开关、度量衡。但代价是依赖原生模块——Expo Go 拒绝加载自定义原生代码,import 就报错,必须 npx expo run:ios 跑开发构建或 eject 到 bare workflow。还在 Expo Go 阶段的项目别碰它;bare workflow 项目两个随便选。

Expo Router 里怎么做国际化?

根 layout 用 useTranslationt() 放在 options.tabBarLabeloptions.title 里,切换语言后组件重渲染、标签名自动变。关键约束:Expo Router 基于文件系统路由,路径名不能动态改,所以路由文件名保持英文(app/settings.tsx),展示文本走 t() 翻译。useSegments() 拿当前路由做翻译 key 映射也是常见做法。

RTL 语言(阿拉伯语、希伯来语)怎么办?

I18nManager.forceRTL(true) 开启 RTL,但要重启才生效——调 Updates.reloadAsync() 即可。样式必须用逻辑属性:marginStart/marginEnd 替代 marginLeft/marginRightpaddingStart/paddingEnd 替代左右内边距,textAlign'start' 不用 'left'。RTL 模式下布局自动翻转,零额外代码。忘了用逻辑属性的后果:文本翻了布局没翻,界面乱套。

翻译资源多了怎么组织?

5 种语言以内 JSON 文件放 locales/en.json,按功能分 key(auth.loginsettings.theme)。语言多了用 i18next 命名空间拆分:commonauthsettings 各一个 namespace,懒加载减少首屏体积。改文案不发版的场景上 i18next-http-backend 从 CDN 拉翻译 JSON,AsyncStorage 缓存离线兜底。翻译量大需要协作时,Lokalise 或 Crowdin 配合 i18next 官方同步插件。开发阶段开 saveMissing: true,缺失 key 自动打 console 警告;上线后 i18next-scanner 扫代码提取 key,和翻译文件做 diff 排查遗漏。

写段代码

typescript
// i18n.ts — 入口文件顶部 import import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; import { getLocales } from 'expo-localization'; i18n.use(initReactI18next).init({ resources: { en: { translation: { welcome: 'Welcome', hello: 'Hello, {{name}}!' } }, zh: { translation: { welcome: '欢迎', hello: '你好,{{name}}!' } }, }, lng: getLocales()[0]?.languageCode ?? 'en', fallbackLng: 'en', saveMissing: __DEV__, interpolation: { escapeValue: false }, }); // 组件 const { t, i18n } = useTranslation(); <Text>{t('hello', { name: '用户' })}</Text> <Button onPress={() => { i18n.changeLanguage('zh'); AsyncStorage.setItem('lang', 'zh'); }} title="中文" />
标签:Expo