KASHIMURA Blog

Webサービス開発のこと、個人的なことを書いているブログ

ReactNativeアプリを多言語化する方法 (react-intl)

f:id:kasssssy:20200414234520p:plain

はじめに

アプリの国際化に伴い、多言語に対応する必要が出てくると思います。
Reactを使っている場合、react-intlを利用すると簡単に実現することが可能です。

この記事では以下のことをやります。

  • React Nativeで作成したアプリを多言語に対応させる
  • 端末の言語設定に沿って翻訳する
  • 英語と日本語に対応する
  • 英語の翻訳情報がない場合は日本語が表示されるようにする
  • サポート外言語が設定されている場合は日本語が表示されるようにする

react-intlとは?

Reactアプリを国際化するためのライブラリです。
文字列、日付、数字など様々なフォーマットに対応しています。
フックが用意されていたり、結構柔軟に対応できます。

環境情報

  • macOS
  • React Native (0.60.5) 
  • Typescript (^3.8.3)
  • react-intl (^4.1.1)
  • yarn
  • cocoapods

セットアップ

サンプルプロジェクト作成

$ npx react-native init LocalizationSample --version 0.60.5  --template react-native-template-typescript
$ yarn add react-intl
$ yarn add intl
$ yarn add lodash @types/lodash
$ yarn add react-native-localize
$ cd ios
$ pod install
  • intlは、ReactNativeでreact-intlを利用する時に必要なので入れます。
  • react-native-localizeは、端末から言語設定を取得するために入れます。
  • lodashは、あとでincludesメソッドを使いたいので入れます。

動作確認

$ cd LocalizationSample
$ npx react-native run-ios
$ npx react-native run-android

iosandroidのシュミレーター で「Welcome to React」と表示されたらOKです。
androidは先にシュミレータを起動しておく必要があります

ロケールファイルを作成する

プロジェクト直下にja.jsonとen.jsonを作成してください。

ja.json

{
  "title": "ホーム",
  "name": "{name} 様",
  "message": "こんにちは"
}

en.json

{
  "title": "HOME",
  "name": "Mr/Ms {name}"
}
  • {}で文字列を囲むと、その文字列をkeyに値を外から差し込めるようになります。
  • en.json側にmessageが存在しませんが、あとで日本語にフォールバックされることを確認したいのでこうしています。

言語設定を取得する関数を用意する

プロジェクト直下にi18n.tsxを作成して、言語設定を取得するgetLocale()を記述します。

import * as React from 'react';
import * as RNLocalize from 'react-native-localize';
import {includes} from 'lodash';

const SUPPORTED_LOCALE = ['ja', 'en'];
const DEFAULT_LOCALE = 'ja';

const getLocale = (): string => {
  const locales = RNLocalize.getLocales();
  const languageCode = locales[0].languageCode;

  if (includes(SUPPORTED_LOCALE, languageCode)) {
    return languageCode;
  }

  return DEFAULT_LOCALE;
};
  • SUPPORTED_LOCALEの値がアプリで対応する言語です。
  • RNLocalize.getLocales() から端末の言語設定を取得しています。
  • 取得した言語がサポート外ならばデフォルトロケールを返すようにしています。

翻訳情報を返す関数を用意する

まず、先ほど作成したi18n.tsxロケールファイルをインポートします。

import ja from './ja.json';
import en from './en.json';
...

次に、翻訳情報を返すgetMesseges()を追記します。

...
const getMessages = (locale: string): {[key: string]: string} => {
  switch (locale) {
    case 'ja':
      return ja;
    case 'en':
      return {
        ...getMessages('ja'),
        ...en,
      };
    default:
      throw new Error('unknown locale');
  }
};
...
  • 'ja'が引数に指定された場合、日本語の翻訳情報を返します。
  • 'en'が引数に指定された場合、英語の翻訳情報を返します。ただ、欠落している部分は日本語が入るようになっています。こうすることで英語がないとき日本語にフォールバックすることができます。

IntlProviderを設定する

react-intlを利用するため、アプリのルートコンポーネント<IntlProvider>でラップする必要があります。
また、Intlポリフィルも合わせてimportする必要があるので、i18n.tsx<IntlProvider>をラップした<IntlProviderWrapper>作成して、それをApp.tsxで呼び出すようにしたいと思います。

まず、i18n.tsxの一行目でポリフィルをインポートします。

import 'intl';
import 'intl/locale-data/jsonp/ja';
import 'intl/locale-data/jsonp/en';
...
  • import 'intl/locale-data/jsonp/ja';は、サポートする言語が増えるたび、同様に追加する必要があります。今回は日本語(ja)と英語(en)だけです。これをインポートしないと起動したときにIntlが見つからないよと怒られます。

次に、i18n.tsx<IntlProvider>をラップした<IntlProviderWrapper>を返すコンポーネントを作成します。 childrenを受け取るようにするので、指定する型(ReactNode)をインポートしておきます。

...
import {ReactNode} from 'react';
...
export const IntlProviderWrapper = ({children}: {children: ReactNode}) => {
  const locale = getLocale();
  return (
    <IntlProvider locale={locale} messages={getMessages(locale)}>
      {children}
    </IntlProvider>
  );
};
...
  • localeには翻訳するロケールを指定します
  • messagesにはロケールファイルから取得したJSONオブジェクトを渡します。こうすることで、<IntlProvider>でラップしたコンポーネントで、keyに紐づく値を取得することができるようになります。

次に、App.tsxを開いて、作成したでルートコンポーネントをラップします。

import * as React from 'react';
import {SafeAreaView, Text} from 'react-native';
import {IntlProviderWrapper} from './i18n';

const App = () => {
  return (
    <SafeAreaView>
      <Text>{'Hello'}</Text>
    </SafeAreaView>
  );
};

export default () => {
  return (
    <IntlProviderWrapper>
      <App />
    </IntlProviderWrapper>
  );
};

これで設定DONE。

実際に翻訳してみる

<FormattedMessage>を使うことで文字列の翻訳ができます。
FormattedMessageをインポートして、<SafeAreaView>の中身を以下のように変更します。

...
import {FormattedMessage} from 'react-intl';
...
const App = () => {
  return (
    <SafeAreaView>
      <Text>
        <FormattedMessage id={'title'} />
      </Text>
      <Text>
        <FormattedMessage id={'name'} values={{name: '太郎'}} />
      </Text>
      <Text>
        <FormattedMessage id={'message'} />
      </Text>
    </SafeAreaView>
  );
};
...
  • idには、keyを指定します。
  • valuesには、差し込む値を指定します。ちなみに、HTMLも差し込めます。 ※ こちらには、文字列以外の対応方法も載っています。

実行結果

端末の言語設定を英語にして実行した場合のキャプチャです。
en.jsonには、messageというkeyがないので日本語にフォールバックされてます。

f:id:kasssssy:20200429202004p:plain

メソッドの引数などに翻訳した文字列を使いたい場合

useIntl()フックを使うことで実現できます。

import {useIntl} from 'react-intl';
...
const {formatMessage} = useIntl();
const name = formatMessage({id: 'name'}, {name: '太郎'})

みたいに使えます。

最後に

最後まで読んでいただきありがとうございます。
誰かの参考になれば嬉しいです。

この記事に書いたコードは、GitHubに全て上げました。

参考

https://github.com/formatjs/react-intl