react native(expo)多语言适配

项目基于 expo框架 开发。请先配置好 expo 开发环境

1.引入i18n-js

复制代码
npx expo install i18n-js

2.新建languages文件夹,其中包括英文、中文等语种目录。结构如下:

*.json文件为语种翻译后的json键值对,用于UI中引用;

复制代码
{   
    "appName": "xxxx",
    "appVersion": "xxxx",
    "Login": {
        "login": "登录"
    },
    "Register": {
        "register": "注册"
    },
    "PaymentWay": {
      "alipay": "支付宝",
      "wxpay": "微信支付",
      "bank": "银行卡",
      "Instant": "即时支付",
      "reviewing": "审核中",
      "ReviewRejected": "审核拒绝",
      "ReviewSuccess": "审核通过",
      "walletAddress": "钱包地址",
      "walletDetail": "钱包详情",
      "info1": "买家将直接使用您选择的收款方式付款。交易时,请始终检查您的收款账户以确认您已收到全额付款。",
      "info2": "请确保您设置的账户为本人实名账户,非本人实名账户付款会导致订单失败且账号被冻结。",
      "PaymentTerms": "收付款方式",
      "PaymentTermsDetail": "收付款方式详情",
      "SelectPaymentTerms": "选择收付款方式",
      "SelectIncomeTerms": "请选择收款方式",
      "IncomeTerms": "选择收款方式",
      "selectCurrency": "请选择货币",
      "Currency": "选择货币",
      "NotImgMessage": "未读取到图片信息",
      "realName": "请输入真实姓名",
      "Name": "姓名",
      "Account": "账号",
      "AccountLimit": "账号长度必须在%{min_limit}-%{max_limit}之间",
      "selectedPaymentWayTips": "请先选择收付款方式",
      "PaymentTermsInfo": "请补全收付款方式信息",
      "bankName": "请输入银行名称",
      "bankName2": "银行名称",
      "openBankName": "请输入开户行",
      "openBankName2": "开户行",
      "qrcode": "请上传二维码",
      "qrcode2": "二维码",
      "addPaymentTerms": "添加收付款账号",
      "info3": "某些支付方式可能会有支付服务提供方设定的手续费和每日限额,请联系支付服务提供方了解详情。",
      "edit": "完成修改",
      "add": "完成添加"
    }
}

languages下的index.js配置如下:

复制代码
import {getLocales} from "expo-localization";
import {I18n} from "i18n-js";
import {enStringJson} from "./en";
import {jaStringJson} from "./japanese";
import {zhStringJson} from "./zh";
import LogUtil from "../../utils/log_util";
import {zhTwStringJson} from "./zh_tw";
import {esStringJson} from "./es";
import AsyncStorage from "@react-native-async-storage/async-storage";

// Set the key-value pairs for the different languages you want to support.
const translations = {
    'en': enStringJson,
    'en-US': enStringJson,
    // ja: jaStringJson,
    'zh': zhStringJson,
    'zh-CN': zhStringJson,
    'zh-Hans-CN': zhStringJson,
    'zh-TW': zhTwStringJson,
    'zh-Hant-TW': zhTwStringJson,
    'zh-HK': zhTwStringJson,
    'zh-SG': zhTwStringJson,
    'es': esStringJson,
    'es-ES': esStringJson,
    'es-AD': esStringJson,
    'es-AR': esStringJson,
    'es-US': esStringJson,
    'es-MX': esStringJson,
    'es-GT': esStringJson,
};
export const i18n = new I18n(translations);

export async function initI18n() {
    let res = await AsyncStorage.getItem('language');
    if (res) {
        i18n.locale = res;
    } else {
        // Set the locale once at the beginning of your app.
        let languageTag = getLocales()[0].languageTag;
        if (translations[languageTag]) {
            i18n.locale = languageTag;
            await AsyncStorage.setItem('language', languageTag);
        } else {
            i18n.locale = 'en-US';
            await AsyncStorage.setItem('language', 'en-US');
        }
    }

    // When a value is missing from a language it'll fall back to another language with the key present.
    i18n.enableFallback = true;
    // To see the fallback mechanism uncomment the line below to force the app to use the Japanese language.
    // i18n.locale = 'ja';

    LogUtil.log('initI18n languageCode = ', getLocales()[0].languageCode, '', getLocales()[0].languageTag);

    return i18n.locale;
}

3.在app.js中初始化引用

复制代码
import {Provider, Toast} from "@ant-design/react-native";
import VConsole from '@kafudev/react-native-vconsole';
import * as Font from 'expo-font';
import { useEffect, useState } from "react";
import { StatusBar, View } from 'react-native';
import Loading from "./components/ui/Loading";
import Navigations from './navigations';
import {initI18n} from "./assets/languages";
import {JPushInit} from "./utils/jpush_utils";
import { eventBus } from "./utils/eventbus_util";
import LogUtil from "./utils/log_util";
import { EVENT } from "./constants/event";

import enUS from '@ant-design/react-native/lib/locale-provider/en_US'
import zhCN from '@ant-design/react-native/lib/locale-provider/zh_CN'

export default () => {
    StatusBar.setBarStyle('dark-content')
    const [isReady, setReady] = useState(false);
    let [currentLocale, setCurrentLocale] = useState();

    useEffect(() => {
        loadAtdFont().then(res => { });
    }, []);

    useEffect(() => {
        let listener = eventBus.addListener(EVENT.GLOBAL_REFRESH_LANGUAGE, (args) => {
            LogUtil.log('application GLOBAL_REFRESH_LANGUAGE args', args);
            setCurrentLocale(args?.language?.includes('zh') ? zhCN : enUS);
        });
        return () => {
            listener.remove()
        }
    }, [])

    const loadAtdFont = async () => {
        // 初始化JPush
        // JPushInit();

        // initial多语言
        const localTag = await initI18n();

        setCurrentLocale(localTag.includes('zh') ? zhCN : enUS);

        await Font.loadAsync(
            'antoutline',
            // eslint-disable-next-line
            require('@ant-design/icons-react-native/fonts/antoutline.ttf')
        );

        await Font.loadAsync(
            'antfill',
            // eslint-disable-next-line
            require('@ant-design/icons-react-native/fonts/antfill.ttf')
        );

        // 吐司全局配置
        Toast.config({ duration: 1.5 });

        // eslint-disable-next-line
        setReady(true);
    }

    return !isReady ? (
        <Loading />
    ) : (<Provider theme={{}} locale={currentLocale}>
            <View style={{ flex: 1 }}>

                <StatusBar translucent={true} backgroundColor="rgba(0, 0, 0, 0)" />
                <Navigations />
                {process.env.EXPO_PUBLIC_ConsoleFetch ? (
                    <VConsole
                        // 使用 'react-native-config-reader' 库获获取额外信息
                        appInfo={{
                            原生构建类型: 'all',
                            原生版本号: '0.0.1',
                            原生构建时间: 'none',
                            热更新版本号: '00001',
                            热更新详情: 'UI更新',
                        }}
                        // 另外的的面板
                        // panels={panels}
                        // console.time 可辨别是否开启 debug 网页
                        console={process.env.EXPO_PUBLIC_ConsoleFetch ? !console.time : true}
                    />
                ) : null}
            </View>
        </Provider>
    );
}  

4.代码中引用

复制代码
i18n.t('appName');
i18n.t('Login.login');
i18n.t('PaymentWay.AccountLimit', { min_limit: 10, max_limit: 100 });

5.多语言切换功能

5.1 引入ant 适用于react native的UI库

复制代码
@ant-design/react-native
@react-native-async-storage/async-storage

新建eventBus、EVENT类用于切换语种后通知页面刷新

复制代码
import RCTDeviceEventEmitter from 'react-native/Libraries/EventEmitter/RCTDeviceEventEmitter';
export const eventBus = RCTDeviceEventEmitter;

export const EVENT = {
    GLOBAL_REFRESH_LANGUAGE: 'GLOBAL_REFRESH_LANGUAGE',
}

新建AntPopup组件用于弹窗选择语种

复制代码
import react from "react";
import {scaleSize, screenH} from "../../utils/screen_util";
import {Modal} from "@ant-design/react-native";
import React from "react";
import TextWrapper from "./TextWrapper";
import stl from "../../stl";
import ViewWrapper from "./ViewWrapper";

const AntPopup = (props) => {
    return (
        <Modal
            style={{
                borderTopStartRadius: scaleSize(6),
                borderTopEndRadius: scaleSize(6),
            }}
            popup={props.popup ?? true}
            transparent={props.transparent ?? false}
            maskClosable={true}
            visible={props.showPopup}
            animationType="slide-up"
            onClose={props.onClose}
            onRequestClose={props.onRequestClose}
        >
            <ViewWrapper style={[{ maxHeight: screenH / 2, paddingVertical: scaleSize(20), paddingHorizontal: 0, alignItems: 'center'}, props.style]}>
                {props.title ? (
                    <TextWrapper style={[stl.fontSize19, stl.FC626262, stl.MB27]}>
                        {props.title}
                    </TextWrapper>
                ) : null}
                {props.children}
            </ViewWrapper>
        </Modal>
    );
}
export default react.memo(AntPopup);

5.2 新建语种切换页面 AnotherSettingScreen

复制代码
import ViewWrapper from "../../../components/ui/ViewWrapper";
import HeaderView2 from "../../../components/HeaderView2";
import NavPaddingGray from "../../../components/ui/NavPaddingGray";
import ArrowLineStyle from "../../../components/ui/ArrowLineStyle";
import React, {useEffect} from "react";
import {scaleSize} from "../../../utils/screen_util";
import AntPopup from "../../../components/ui/AntPopup";
import stl from "../../../stl";
import {Radio} from "@ant-design/react-native";
import ImageWrapper from "../../../components/ui/ImageWrapper";
import TextWrapper from "../../../components/ui/TextWrapper";
import LogUtil from "../../../utils/log_util";
import {i18n} from "../../../assets/languages";
import {getLocales, useLocales} from "expo-localization";
import AsyncStorage from "@react-native-async-storage/async-storage";
import {View} from "react-native";
import {eventBus} from "../../../utils/eventbus_util";
import {EVENT} from "../../../constants/event";
import { APP_VERSION_CODE } from "../../../constants/common";
import Loading from "../../../components/ui/Loading";

const AnotherSettingScreen = ({route, navigation}) => {
    const languageOptions = {
        type: 'language',
        title: i18n.t('MyRoute.anotherSetting.selectLang'),
        list: [
            {icon: '', value: 'zh-Hans-CN', label: i18n.t('MyRoute.anotherSetting.zh'), desc: ''},
            {icon: '', value: 'zh-Hant-TW', label: i18n.t('MyRoute.anotherSetting.zh-tw'), desc: ''},
            {icon: '', value: 'en-US', label: i18n.t('MyRoute.anotherSetting.en-US'), desc: ''},
            // {icon: '', value: 'es-ES', label: i18n.t('MyRoute.anotherSetting.es-ES'), desc: ''}
        ]
    }

    const [showPopup, setShowPopup] = React.useState(false);
    const [selectValue, setSelectValue] = React.useState();
    const [currentLanguage, setCurrentLanguage] = React.useState();

    useEffect(() => {
        AsyncStorage.getItem('language').then(res => {
            LogUtil.log('AsyncStorage.getItem language = ', res);
            if (res) {
                setSelectValue(res);
                setCurrentLanguage(formatCurrentLanguage(res));
            }
        })
    }, []);

    useLocales();

    function formatCurrentLanguage(value) {
        LogUtil.log('formatCurrentLanguage value = ', value);
        return value === 'zh-Hans-CN' ? i18n.t('MyRoute.anotherSetting.zh') : (value === 'zh-Hant-TW' ? i18n.t('MyRoute.anotherSetting.zh-tw') : (value === 'en-US' ? i18n.t('MyRoute.anotherSetting.en-US') : (value === 'es-ES' ? i18n.t('MyRoute.anotherSetting.es-ES') : null)));
    }

    function handleChangeLang() {
        setShowPopup(true);
    }

    function handleUpdateAppVersion() {

    }

    const onChange = async (event, type) => {
        LogUtil.log('radio checked', event.target.value);
        // let eventToJson = JSON.parse(event.target.value);
        Loading.show();
        await AsyncStorage.setItem('language', event.target.value).then(r => { });
        i18n.locale = event.target.value;
        setSelectValue(event.target.value);
        setCurrentLanguage(formatCurrentLanguage(event.target.value));
        eventBus.emit(EVENT.GLOBAL_REFRESH_LANGUAGE, { language: event.target.value });
        Loading.hide();
        setShowPopup(false);
    }

    return (
        <ViewWrapper>
            <HeaderView2 title={i18n.t('MyRoute.anotherSetting.setting')}/>
            <NavPaddingGray/>
            <ArrowLineStyle
                style={{backgroundColor: "white", paddingHorizontal: scaleSize(20.5)}}
                title={i18n.t('MyRoute.anotherSetting.switchLang')}
                subTitle={currentLanguage}
                onPress={() => {
                    handleChangeLang();
                }}
            />
            <ArrowLineStyle
                style={{backgroundColor: "white", paddingHorizontal: scaleSize(20.5)}}
                title={i18n.t('MyRoute.anotherSetting.updateApp')}
                subTitle={i18n.t('version') + `:${APP_VERSION_CODE}`}
                onPress={() => {
                    handleUpdateAppVersion();
                }}
            />
            <AntPopup
                title={languageOptions.title}
                showPopup={showPopup}
                onClose={() => {
                    setShowPopup(false)
                }}
                onRequestClose={() => {
                    setShowPopup(false);
                    return true;
                }}>
                <ViewWrapper style={[stl.widthPercent100]}>
                    <>
                        <Radio.Group
                            styles={{checkbox_label: {fontSize: scaleSize(15), color: '#626262'}}}
                            // options={options}
                            onChange={onChange}
                            value={selectValue}>
                            {languageOptions?.list?.map((option, index) => (
                                <Radio.RadioItem 
                                key={index} 
                                    styles={{
                                        Line: { paddingRight: scaleSize(4) },
                                        Item: { paddingLeft: scaleSize(0) },
                                        Content: { paddingLeft: scaleSize(20) },
                                    }}
                                value={option.value}
                                >
                                    <View style={{ flexDirection: 'row', alignItems: 'center' }}>
                                        {option.icon ? (
                                            <ImageWrapper
                                                style={{
                                                    width: scaleSize(16),
                                                    height: scaleSize(16),
                                                }}
                                                source={option.icon}
                                            />
                                        ) : null}
                                        <ViewWrapper style={{width: scaleSize(12)}}/>
                                        <TextWrapper style={[stl.fontSize15, stl.FC626262]}>{option.label}</TextWrapper>
                                        <ViewWrapper style={{width: scaleSize(12)}}/>
                                        <TextWrapper style={[stl.fontSize12, stl.FCB2B2B2]}>{option.desc}</TextWrapper>
                                    </View>
                                </Radio.RadioItem>
                            ))}
                        </Radio.Group>
                    </>
                </ViewWrapper>
            </AntPopup>
        </ViewWrapper>
    );
}
export default AnotherSettingScreen;

5.3 新建一个测试页面 mainPage用于验证切换语种后文案刷新

复制代码
import React, { useEffect, useState } from 'react';

const MainPageScreen = ({route, navigation}) => {

    let [fresh, setFresh] = useState(1);

    useEffect(() => {
        let listener = eventBus.addListener(EVENT.GLOBAL_REFRESH_LANGUAGE, (args) => {
            LogUtil.log('MainPageScreen GLOBAL_REFRESH_LANGUAGE args', args);
            // 切换了语种,通知页面刷新
            setFresh(prevState => prevState + 1);
        });
        return () => {
            listener.remove()
        }
    }, [])

    return (
        <View>
            <Text>{ i18n.t('appName') }</Text>
            <Text>{ i18n.t('Login.login') }</Text>
            <Text>{ i18n.t('PaymentWay.AccountLimit', { min_limit: 10, max_limit: 100 }) }</Text>
            <Button onPress={()=>{
                navigation.push('AnotherSettingScreen');
            }}>
                跳语种切换页面
            </Button>
        </View>
    );
}
相关推荐
JSON_L14 分钟前
Vue 电影导航组件
前端·javascript·vue.js
爱编程的喵1 小时前
深入理解JSX:从语法糖到React的魔法转换
前端·react.js
xptwop2 小时前
05-ES6
前端·javascript·es6
海底火旺2 小时前
单页应用路由:从 Hash 到懒加载
前端·react.js·性能优化
Heo2 小时前
调用通义千问大模型实现流式对话
前端·javascript·后端
前端小巷子3 小时前
深入 npm 模块安装机制
前端·javascript·面试
深职第一突破口喜羊羊4 小时前
记一次electron开发插件市场遇到的问题
javascript·electron
cypking4 小时前
electron中IPC 渲染进程与主进程通信方法解析
前端·javascript·electron
西陵4 小时前
Nx带来极致的前端开发体验——借助playground开发提效
前端·javascript·架构
江城开朗的豌豆4 小时前
Element UI动态组件样式修改小妙招,轻松拿捏!
前端·javascript·vue.js