用 React 和 Electron 開發多國語系的應用程式（i18n）
By: 許聖泉 Michael Hsu
Tags: JavaScript, Electron, React, Redux, I18n

首先，完成 React & Redux 的部分

使用 create-react-app 來初始化 React 專案

npx create-react-app react-electron-i18n-example

cd react-electron-i18n-example

安裝 Material UI

Redux

安裝 Redux

npm install --save-dev redux react-redux redux-observable redux-devtools-extension electron-devtools-installer

建立 Redex Store

src/redux/stores/index.js

import { createStore, applyMiddleware } from 'redux';

import { createEpicMiddleware } from 'redux-observable';

import { composeWithDevTools } from 'redux-devtools-extension';



import rootReducer from '../reducers';

import rootEpic from '../epics';



const epicMiddleware = createEpicMiddleware();



const configureStore = () => {

const store = createStore(

rootReducer,

composeWithDevTools({ trace: true })(applyMiddleware(epicMiddleware)),

);



epicMiddleware.run(rootEpic);



return store;

};



export default configureStore;

建立 Redux reducer，並且先建立一個 root 的 reducer 作為範本

src/redux/reducers/index.js

import { combineReducers } from 'redux';



import root from './root';



const reducers = { root };



export default combineReducers(reducers);

src/redux/reducers/root.js

const initState = {};



const root = (state = initState, action) => {

const {type} = action;

switch (type) {

default:

return state;

}

};



export default root;

建立 Redux epic

src/redux/epics/index.js

import { combineEpics } from 'redux-observable'; export default combineEpics(...Object.values({ }));

Container & Component

安裝 React Router

npm install --save-dev react-router react-router-dom react-loadable

建立主要頁面的 container，用來設定 router、props 和 translation

src/containers/AppFrame/index.js

import { compose } from 'redux';

import { connect } from 'react-redux';

import { withRouter } from 'react-router';

import { withTranslation } from 'react-i18next'; import AppFrame from '../../components/AppFrame'; const mapStateToProps = state => ({ }); const mapDispatchToProps = dispatch => ({ }); export default compose(

withRouter,

connect(

mapStateToProps,

mapDispatchToProps,

),

withTranslation(['common']),

)(AppFrame);

src/containers/index.js

import React from 'react';

import Loadable from 'react-loadable';

import { Provider } from 'react-redux';

import { Route, Switch } from 'react-router-dom';

import { startIPCEventListener } from '../event/ipc-event-listener';

import configureStore from '../redux/stores'; const store = configureStore();

// Import IPC event listener

startIPCEventListener(store); const Index = (props) => {

const match = props.match;

const AsyncAppFrame = new Loadable({

loader: () => import('./AppFrame'),

loading: () => [],

});

return (

<Provider store={store}>

<React.Fragment>

<Switch>

<Route exact path={match.url} render={() => <AsyncAppFrame />} />

</Switch>

</React.Fragment>

</Provider>

);

}; export default Index;

主要頁面的 component，這邊將頁面分為上方、左邊和右邊，總共三個區塊，並且在上方和右邊區塊中留有內文變數（t(‘TopBar’) 和 t(‘RightPanel’)），來做為後面切換語系的 Demo

src/components/AppFrame/index.js

import React from 'react';

import { withStyles } from '@material-ui/core/styles';



import styles from './styles';



const AppFrame = (props) => {

const classes = props.classes;

const t = props.t;



return (

<div className={classes.container}>

<div className={classes.topBar}>{ t('TopBar') }</div>

<div className={classes.main}>

<div className={classes.leftPanel}>

</div>

<div className={classes.rightPanel}>{ t('RightPanel') }</div>

</div>

</div>

);

};



export default withStyles(styles, { withTheme: true })(AppFrame);

src/components/AppFrame/styles.js

const styles = theme => ({

container: {

height: '100vh',

width: '100vw',

display: 'flex',

flexDirection: 'column',

},

main: {

width: '100%',

height: '100%',

display: 'flex',

flexDirection: 'row',

},

topBar: {

minHeight: 56,

width: '100vw',

},

leftPanel: {

minWidth: 234,

},

rightPanel: {

minWidth: 234,

}

});



export default styles;

最後完成 App.js 和 index.js 就完成 React 頁面的部分了，接下來就是 Electron 和 i18n 的工作

src/App.js

import React from 'react';

import { Route, Switch, Redirect } from 'react-router-dom';

import { Router } from 'react-router';

import { createBrowserHistory } from 'history'

import Root from './containers';



const history = createBrowserHistory();



export default () => (

<Router history={history}>

<Switch>

<Route path="/" component={Root} />

<Redirect to="/" />

</Switch>

</Router>

);

src/index.js