react uses umi-plugin-locale to configure internationalization

Table of contents

1. What is umi-plugin-locale

Second, the use of umi-plugin-locale

2.1 umi-plugin-locale configuration

2.1.1 config file configuration

 2.1.2 Internationalization Folder Settings

2.2 umi-plugin-locale use

2.2.1 Method introduction

2.2.2 formatMessage method usage

2.2.3 FormattedMessage method usage

2.2.4 Value transfer between formatMessage and FormattedMessage

3. The language changes with the default language of the browser

4. Matters needing attention

4.1 placeholder is displayed as [obj, obj]

4.2 Style confusion

5. Project address 


1. What is umi-plugin-locale

        umi-plugin-locale is an enterprise-level internationalization plug-in for multi-language switching effects.

        But I have to remind you that if your project is a project that has been developed for a long time, or a very large project, please remind me that you need to manually assign the id and change the label, which is very troublesome. My front-end big brother and I translated for two full weeks to complete the internationalization of this huge project that has been online for 3 years. I really want to vomit.

        So when facing large-scale projects, you should be cautious when choosing this plugin. (Maybe there is a better plug-in, you can find it)

Second, the use of umi-plugin-locale

2.1 umi-plugin-locale configuration

2.1.1 config file configuration

 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: '-',
        }
      }
    ]
  ],

          Configure it under the config file.

 2.1.2 Internationalization Folder Settings

        Set the locales folder

         Put the English files under en-US and the Chinese files under zh-CN

         Import the files under en and cn into the corresponding files and throw

        Note: The small files under en and cn must correspond one by one. If there is a Chinese, there is an English.

        After the integration, throw out the content of the small file. The Object.assign({},) method is used here. If you don't know, you can check it out.

         Be sure to pay attention to the export of objects in each small file, and the introduction of en-US and zh-CN under locales for exporting small files, otherwise it may not be displayed.

2.2 umi-plugin-locale use

2.2.1 Method introduction

import { setLocale } from 'umi/locale';

import { formatMessage, FormattedMessage  } from 'umi-plugin-locale';

2.2.2 formatMessage method usage

        It is suitable for the internationalization of table headers, callback functions, etc. The feature is that formatMessage is used for all the messages with ":".

 Example:

//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();
    }
  }

2.2.3 FormattedMessage method usage

        It is suitable for the internationalization of text replacement in tags such as <span><div>. Places with "=" can also be used.

Example:

//特征就是 “=” 号用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>

        Of course, formatMessage can also be used like this, and it can also take effect. Overall, the formatMessage method can cover almost all usage scenarios.

//用formatMessage也可以生效

 title={title || data.name ? formatMessage({id:'componentOverview.body.tab.AddCustomMonitor.edit'}) : formatMessage({id:'componentOverview.body.tab.AddCustomMonitor.add'})}

2.2.4 Value transfer between formatMessage and FormattedMessage

        FormattedMessage pass value        

        formatMessage pass value

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

        Note here that the key name of the passed value must be consistent with the key name written by yourself. 

3. The language changes with the default language of the browser

        The main use here is  the navigator.language  method.

componentDidMount(){
    let lan = navigator.systemLanguage || navigator.language;

    if(lan.toLowerCase().indexOf('zh')!==-1){
      console.log('中文');

    }else if(lan.toLowerCase().indexOf('en')!==-1){
      console.log('英文');

    }
    }

        Note here that zh contains two languages, zh and zh-CN, which seem to be one traditional and one simplified. Didn't pay much attention to it. Just to tell you that this judgment is not rigorous enough to distinguish between the two.

        case:

/* 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. Matters needing attention

4.1 placeholder is displayed as [obj, obj]

  Solution:

placeholder={formatMessage({id:'applicationMarket.HelmForm.input_name'})}

4.2 Style confusion

This is mainly due to the change in the length of words. You can write two sets of css styles through language changes.

Here I put the language in the cookie, as long as everyone understands the meaning

language: cookie.get('language') === 'zh-CN' ?  true : false ,

Then use language to make judgments, such as this jump address.

href={ language ?  "https://www.rainbond.com/enterprise_server/" :'https://www.rainbond.com/en/enterprise_server/'}

Will add more later if there are other questions. 

5. Project address 

Main project address:

GitHub - goodrain/rainbond: Cloud native multi cloud application management platform | Cloud native multi cloud application management platform

Front-end project address: 

GitHub - goodrain/rainbond-ui: Rainbond front-end project

Welcome to fork, like it. 

Guess you like

Origin blog.csdn.net/qq_45799465/article/details/126747161