• react使用umi-plugin-locale配置国际化


    目录

    一、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文件配置

    1. plugins: [
    2. [
    3. 'umi-plugin-react',
    4. {
    5. antd: true,
    6. dva: {
    7. hmr: true
    8. },
    9. dynamicImport: {
    10. loadingComponent: './components/PageLoading/index',
    11. webpackChunkName: true,
    12. level: 3
    13. },
    14. locale: {
    15. // default false
    16. enable: false,
    17. // default 'zh-CN',
    18. // default: 'zh-CN',
    19. // default true, when it is true, will use `navigator.language` overwrite default
    20. baseNavigator: true,
    21. baseSeparator: '-',
    22. }
    23. }
    24. ]
    25. ],

              在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  方法引入

    1. import { setLocale } from 'umi/locale';
    2. import { formatMessage, FormattedMessage } from 'umi-plugin-locale';

    2.2.2   formatMessage 方法使用

            适用于表格的表头,callback函数等地方的国际化。特征就是带 “:”的一律使用formatMessage。

     示例:

    1. //title 替换 注意是以':'为分割线的
    2. const columns = [
    3. {
    4. title: formatMessage({id:'enterpriseColony.import.recognition.tabs.configFiles.config_name'}),
    5. dataIndex: 'config_name',
    6. key:'config_name',
    7. render: (text) => {
    8. return <>
    9. {text ? text :"-"}
    10. }
    11. },
    12. {
    13. title: formatMessage({id:'enterpriseColony.import.recognition.tabs.configFiles.config_path'}),
    14. dataIndex: 'config_path',
    15. key:"config_path",
    16. render: (text) => {
    17. return <>
    18. {text ? text : "-"}
    19. }
    20. },
    21. {
    22. title: formatMessage({id:'enterpriseColony.import.recognition.tabs.configFiles.mode'}),
    23. dataIndex: 'mode',
    24. key:"mode",
    25. render: (text) =>{
    26. return <>
    27. {text ? text : "-"}
    28. }
    29. },
    30. ]
    1. //callback 内容也用formatMessage方法
    2. validateNoChinese = (rule, value, callback) => {
    3. let reg = /^[^\u4e00-\u9fa5]+$/g;
    4. let regEmpty = /^\s*$/g;
    5. if (value && !reg.test(value)) {
    6. callback(formatMessage({id:'placeholder.reg_Chinese'}));
    7. } else if (value && regEmpty.test(value)) {
    8. callback(formatMessage({id:'placeholder.regEmpty'}));
    9. } else {
    10. callback();
    11. }
    12. }

    2.2.3   FormattedMessage 方法使用

            适用于标签内的文字替换如

    等地方的国际化。带“=”号的地方也可以使用。

    示例:

    1. //特征就是 “=” 号用FormattedMessage方法。
    2. //或者 直接在div span 等标签内直接使用。
    3. <Alert
    4. style={{ textAlign: 'center', marginBottom: 16 }}
    5. message={<FormattedMessage id='componentOverview.body.AddDomain.message'/>}
    6. type="warning"
    7. />
    8. <Form onSubmit={this.handleSubmit}>
    9. <FormItem {...is_language} label={<FormattedMessage id='componentOverview.body.AddDomain.title'/>}>
    10. {getFieldDecorator('protocol', {
    11. initialValue: 'http',
    12. rules: [
    13. {
    14. required: true,
    15. message: formatMessage({id:'componentOverview.body.AddDomain.label_protocol.required'})
    16. }
    17. ]
    18. })(
    19. <Select getPopupContainer={triggerNode => triggerNode.parentNode}>
    20. <Option value="http">HTTPOption>
    21. <Option value="https">HTTPSOption>
    22. <Option value="httptohttps"><FormattedMessage id='componentOverview.body.AddDomain.httptohttps'/>Option>
    23. <Option value="httpandhttps"><FormattedMessage id='componentOverview.body.AddDomain.httpandhttps'/>Option>
    24. Select>
    25. )}
    26. FormItem>

            当然,像这种也可以使用 formatMessage,也是可以生效的。 总体下来formatMessage方法几乎可以囊括所有使用场景。

    1. //用formatMessage也可以生效
    2. title={title || data.name ? formatMessage({id:'componentOverview.body.tab.AddCustomMonitor.edit'}) : formatMessage({id:'componentOverview.body.tab.AddCustomMonitor.add'})}

    2.2.4   formatMessage 与 FormattedMessage 传值

            FormattedMessage传值        

            formatMessage 传值

    {formatMessage({id:'componentOverview.body.Expansion.InstanceList.example'},{num:num})

            这里注意,传值的键名与自己写的键名要一致。 

    三、语言随浏览器默认语言更换

            这里主要用的就是 navigator.language 方法。

    1. componentDidMount(){
    2. let lan = navigator.systemLanguage || navigator.language;
    3. if(lan.toLowerCase().indexOf('zh')!==-1){
    4. console.log('中文');
    5. }else if(lan.toLowerCase().indexOf('en')!==-1){
    6. console.log('英文');
    7. }
    8. }

            这里注意zh包含了,zh与zh-CN 两种语言,好像是一个繁体一个简体。没有太注意去看。只是告诉一下这个判断在区分这俩时不够严谨。

            案例:

    1. /* eslint-disable react/jsx-no-target-blank */
    2. /* eslint-disable import/extensions */
    3. /* eslint-disable no-underscore-dangle */
    4. /* eslint-disable class-methods-use-this */
    5. /* eslint-disable react/sort-comp */
    6. /* eslint-disable prettier/prettier */
    7. import rainbondUtil from '@/utils/rainbond';
    8. import {
    9. Avatar,
    10. Dropdown,
    11. Icon,
    12. Layout,
    13. Menu,
    14. notification,
    15. Popconfirm,
    16. Spin
    17. } from 'antd';
    18. import { connect } from 'dva';
    19. import { formatMessage, setLocale, getLocale, FormattedMessage } from 'umi/locale'
    20. import { routerRedux } from 'dva/router';
    21. import Debounce from 'lodash-decorators/debounce';
    22. import React, { PureComponent } from 'react';
    23. import userIcon from '../../../public/images/user-icon-small.png';
    24. import { setNewbieGuide } from '../../services/api';
    25. import ChangePassword from '../ChangePassword';
    26. import styles from './index.less';
    27. import cookie from '../../utils/cookie';
    28. const { Header } = Layout;
    29. @connect(({ user, global, appControl }) => ({
    30. rainbondInfo: global.rainbondInfo,
    31. appDetail: appControl.appDetail,
    32. currentUser: user.currentUser,
    33. enterprise: global.enterprise
    34. // enterpriseServiceInfo: order.enterpriseServiceInfo
    35. }))
    36. export default class GlobalHeader extends PureComponent {
    37. constructor(props) {
    38. super(props);
    39. const { enterprise } = this.props;
    40. this.state = {
    41. isNewbieGuide: false && rainbondUtil.isEnableNewbieGuide(enterprise),
    42. showChangePassword: false,
    43. language: cookie.get('language') === 'zh-CN' ? true : false ,
    44. };
    45. }
    46. componentDidMount(){
    47. let lan = navigator.systemLanguage || navigator.language;
    48. const Language = cookie.get('language')
    49. if(Language == null) {
    50. if(lan.toLowerCase().indexOf('zh')!==-1){
    51. const language = 'zh-CN'
    52. cookie.set('language', language)
    53. const lang = cookie.get('language')
    54. setLocale('zh-CN')
    55. this.setState({
    56. language:true,
    57. })
    58. }else if(lan.toLowerCase().indexOf('en')!==-1){
    59. const language = 'en-US'
    60. cookie.set('language', language)
    61. const lang = cookie.get('language')
    62. setLocale('en-US')
    63. this.setState({
    64. language:false,
    65. })
    66. }else{
    67. const language = 'zh-CN'
    68. cookie.set('language', language)
    69. const lang = cookie.get('language')
    70. setLocale('zh-CN')
    71. this.setState({
    72. language:true,
    73. })
    74. }
    75. }
    76. }
    77. handleMenuClick = ({ key }) => {
    78. const { dispatch } = this.props;
    79. if (key === 'userCenter') {
    80. dispatch(routerRedux.push(`/account/center`));
    81. }
    82. if (key === 'cpw') {
    83. this.showChangePass();
    84. }
    85. if (key === 'logout') {
    86. dispatch({ type: 'user/logout' });
    87. }
    88. };
    89. handleMenuCN = (val) => {
    90. cookie.set('language', val)
    91. const lang = cookie.get('language')
    92. if(val === 'zh-CN'){
    93. setLocale('zh-CN')
    94. }else if(val === 'en-US'){
    95. setLocale('en-US')
    96. }
    97. this.setState({
    98. language: !language
    99. })
    100. }
    101. showChangePass = () => {
    102. this.setState({ showChangePassword: true });
    103. };
    104. cancelChangePass = () => {
    105. this.setState({ showChangePassword: false });
    106. };
    107. handleChangePass = vals => {
    108. this.props.dispatch({
    109. type: 'user/changePass',
    110. payload: {
    111. ...vals
    112. },
    113. callback: () => {
    114. notification.success({ message: formatMessage({id:'GlobalHeader.success'}) });
    115. }
    116. });
    117. };
    118. toggle = () => {
    119. const { collapsed, onCollapse } = this.props;
    120. onCollapse(!collapsed);
    121. };
    122. @Debounce(600)
    123. handleVip = () => {
    124. const { dispatch, eid } = this.props;
    125. dispatch(routerRedux.push(`/enterprise/${eid}/orders/overviewService`));
    126. };
    127. handlIsOpenNewbieGuide = () => {
    128. const { eid, dispatch } = this.props;
    129. setNewbieGuide({
    130. enterprise_id: eid,
    131. data: {
    132. NEWBIE_GUIDE: { enable: false, value: '' }
    133. }
    134. }).then(() => {
    135. notification.success({
    136. message: formatMessage({id:'notification.success.close'})
    137. });
    138. dispatch({
    139. type: 'global/fetchEnterpriseInfo',
    140. payload: {
    141. enterprise_id: eid
    142. },
    143. callback: info => {
    144. if (info && info.bean) {
    145. this.setState({
    146. isNewbieGuide: rainbondUtil.isEnableNewbieGuide(info.bean)
    147. });
    148. }
    149. }
    150. });
    151. });
    152. };
    153. render() {
    154. const { currentUser, customHeader, rainbondInfo, collapsed } = this.props;
    155. const { language } = this.state
    156. if (!currentUser) {
    157. return null;
    158. }
    159. const { isNewbieGuide } = this.state;
    160. const handleUserSvg = () => (
    161. <svg viewBox="0 0 1024 1024" width="13" height="13">
    162. <path
    163. 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"
    164. fill="#555555"
    165. p-id="1138"
    166. />
    167. svg>
    168. );
    169. const handleEditSvg = () => (
    170. <svg width="15px" height="15px" viewBox="0 0 1024 1024">
    171. <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" />
    172. svg>
    173. );
    174. const handleLogoutSvg = () => (
    175. <svg width="15px" height="15px" viewBox="0 0 1024 1024">
    176. <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" />
    177. svg>
    178. );
    179. const en_language = (
    180. <svg class="icon" width="25px" height="25px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
    181. <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" />
    182. svg>
    183. )
    184. const cn_language = (
    185. <svg class="icon" width="25px" height="25px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
    186. <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" />
    187. svg>
    188. )
    189. const MenuItems = (key, component, text) => {
    190. return (
    191. <Menu.Item key={key}>
    192. <Icon
    193. component={component}
    194. style={{
    195. marginRight: 8
    196. }}
    197. />
    198. {text == 1 && <FormattedMessage id="GlobalHeader.core"/>}
    199. {text == 2 && <FormattedMessage id="GlobalHeader.edit"/>}
    200. {text == 3 && <FormattedMessage id="GlobalHeader.exit"/>}
    201. Menu.Item>
    202. );
    203. };
    204. const menu = (
    205. <div className={styles.uesrInfo}>
    206. <Menu selectedKeys={[]} onClick={this.handleMenuClick}>
    207. {MenuItems('userCenter', handleUserSvg, 1 )}
    208. {MenuItems('cpw', handleEditSvg, 2 )}
    209. {!rainbondUtil.logoutEnable(rainbondInfo) &&
    210. MenuItems('logout', handleLogoutSvg, 3)}
    211. Menu>
    212. div>
    213. );
    214. const MenuCN = (key, text) => {
    215. return (
    216. <Menu.Item key={key}>
    217. {text}
    218. Menu.Item>
    219. );
    220. };
    221. const enterpriseEdition = rainbondUtil.isEnterpriseEdition(rainbondInfo);
    222. const platformUrl = rainbondUtil.documentPlatform_url(rainbondInfo);
    223. return (
    224. <Header className={styles.header}>
    225. <Icon
    226. className={styles.trigger}
    227. type={!collapsed ? 'menu-unfold' : 'menu-fold'}
    228. style={{ color: '#ffffff', float: 'left' }}
    229. onClick={this.toggle}
    230. />
    231. {customHeader && customHeader()}
    232. <div className={styles.right}>
    233. <a
    234. className={styles.action}
    235. style={{ color: '#fff' }}
    236. href={ language ? "https://www.rainbond.com/enterprise_server/" :'https://www.rainbond.com/en/enterprise_server/'}
    237. target="_blank"
    238. >
    239. <FormattedMessage id="GlobalHeader.serve"/>
    240. a>
    241. {isNewbieGuide && (
    242. <Popconfirm
    243. title={formatMessage({id:'GlobalHeader.close'})}
    244. onConfirm={this.handlIsOpenNewbieGuide}
    245. okText={formatMessage({id:'button.close'})}
    246. cancelText={formatMessage({id:'button.cancel'})}
    247. >
    248. <a
    249. className={styles.action}
    250. style={{ color: '#fff' }}
    251. target="_blank"
    252. rel="noopener noreferrer"
    253. >
    254. <FormattedMessage id="GlobalHeader.new"/>
    255. a>
    256. Popconfirm>
    257. )}
    258. {platformUrl && (
    259. <a
    260. className={styles.action}
    261. style={{ color: '#fff' }}
    262. href={language ? 'https://www.rainbond.com/docs/' : 'https://www.rainbond.com/en/docs/'}
    263. target="_blank"
    264. rel="noopener noreferrer"
    265. >
    266. <FormattedMessage id="GlobalHeader.manual"/>
    267. a>
    268. )}
    269. <span
    270. style={{
    271. verticalAlign: '-9px',
    272. cursor: 'pointer',
    273. }}
    274. onClick = {language ? () => this.handleMenuCN("en-US") : () => this.handleMenuCN("zh-CN")}
    275. >
    276. {language ? en_language : cn_language}
    277. span>
    278. {currentUser ? (
    279. <Dropdown overlay={menu}>
    280. <span className={`${styles.action} ${styles.account}`}>
    281. <Avatar size="small" className={styles.avatar} src={userIcon} />
    282. <span className={styles.name}>{currentUser.user_name}span>
    283. span>
    284. Dropdown>
    285. ) : (
    286. <Spin
    287. size="small"
    288. style={{
    289. marginLeft: 8
    290. }}
    291. />
    292. )}
    293. div>
    294. {/* change password */}
    295. {this.state.showChangePassword && (
    296. <ChangePassword
    297. onOk={this.handleChangePass}
    298. onCancel={this.cancelChangePass}
    299. />
    300. )}
    301. Header>
    302. );
    303. }
    304. }

    四、注意事项

    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,点赞。 

  • 相关阅读:
    基于开源ERDDAP的海洋学科数据分发技术简介
    [ vulhub漏洞复现篇 ] Apache Solr RemoteStreaming 文件读取与SSRF漏洞 (CVE-2021-27905)
    LeetCode简单题之统计共同度过的日子数
    华为方舟编译器开源项目编译第三弹——自带测试框架使用
    【论文笔记】A Review of Motion Planning for Highway Autonomous Driving
    jekins完成自动化部署
    C++编程语言的深度解析: 从零开始的学习路线
    JavaScript基本语法、函数
    NRF52840 SOC 在空气净化市场应用的发展趋势
    CVT代码及修改
  • 原文地址:https://blog.csdn.net/qq_45799465/article/details/126747161