利用 React-Router 實作多國語系網址參數

Ryan Hsu
Its Ok to Make Mistakes
7 min readMay 29, 2017

目標

做出 URL 可以符合 https://{domain/language/component} 的 single page app

需要的套件

這邊我們使用 i18next 來作為多國語系的範例套件

$ npm install i18next i18next-xhr-backend 

i18next 設定檔

設定需要使用的語系為英文 (en) 與中文 (zh),並設定所需要的模式

import i18n from 'i18next';
import XHR from 'i18next-xhr-backend';
i18n
.use(XHR)
.init({
whitelist: ['en', 'zh'],
load: 'currentOnly',
lowerCaseLng: true,
ns: ['app'],
defaultNS: 'app',
debug: false,
});
module.exports = i18n;

你可以從官方文件看到更多設定與說明: i18next-doc

routerSetting 定義 App 路由

這邊我們定義整個 App 的路由設定,透過 React-Router 來做!設定完畢只要把 routerSetting 餵給 index.js,那麼 React-Router 就會幫你管理 App 路由!

import React, { Component } from 'react';
import { Router, Route, browserHistory, IndexRoute } from 'react-router';
import { syncHistoryWithStore, routerReducer } from 'react-router-redux';
import store from '../store/store';
import App from './global/app';
import Home from './home/home';
import AComponent from './functional/AComponent';
import GetParam from './functional/getParam';
import NotFound from './functional/notFound';
const history = syncHistoryWithStore(browserHistory, store)const RouterSetting = (props) => (
<Router history={browserHistory}>
<Route path='/:lang/' component={App}>
<IndexRoute component={Home} />
<Route path='AComponent' component={AComponent} />
<Route path='getParam/:toShow' component={GetParam} />
</Route>
<Route path="*" component={NotFound} />
</Router>
);
module.exports = RouterSetting;

簡單說明一下,讓 App 負責 /language/ 這個 URL 的路由,那麼 App 本身並沒有 UI 只 render child,把 UI 部分讓底下的 child Component 來決定!

  • Home 對應到 domain/language/
  • AComponent 對應到 domain/language/AComponent
  • getParam 對應到 domain/language/getParam/(toShow 參數)
  • NotFound 則是路由都沒有對應到,就會自動被導到客製化的 NotFound 頁面

讓 App.js 來負責偵測 Language 路由的部分

import React, { Component } from 'react';
import { connect } from 'react-redux';
import i18n from '../../i18n';
class AppContainer extends Component {
constructor(props) {
super(props);
this.toggleLanguage = this.toggleLanguage.bind(this);
}
componentWillMount() {
// 偵測轉換當前語系
this.toggleLanguage(this.props.params.lang)
}
toggleLanguage(newLanguage) {
if(newLanguage !== i18n.language) {
// 若 URL 語系與當前語系不同,則轉換語系
i18n.changeLanguage(newLanguage);
}
}
render() {
const {
children,
} = this.props;
return(<div>{children}</div>);
}
};
module.exports = App;

這邊則是實作 App Component 來偵測目前的 URL 路由上的語系,使用 i18n 的 changeLanguage API 來轉換語系!

使用 High-Order Component 或是使用 Class decorator 來更簡單的套用在需要的 Component 上

import React, { Component } from 'react';
import { translate } from 'react-i18next';
// HOC
const
MultiLanguageComponent = ({ t }) => (
<div>
{t('app:testMultiLang')}
</div>
);
module.exports = translate(
['app'],
{ wait: true }
)(MultiLanguageComponent);
// Decorator
@translate(['app'], { wait: true })
class MultiLanguageComponent extends Component{
constructor(props) {
super(props);
}
render() {
const { t } = this.props;
return(
<div>
{t('app:testMultiLang')}
</div>
);
}
}
module.exports = MultiLanguageComponent;

這邊使用 t 這個 API 來幫我們轉換多國語系,裡面則是指定 app 這個 namespace 來做為區別!在 translate 第一個參數指定要使用的檔案名稱,第二個則是可選的選項!

那 Multi-Language 的定義檔要放在哪呢?

在 output 資料夾中新增一個 locales 資料夾,資料夾結構與範例資料如下:

// Folder structure
.
├── index.html
└── locales
├── en
│ └── app.json
└── zh
└── app.json
// project/output/locales/en/app.json
{
"testMultiLang": "test multi language"
}
// project/output/locales/zh/app.json
{
"testMultiLang": "測試多國語系"
}

會遇到的問題

為什麼我重新整理後會找不到正確的路徑?

舉例:我到了 localhost/en/home 這個路徑底下,按下重新整理,卻沒辦法正確顯示剛才的路由畫面?

因為你必須在 webpack-dev-server 加上一個設定

const server = new WebpackDevServer(webpack(config), {
historyApiFallback: true,
...otherSettings
}

這樣再重新整理的時候就會正確幫你重新導到正確的路由囉!

--

--