WdBly Blog

懂事、有趣、保持理智

WdBly Blog

懂事、有趣、保持理智

周维 | Jim

603927378@qq.com

React Native + React Navigation项目简繁转换解决方案

在React Native项目中处理简繁体转换的方案, 路由使用React Navigation, 状态管理使用Mobx

近期公司需要上一个新的海外版App, 用于群体主要是港澳台的同胞, 需要在现有React Native项目中新增简繁转换功能, 然后将此App投放到海外应用市场。

需求分析

需要默认为繁体页面, 用户可以在个人中心切换成简体。

设想方案

需要快速完成此功能我首先想到的是使用两套页面, 一套简体,一套繁体页面,当用户切换语言后,跳转到对应的路由,并reset路由。

实现

1、将我们的内容页面和Store页面拷贝一份繁体版的, 这里我使用的node脚本实现。

var OpenCC = require("opencc"); var fs = require("fs"); // t2s.json 繁体到简体 var opencc = new OpenCC("s2t.json"); // screen转换 const srcPath = `${__dirname}/app/screen`; // 源目录 const destPath = `${__dirname}/app/screen-tc`; // 目标目录 main(srcPath, destPath); async function main(src, dest, replace) { // 1检查screen-tc目录 await checkDirectory(dest); // 2执行 handleChange(src, dest); } // 遍历目录, 简繁转换后写入新文件夹 function handleChange(src, dest) { let files = fs.readdirSync(src); files.forEach(function(item) { let _src = `${src}/${item}`; let _dest = `${dest}/${item}`; let stat = fs.statSync(_src); if(stat.isDirectory() === true) { // 判断是否存在_dest文件夹 let exists = fs.existsSync(_dest); if(!exists) { fs.mkdirSync(_dest); } handleChange(_src, _dest); } if (stat.isFile() === true) { // 读文件内容 let srcContent = fs.readFileSync(_src, "utf-8"); // 修改store名称, 匹配规则为 大写字母开头 Store结尾的单词 let reg = /([A-Z]{1}[a-z]+Store)/gm; srcContent = srcContent.replace(reg, "$1Tc"); // 创建繁体 let destContent = opencc.convertSync(srcContent); // 写文件 fs.writeFileSync(_dest, destContent); console.log(`screen ${_src} is done`); } }); } async function checkDirectory(path) { return new Promise(resolve => { fs.access(path, fs.constants.F_OK, err => { if(err) { fs.mkdir(path, () => { resolve(); }); }else { resolve(); } }); }); }

在这里将简体页面文件读取后, 使用正则将满足大小字母开头的且以Store结尾的字符串后面添加Tc后缀, 这样繁体页面注入的也就是对应的繁体的Store, 效果如下

image.png

2、拷贝Store文件夹的繁体版, 代码类似上方代码,也是遍历文件夹,简繁转换后写入新文件夹, 只是正则匹配规则有些变化

简体版的store文件夹下的index.js文件如下
image.png

复制拷贝后如下
image.png

给原Store添加Tc后缀, 替换规则如下:

let reg = /(\w+\s*):\s*new/gm; srcContent = srcContent.replace(reg, "$1Tc: new");

如果你不使用Mobx数据管理, 就不用去做这些规则替换的工作。

3、在App.js中使用两套store

// 简体的store文件夹 import * as stores from "./stores"; // 经过脚本复制后的繁体的store文件夹 import * as storesTc from "./stores-tc"; // 路由配置文件 import {AppContainer} from "./router/router-config"; render() { return ( <Provider {...stores.default} {...storesTc.default}> <AppContainer ref={navigatorRef => { NavigationService.setTopLevelNavigator(navigatorRef); }} /> </Provider> ); }

4、新增一套繁体的路由配置,并添加到原路由后。 路由名称相比原路由新增Tc后缀。

// router-config.js const routerConfig = { // 初始化页面 Init: { screen: InitScreen }, // 简体的根页面 Root: { screen: TabStack }, ... // 繁体的根页面 RootTc: { screen: TabStackTc }, ... } export const routerConfig const RootStack = createStackNavigator(routerConfig, { headerMode: "none" } ); export const AppContainer = createAppContainer(RootStack);

5、用户启动应用时,在InitScreen中判断用于设置的语言,然后跳转到对应的首页。

// 重置路由 _rest = route => { return StackActions.reset({ index: 0, actions: [ NavigationActions.navigate({routeName: route}) ] }); } // 路由进入时触发 _willFocus = async() => { let systemLanguage = await global.Storage.get("systemLanguage"); if(systemLanguage === "tc") { this.props.navigation.dispatch(this._rest("RootTc")); }else { this.props.navigation.dispatch(this._rest("Root")); } } render() { return ( <View> <NavigationEvents onWillFocus={this._willFocus} /> </View> ); }

在这里使用reset重置路由的原因是防止用户通过返回键返回到了Init页面,虽然这个页面啥也没有。当然你也可以使用这个页面做个广告页面展示两秒后再跳转也可以的

6、在全局的路由跳转中判断语言状态,然后前往相应的page

async function navigate(routeName, params = {}) { let language = await global.Storage.get("systemLanguage"); let routerPermission = routerConfig[routeName]; // 路由需要登录且用于未登录 if(routerPermission && routerPermission.needLogin && !(await userAuth.isLogin())) { routeName = "UserAuth"; } // 如果是繁体, 跳转到繁体状态页面 if(language === "tc") { routeName = `${routeName}Tc`; } _navigator.dispatch( NavigationActions.navigate({ routeName, params }) ); }

7、在设置页面设置新的语言项后,replace到新页面, 点击返回后,reset到个人中心tab页面。

/* 语言设置 */ _setLanguage = () => { BasePicker.show(["简体中文", "繁体中文"], { title: "请选泽系统语言", defaultValue: [this._language[this.state.systemLanguage]] }, async(value, index) => { // 无论是否选择重复的都要重置,只要选择 let language = index[0] === 0 ? "zh" : "tc"; this.setState({ systemLanguage: language }); await global.Storage.set("systemLanguage", language); this.props.navigation.replace(`Setting${index[0] === 0 ? "" : "Tc"}`); }); } /* 设置后点击返回 */ _goBack = () => { if(this.state.systemLanguage === "tc") { this.props.navigation.dispatch(this._rest("RootTc", "MyTc")); }else { this.props.navigation.dispatch(this._rest("Root", "My")); } } /* 重置路由到某个Tab页面 */ _rest = (route, tabName) => { return StackActions.reset({ index: 0, actions: [ NavigationActions.navigate({ routeName: route, action: NavigationActions.navigate({ routeName: tabName }) }) ] }); }

8、接口数据转化, 本地简繁转化已经完成,剩下的就是对接口输出的数据进行转化。

一、偷懒式, 让后端去处理接口输出,我们只需添加header说明即可

let language = await global.Storage.get("systemLanguage"); config.headers["X-ACCESS-LANGUAGE"] = language;

二、对后端接口输出的数据使用API转化。

我自己开放的免费简繁转化接口供大家使用