実装したいもの
Reactの国際化/多言語化について少し探すとreact-intl, react-i18nextの2つが挙がり、結論としては
以下は書きながら感じたことを雑多に記す。
react-intl
言語切替のサンプルコードを見てみると、urlの?locale
を見て生成するコードを変更するSPAに適さない手法が使われており、参考にならなかった。
代わりに、issues内の投稿で参考になるコードがあったので、そちらを真似した。
import React from "react";
import { render } from "react-dom";
import { createStore } from "redux";
import { connect, Provider } from "react-redux";
import { addLocaleData, IntlProvider, FormattedMessage } from "react-intl";
///...
const initialState = {
intl: locales.ja
};
const store = createStore((state, action) => {
if (action.error) throw action.error;
switch (action.type) {
case "CHANGE_LOCALE":
return Object.assign({}, state, { intl: locales[action.payload] });
default:
return state;
}
}, initialState);
const ConnectedIntlProvider = connect(state => state.intl)(IntlProvider);
const App = () => (
<Provider store={store}>
<ConnectedIntlProvider>
<div>
<h1>
<FormattedMessage id="greet" />
</h1>
{["en", "ja"].map(locale => (
<button
key={locale}
onClick={() => {
store.dispatch({ type: "CHANGE_LOCALE", payload: locale });
}}
>
{locale}
</button>
))}
</div>
</ConnectedIntlProvider>
</Provider>
);
render(<App />, document.querySelector("#root"));
<IntlProvider>
は言語切替機能を提供せず、単一の翻訳情報をprops
に求める。上記コードは現在の言語をredux
に持たせ、react-redux.connect
で反映する形にした。
ただ、react-intl
の初期化の手順が複雑で、以下のようなコードで動作した。
///...
const locales = {
en: {
locale: "en",
messages: {
greet: "hello world"
},
// 数値整形関数。addLocaleData時に定義しないとconsoleで警告が出る
pluralRuleFunction: a => a
},
ja: {
locale: "ja",
messages: {
greet: "ハローワールド"
},
pluralRuleFunction: a => a
}
};
addLocaleData(locales.en);
addLocaleData(locales.ja);
///...
addLocaleData
時にpluralRuleFunction
が必須らしい。ドキュメントで確認できず、console上のエラーの意味を理解するのに時間が掛かった。
react-i18next
初期化が楽。i18nextインスタンス(以下i18n
と呼ぶ)に、デフォルト言語と多言語情報を設定する。
// ...
const locales = {
en: {
greet: "hello world"
},
ja: {
greet: "ハローワールド"
}
};
const i18n = i18next.init({ fallbackLng: "en", fallbackNS: "translation" });
i18n.addResourceBundle("en", "translation", locales.en);
i18n.addResourceBundle("ja", "translation", locales.ja);
// ...
react-i18nextのTranslate HOCで梱包したMain
はprops
に翻訳関数である.t
を持つ。
import React from "react";
import { render } from "react-dom";
import i18next from "i18next";
import { I18nextProvider, translate } from "react-i18next";
//...
const Main = translate()(props => (
<div>
<h1>{props.t("greet")}</h1>
{["en", "ja"].map(locale => (
<button
key={locale}
onClick={() => {
i18n.changeLanguage(locale);
}}
>
{locale}
</button>
))}
</div>
));
const App = () => (
<I18nextProvider i18n={i18n}>
<Main />
</I18nextProvider>
);
render(<App />, document.querySelector("#root"));
i18n.changeLanguage('ja')
でI18nextProvider
内のtranslate
したMain
に即時反映されるため、下記のように言語がすぐに切り替わる。
結論
単純なkey-value置換と即時反映ならreact-i18next
で良い。