tag:crieit.net,2005:https://crieit.net/users/kasssssy0000/feed
かしむらの投稿 - Crieit
Crieitでユーザーかしむらによる最近の投稿
2020-04-29T20:21:02+09:00
https://crieit.net/users/kasssssy0000/feed
tag:crieit.net,2005:PublicArticle/15879
2020-04-29T12:27:54+09:00
2020-04-29T20:21:02+09:00
https://crieit.net/posts/ReactNative-react-intl
ReactNativeアプリを多言語化する方法 (react-intl)
<p><a href="https://crieit.now.sh/upload_images/fe830e8631a6c3778511aa813c7fdf665ea8f37986446.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/fe830e8631a6c3778511aa813c7fdf665ea8f37986446.png?mw=700" alt="image" /></a></p>
<h1 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h1>
<p>アプリの国際化に伴い、多言語に対応する必要が出てくると思います。<br />
Reactを使っている場合、react-intlを利用すると簡単に実現することが可能です。</p>
<p>この記事では以下のことをやります。</p>
<ul>
<li>React Nativeで作成したアプリを多言語に対応させる</li>
<li>端末の言語設定に沿って翻訳する</li>
<li>英語と日本語に対応する</li>
<li>英語の翻訳情報がない場合は日本語が表示されるようにする</li>
<li>サポート外言語が設定されている場合は日本語が表示されるようにする</li>
</ul>
<h1 id="react-intlとは?"><a href="#react-intl%E3%81%A8%E3%81%AF%EF%BC%9F">react-intlとは?</a></h1>
<p>Reactアプリを国際化するためのライブラリです。<br />
文字列、日付、数字など様々なフォーマットに対応しています。<br />
フックが用意されていたり、結構柔軟に対応できます。</p>
<h1 id="環境情報"><a href="#%E7%92%B0%E5%A2%83%E6%83%85%E5%A0%B1">環境情報</a></h1>
<ul>
<li>macOS</li>
<li>React Native (0.60.5) </li>
<li>Typescript (^3.8.3)</li>
<li>react-intl (^4.1.1)</li>
<li>yarn</li>
<li>cocoapods</li>
</ul>
<h1 id="セットアップ"><a href="#%E3%82%BB%E3%83%83%E3%83%88%E3%82%A2%E3%83%83%E3%83%97">セットアップ</a></h1>
<p>サンプルプロジェクト作成</p>
<pre><code>$ 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
</code></pre>
<ul>
<li>intlは、ReactNativeでreact-intlを利用する時に必要なので入れます。</li>
<li>react-native-localizeは、端末から言語設定を取得するために入れます。</li>
<li>lodashは、あとでincludesメソッドを使いたいので入れます。</li>
</ul>
<p>動作確認</p>
<pre><code>$ cd LocalizationSample
$ npx react-native run-ios
$ npx react-native run-android
</code></pre>
<p>iosとandroidのシュミレーター で「Welcome to React」と表示されたらOKです。<br />
※ androidは先にシュミレータを起動しておく必要があります</p>
<h1 id="ロケールファイルを作成する"><a href="#%E3%83%AD%E3%82%B1%E3%83%BC%E3%83%AB%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B">ロケールファイルを作成する</a></h1>
<p>プロジェクト直下にja.jsonとen.jsonを作成してください。</p>
<p>ja.json</p>
<pre><code>{
"title": "ホーム",
"name": "{name} 様",
"message": "こんにちは"
}
</code></pre>
<p>en.json</p>
<pre><code>{
"title": "HOME",
"name": "Mr/Ms {name}"
}
</code></pre>
<ul>
<li>{}で文字列を囲むと、その文字列をkeyに値を外から差し込めるようになります。</li>
<li>en.json側にmessageが存在しませんが、あとで日本語にフォールバックされることを確認したいのでこうしています。</li>
</ul>
<h1 id="言語設定を取得する関数を用意する"><a href="#%E8%A8%80%E8%AA%9E%E8%A8%AD%E5%AE%9A%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B%E9%96%A2%E6%95%B0%E3%82%92%E7%94%A8%E6%84%8F%E3%81%99%E3%82%8B">言語設定を取得する関数を用意する</a></h1>
<p>プロジェクト直下にi18n.tsxを作成して、言語設定を取得するgetLocale()を記述します。</p>
<pre><code class="typescript">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;
};
</code></pre>
<ul>
<li>SUPPORTED_LOCALEの値がアプリで対応する言語です。</li>
<li>RNLocalize.getLocales() から端末の言語設定を取得しています。</li>
<li>取得した言語がサポート外ならばデフォルトロケールを返すようにしています。</li>
</ul>
<h1 id="翻訳情報を返す関数を用意する"><a href="#%E7%BF%BB%E8%A8%B3%E6%83%85%E5%A0%B1%E3%82%92%E8%BF%94%E3%81%99%E9%96%A2%E6%95%B0%E3%82%92%E7%94%A8%E6%84%8F%E3%81%99%E3%82%8B">翻訳情報を返す関数を用意する</a></h1>
<p>まず、先ほど作成したi18n.tsxでロケールファイルをインポートします。</p>
<pre><code class="typescript">import ja from './ja.json';
import en from './en.json';
...
</code></pre>
<p>次に、翻訳情報を返すgetMesseges()を追記します。</p>
<pre><code class="typescript">...
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');
}
};
...
</code></pre>
<ul>
<li>'ja'が引数に指定された場合、日本語の翻訳情報を返します。</li>
<li>'en'が引数に指定された場合、英語の翻訳情報を返します。ただ、欠落している部分は日本語が入るようになっています。こうすることで英語がないとき日本語にフォールバックすることができます。</li>
</ul>
<h1 id="IntlProviderを設定する"><a href="#IntlProvider%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B">IntlProviderを設定する</a></h1>
<p>react-intlを利用するため、アプリのルートコンポーネントを<code><IntlProvider></code>でラップする必要があります。<br />
また、Intlポリフィルも合わせてimportする必要があるので、i18n.tsxで<code><IntlProvider></code>をラップした<code><IntlProviderWrapper></code>作成して、それをApp.tsxで呼び出すようにしたいと思います。</p>
<p>まず、i18n.tsxの一行目でポリフィルをインポートします。</p>
<pre><code class="typescript">import 'intl';
import 'intl/locale-data/jsonp/ja';
import 'intl/locale-data/jsonp/en';
...
</code></pre>
<ul>
<li><code>import 'intl/locale-data/jsonp/ja';</code>は、サポートする言語が増えるたび、同様に追加する必要があります。今回は日本語(ja)と英語(en)だけです。これをインポートしないと起動したときにIntlが見つからないよと怒られます。</li>
</ul>
<p>次に、i18n.tsxに<code><IntlProvider></code>をラップした<code><IntlProviderWrapper></code>を返すコンポーネントを作成します。<br />
childrenを受け取るようにするので、指定する型(ReactNode)をインポートしておきます。</p>
<pre><code class="typescript">...
import {ReactNode} from 'react';
...
export const IntlProviderWrapper = ({children}: {children: ReactNode}) => {
const locale = getLocale();
return (
<IntlProvider locale={locale} messages={getMessages(locale)}>
{children}
</IntlProvider>
);
};
...
</code></pre>
<ul>
<li>localeには翻訳するロケールを指定します</li>
<li>messagesにはロケールファイルから取得したJSONオブジェクトを渡します。こうすることで、<code><IntlProvider></code>でラップしたコンポーネントで、keyに紐づく値を取得することができるようになります。</li>
</ul>
<p>次に、App.tsxを開いて、作成したでルートコンポーネントをラップします。</p>
<pre><code class="typescript">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>
);
};
</code></pre>
<p>これで設定DONE。</p>
<h1 id="実際に翻訳してみる"><a href="#%E5%AE%9F%E9%9A%9B%E3%81%AB%E7%BF%BB%E8%A8%B3%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">実際に翻訳してみる</a></h1>
<p><code><FormattedMessage></code>を使うことで文字列の翻訳ができます。<br />
<code>FormattedMessage</code>をインポートして、<code><SafeAreaView></code>の中身を以下のように変更します。</p>
<pre><code class="typescript">...
import {FormattedMessage} from 'react-intl';
...
const App = () => {
return (
<SafeAreaView>
<Text>
<FormattedMessage id={'title'} />
</Text>
<Text>
<FormattedMessage id={'name'} values=<span>{</span><span>{</span>name: '太郎'<span>}</span><span>}</span> />
</Text>
<Text>
<FormattedMessage id={'message'} />
</Text>
</SafeAreaView>
);
};
...
</code></pre>
<ul>
<li>idには、keyを指定します。</li>
<li>valuesには、差し込む値を指定します。ちなみに、HTMLも差し込めます。<br />
※ こちらには、文字列以外の対応方法も載っています。</li>
</ul>
<h1 id="実行結果"><a href="#%E5%AE%9F%E8%A1%8C%E7%B5%90%E6%9E%9C">実行結果</a></h1>
<p>端末の言語設定を英語にして実行した場合のキャプチャです。<br />
en.jsonには、<code>message</code>というkeyがないので日本語にフォールバックされてます。</p>
<p><a href="https://crieit.now.sh/upload_images/ab46b254c9c66739dd121dcf1554eeff5ea96319e8fc5.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/ab46b254c9c66739dd121dcf1554eeff5ea96319e8fc5.png?mw=700" alt="image" /></a></p>
<h1 id="メソッドの引数などに翻訳した文字列を使いたい場合"><a href="#%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AE%E5%BC%95%E6%95%B0%E3%81%AA%E3%81%A9%E3%81%AB%E7%BF%BB%E8%A8%B3%E3%81%97%E3%81%9F%E6%96%87%E5%AD%97%E5%88%97%E3%82%92%E4%BD%BF%E3%81%84%E3%81%9F%E3%81%84%E5%A0%B4%E5%90%88">メソッドの引数などに翻訳した文字列を使いたい場合</a></h1>
<p><code>useIntl()</code>フックを使うことで実現できます。</p>
<pre><code>import {useIntl} from 'react-intl';
...
const {formatMessage} = useIntl();
const name = formatMessage({id: 'name'}, {name: '太郎'})
</code></pre>
<p>みたいに使えます。</p>
<h1 id="最後に"><a href="#%E6%9C%80%E5%BE%8C%E3%81%AB">最後に</a></h1>
<p>最後まで読んでいただきありがとうございます。<br />
誰かの参考になれば嬉しいです。</p>
<p>この記事に書いたコードは、<a target="_blank" rel="nofollow noopener" href="https://github.com/kashimura0001/localization-sample">GitHub</a>に全て上げました。</p>
<h1 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h1>
<p><a target="_blank" rel="nofollow noopener" href="https://github.com/formatjs/react-intl">https://github.com/formatjs/react-intl</a></p>
かしむら