目录
一、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是一款企业级的国际化插件,用于多语言切换的效果。之前也使用过react-intl,但是整体感受下来还是umi-plugin-local更好一些(其实主要是项目是用的umi框架写的,所以才选择了?umi-plugin-locale)。
????????但不得不提醒一下,如果你的项目是已经开发了很久的项目,或者说是非常庞大的项目,提醒一下,这个是需要手动给id,换标签的,非常麻烦。我和我的前端老大哥翻译了整整俩星期才把这个上线了3年的庞大项目国际化完成。真的是要翻吐了。
????????所以面对大型项目时,选择这个插件还是要慎重。(也许有更好的插件,大家可以去找一找)
二、umi-plugin-locale的使用
2.1? umi-plugin-locale配置
2.1.1 config文件配置
plugins: [
[
'umi-plugin-react',
{
antd: true,
dva: {
hmr: true
},
dynamicImport: {
loadingComponent: './components/PageLoading/index',
webpackChunkName: true,
level: 3
},
locale: {
// default false
enable: false,
// default 'zh-CN',
// default: 'zh-CN',
// default true, when it is true, will use `navigator.language` overwrite default
baseNavigator: true,
baseSeparator: '-',
}
}
]
],
? ????????在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 替换 注意是以':'为分割线的
const columns = [
{
title: formatMessage({id:'enterpriseColony.import.recognition.tabs.configFiles.config_name'}),
dataIndex: 'config_name',
key:'config_name',
render: (text) => {
return <>
{text ? text :"-"}
</>
}
},
{
title: formatMessage({id:'enterpriseColony.import.recognition.tabs.configFiles.config_path'}),
dataIndex: 'config_path',
key:"config_path",
render: (text) => {
return <>
{text ? text : "-"}
</>
}
},
{
title: formatMessage({id:'enterpriseColony.import.recognition.tabs.configFiles.mode'}),
dataIndex: 'mode',
key:"mode",
render: (text) =>{
return <>
{text ? text : "-"}
</>
}
},
]
//callback 内容也用formatMessage方法
validateNoChinese = (rule, value, callback) => {
let reg = /^[^\u4e00-\u9fa5]+$/g;
let regEmpty = /^\s*$/g;
if (value && !reg.test(value)) {
callback(formatMessage({id:'placeholder.reg_Chinese'}));
} else if (value && regEmpty.test(value)) {
callback(formatMessage({id:'placeholder.regEmpty'}));
} else {
callback();
}
}
????????适用于标签内的文字替换如<span><div>等地方的国际化。带“=”号的地方也可以使用。
示例:
//特征就是 “=” 号用FormattedMessage方法。
//或者 直接在div span 等标签内直接使用。
<Alert
style={{ textAlign: 'center', marginBottom: 16 }}
message={<FormattedMessage id='componentOverview.body.AddDomain.message'/>}
type="warning"
/>
<Form onSubmit={this.handleSubmit}>
<FormItem {...is_language} label={<FormattedMessage id='componentOverview.body.AddDomain.title'/>}>
{getFieldDecorator('protocol', {
initialValue: 'http',
rules: [
{
required: true,
message: formatMessage({id:'componentOverview.body.AddDomain.label_protocol.required'})
}
]
})(
<Select getPopupContainer={triggerNode => triggerNode.parentNode}>
<Option value="http">HTTP</Option>
<Option value="https">HTTPS</Option>
<Option value="httptohttps"><FormattedMessage id='componentOverview.body.AddDomain.httptohttps'/></Option>
<Option value="httpandhttps"><FormattedMessage id='componentOverview.body.AddDomain.httpandhttps'/></Option>
</Select>
)}
</FormItem>
????????当然,像这种也可以使用?formatMessage,也是可以生效的。 总体下来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?方法。
componentDidMount(){
let lan = navigator.systemLanguage || navigator.language;
if(lan.toLowerCase().indexOf('zh')!==-1){
console.log('中文');
}else if(lan.toLowerCase().indexOf('en')!==-1){
console.log('英文');
}
}
? ? ? ? 这里注意zh包含了,zh与zh-CN?两种语言,好像是一个繁体一个简体。没有太注意去看。只是告诉一下这个判断在区分这俩时不够严谨。
? ? ? ? 案例:
/* eslint-disable react/jsx-no-target-blank */
/* eslint-disable import/extensions */
/* eslint-disable no-underscore-dangle */
/* eslint-disable class-methods-use-this */
/* eslint-disable react/sort-comp */
/* eslint-disable prettier/prettier */
import rainbondUtil from '@/utils/rainbond';
import {
Avatar,
Dropdown,
Icon,
Layout,
Menu,
notification,
Popconfirm,
Spin
} from 'antd';
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
// enterpriseServiceInfo: order.enterpriseServiceInfo
}))
export default class GlobalHeader extends PureComponent {
constructor(props) {
super(props);
const { enterprise } = this.props;
this.state = {
isNewbieGuide: false && rainbondUtil.isEnableNewbieGuide(enterprise),
showChangePassword: false,
language: cookie.get('language') === 'zh-CN' ? true : false ,
};
}
componentDidMount(){
let lan = navigator.systemLanguage || navigator.language;
const Language = cookie.get('language')
if(Language == null) {
if(lan.toLowerCase().indexOf('zh')!==-1){
const language = 'zh-CN'
cookie.set('language', language)
const lang = cookie.get('language')
setLocale('zh-CN')
this.setState({
language:true,
})
}else if(lan.toLowerCase().indexOf('en')!==-1){
const language = 'en-US'
cookie.set('language', language)
const lang = cookie.get('language')
setLocale('en-US')
this.setState({
language:false,
})
}else{
const language = 'zh-CN'
cookie.set('language', language)
const lang = cookie.get('language')
setLocale('zh-CN')
this.setState({
language:true,
})
}
}
}
handleMenuClick = ({ key }) => {
const { dispatch } = this.props;
if (key === 'userCenter') {
dispatch(routerRedux.push(`/account/center`));
}
if (key === 'cpw') {
this.showChangePass();
}
if (key === 'logout') {
dispatch({ type: 'user/logout' });
}
};
handleMenuCN = (val) => {
cookie.set('language', val)
const lang = cookie.get('language')
if(val === 'zh-CN'){
setLocale('zh-CN')
}else if(val === 'en-US'){
setLocale('en-US')
}
this.setState({
language: !language
})
}
showChangePass = () => {
this.setState({ showChangePassword: true });
};
cancelChangePass = () => {
this.setState({ showChangePassword: false });
};
handleChangePass = vals => {
this.props.dispatch({
type: 'user/changePass',
payload: {
...vals
},
callback: () => {
notification.success({ message: formatMessage({id:'GlobalHeader.success'}) });
}
});
};
toggle = () => {
const { collapsed, onCollapse } = this.props;
onCollapse(!collapsed);
};
@Debounce(600)
handleVip = () => {
const { dispatch, eid } = this.props;
dispatch(routerRedux.push(`/enterprise/${eid}/orders/overviewService`));
};
handlIsOpenNewbieGuide = () => {
const { eid, dispatch } = this.props;
setNewbieGuide({
enterprise_id: eid,
data: {
NEWBIE_GUIDE: { enable: false, value: '' }
}
}).then(() => {
notification.success({
message: formatMessage({id:'notification.success.close'})
});
dispatch({
type: 'global/fetchEnterpriseInfo',
payload: {
enterprise_id: eid
},
callback: info => {
if (info && info.bean) {
this.setState({
isNewbieGuide: rainbondUtil.isEnableNewbieGuide(info.bean)
});
}
}
});
});
};
render() {
const { currentUser, customHeader, rainbondInfo, collapsed } = this.props;
const { language } = this.state
if (!currentUser) {
return null;
}
const { isNewbieGuide } = this.state;
const handleUserSvg = () => (
<svg viewBox="0 0 1024 1024" width="13" height="13">
<path
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"
fill="#555555"
p-id="1138"
/>
</svg>
);
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" />
</svg>
);
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>
);
const en_language = (
<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>
)
const cn_language = (
<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" />
</svg>
)
const MenuItems = (key, component, text) => {
return (
<Menu.Item key={key}>
<Icon
component={component}
style={{
marginRight: 8
}}
/>
{text == 1 && <FormattedMessage id="GlobalHeader.core"/>}
{text == 2 && <FormattedMessage id="GlobalHeader.edit"/>}
{text == 3 && <FormattedMessage id="GlobalHeader.exit"/>}
</Menu.Item>
);
};
const menu = (
<div className={styles.uesrInfo}>
<Menu selectedKeys={[]} onClick={this.handleMenuClick}>
{MenuItems('userCenter', handleUserSvg, 1 )}
{MenuItems('cpw', handleEditSvg, 2 )}
{!rainbondUtil.logoutEnable(rainbondInfo) &&
MenuItems('logout', handleLogoutSvg, 3)}
</Menu>
</div>
);
const MenuCN = (key, text) => {
return (
<Menu.Item key={key}>
{text}
</Menu.Item>
);
};
const enterpriseEdition = rainbondUtil.isEnterpriseEdition(rainbondInfo);
const platformUrl = rainbondUtil.documentPlatform_url(rainbondInfo);
return (
<Header className={styles.header}>
<Icon
className={styles.trigger}
type={!collapsed ? 'menu-unfold' : 'menu-fold'}
style={{ color: '#ffffff', float: 'left' }}
onClick={this.toggle}
/>
{customHeader && customHeader()}
<div className={styles.right}>
<a
className={styles.action}
style={{ color: '#fff' }}
href={ language ? "https://www.rainbond.com/enterprise_server/" :'https://www.rainbond.com/en/enterprise_server/'}
target="_blank"
>
<FormattedMessage id="GlobalHeader.serve"/>
</a>
{isNewbieGuide && (
<Popconfirm
title={formatMessage({id:'GlobalHeader.close'})}
onConfirm={this.handlIsOpenNewbieGuide}
okText={formatMessage({id:'button.close'})}
cancelText={formatMessage({id:'button.cancel'})}
>
<a
className={styles.action}
style={{ color: '#fff' }}
target="_blank"
rel="noopener noreferrer"
>
<FormattedMessage id="GlobalHeader.new"/>
</a>
</Popconfirm>
)}
{platformUrl && (
<a
className={styles.action}
style={{ color: '#fff' }}
href={language ? 'https://www.rainbond.com/docs/' : 'https://www.rainbond.com/en/docs/'}
target="_blank"
rel="noopener noreferrer"
>
<FormattedMessage id="GlobalHeader.manual"/>
</a>
)}
<span
style={{
verticalAlign: '-9px',
cursor: 'pointer',
}}
onClick = {language ? () => this.handleMenuCN("en-US") : () => this.handleMenuCN("zh-CN")}
>
{language ? en_language : cn_language}
</span>
{currentUser ? (
<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>
</span>
</Dropdown>
) : (
<Spin
size="small"
style={{
marginLeft: 8
}}
/>
)}
</div>
{/* change password */}
{this.state.showChangePassword && (
<ChangePassword
onOk={this.handleChangePass}
onCancel={this.cancelChangePass}
/>
)}
</Header>
);
}
}
四、注意事项
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,点赞。?
|