React+Redux アプリを react-i18next で多言語化する

React+Redux アプリを多言語化する方法を調べてみました。

前提

TypeScript

多言語化ライブラリー

JavaScript で多言語化するライブラリーはいろいろありますが、React で使えるものは限られてくるようです。

いくつか調べて、i18nextreact-i18next を使うことにしました。

i18next の方は、特定のプラットフォームやフレームワークに依存しない多言語化ライブラリーで、react-i18next は i18next を React で使いやすくするプラグインです。

インストー

まず、ライブラリーをインストールします:

npm install -S i18next react-i18next
npm install -D @types/i18next @types/react-i18next

セットアップ

i18next の設定ファイルを作成します。

// I18n.ts
import * as i18next from 'i18next';

const i18n = i18next
  .init({
    resources: {
      en: {
        general: {
          appName: 'Super App',
        }
      },
      ja: {
        general: {
          appName: 'スーパーアプリ',
        }
      }
    },
    
    fallbackLng: 'en',

    // string or array of namespaces to load
    ns: ['general'],

    defaultNS: 'general',

    interpolation: {
      escapeValue: false, // not needed for react
      formatSeparator: ',',
    },

    // react-i18next special options (optional)
    react: {
      wait: true,  // true: wait for loaded in every translated hoc
    }
  });

export default i18n;

i18next のサンプルと比べて、import の書き方が違ったり、init() の返り値をエクスポートしたりしていますが、TypeScript の場合、こうしないと動かないようです。

Ref. Using with typescript · react-i18next documentation

また、オプションについては、以下のページを参照してください:

プロバイダーの組み込み

トップレベルの Component(index.tsx や App.tsx)に I18nextProvider を組み込みます:

// index.tsx
.....
import { I18nextProvider } from 'react-i18next';
import i18n from './I18n';
.....
ReactDOM.render(
  <Provider store={store}>
    <I18nextProvider i18n={i18n} >
      <App />
    </I18nextProvider>
  </Provider>,
  document.getElementById('root') as HTMLElement
);

Componentでの使用

ここまでで準備ができたので、Component に組み込みます:

.....
import { translate } from 'react-i18next';

interface IProps {
  t?: any;
}

class ToolbarComponent extends React.Component<IProps> {
  render() {
    const { t } = this.props;
    return (
      <div>{t('appName')}</div>
    );
  }
}

export default translate()(ToolbarComponent);

翻訳結果を得るのは t() という関数です。

これはプロパティに自動的に入ってくるので、TypeScript ではインタフェースで定義してあげます。

なお、t のインタフェース定義は省略して any で逃げています。

また、オプショナルにしていますが、自分でセットしていないので、念ためです。

 

最後の export のところで、translate()() をかませます。

translate() のパラメーターにネームスペースを指定することもできます。

言語の指定

ブラウザーの言語を使用する場合は、i18next-browser-languageDetector を入れます。

UIで指定できるようにする場合は、changeLanguage() を使います。

僕は、まだどちらも試していません。

翻訳リソースの分離

上の例では、設定ファイルに翻訳リソースを直書きしていましたが、翻訳データが多くなると別ファイルに分離したくなります。

1つの方法としては、言語ごとに .ts ファイルを作成し、そこでは翻訳データのみを記述してエクスポートし、それを設定ファイルで読み込むことができます:

// I18nEn.ts
export const general = {
  appName: 'Super App',
  .....
};
// I18n.ts
.....
import * as en from './I18nEn';

const i18n = i18next
  .init({
    resources: {
      en: {
        general: en.general,
      },
.....

あと、init() の中に書くのもあまりきれいではないので、addResourceBundle() を使って、別で読み込むこともできます。

もしかすると、JSONファイルにして読み込ませることもできるかもしれませんが、まだ調べ切れていません。

あとがき

i18next は結構多機能で、翻訳リソースをバックエンドサーバーに置いて、言語が切り替わったら読み込む、みたいなこともできそうなので、後で調べてみたいと思います。