i18n(Internationalization)

1,972 阅读3分钟

原理

  • 语言包作为作为静态资源单独保存
  • 每种语言对应一个文件
  • 切换语言设置时,语言文件随之切换

工具

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,
})

现在我们就完全实现了语言的切换功能。