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