react-native 使用 react-i18next 实现国际化
目录结构
├── locales
│ ├── zh-CN.json
│ ├── en-US.json
├── src
│ ├── utils
│ │ ├── i18next.ts
│ ├── App.js
国际化库配置
安装依赖
yarn add react-i18next i18next
yarn add react-native-localize
iOS
npx pod-install
提示:如果是 react web 项目,可以换一个系统语言检测插件,如:
i18next-browser-languagedetector
。
准备好资源文件
新建 en-US.json
:
{
"语言key": "language"
}
和 zh-CN.json
:
{
"语言key": "语言"
}
配置 i18next
新建文件 i18next.ts
:
import i18next, { ModuleType } from 'i18next';
import { initReactI18next } from 'react-i18next';
import * as RNLocalize from "react-native-localize";
import storage from './storage';
import { reset } from './RootNavigation';
export const lngKey = '@lng';
const languageDetector = {
type: 'languageDetector' as ModuleType,
async: true,
detect: function (callback) {
// 获取上次选择的语言
storage.get(lngKey, 'locale').then(lng => {
// 如果是跟随本地,则获取系统语言
if (lng === 'locale') {
callback(getSystemLanguage());
} else {
callback(lng);
}
});
},
};
// 初始化i18next配置
i18next
.use(languageDetector)
.use(initReactI18next)
.init({
fallbackLng: 'zh', // 切换语言失败时的使用的语言
debug: __DEV__, // 开发环境开启调试
// 资源文件
resources: {
en: {
translation: require('./../../locales/en-US.json'),
},
zh: {
translation: require('./../../locales/zh-CN.json'),
},
},
react: {
useSuspense: false,
},
});
/**
* 获取当前系统语言
* @returns
*/
export const getSystemLanguage = (): string => {
const locales = RNLocalize.getLocales();
return locales[0].languageCode;
};
/**
* 切换语言
* @param lng
*/
export const changeLanguage = async (lng?: 'en' | 'zh' | 'locale') => {
// 切换语言
await i18next.changeLanguage(lng === 'locale' ? getSystemLanguage() : lng);
// 持久化当前选择
await storage.set(lngKey, lng);
};
export default i18next;
在入口文件 App.js
导入:
import 'i18next';
在页面中使用
不管那种类型的使用方式,都是通过注入 t
函数实现的,参数为资源文件的 key。
类组件 (HOC)
import React from 'react';
import { Text } from 'react-native';
import { withTranslation } from 'react-i18next';
@withTranslation
function MyComponent({ t, i18n }) {
return <Text>{t('语言key')}</Text>
}
export default MyComponent;
函数组件 (hook)
import React from 'react';
import { Text } from 'react-native';
import { useTranslation } from 'react-i18next';
export function MyComponent() {
const { t, i18n } = useTranslation();
// or const [t, i18n] = useTranslation();
return <Text>{t('语言key')}</Text>
}
Translation 组件(render props)
import React from 'react';
import { Text } from 'react-native';
import { Translation } from 'react-i18next';
export function MyComponent() {
return (
<Translation>
{
(t, { i18n }) => <Text>{t('语言key')}</Text>
}
</Translation>
)
}
字符串中有变量
资源文件:
{
使用次数为:"使用次数为 {{count}}"
}
使用:
t('使用次数为', { count: 10 });
更多字符串格式化用法可以查看:Formatting - i18next documentation。
替换文本
配置好了,接下来就是文本替换,需要将所有页面的文本替换掉了,如果一个一个页面人工扫描,出错率会很高,这里推荐两个工具。
di18n
一个自动转换、基于配置的前端国际化方案。
用法也很简单,安装:
yarn add -D di18n-cli
初始化:
npx di18n init
会生成一个配置文件,根据项目实际情况修改。
同步:
npx di18n sync
会替换掉源码的中文,换成指定格式,如:t('中文key')
。
注意:这个库运行
init
的时候,依赖有缺失,安装缺失依赖可解决,issues。
i18n-ally
vscode 插件,在 vscode 插件中搜索安装即可。这个插件具备回显国际化内容,检查当前文件的 hard code 代码,一键抽取当前文本等功能。虽然不能批量替换文件,需要手动一个个文件检查,但是后期维护起来用这个工具进行可视化管理会很方便,文档链接 在文末的参考资料中。
问题记录
部分文本在切换语言后不变
原因: 那部分的文本没有办法触发 react 的刷新。例如下面的写法:
import { Text } from 'react-native';
import i18next from '@utils/i18next';
const data = { name: i18next.t("姓名") };
function Comp(){
return <Text>{data.name}</Text>
}
解决方案:
- 换个写法。
import { Text } from 'react-native';
import { useTranslation } from 'react-i18next';
const data = { name: 姓名 };
function Comp(){
const { t } = useTranslation();
return <Text>{t(data.name)}</Text>
}
但是这种写法可能很多,每个地方都要改一遍,工作量大,也容易引入新的风险。
- 每次切换语言都重新加载 js bundle。
使用 react-native-restart
库:
yarn add react-native-restart
npx pod-install
修改切换语言的函数:
import RNRestart from 'react-native-restart';
/**
* 切换语言,切换完成自动重启
* @param lng
*/
export const changeLanguage = async (lng?: 'en' | 'zh' | 'locale') => {
// 持久化当前选择
await storage.set('@lng', lng);
// 重启app
RNRestart.Restart();
};
- 重新渲染整个 app。
import { reset } from './RootNavigation';
/**
* 切换语言
* @param lng
*/
export const changeLanguage = async (lng?: 'en' | 'zh' | 'locale') => {
// 切换语言
await i18next.changeLanguage(lng);
// 持久化当前选择
await storage.set('@lng', lng);
// 跳转回首页
reset({
index: 0,
routes: [{ name: 'Main' }],
});
};
由于我们的 app 是使用 react-navigation
作为导航库的,所以就通过跳转回首页的方式让所有页面重新渲染。这里也可以使用其他方式,能达到强制重新渲染页面的效果就行。
最后选择了方案3,最终的完整配置代码如下:
import i18next, { ModuleType } from 'i18next';
import { initReactI18next } from 'react-i18next';
import * as RNLocalize from "react-native-localize";
import storage from './storage';
import { reset } from './RootNavigation';
export const lngKey = '@lng';
const languageDetector = {
type: 'languageDetector' as ModuleType,
async: true,
detect: function (callback) {
// 获取上次选择的语言
storage.get(lngKey, 'locale').then(lng => {
// 如果是跟随本地,则获取系统语言
if (lng === 'locale') {
callback(getSystemLanguage());
} else {
callback(lng);
}
});
},
};
// 初始化i18next配置
i18next
.use(languageDetector)
.use(initReactI18next)
.init({
fallbackLng: 'zh', // 切换语言失败时的使用的语言
debug: __DEV__, // 开发环境开启调试
// 资源文件
resources: {
en: {
translation: require('./../../locales/en-US.json'),
},
zh: {
translation: require('./../../locales/zh-CN.json'),
},
},
react: {
useSuspense: false,
},
});
/**
* 获取当前系统语言
* @returns
*/
export const getSystemLanguage = (): string => {
const locales = RNLocalize.getLocales();
return locales[0].languageCode;
};
/**
* 切换语言
* @param lng
*/
export const changeLanguage = async (lng?: 'en' | 'zh' | 'locale') => {
// 切换语言
await i18next.changeLanguage(lng === 'locale' ? getSystemLanguage() : lng);
// 持久化当前选择
await storage.set(lngKey, lng);
// 跳转回首页
reset({
index: 0,
routes: [{ name: 'Main' }],
});
};
export default i18next;
android端 App 中选择跟随系统,然后去系统设置页面切换语言,再次进入 App,语言没有切换成功
原因: 和 iOS不同,android 切换语言的时候,不会杀掉 App 的进程,所以 jsbundle 不会重新加载,导致再次进入 App 语言没有切换成功。
解决方案: 每次进入 App 的时候判断一下持久化存储的语言是不是跟随系统,如果是,判断系统语言和当前 App 使用的语言是否一致,不一致则切换成正确的语言。
可以封装成一个 hook
,在入口文件引入,监听每一次系统语言的变化:
import { Platform } from 'react-native';
import i18next, { changeLanguage, getSystemLanguage, lngKey } from '@utils/i18next';
import storage from '@utils/storage';
import { useCallback, useEffect } from 'react';
import * as RNLocalize from 'react-native-localize';
export default function useLanguageChange() {
const handleLocalizationChange = useCallback(() => {
if (Platform.OS === 'android') {
storage.get(lngKey).then(res => {
if (res === 'locale' && getSystemLanguage() !== i18next.language) {
changeLanguage('locale');
}
});
}
}, []);
useEffect(() => {
RNLocalize.addEventListener('change', handleLocalizationChange);
return () => {
RNLocalize.removeEventListener('change', handleLocalizationChange);
};
}, []);
}
参考资料
- react-i18next 官方文档
- react-i18next react-native 官方案例
- i18next 官方文档
- GitHub - zoontek/react-native-localize: 🌍 A toolbox for your React Native app localization
- GitHub - avishayil/react-native-restart: React Native Package With One Purpose: To Restart Your React Native Project
- react-navigatioin 不在组件内实现页面跳转
- GitHub - didi/di18n: 一种自动转换、基于配置的前端国际化方案
- GitHub - lokalise/i18n-ally: 🌍 All in one i18n extension for VS Code
- Installation | Async Storage
转载自:https://juejin.cn/post/7204731868137979941