react native(expo)多语言适配

embedded/2024/11/14 0:51:32/

项目基于 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-linerequire('@ant-design/icons-react-native/fonts/antoutline.ttf'));await Font.loadAsync('antfill',// eslint-disable-next-linerequire('@ant-design/icons-react-native/fonts/antfill.ttf'));// 吐司全局配置Toast.config({ duration: 1.5 });// eslint-disable-next-linesetReady(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 (<Modalstyle={{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/><ArrowLineStylestyle={{backgroundColor: "white", paddingHorizontal: scaleSize(20.5)}}title={i18n.t('MyRoute.anotherSetting.switchLang')}subTitle={currentLanguage}onPress={() => {handleChangeLang();}}/><ArrowLineStylestyle={{backgroundColor: "white", paddingHorizontal: scaleSize(20.5)}}title={i18n.t('MyRoute.anotherSetting.updateApp')}subTitle={i18n.t('version') + `:${APP_VERSION_CODE}`}onPress={() => {handleUpdateAppVersion();}}/><AntPopuptitle={languageOptions.title}showPopup={showPopup}onClose={() => {setShowPopup(false)}}onRequestClose={() => {setShowPopup(false);return true;}}><ViewWrapper style={[stl.widthPercent100]}><><Radio.Groupstyles={{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 ? (<ImageWrapperstyle={{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.ppmy.cn/embedded/115264.html

相关文章

Parallels Desktop 20(Mac虚拟机) v20.0.0 for Mac 最新破解版(支持M系列)

Parallels Desktop 20 for Mac 正式发布&#xff0c;完全支持 macOS Sequoia 和 Windows 11 24H2&#xff0c;并且在企业版中引入了全新的管理门户。 据介绍&#xff0c;新版本针对 Windows、macOS 和 Linux 虚拟机进行了大量更新&#xff0c;最大的亮点是全新推出的 Parallels…

Cubieboard2(三) 系统构建 —— WSL Ubuntu 中挂载 U 盘(SDCard)

文章目录 1 WSL Ubuntu 中挂载 U 盘(SDCard)2 usbipd 搭建虚拟机与宿主机 USB 通信桥梁3 WSL 内核添加 USB 设备驱动3.1 编译 WSL Linux 内核3.2 挂载 USB(SDCard) 设备 附录&#xff1a;WSL 操作命令附录&#xff1a;git 仓库检出 1 WSL Ubuntu 中挂载 U 盘(SDCard) Linux 驱动…

聚焦于 Web 性能指标 TTI

在优化网站性能的过程中&#xff0c;我们经常遇到一个“为指标而优化”的困境。指标并不能真正反映用户体验&#xff0c;而应该最真实地反映用户行为。 在本节中&#xff0c;我们将研究 TTI&#xff08;Time to Interactive&#xff09;。在深入探讨这个话题之前&#xff0c;我…

KG Structure as Prompt:利用知识图谱构建Prompt,提高大模型对因果关系的理解

KG Structure as Prompt&#xff1a;利用知识图谱构建Prompt&#xff0c;提高大模型对因果关系的理解 秒懂大纲提出背景解法拆解创意视角中文意译 论文&#xff1a;Knowledge Graph Structure as Prompt: Improving Small Language Models Capabilities for Knowledge-based Ca…

如何写数学建模竞赛论文

撰写数学建模论文的重要性不言而喻&#xff0c;它直接决定了成绩的好坏和获奖级别的高低。论文是竞赛成果的书面体现&#xff0c;同时也是科技写作的基础训练。评审论文的标准包括假设的合理性、建模的创新性、结果的合理性以及表述的清晰性。 一、论文的基本内容和需要注意的…

Pycharm Debug Django项目时报错

在对Django项目进行Debug时出现启动失败的问题&#xff0c;经过多方查询未果&#xff0c;在解决问题后前来记录&#xff0c;以此帮助后来者。觉得有帮助的记得点赞噢&#xff01;&#xff01;&#xff01; 错误内容如下 Unknown command: D:\\PyCharm 2023.2.6\\plugins\\pyt…

大数据与人工智能关联性辨析

谈及当前最流行也最为被看重的两项技术&#xff0c;就不得不提及大数据和人工智能。随着人们日常对信息量的需求、计算机技术的发展和网络的普及&#xff0c;但由于大规模的数据处理对于个人用户级电脑还是有一定的负担和难度的。也因此&#xff0c;大数据与人工智能应运而生&a…

mysql默认隔离级别为什么要设置为RC?

结论&#xff1a;为了防止死锁。 隔离级别 mysql有四种隔离级别&#xff0c;默认不是RR就是RC。 数据库的锁&#xff0c;在不同的事务隔离级别下&#xff0c;是采用了不同的机制的。在 MySQL 中&#xff0c;有三种类型的锁&#xff0c;分别是Record Lock、Gap Lock和 Next-K…