React(Flux)アプリをPolyglot.jsで多言語化する

リンガルボックスでは、現状のスタックは下記のようになっています。

クライアントサイド: Flux(Alt.js) + ReactRouter + Webpack + Polyglot.js(多言語化)
サーバーサイド: Rails + Active Model Serializer

Polyglot.jsよりもUS Yahooのreact-intlの方が有名かと思うのですが、僕がRailsのi18nの方法に慣れていることもあって同じように書けるPolyglot.jsを選びました。Polyglot.jsはAirBnBがオープンソースとして公開してるパッケージでAirBnBの多言語化に利用されています。

今回はこのスタックで、多言語対応をどのように行っているのか簡単に書いていきます。

1. 各言語のロケールをlocalesフォルダーに保存

現状はlocalesフォルダーにen-US.js、ja-JP.js、es-ES.jsなど言語別にファイルを保存しています。

例えばこんな感じです。

export default {
messages: {
keywords: "aprender Inglés online, aprender a hablar Inglés, Cursos online de Inglés, Cursos de Inglés",
description: {
default: "Aprenda a falar inglês online com professores Filipinos certificados, o LingualBox oferece lições de alta qualidade por preços acessíveis começando a US$2 por seção. Comece suas suas aulas grátis agora."
}....
}

2. localeの判断

リンガルボックスの場合ですが、ログインしてないユーザーに関してはサブドメインに併せて言語を変更しています。サブドメインが”ja”だと日本語、”www”だと英語、”pt”だとポルトガル語という具合です。ログイン後のサブドメインは必ず”www”になるようにしていて、cookieにlocale情報を保存しています。

3. 必要な言語の翻訳だけを読み込んで、AltBootstrapでLocaleStoreに渡す

Alt.jsにはAlt.bootstrapというメソッドがあってここで、Storeに任意の初期データを保存することが出来るようになっています。
このメソッドを使ってページが表示される前にStoreに必要な言語の翻訳を渡しています。

// localeには2で書いたようにサブドメインやCookieのLocaleに併せて'en-US'、'es-ES'といった文字列をいれています。
const { messages } = require(`./locales/${locale}.js`).default; // localesフォルダから翻訳を取ってきて
const storeData = {
LocaleStore: {
currentLocale: locale,
messages,
},
};
alt.bootstrap(JSON.stringify(storeData)); // LocaleStoreに翻訳を渡してます。

4. App.jsからChildContextにPolyglotインスタンスを渡す

リンガルボックスでは下記のようにして、ChildContextに翻訳データを渡しています。

getChildContext() {
const polyglot = new Polyglot();
polyglot.extend(this.state.i18nMessages);
return {
polyglot,
....
};
}

こうすることで、Component内で、下記のように翻訳を表示出来ます。

render() {
const {polyglot} = context
return <p>{polyglot.t('general.loading')}</p>;
}

5. 言語の変更

リンガルボックスの場合は、言語を変更した場合に都度Rails API側から翻訳データを取ってくるという風にしています。

■LocaleActions

import alt from '../alt.js';
import LocaleApiUtils from '../utils/LocaleApiUtils.js';
import logError from '../utils/logError.js';
class LocaleActions {
constructor() {
this.generateActions(
'switchLocaleSucceeded'
);
}
switchLocale(newLocale) {
LocaleApiUtils.fetchMessages(newLocale).then(
(data) => {
this.switchLocaleSucceeded({
locale: newLocale,
messages: data.messages,
});
document.body.lang = newLocale.split('-')[0];
}
).catch(logError);
}
}
export default alt.createActions(LocaleActions);

■LocaleStore

import alt from '../alt';
import LocaleActions from '../actions/LocaleActions.js';
import UserActions from '../actions/UserActions.js';
import cookie from 'cookies-js';
const THREE_MONTHS = 7948800;
class LocaleStore {
constructor() {
this.bindListeners({
onSwitchLocaleSucceeded: LocaleActions.SWITCH_LOCALE_SUCCEEDED,
....
});
....
}
onSwitchLocaleSucceeded(data) {
cookie.expire('_locale', { domain: baseDomain })
.set('_locale', data.locale, {
domain: 'lingualbox.com',
secure: true,
expires: THREE_MONTHS,
});
this.currentLocale = data.locale;
this.messages = data.messages;
}
....
}

Rails側は例えばこんな感じです。

def fetch_messages
query_params = CGI::parse(request.query_string)
locale = query_params['locale'].first || 'en-US'
json_file_path = Rails.root.join('public', 'js_locales', locale + '.json')
messages = open(json_file_path) do |io|
JSON.load(io)
end
render json: {
messages: messages
}
end

後は取ってきた翻訳データをStoreに保存して、Componentに反映するという通常の流れで言語変更が簡単に出来ます。

まとめ

Railsでi18nをするのに比べて割と大変だったので、記事にしました。Fluxアプリの多言語化を検討されている方の参考になれば嬉しいです。


ライターについて
プログラミング学習サービスCodeGrit(現在立上げ中)とオンライン英会話サービスのリンガルボックスを運営しています。