Skip to main content

国际化

安装与配置

软件版本

项目版本
React> =17.0.0
React-i18next> =20.0.0
Ant-Design-react> =4.15.0

安装 React-I18next

npm install react-i18next i18next --save

语言包文件示例

Common语言包 common.json

{
"actions": {
"new": "新建",
"add": "添加",
"delete": "删除",
"edit": "编辑",
"search": "查询",
"refresh": "刷新",
"detail": "详情",
"confirm": "确认",
"cancel": "取消",
"submit": "提交",
"export": "导出",
"import": "导入",
"next": "下一步",
"previous": "上一步"
},
"messages": {
"success": "操作成功",
"delete": "删除成功"
},
"others": {
"pagination": "共 { 0 } 条,每页显示 { 1 } 条"
}
}

模块语言包 demo.json

React-i18next 并不支持文件内引用,所以模块语言包内没有common部分,直接按照页面划分。

{
// list 页面内语言包 共通部分使用引用方式
"list": {
"columns": {
"time": "操作时间",
"orderNo": "订单编号",
"...": "..."
},
"status": {},
"placeholder": {
"user/action": "用户/操作"
}
},
"detail": {},
"add": {},
"edit": {}
}

语言包的组合

src/i18n/zh_CN/index.ts

import common from './common.json';     // common语言包
// import 模块名 from './modules/模块名.json'
import demo from './modules/demo.json'; // 模块语言包


const en = {
common, // 在根语言包中,引入共通语言文件
modules: {
demo // 在根语言包中,引入模块语言文件
}
}
export default en;

注册i8n实例

创建 src/i18n/index.tsx

import {Locale} from "antd/lib/locale-provider";

// 引入AntD语言包
import ZH_CN from "antd/lib/locale-provider/zh_CN";
import EN_GB from "antd/lib/locale-provider/en_GB";
import JA_JP from "antd/lib/locale-provider/ja_JP";

// 引入本地语言包
import zh from "@/i18n/zh_CN";
import ja from "./ja_JP";
import en from "@/i18n/en_GB";

import i18n from "i18next";
import {initReactI18next} from "react-i18next";

export const AntLanguageMap: { [key: string]: Locale } = {
'zh-cn': ZH_CN,
'en-gb': EN_GB,
'ja-jp': JA_JP,
};


export const LanguageNameMap: { [key: string]: string } = {
'zh-cn': '简体中文',
'en-gb': 'English',
'ja-jp': '日本語'
};

// 默认语言
export const DefaultLanguage = 'en-gb';

// 整合语言包
export const resources = {
'zh-cn': zh,
'ja-jp': ja,
'en-gb': en
};

i18n
.use(initReactI18next) // passes i18n down to react-i18next
.init({
resources,
lng: localStorage.getItem('language') || DefaultLanguage, // 默认显示的语言
fallbackLng: DefaultLanguage, // 缺省时默认语言
lowerCaseLng: true,
interpolation: {
escapeValue: false // react already safes from xss
}
});

export default i18n;

货币国际化

react-i18next 现阶段并不支持货币国际化,我们选择引入Numeral.js用来处理货币国际化。

安装Numeral.js

npm install numeral

国际化配置

在 src/i18n/index.tsx 中添加

import numeral from "numeral";
// numral 默认为en,所以我们无需创建针对en的国际化配置
numeral.register('locale', 'zh', {
delimiters: {
thousands: ',',
decimal: '.',
},
abbreviations: {
thousand: '千',
million: '百万',
billion: '十亿',
trillion: '兆',
},
ordinal:()=>(''),
currency: {
symbol: '¥',
},
});


numeral.register('locale', 'ja', {
delimiters: {
thousands: ',',
decimal: '.',
},
abbreviations: {
thousand: '千',
million: '百万',
billion: '十亿',
trillion: '兆',
},
ordinal:()=>(''),
currency: {
symbol: '¥',
},
});

时间国际化

react-i18next 现阶段并不支持时间国际化,我们选择引入Moment.js用来处理时间国际化。

安装Moment.js

npm install moment

配置Store

Action
import {createAction} from '@reduxjs/toolkit';

export const setLanguage = createAction<string>('[I18n] 设置语言环境');
Reducer
import {createReducer} from '@reduxjs/toolkit';
import {setLanguage} from '../actions';

export interface I18nState {
language: string;
}

const initalState: I18nState = {
language: '',
};


export const i18nReducer = createReducer(initalState, {
[setLanguage.type]: (state, {payload}) => {
state.language = payload;
},
});

配置App.tsx

import '../i18n/index'; // 国际化配置文件
import {useTranslation} from "react-i18next"; // i18n hook
import {useMount, useUpdateEffect} from "ahooks"; // 生命周期函数
// moment 语言包
import 'moment/locale/zh-cn';
import 'moment/locale/en-gb';
import 'moment/locale/ja';
// moment
import moment from "moment";
// numeral
import numeral from 'numeral';

// 常量
import {AntLanguageMap, DefaultLanguage} from '@/i18n';

// AntD ConfigProvider组件
import {ConfigProvider} from 'antd';

interface RootProps {
language: string;
changeLanguage: (language: string) => AnyAction;
}

const Root: React.FC<RootProps> = ({language, changeLanguage}) => {

const [i18n] = useTranslation();
// 初始化国际化
useMount(() => {
// 获取历史设置
const lng = localStorage.getItem('language');
// 初始化 store 数据
if (lng) {
changeLanguage(lng);
} else {
changeLanguage(DefaultLanguage);
}
});

// 通过监听 store 中的 language 进行热更新
useUpdateEffect(() => {
// 设置 localStorage
localStorage.setItem('language', language || DefaultLanguage);
// 设置 i18next
i18n.changeLanguage(language || DefaultLanguage);
// 设置 moment
moment.locale(language || DefaultLanguage);
// 设置 numeral
numeral.locale((language || DefaultLanguage).split('-')[0]);
}, [language]);


return (
<ConfigProvider form={{validateMessages}} locale={AntLanguageMap[language]}>
<App/>
</ConfigProvider>
);
};

const mapStateToProps = (state: AppState) => {
const {language} = state.i18n;
return {language};
};

const mapDispatchToPros = (dispatch: ThunkDispatch<AuthState, any, AnyAction>) => ({
changeLanguage: (language: string) => dispatch(setLanguage(language))
});

export default withRouter(connect(mapStateToProps, mapDispatchToPros)(Root));

使用方法

在render中引用

<!-- 共通部分引用 -->
<button>{{t('common:actions:submit')}}</button>
<!-- 组件部分引用 -->
<span>{{t('modules:demo:detail:label:orderNo')}}</span>

在Script中引用

const [i18n] = useTranslation();
// 模块名
const moduleName = 'demo:list';
// 获取国际化文言
const getI18nText = (path: string) => i18n.t(`modules:${moduleName}.${path}`);

导航及面包屑

在 meta 中增加参数 i18nTitle

 {
path: '/demo/i18n',
component: AsyncComponent(I18nList),
meta: {
title: '国际化',
i18nTitle: {
'zh-cn':'国际化',
'en-gb':'I18n',
'ja-jp':'グローバリゼーション',
},
},
},

将导航和面包屑取值方式从item.meta?.title变成(item.meta?.i18nTitle||{})[language] || item.meta?.title