国际化
安装与配置
软件版本
项目 | 版本 |
---|---|
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