为什么鸿蒙 RN 项目绕不开 I18nManager
做阿拉伯语、希伯来语时,React Native 的 I18nManager 基本躲不掉。它负责判断和控制布局方向。鸿蒙侧基于 RNOH,RTL 表现与 Android 接近,但调试入口、API 生效时机和持久化有差异。本文基于 React Native 0.84 + RNOH 0.84.1,鸿蒙设备为 HarmonyOS 6.0,按实测整理。
核心 API 与生效时机
I18nManager 常用成员包括 isRTL、forceRTL(boolean)、allowRTL(boolean)、swapLeftAndRightInRTL(boolean)、doLeftAndRightSwapInRTL。- import { I18nManager } from 'react-native';
- const isRTL = I18nManager.isRTL;
- I18nManager.forceRTL(true);
- I18nManager.allowRTL(true);
- I18nManager.swapLeftAndRightInRTL(true);
复制代码
isRTL 用于动态调整 UI:- const isRTL = I18nManager.isRTL;
- const sortIcon = isRTL ? 'sort-descending' : 'sort-ascending';
- const arrowIcon = isRTL ? 'arrow-left' : 'arrow-right';
- const align = isRTL ? 'right' : 'left';
复制代码 鸿蒙上取值逻辑与 Android 类似:forceRTL 为 true 时返回 true;allowRTL 为 false 时返回 false;否则看系统语言。但鸿蒙上系统切到阿拉伯语后,isRTL 可能不会自动变成 true。我在鸿蒙平板试过,把系统语言改成阿拉伯语并重启应用后,isRTL 才更新。原因是 isRTL 在应用启动时初始化一次,不会动态变化。
forceRTL 是开发调试入口,但重启后才生效,通常配合 DevSettings.reload:- import { I18nManager, DevSettings } from 'react-native';
- if (__DEV__) {
- DevSettings.addMenuItem('切换 RTL 模式', () => {
- I18nManager.forceRTL(!I18nManager.isRTL);
- DevSettings.reload();
- });
- }
复制代码 注意 forceRTL 忘删会提交测试分支,界面会全反。建议用 __DEV__ 包裹。
allowRTL 决定是否允许 RTL。没做 RTL 适配但语言列表包含阿拉伯语时,布局可能自动进入 RTL,UI 会非常怪。要么 allowRTL(false),要么认真做适配。
swapLeftAndRightInRTL 控制 RTL 下 left/right 是否自动交换:- I18nManager.swapLeftAndRightInRTL(true);
- const swapEnabled = I18nManager.doLeftAndRightSwapInRTL;
复制代码 鸿蒙上交换行为与 Android 接近,但某些自定义 View 里 left/right 不会被交换,需要手动处理。
常见布局错误与修复
第一种是绝对定位写死 left。错误写法:- const BackButton = ({ onPress }) => {
- return (
- <View style={{ position: 'absolute', left: 16, top: 16 }}>
- <Text onPress={onPress}>← 返回</Text>
- </View>
- );
- };
复制代码 RTL 下箭头应该在右边指向左,但位置和箭头方向都没变。修复:- const BackButton = ({ onPress }) => {
- const isRTL = I18nManager.isRTL;
- return (
- <View style={{ position: 'absolute', [isRTL ? 'right' : 'left']: 16, top: 16 }}>
- <Text onPress={onPress}>{isRTL ? '→ ' : '← '}返回</Text>
- </View>
- );
- };
复制代码 开启 swapLeftAndRightInRTL 后 left 会自动变 right,但它只交换 left/right,不会反转箭头符号,箭头仍需手动控制。
第二种是 Text 组件忘记处理文本方向。阿拉伯语商品名开头字符不见,就是 textAlign 默认 left 导致文字被左边裁剪。修复:- const ProductCard = ({ title, price }) => {
- const isRTL = I18nManager.isRTL;
- return (
- <View>
- <Text style={{ textAlign: isRTL ? 'right' : 'left' }}>{title}</Text>
- <Text style={{ textAlign: isRTL ? 'right' : 'left' }}>{price}</Text>
- </View>
- );
- };
复制代码 数字也要注意:阿拉伯语数字 ٠١٢٣٤٥٦٧٨٩ 与英文数字不同,混排时顺序可能乱,建议数字单独放 Text。
第三种是动画方向。滑入动画要按 RTL 反转:- const translateX = anim.interpolate({
- inputRange: [0, 1],
- outputRange: isRTL ? [-100, 0] : [100, 0],
- });
复制代码 漏掉动画时,侧边栏从左侧滑入,在 RTL 下会特别别扭。
列表加载更多也要适配:- const LoadMoreFooter = ({ loading }) => {
- const isRTL = I18nManager.isRTL;
- return (
- <View style={{ flexDirection: isRTL ? 'row-reverse' : 'row', justifyContent: 'center', padding: 16 }}>
- {loading && <ActivityIndicator size='small' />}
- <Text style={{ marginHorizontal: 8 }}>
- {isRTL ? 'تحميل المزيد' : '加载更多'}
- </Text>
- </View>
- );
- };
复制代码
鸿蒙调试 RTL 的替代方案
鸿蒙开发者选项里没有强制 RTL 开关,Android 上是有的。可以在 App 入口加调试入口,连续点击标题 5 次切换:- if (__DEV__) {
- const RTLDebugger = () => {
- const [count, setCount] = useState(0);
- const handleTitlePress = () => {
- const next = count + 1;
- setCount(next);
- if (next >= 5) {
- setCount(0);
- I18nManager.forceRTL(!I18nManager.isRTL);
- DevSettings.reload();
- }
- setTimeout(() => setCount(0), 3000);
- };
- return (
- <Pressable onPress={handleTitlePress}>
- <Text>点击 5 次切换 RTL</Text>
- </Pressable>
- );
- };
- }
复制代码 这个方法不用摇一摇,也不用改系统设置,测试时效率更高。
鸿蒙上的坑要单独记一下:
1. isRTL 不随系统语言实时更新。应用启动时初始化一次,监听 AppState 也不会让它变。
2. forceRTL 在鸿蒙上可能不持久化。调用后关闭应用再打开,设置可能丢失。原文实践中最后建议用系统语言切换来测试 RTL。
3. doLeftAndRightSwapInRTL 在某些鸿蒙版本可能返回 undefined。可给默认值:- const swapEnabled = I18nManager.doLeftAndRightSwapInRTL ?? true;
复制代码 0.84 版本遇到过,升级到 0.84.1 后恢复。
4. writingDirection 样式在鸿蒙上可能不生效。解决办法是用 textAlign + flexDirection 实现 RTL 效果。
平台差异可以这样看:iOS 的 RTL 最好,系统语言切换后 isRTL 能正确更新,Xcode localization 完善,Text 的 writingDirection 可用;Android 开发者选项可强制 RTL,isRTL 需重启更新,left/right swap 与鸿蒙差不多;鸿蒙没有开发者选项强制 RTL,forceRTL 可能不持久化,writingDirection 不支持,整体最弱。但鸿蒙对 RTL 的需求场景本身少。如果产品面向中东,重点做好 iOS 和 Android,鸿蒙保证基础功能可用。
服务端下发语言与 RTL 判断
语言包从服务端下发时,isRTL 判断要跟配置同步:- type LangConfig = {
- locale: string;
- direction: 'ltr' | 'rtl';
- messages: Record<string, string>;
- };
- const applyLanguage = (config: LangConfig) => {
- if (config.direction === 'rtl') {
- I18nManager.forceRTL(true);
- } else {
- I18nManager.forceRTL(false);
- }
- AsyncStorage.setItem('@locale', config.locale);
- DevSettings.reload();
- };
复制代码 应用内切换语言后,isRTL 要重启才能更新。建议在语言切换页提示用户重启应用。
可复用的 RTL 组件封装
RTLRow:- const RTLRow = ({ children, style }) => {
- const isRTL = I18nManager.isRTL;
- return (
- <View style={[{ flexDirection: isRTL ? 'row-reverse' : 'row', alignItems: 'center' }, style]}>
- {children}
- </View>
- );
- };
复制代码 RTLText:- const RTLText = ({ children, style }) => {
- const isRTL = I18nManager.isRTL;
- return (
- <Text style={[{ textAlign: isRTL ? 'right' : 'left', writingDirection: isRTL ? 'rtl' : 'ltr' }, style]}>
- {children}
- </Text>
- );
- };
复制代码 ArrowIcon:- const ArrowIcon = ({ direction = 'right' }) => {
- const isRTL = I18nManager.isRTL;
- const iconName = isRTL
- ? (direction === 'right' ? 'left' : 'right')
- : direction;
- return <Icon name={`arrow-${iconName}`} size={20} />;
- };
复制代码 用 flexDirection: 'row-reverse' 不只是改位置,元素排列顺序也会跟着变,这才是完整的 RTL 适配。
完整商品页示例中,导航栏、列表项、底部按钮的 flexDirection 都按 isRTL 切换,箭头方向手动反转,Text 加 textAlign。原文代码已在鸿蒙设备测试通过。
踩坑总结
- isRTL 不会动态更新:启动初始化,改系统语言后重启才变。
- forceRTL 在鸿蒙上可能不持久化:调用后关闭再打开可能丢失。
- doLeftAndRightSwapInRTL 可能返回 undefined:给默认值。
- writingDirection 不生效:用 textAlign + flexDirection 替代。
- 箭头方向不会自动反转:根据 isRTL 手动控制。
- 鸿蒙 RTL 适配建议:用 flexDirection: 'row-reverse' 代替 left/right。
- forceRTL 用完记得删,不要提交生产包。
- 测试要包含 LTR 和 RTL 两个模式。
- 设计阶段就要考虑 RTL,否则返工成本高。原文项目前期没考虑,后来花了两周重构布局。
本文基于 React Native 0.84 + RNOH 0.84.1,鸿蒙设备为 HarmonyOS 6.0。不同版本之间可能存在差异,以实际测试结果为准。 |