原理
- 语言包作为作为静态资源单独保存
- 每种语言对应一个文件
- 切换语言设置时,语言文件随之切换
工具
i18next:目前最主流的框架 react-i18next:提供了更多面向react的api(HOC、hooks)
配置i18n
首先创建i18n文件夹,创建configs.ts
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
进行下一步之前,我们要先引入语言文件包,这里我使用中文、英文两种语言,文件是json格式的,所以引入了中文版zh.json和英文版en.json。其实就是普通的json对象,两个文件的结构一样,只有最后的值不同,一个是中文一个是英文。
接着,我们要在配置文件中引入这两个文件,然后定义一个代表语言资源的本地变量:
import translation_en from './en.json'
import translation_zh from './zh.json'
const resources = {
en: {
translation: translation_en
},
zh: {
translation: translation_zh
}
}
接着按照官方文档进行初始化:
i18n
.use(initReactI18next) // 通过react-i18next进行初始化
.init({
resources, // 传入本地资源变量
lng: "zh", // 默认语言:zh
// keySeparator为true,代表我们可以通过链式结构访问字符串,如:"header.slogan"
// keySeparator: false, // we do not use keys in form messages.welcome
interpolation: {
// 不会强行把html字符串转换为普通字符串
escapeValue: false // react already safes from xss
}
});
最后导出配置:
export default i18n
使用i18n
i18next的基本原理是Context,就是在全局注入provider,然后子组件中使用相应API获取数据。但实际上,我们只需在index.js文件中引入配置文件,就大功告成了,因为react-i18next这个框架在初始化对象的时候,就帮我们完成了context API的注入。所以我们现在已经可以在各组件中使用这个context API了。
类组件
类组件中,我们使用高阶函数完成语言的注入,导入react-i18next的高阶函数withTranslation,并修改组件的结构:
import { withTranslation } from "react-i18next"
class HomeComponent extends React.Component {
...
}
export default withTranslation()(HomeComponent)
这个高阶函数需要写两个小括号,第一个代表命名空间,第二个才是我们的组件。这样,我们就可以在props中访问到函数t了,利用这个函数,我们可以以字符串的形式访问到语言文件的json对象。
因为要在props中使用t,我们要先传入i18n的typescript定义。导入WithTranslation,这个就是typescript的类型定义:
import { withTranslation, WithTranslation } from "react-i18next";
type PropsType = WithTranslation
class HomeComponent extends React.Component<PropsType> {
...
}
然后我们就用t函数来替换硬编码的字符串。比如说把首页的“爆款推荐”换成{t("home_page.hot_recommend")}
。
函数式组件
在函数式组件中调取全局数据,用的是钩子函数,首先,我们要引入useTranslation这个hook
import { useTranslation } from 'react-i18next';
然后直接使用得到t函数即可,之后的用法和类组件一模一样。
const { t } = useTranslation();
语言切换
现在,我们可以正常显示语言了,还差最后一步,就是实现中英切换。由于language保存在store中,所以我们点击语言切换时,用reducer来改变store中的状态,在处理全局language数据的同时,我们调用i18next的API来切换语言即可。
// languageReducer.ts
import i18n from 'i18next';
switch (action.type) {
case CHANGE_LANGUAGE:
// 传入的是语言的key
i18n.changeLanguage(action.payload);
return { ...state, language: action.payload };
...
}
现在我们功能已经实现了,但是这么实现是有问题的。根据redux的定义所以的reducer都是纯函数,也就是没有副作用的函数,但是我们调用i18n.changeLanguage()这个函数时,这个reducer就不再是纯函数了,所以我们要使用中间件来改进。
在middlewares文件夹中创建changeLanguage.ts,编写中间件:
import { Middleware } from 'redux';
import { CHANGE_LANGUAGE } from "../language/languageActions";
import i18n from "i18next";
export const changeLanguage : Middleware = (store) => (next) => (action) => {
if(action.type === CHANGE_LANGUAGE){
i18n.changeLanguage(action.payload);
}
next(action);
}
然后在store文件中引入即可:
const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) => [...getDefaultMiddleware(), changeLanguage, actionLog],
devTools: true,
})
现在我们就完全实现了语言的切换功能。