前言
多语言国际化在日常开发中算是一个比较常见的需求,一般应用中都至少需要中文以及英文的国际化处理,Vue中使用vue-i18n可以快速实现需求,这次来聊聊React Native中怎么实现此需求(React实现并无特别)。
一、需求分析
首先通过功能拆解,我们可以主要区分为两个功能点:
1、读取本地国际化实现对应多语言
主要先通过识别获取设备环境(浏览器,手机…)的系统语言,然后使应用在初始化时加载对应的语言文件实现初始化多语言。
2、通过语言修改选项切换应用当前语言
当用户主动进行修改语言操作后,我们将用户的语言修改结果作为应用初始化的优先级最高的预设,如无则次级再去获取设备环境语言并应用,所以这里的用户修改语言的结果我们需要做到本地存储化并读取。
二、解决方案
因为当前使用的项目是基于React Native开发的,所以先寻求最贴近的解决方案,大多数人使用的是 react-native-i18n,但是经过查看Github,发现该仓库已废弃并停止维护。
官方推荐的是使用 react-native-localize 配合 i18n.js实现这个需求,那么我们也就采用这个方案来实现吧。
Setup
$ npm install --save react-native-localize
$ npm install --save i18n-js
# --- or ---
$ yarn add react-native-localize
$ yarn add i18n-js
三、识别系统语言并自动初始化
我们通过使用 react-native-localize 获取手机的系统语言,然后赋值为i18n的locale变量即可,与vue-i18n有点不同的是,语言包需设置给translations字段,注意这里的对象键名即为语言包使用时的属性值,如果使用与定义的对不上就会语言翻译错误。
/**
* 多语言配置文件
*/
import I18n from 'i18n-js';
import * as RNLocalize from 'react-native-localize';
import cn from './lang/zh-cn';
import en from './lang/en-us';
// 获取手机本地国际化信息
const locales = RNLocalize.getLocales();
const systemLanguage = locales[0]?.languageCode; // 用户系统偏好语言
// 如果获取到了即使用,否则启用默认语言
if (systemLanguage) {
I18n.locale = systemLanguage;
} else {
I18n.locale = 'en'; // 用户既没有设置,也没有获取到系统语言,默认加载英语语言资源
}
I18n.fallbacks = true;
// 加载语言包
I18n.translations = {
cn,
en,
};
export default I18n;
至于多语言的语言包文件可按项目需求进行添加,推荐单独目录管理,便于维护,语言包的格式要求较为宽松,通过文档便可了解到,一般我们使用js或者json格式即可。
e.g.:
// lang/zh-cn.js
export default {
signIn: {
title: '欢迎登陆',
},
};
// lang/en-us.js
export default {
signIn: {
title: 'Welcome',
},
};
使用时也非常简单,对应的更多使用示例可参考i18n的文档,最简单的视图文本使用如下:
I18n.t('signIn.title')
至此,完成了简单的需求功能一的开发,但是一般也允许用户自主修改App语言,于是我们继续修改实现功能二的需求。
四、支持用户手动修改App语言并持久化
中心思路很简单,持久化存储用户修改的语言,并修改现有逻辑首选读取它进行预设加载App,这里主要使用 redux 和 持久化插件 redux-persist。
1、新增操作的 action type
// 具体名称根据自己喜好
USER_SET_LANGUAGE: 'USER_SET_LANGUAGE',
2、新增派发的 actionCreator函数
export const userSetLanguage = languageCode => {
return {
type: Types.USER_SET_LANGUAGE,
payload: {
languageCode,
},
};
};
3、在 reducers 中进行状态更新处理
import Types from '../../action/types';
const defaultState = {
...,
userLanguageSetting: null, //用户手动设置的语言
};
const onAction = (state = defaultState, action) => {
const {type, payload} = action;
switch (type) {
case Types.USER_SET_LANGUAGE:
const {languageCode} = payload;
return {
...state,
userLanguageSetting: languageCode,
};
default:
return state;
}
};
export default onAction;
4、修改核心逻辑读取变量并应用
刚已经通过 redux 维护了一个名为 userLanguageSetting 的变量来管理用户设置的语言设置,如未设置将为 null,接下来修改核心逻辑,首选读取应用该字段,无则读取使用手机的系统语言,兜底默认加载英文。
使用 redux 保存应用状态会在应用重新启动时全部丢失,所以这里要对 redux 状态进行持久化,推荐使用 redux-persist 进行处理,具体查阅文档即可。
/**
* 多语言配置文件
*/
import I18n from 'i18n-js';
import * as RNLocalize from 'react-native-localize';
import cn from './lang/zh-cn';
import en from './lang/en-us';
import store from '../store';
// 获取手机本地国际化信息
const locales = RNLocalize.getLocales();
const systemLanguage = locales[0]?.languageCode; // 用户系统偏好语言
const {
app: {userLanguageSetting},
} = store.getState();
// 首选用户设置的语言 其次手机系统语言 最后兜底默认语言
if (userLanguageSetting) {
I18n.locale = userLanguageSetting;
} else if (systemLanguage) {
I18n.locale = systemLanguage;
} else {
I18n.locale = 'en'; // 用户既没有设置,也没有获取到系统语言,默认加载英语语言资源
}
// 订阅store变化 如果用户设置语言变动相应更新i18n的locale字段
store.subscribe(() => {
// 使用解构获取store.app.userLanguageSetting
const {
app: {userLanguageSetting: newUserLanguageSetting},
} = store.getState();
if (
newUserLanguageSetting &&
newUserLanguageSetting !== userLanguageSetting
) {
I18n.locale = newUserLanguageSetting;
}
});
I18n.fallbacks = true;
// 加载语言包
I18n.translations = {
cn,
en,
};
export default I18n;
export {systemLanguage};
使用 store.getState() 获取 store 中的 userLanguageSetting 字段值,然后通过 store.subscribe 来订阅 redux 状态改变,并更新 i18n 的 locale 字段。
五、切换语言后实现已渲染页面的响应重绘
现在我们已经能实现用户切换语言的需求,但是仔细会发现,如果页面已完成渲染再修改语言,已渲染的页面并不会随着语言更新进行重绘。
查阅网上解决方案,stackoverflow的开发者们有推荐使用 react-native-restart 重新 reload 应用来使 js 重新加载,可是 reload 应用来的效果在用户体验上的确很差,所以暂不考虑。
分析下核心思路是在语言修改后,让已渲染的页面监听到并触发重绘即可解决问题,可以通过编写一个自定义 hook 解决。
// @/hooks/useLanguageUpdate.js
import React, {useState, useEffect} from 'react';
import store from '../store';
import I18n from '../i18n';
export const useLanguageUpdate = (funcWhenUpdate, listenParamArr = []) => {
const [currentLanguageCode, setCurrentLanguageCode] = useState(I18n.locale);
useEffect(() => {
return store.subscribe(() => {
const {
app: {userLanguageSetting: newLanguageCode},
} = store.getState();
console.log('change');
if (newLanguageCode && newLanguageCode != currentLanguageCode) {
setCurrentLanguageCode(newLanguageCode);
console.log('123');
if (funcWhenUpdate) {
funcWhenUpdate();
}
}
});
}, [currentLanguageCode, ...listenParamArr]);
return currentLanguageCode;
};
然后在涉及的页面使用该 hook,e.g.:
function SignInPage(props) {
...
useLanguageUpdate();
需要使用更新后的语言进行业务逻辑可以直接使用该 hook 的返回值进行处理,e.g.:
let currentLanguage = useLanguageUpdate();
switch(currentLanguage) {
case 'cn':
...
break;
case 'en':
...
break;
...
}
或者需要在语言更新时,执行业务逻辑,可以直接传入回调函数 funcWhenUpdate 实现,第二个参数可以传入监听的变量合集,默认为空数组。
使用回调函数
useLanguageUpdate(() => {
doSomeThing();
});
总结
到此已经实现所有功能需求,仔细梳理回顾下会发现其实逻辑并不困难,实现起来也并不困难,好的思路解刨会使开发效率提升很多,这次记录就先到这啦,溜溜球!
Mark!感谢分享!