iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 26
1
Modern Web

React 30天系列 第 26

Day 26-讓todos變得國際化吧

前情提要:昨天大略說明了一下react-intl有哪些功用,今天就來把訊息字串換成多國語言吧!

很老套的先安裝react-intl

yarn add react-intl

目標步驟

  1. 設定<IntlProvider>
  2. 設定需替換的訊息內容
  3. 切換語言(透過dispatch action更換state)

  1. 設定<IntlProvider>
    這邊使用<IntlProvider>把整個root component包起來,因為不想加在index.js也不想加在router.js,所以又增加了一個intl.js來放react-intl的相關設定,不知道這樣的做法好不好,反正就先這樣吧哈哈...
    IntlProvider需要的props常見的有key、locale和messages,資料主要存在store給redux管理,預設可以替換的語言有中文、英文和日文,addLocaleData這邊用array和spread operator把大家集合,接著設定reducer。
import React from 'react';
import { connect } from 'react-redux';
import { addLocaleData, IntlProvider } from "react-intl";
import en from "react-intl/locale-data/en";
import zh from "react-intl/locale-data/zh";
import ja from "react-intl/locale-data/ja";
addLocaleData([...en, ...zh, ...ja]);

import RootRouter from "./router";

const Intl = ({locale, messages}) => {
  return (
    <IntlProvider key={locale} locale={locale} messages={messages}>
      <RootRouter />
    </IntlProvider>
  );
};

export default connect(({lang: { locale, messages }}) => (
  { locale, messages }
))(Intl);

因為和todoList與news資料完全不同,所以在combieReducers新增lang

import langReducer from './lang_reducer';

const rootReducer = combineReducers({
  // ...
  lang: langReducer
});
// ...

lang_reducers.js內容,這邊的zhMessages內容就是自己設定的各語系訊息

// ...
import zhMessages from "../lang/zh";

export default function(state = {
  locale: "zh",
  messages: zhMessages
}, action) {
  switch (action.type) {
    case CHANGE_LANG:
      return { ...mapLang(action.lang) }
    default:
      return state;
  }
}

messages內容會是這樣:
之後就是透過key值去取對應的value顯示

const zh = {
  home: "首頁",
  todos: "待辦事項",
  news: "新聞",
  // ...
};
const en = {
  home: "Home",
  todos: "Todos",
  news: "News",
  // ...
};
const ja = {
  home: "ホームページ",
  todos: "ToDoリスト",
  news: "ニュース",
  // ...
}

  1. 設定需替換的訊息內容
    設定完<IntlProvider>後已經完成1/3了
    接下來的1/3就是把要替換的訊息使用FormattedMessage component設定id,其他還能設定description和defaultMessage。
    這些繁瑣的步驟大概會是重複以下動作,下面的id就是我們在messages設定的key值
import { FormattedMessage } from "react-intl";
<FormattedMessage id="heading" description="heading" />

值得一提的是有個調整需要改select內option的value,因為option內只能是純文字,所以使用了injectIntl,把intl作為props匯入,使用方式會換成formatMessage({ id: "xxx", description: "xxx" }),如下:

import React from "react";
import { injectIntl } from "react-intl";

const OPTIONS = [
  { value: "", textId: "defaultOption", attr: { disabled: "disabled" } },
  { value: "tw", textId: "tw" },
  { value: "jp", textId: "jp" },
  { value: "us", textId: "usa" }
];

const NewsSelect = ({ country, fetchNews, intl: { formatMessage } }) => {
  return (
    <div className="news-selection">
      <form className="vertical-center">
        <label htmlFor="newsCountry" className="news-selection-label">
          {formatMessage({ id: "topHeadlines" })}
        </label>
        <select
          className="news-selection-select"
          name="country"
          id="newsCountry"
          onChange={e => fetchNews(e.target.value)}
          value={country}
        >
          {OPTIONS.map(({ value, textId: id, attr }, index) => (
            <option key={index} value={value} {...attr}>
              {formatMessage({ id })}
            </option>
          ))}
        </select>
      </form>
    </div>
  );
};

export default injectIntl(NewsSelect);

因為只替換訊息有點寂寞,所以在很空的Home頁加上FormattedTime和FormattedDate顯示目前時間和日期

import { FormattedMessage, FormattedTime, FormattedDate } from "react-intl";

const Home = () => {
  return (
    // ...
      <FormattedDate value={new Date()}/>  
      <FormattedTime value={new Date()}/>
    //...
  );
};

  1. 切換語言(透過dispatch action更換state)
    剩最後一哩路了,切換語言部分我把它獨立成一個component叫做Lang.js,其他的動作設定跟就是dispatch action以及更換state,就不多述了。
    import React, { PureComponent } from 'react';
    import { connect } from 'react-redux';
    import { changeLang } from '../actions';
    
    class Lang extends PureComponent {
      constructor(props) {
        super(props);
        this.state = {
          showOptionFlag: false
        }
        this.toggleOptions = this.toggleOptions.bind(this);
      }
    
      toggleOptions() {
        this.setState({showOptionFlag: !this.state.showOptionFlag});
      }
    
      handleLang(lang) {
        this.props.changeLang(lang);
        this.setState({showOptionFlag: false})
      }
    
      render() {
        let showOption = this.state.showOptionFlag ? {display: "flex"} : {display: "none"}
        return (
          <div className="lang" >
            <span onClick={this.toggleOptions}>Language</span>
            <ul className="lang-options" style={showOption}>
              <li className="icon icon-taiwan"  onClick={() => this.handleLang('zh')}></li>
              <li className="icon icon-japan" onClick={() => this.handleLang('ja')}></li>
              <li className="icon icon-us"  onClick={() => this.handleLang('en')}></li>
            </ul>
          </div>
        );
      }
    }
    
    export default connect(null, { changeLang })(Lang);
    

其他細節部分如果有興趣的客倌可以去github看看
實際操作如下:
https://i.imgur.com/TgMTfas.gif


本日總結:
使用react-intl最困難的地方大概就是翻譯了吧~!
其他只要參考API就可以順利達成目標了。


上一篇
Day 25-你好,Hello,こんにちは(react-intl初認識)
下一篇
Day 27-使用PropTypes進行型別檢查
系列文
React 30天30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言