react native(expo)多语言适配

news2024/12/23 10:26:57

项目基于 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>
    );
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2134870.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C语言】(指针系列3)数组指针+函数指针+typedef+函数数组指针+转移表

前言&#xff1a;前言&#xff1a;开始之前先感谢一位大佬&#xff0c;清风~徐~来-CSDN博客&#xff0c;由于是时间久远&#xff0c;博主指针的系列忘的差不多了&#xff0c;所以有顺序部分借鉴了该播主的&#xff0c;同时也加入了博主自己的理解&#xff0c;有些地方如果解释的…

MySQL语句案例编写复习

先看我的表数据和结构 1.查询年龄为16,17,18,19岁的女性员工信息。 select * from emp where gender 女 and age in(16,17,18,19); 2.查询性别为 男 &#xff0c;并且年龄在 20-40 岁(含)以内的姓名为三个字的员工。 select * from emp where gender 男 and age between …

猫罐头多久喂一次?营养健康的罐头推荐

一&#xff0e;猫罐头多久喂一次 猫咪长期只食用干粮&#xff0c;容易饮水不足&#xff0c;从而引发上尿道或膀胱结石、堵塞等问题&#xff0c;所以最好每周喂至少2个猫罐头&#xff0c;帮助猫咪补充水分。如果条件允许&#xff0c;全罐喂养&#xff0c;每天都给猫咪吃猫罐头是…

车机中 Android Audio 音频常见问题分析方法实践小结

文章目录 前言1. 无声2. 断音3. 杂音4. 延迟播放5. 焦点问题6. 无声问题(连上 BT )其他完善中…… 前言 本文主要总结了一下车机开发中遇到的 Audio 有关的问题&#xff0c;同时参考网上的一案例&#xff0c;由于Audio 模块出现音频问题的场景很多&#xff0c;对每一个出现的问…

Blender渲染太慢怎么办?blender云渲染已开启

动画行业蓬勃发展&#xff0c;动画制作软件亦持续推陈出新&#xff0c;当制作平台日益丰富&#xff0c;创作难度降低&#xff0c;创作效率提升&#xff0c;如何高效完成复杂动画的渲染就成了从业者更关心的问题。 云渲染技术的出现&#xff0c;无疑为动画制作者提供了前所未有…

家庭理财管理系统

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 家庭理财管理系统拥有多种角色&#xff0c;可以自行设置权限和用户等&#xff0c;主要功能有&#xff1a; 收入管理、支付管理、资产管理、负债详情、统计报表、家庭成员管理、用户管理等…

JavaSE - 易错题集 - 006

1. 哪个正确 A abstract类只能用来派生子类&#xff0c;不能用来创建abstract类的对象。 B final类不但可以用来派生子类&#xff0c;也可以用来创建final类的对象。 C abstract不能与final同时修饰一个类。 D abstract类定义中可以没有abstract方法。 正确答案&#xff1…

决策树算法上篇

决策树概述 决策树是属于有监督机器学习的一种&#xff0c;起源非常早&#xff0c;符合直觉并且非常直观&#xff0c;模仿人类做决策的过程&#xff0c;早期人工智能模型中有很多应用&#xff0c;现在更多的是使用基于决策树的一些集成学习的算法。 示例一&#xff1a; 上表根据…

1.C++中程序的基本结构

在教孩子的学习过程中&#xff0c;使用的开发IDE为小熊猫Dev-C 6.7.5版本&#xff0c;以后的复杂截图&#xff0c;基本上都是基于此版本进行的&#xff0c;同时在适当的时候&#xff0c;录制视频也会基于此版本来完成。 以下为一个最基本的C程序 int main() {// 程序主体retur…

无痛生娃,00后当妈啦

姐妹们&#xff0c;你们家开始催婚了吗&#xff1f;我是00后&#xff0c;大学也才毕业一年啊&#xff0c;我妈已经开始给我物色对象&#xff0c;过年让我去相亲了&#xff01;大学的时候不让谈&#xff0c;说怕异地以后感情不稳定&#xff0c;结果呢&#xff0c;一毕业要我结婚…

频域滤波为什么使用psf2otf函数?线性卷积和循环卷积等效的条件

线性卷积和循环卷积是本质不同的运算。然而&#xff0c;在某些条件下&#xff0c;线性卷积和循环卷积是等效的。建立这种等效关系具有重要意义。对于两个向量 x 和 y&#xff0c;循环卷积等于二者的离散傅里叶变换 (DFT) 之积的逆 DFT 变换。 禹晶、肖创柏、廖庆敏《数字图像处…

基于python+django+mysql+Nanodet检测模型的水稻虫害检测系统

博主介绍&#xff1a; 大家好&#xff0c;本人精通Java、Python、C#、C、C编程语言&#xff0c;同时也熟练掌握微信小程序、Php和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我有丰富的成品Java、Python、C#毕设项目经验&#xff0c;能够为学生提供各类…

『功能项目』项目优化 - 框架加载资源【41】

我们打开上一篇40播放动画时禁止点击移动的项目&#xff0c; 本章要做的事情是搭建一个资源加载框架&#xff0c;让UI界面&#xff0c;人物模型以及场景都存放在资源文件夹中在运行时加载出来 首先在资源商店加载资源 将怪物模型放置场景中 将普通管线模型切换成URP 重命名为…

重学SpringBoot3-集成RocketMQ(二)

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ 重学SpringBoot3-集成RocketMQ&#xff08;二&#xff09; 1. 基础概念2. 准备工作3. 实现事务消息的生产者4. 事务监听器实现5. 消费者示例6. 发送事务消息7. 测试7.1 模…

rust学习——关联类型

什么是关联类型 关联类型是Rust中一种特殊的泛型抽象机制。在trait中&#xff0c;可以定义一个或多个关联类型&#xff0c;这些关联类型与trait的实现类型相关联。关联类型允许我们在trait中使用泛型&#xff0c;但不需要提前指定具体的类型。 不使用关联类型存在的问题 tra…

AI生成头像表情包,一次十分钟,就能实现月入过万的玩法,无脑操作

今天给大家带来的项目是AI生成表情包和头像&#xff0c;这个项目对于我们做ip来说是真心不错&#xff0c;就比如我这个头像。 为什么说每天只需要10分钟呢&#xff0c;那么我们继续往下看。 "项目介绍 这个项目的核心其实就是使用AI生成表情包或者说生成头像&#xff0c…

华为HCIA、HCIP和HCIE认证考试明细

华为认证体系包括三个主要等级&#xff1a;HCIA&#xff08;华为认证ICT助理&#xff09;、HCIP&#xff08;华为认证ICT高级工程师&#xff09;和HCIE&#xff08;华为认证ICT专家&#xff09;。每个等级的认证都有其特定的考试内容和费用。 HCIA&#xff08;华为认证ICT助理…

天翼云2024年最新版本认证必过资料(应知+从业者+解决方案架构师+高级解决方案架构师)

本资料为2024年认证最新材料&#xff0c;笔者因为工作需要考几个认证。天冀云全套认证包含如下图所示&#xff0c;本材料包含下图中红框内的 4个认证&#xff08;应知从业者解决方案架构师高级解决方案架构师&#xff09;。 笔者&#xff0c;亲测必过。见文章下面第二张考试记录…

模型加载pytorch版本不匹配的解决思路

模型部署总是会遇到pytorch版本推理与训练不匹配的问题&#xff0c;一般报错&#xff1a; AttributeError: Cant get attribute _rebuild_parameter_v2 on <module torch._utils from /usr/local/python3.9.0/lib/python3.9/site-packages/torch/_utils.py>提示pytorch …

医疗机构关于DIP/DRG信息化建设

推进DIP/DRG支付方式改革是一项系统性工程&#xff0c;牵一发而动全身。作为河北省DIP试点医院&#xff0c;河北医科大学第二医院将信息化与创新性管理理念融合&#xff0c;用好支付工具做好精细化管理&#xff0c;积极应对改革。 ■ 改革背景 国家医疗保障局制定的《DRG/DIP支…