目录
一、umi-plugin-locale是什么
二、umi-plugin-locale的使用
2.1 umi-plugin-locale配置
2.1.1 config文件配置
2.1.2 国际化文件夹设置
2.2 umi-plugin-locale使用
2.2. 1 方法引入
2.2.2 formatMessage 方法使用
2.2.3 FormattedMessage 方法使用
2.2.4 formatMessage 与 FormattedMessage 传值
三、语言随浏览器默认语言更换
四、注意事项
4.1 placeholder显示为[obj,obj]
4.2 样式错乱
五、项目地址
一、umi-plugin-locale是什么
umi-plugin-locale是一款企业级的国际化插件,用于多语言切换的效果。
但不得不提醒一下,如果你的项目是已经开发了很久的项目,或者说是非常庞大的项目,提醒一下,这个是需要手动给id,换标签的,非常麻烦。我和我的前端老大哥翻译了整整俩星期才把这个上线了3年的庞大项目国际化完成。真的是要翻吐了。
所以面对大型项目时,选择这个插件还是要慎重。(也许有更好的插件,大家可以去找一找)
二、umi-plugin-locale的使用
2.1 umi-plugin-locale配置
2.1.1 config文件配置
loadingComponent: './components/PageLoading/index',
在config文件下配置。
2.1.2 国际化文件夹设置
设置locales文件夹

将英文文件放在en-US下,将中文文件放在zh-CN文件下

将en,cn下的文件各自导入到对应的文件中并抛出

注意:en,cn下的小文件一定要一一对应,有一个中文就有一个英文。
整合以后再抛出小文件的内容。这里要用到Object.assign({},)方法。不懂得可以去查查。
一定要注意每个小文件内对象的导出,以及locales下en-US和zh-CN对于导出小文件的引入,不然可能会导致不不显示。
2.2 umi-plugin-locale使用
2.2. 1 方法引入
import { setLocale } from 'umi/locale';
import { formatMessage, FormattedMessage } from 'umi-plugin-locale';
适用于表格的表头,callback函数等地方的国际化。特征就是带 “:”的一律使用formatMessage。

示例:
title: formatMessage({id:'enterpriseColony.import.recognition.tabs.configFiles.config_name'}),
dataIndex: 'config_name',
title: formatMessage({id:'enterpriseColony.import.recognition.tabs.configFiles.config_path'}),
dataIndex: 'config_path',
title: formatMessage({id:'enterpriseColony.import.recognition.tabs.configFiles.mode'}),
validateNoChinese = (rule, value, callback) => {
let reg = /^[^\u4e00-\u9fa5]+$/g;
if (value && !reg.test(value)) {
callback(formatMessage({id:'placeholder.reg_Chinese'}));
} else if (value && regEmpty.test(value)) {
callback(formatMessage({id:'placeholder.regEmpty'}));
适用于标签内的文字替换如等地方的国际化。带“=”号的地方也可以使用。
示例:
style={{ textAlign: 'center', marginBottom: 16 }}
message={<FormattedMessage id='componentOverview.body.AddDomain.message'/>}
<Form onSubmit={this.handleSubmit}>
<FormItem {...is_language} label={<FormattedMessage id='componentOverview.body.AddDomain.title'/>}>
{getFieldDecorator('protocol', {
message: formatMessage({id:'componentOverview.body.AddDomain.label_protocol.required'})
<Select getPopupContainer={triggerNode => triggerNode.parentNode}>
<Option value="http">HTTPOption>
<Option value="https">HTTPSOption>
<Option value="httptohttps"><FormattedMessage id='componentOverview.body.AddDomain.httptohttps'/>Option>
<Option value="httpandhttps"><FormattedMessage id='componentOverview.body.AddDomain.httpandhttps'/>Option>
当然,像这种也可以使用 formatMessage,也是可以生效的。 总体下来formatMessage方法几乎可以囊括所有使用场景。
title={title || data.name ? formatMessage({id:'componentOverview.body.tab.AddCustomMonitor.edit'}) : formatMessage({id:'componentOverview.body.tab.AddCustomMonitor.add'})}
FormattedMessage传值 
formatMessage 传值
{formatMessage({id:'componentOverview.body.Expansion.InstanceList.example'},{num:num})
这里注意,传值的键名与自己写的键名要一致。
三、语言随浏览器默认语言更换
这里主要用的就是 navigator.language 方法。
let lan = navigator.systemLanguage || navigator.language;
if(lan.toLowerCase().indexOf('zh')!==-1){
}else if(lan.toLowerCase().indexOf('en')!==-1){
这里注意zh包含了,zh与zh-CN 两种语言,好像是一个繁体一个简体。没有太注意去看。只是告诉一下这个判断在区分这俩时不够严谨。
案例:
import rainbondUtil from '@/utils/rainbond';
import { connect } from 'dva';
import { formatMessage, setLocale, getLocale, FormattedMessage } from 'umi/locale'
import { routerRedux } from 'dva/router';
import Debounce from 'lodash-decorators/debounce';
import React, { PureComponent } from 'react';
import userIcon from '../../../public/images/user-icon-small.png';
import { setNewbieGuide } from '../../services/api';
import ChangePassword from '../ChangePassword';
import styles from './index.less';
import cookie from '../../utils/cookie';
const { Header } = Layout;
@connect(({ user, global, appControl }) => ({
rainbondInfo: global.rainbondInfo,
appDetail: appControl.appDetail,
currentUser: user.currentUser,
enterprise: global.enterprise
export default class GlobalHeader extends PureComponent {
const { enterprise } = this.props;
isNewbieGuide: false && rainbondUtil.isEnableNewbieGuide(enterprise),
showChangePassword: false,
language: cookie.get('language') === 'zh-CN' ? true : false ,
let lan = navigator.systemLanguage || navigator.language;
const Language = cookie.get('language')
if(lan.toLowerCase().indexOf('zh')!==-1){
cookie.set('language', language)
const lang = cookie.get('language')
}else if(lan.toLowerCase().indexOf('en')!==-1){
cookie.set('language', language)
const lang = cookie.get('language')
cookie.set('language', language)
const lang = cookie.get('language')
handleMenuClick = ({ key }) => {
const { dispatch } = this.props;
if (key === 'userCenter') {
dispatch(routerRedux.push(`/account/center`));
dispatch({ type: 'user/logout' });
handleMenuCN = (val) => {
cookie.set('language', val)
const lang = cookie.get('language')
}else if(val === 'en-US'){
this.setState({ showChangePassword: true });
cancelChangePass = () => {
this.setState({ showChangePassword: false });
handleChangePass = vals => {
notification.success({ message: formatMessage({id:'GlobalHeader.success'}) });
const { collapsed, onCollapse } = this.props;
const { dispatch, eid } = this.props;
dispatch(routerRedux.push(`/enterprise/${eid}/orders/overviewService`));
handlIsOpenNewbieGuide = () => {
const { eid, dispatch } = this.props;
NEWBIE_GUIDE: { enable: false, value: '' }
message: formatMessage({id:'notification.success.close'})
type: 'global/fetchEnterpriseInfo',
isNewbieGuide: rainbondUtil.isEnableNewbieGuide(info.bean)
const { currentUser, customHeader, rainbondInfo, collapsed } = this.props;
const { language } = this.state
const { isNewbieGuide } = this.state;
const handleUserSvg = () => (
<svg viewBox="0 0 1024 1024" width="13" height="13">
d="M511.602218 541.281848a230.376271 230.376271 0 1 0 0-460.752543 230.376271 230.376271 0 0 0 0 460.752543zM511.960581 0a307.168362 307.168362 0 0 1 155.63197 572.049879c188.806153 56.826147 330.615547 215.939358 356.059326 413.551004 2.406152 18.788465-11.570008 35.836309-31.228783 38.140072-19.60758 2.303763-37.525735-11.006866-39.931887-29.795331-27.645153-214.505906-213.430817-376.025269-438.73881-376.02527-226.536667 0-414.728483 161.826532-442.322441 376.02527-2.406152 18.788465-20.324307 32.099094-39.931887 29.795331-19.658775-2.303763-33.634936-19.351607-31.228783-38.140072 25.392585-196.79253 167.969899-355.700963 357.08322-413.039057A307.168362 307.168362 0 0 1 511.960581 0z"
const handleEditSvg = () => (
<svg width="15px" height="15px" viewBox="0 0 1024 1024">
<path d="M626.9 248.2L148.2 726.9 92.1 932.3l204.6-57 480.5-480.5-150.3-146.6z m274.3-125.8c-41-41-107.5-41-148.5 0l-80.5 80.5L823.1 349l78.1-78.2c41-41 41-107.5 0-148.4zM415.1 932.3h452.2v-64.6H415.1v64.6z m193.8-193.8h258.4v-64.6H608.9v64.6z" />
const handleLogoutSvg = () => (
<svg width="15px" height="15px" viewBox="0 0 1024 1024">
<path d="M1024 445.44 828.414771 625.665331l0-116.73472L506.88 508.930611l0-126.98112 321.53472 0 0-116.73472L1024 445.44zM690.174771 41.985331 100.34944 41.985331l314.37056 133.12 0 630.78528 275.45472 0L690.17472 551.93472l46.08 0 0 296.96L414.72 848.89472 414.72 1024 0 848.894771 0 0l736.25472 0 0 339.97056-46.08 0L690.17472 41.98528 690.174771 41.985331zM690.174771 41.985331" />
<svg class="icon" width="25px" height="25px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path fill="#ffffff" d="M229.248 704V337.504h271.744v61.984h-197.76v81.28h184v61.76h-184v99.712h204.768V704h-278.72z m550.496 0h-70.24v-135.488c0-28.672-1.504-47.232-4.48-55.648a39.04 39.04 0 0 0-14.656-19.616 41.792 41.792 0 0 0-24.384-7.008c-12.16 0-23.04 3.328-32.736 10.016-9.664 6.656-16.32 15.488-19.872 26.496-3.584 11.008-5.376 31.36-5.376 60.992V704h-70.24v-265.504h65.248v39.008c23.168-30.016 52.32-44.992 87.488-44.992 15.52 0 29.664 2.784 42.496 8.352 12.832 5.6 22.56 12.704 29.12 21.376 6.592 8.672 11.2 18.496 13.76 29.504 2.56 11.008 3.872 26.752 3.872 47.264V704zM160 144a32 32 0 0 0-32 32V864a32 32 0 0 0 32 32h688a32 32 0 0 0 32-32V176a32 32 0 0 0-32-32H160z m0-64h688a96 96 0 0 1 96 96V864a96 96 0 0 1-96 96H160a96 96 0 0 1-96-96V176a96 96 0 0 1 96-96z" />
<svg class="icon" width="25px" height="25px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path fill="#ffffff" d="M160 144a32 32 0 0 0-32 32V864a32 32 0 0 0 32 32h688a32 32 0 0 0 32-32V176a32 32 0 0 0-32-32H160z m0-64h688a96 96 0 0 1 96 96V864a96 96 0 0 1-96 96H160a96 96 0 0 1-96-96V176a96 96 0 0 1 96-96zM482.176 262.272h59.616v94.4h196v239.072h-196v184.416h-59.616v-184.416H286.72v-239.04h195.456V262.24z m-137.504 277.152h137.504v-126.4H344.64v126.4z m197.12 0h138.048v-126.4H541.76v126.4z" />
const MenuItems = (key, component, text) => {
{text == 1 && <FormattedMessage id="GlobalHeader.core"/>}
{text == 2 && <FormattedMessage id="GlobalHeader.edit"/>}
{text == 3 && <FormattedMessage id="GlobalHeader.exit"/>}
<div className={styles.uesrInfo}>
<Menu selectedKeys={[]} onClick={this.handleMenuClick}>
{MenuItems('userCenter', handleUserSvg, 1 )}
{MenuItems('cpw', handleEditSvg, 2 )}
{!rainbondUtil.logoutEnable(rainbondInfo) &&
MenuItems('logout', handleLogoutSvg, 3)}
const MenuCN = (key, text) => {
const enterpriseEdition = rainbondUtil.isEnterpriseEdition(rainbondInfo);
const platformUrl = rainbondUtil.documentPlatform_url(rainbondInfo);
<Header className={styles.header}>
className={styles.trigger}
type={!collapsed ? 'menu-unfold' : 'menu-fold'}
style={{ color: '#ffffff', float: 'left' }}
{customHeader && customHeader()}
<div className={styles.right}>
className={styles.action}
style={{ color: '#fff' }}
href={ language ? "https://www.rainbond.com/enterprise_server/" :'https://www.rainbond.com/en/enterprise_server/'}
<FormattedMessage id="GlobalHeader.serve"/>
title={formatMessage({id:'GlobalHeader.close'})}
onConfirm={this.handlIsOpenNewbieGuide}
okText={formatMessage({id:'button.close'})}
cancelText={formatMessage({id:'button.cancel'})}
className={styles.action}
style={{ color: '#fff' }}
rel="noopener noreferrer"
<FormattedMessage id="GlobalHeader.new"/>
className={styles.action}
style={{ color: '#fff' }}
href={language ? 'https://www.rainbond.com/docs/' : 'https://www.rainbond.com/en/docs/'}
rel="noopener noreferrer"
<FormattedMessage id="GlobalHeader.manual"/>
onClick = {language ? () => this.handleMenuCN("en-US") : () => this.handleMenuCN("zh-CN")}
{language ? en_language : cn_language}
<Dropdown overlay={menu}>
<span className={`${styles.action} ${styles.account}`}>
<Avatar size="small" className={styles.avatar} src={userIcon} />
<span className={styles.name}>{currentUser.user_name}span>
{this.state.showChangePassword && (
onOk={this.handleChangePass}
onCancel={this.cancelChangePass}
四、注意事项
4.1 placeholder显示为[obj,obj]
解决方法:
placeholder={formatMessage({id:'applicationMarket.HelmForm.input_name'})}
4.2 样式错乱
这里主要是由于单词长短变化导致,可以通过语言变化写两套css样式。
这里我是把 language 放在了cookie里,大家了解这个意思就行
language: cookie.get('language') === 'zh-CN' ? true : false ,
然后再拿 language 去做判断,比如这个跳转地址。
href={ language ? "https://www.rainbond.com/enterprise_server/" :'https://www.rainbond.com/en/enterprise_server/'}
后续如果有其他问题会再补充。
五、项目地址
主项目地址:
GitHub - goodrain/rainbond: Cloud native multi cloud application management platform | 云原生多云应用管理平台
前端项目地址:
GitHub - goodrain/rainbond-ui: Rainbond front-end project
欢迎fork,点赞。