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> かしむら