はじめに
こんにちは! konishi と申します。普段は、仕事でも趣味でもReact Nativeを使ってアプリを作っています。今回は、アプリの多言語対応の方法について説明したいと思います。
スマホアプリに限らず、あらゆるサービスで多言語対応は重要です。
日本語だけの場合、日本語を問題なく理解できる人 (≒日本人) にユーザーは限定されてしまいます。
一方、他の言語、例えば英語だけでも対応することで、そのサービスを使ってくれる人は大きく広がります。これは日本だけで展開するサービスであっても、近年増えてきた外国人観光客やそもそも日本に住んでいる外国人などにアプローチすることができるので、十分に大きな可能性を秘めています。
海外展開を考えているサービスなら、当然、対応しておいたほうがいいです。
少しでも多言語対応する可能性があるなら、最初は英語などだけ対応しておくと、将来、本格的に多言語対応する際の手間がぐっと減ります。そのため、これからアプリを始める人も、多言語対応しておくのがいいと思います。
また後述の方法を使うと、日本語と英語を用意する場合であっても、日本語の訳は設定せずに、英語の訳だけを用意するだけで多言語対応を行うことができます。これにより、メンテナンス性が格段にあがります。
ちなみにこの記事ではiOSのみの対応方法を紹介します。Androidについては後日更新できたらと思います。
技術スタック
※ 各パッケージのバージョンや各情報は記事作成日時点のものです。バージョンなどは適切なものに置き換えてください。
多言語対応に必要なパッケージ
-
i18next23.12.2 -
react-i18next15.0.0 -
react-native-localize3.6.0- システム言語設定と連携したいなら追加 (推奨)
開発環境など
-
WSL(ubuntu)- Windows PCで開発する場合は必要、導入方法については割愛
-
expo54.0.7 -
expo-build-properties1.0.8- react-native-localize でネイティブモジュールのインストールが必要になるため
-
react19.1.0 -
react-native0.81.4 -
react-native-async-storage/async-storage2.2.0- 設定に変更がある場合に、DBに保存したいなら追加
-
expo-notifications: 0.32.11- プッシュ通知を送りたいなら追加
-
expo-device8.0.7- OS (iOS or Android) を取得したいなら追加
- iPhone 16
- 実機でなくてもMacbookがあればsimulatorでアプリを動作できます (おそらく普通はこっち)
導入の流れ
- 端末の言語を
react-native-localizeで取得し、その言語を使用する - 多言語対応は
i18nextとreact-i18nextを使用する- いったん多言語対応の動作を試したいなら、こちらから先に試すのがおすすめです
言語設定 (react-native-localize)
react-native-localizeのインストール、ビルド
まずは端末のシステム言語を取得するために、 react-native-localizeを導入しましょう。
このパッケージは、端末の情報(言語、国、カレンダーの表示形式など) を取得できるパッケージです。
このパッケージはネイティブモジュールを利用しているのでビルドする必要があります。
npm i react-native-localizeをしたら、app.config.js (もしくはapp.jsonなど) に以下の設定を追加しましょう。
expo: {
...,
plugins: [
...,
[
"react-native-localize",
{
locales: ["ja", "en"],
},
],
],
}
この状態でビルドしましょう。WSL (Windows PC) でiOSを作っている場合でも、EASサーバーでビルドすることで、開発環境用のアプリをインストールできます。(Macbookなら、Xcodeを使ってローカルでビルドするほうがコスト的にも時間的にもいいです。) ビルドの方法については、他の記事を参考にしてください。
react-native-localize を使った言語設定の取得
ビルドが成功したら、ネイティブモジュールがアプリに組み込まれて、react-native-localizeが使えるようになりました。
ではreact-native-localizeを使って端末のシステムの言語設定を取得してみましょう。
import { findBestLanguageTag, getLocales } from "react-native-localize"
import { Text } from "react-native"
export default function App() {
const locales = getLocales()
const bestLanguage = findBestLanguageTag(locales.map(locale => locale.languageTag))
const languageCode = bestLanguage?.languageTag.split("-")[0]
console.log("languageCode", languageCode)
...
}
これで、システムの言語が日本語で設定されているならja、英語で設定されているならenと表示されるはずです。ちなみに、getLocales関数でも言語情報を取得できますが、findBestLanguageTag関数を使うと、アプリで使えそうな言語を順番に並べてくれます。この処理は、その最初の言語を返す、というものです。
言語設定の変更方法
このコードで取得できる言語は、デフォルトはシステムの言語ですが、アプリごとにも言語を設定することができます。
iPhoneの設定アプリの、アプリごとの設定の「優先する言語」というところです。ここに、app.config.jsで設定した言語の一覧が表示されます。
- アプリの設定
- アプリの言語設定
アプリ内から、設定アプリの言語設定を開く方法
Android 13+ の場合は、react-native-localizeのopenAppLanguageSettings関数を使って開けます。しかし、iOSや、古いAndroidの場合は、この関数を使えないので、他の方法を使う必要があります。
他に使える方法としては、react-nativeのLinking APIを使って開く方法です。ただし、これはアプリの言語設定ではなく、アプリの設定が開くので、必要な操作は1つ多くなってしまいます。
import { openAppLanguageSettings } from "react-native-localize";
import { Linking, Pressable, Text } from "react-native"
export default function Setting() {
const onPress = () = {
// Android 13+の場合は、アプリの言語設定を開く
openAppLanguageSettings("application")
// iOS、未対応のAndroidの場合は、アプリの設定を開く
Linking.openSettings()
}
return (
<Pressable onPress={onPress}>
<Text>言語設定を変更する</Text>
</Pressable>
)
}
react-native-localize を使う理由
ちなみに、言語の設定はアプリ内でUIを用意して、状態管理 (useStateや各種状態管理パッケージ) を使っても動作自体は可能です。
ただし、システム言語を取得してそれを初期値にしたい場合、アプリで対応していない言語がシステム言語の場合に、いい感じに言語を寄せたい (=フォールバック) 場合に必要になります。
アプリの初回起動時にユーザーが使いたい言語に自動的に設定されている方がUX的にもいいと思います。
また、他のアプリでは設定アプリから変更できるので、そちらの方がユーザーは慣れている可能性もあります。
文字の多言語対応 (i18next)
i18nextの導入
i18nextとreact-i18nextの二つが必要なので、npm i i18next react-i18nextしましょう。こちらはJSのパッケージなので、ネイティブビルドは不要です。
i18nextの設定
パスはなんでもいいので、アプリ起動時に読み込まれる部分に以下のコードを追加しましょう。私は、src/texts/i18n.tsで定義して、src/app/_layout.tsでimportして実行しています。
import i18n from "i18next"
import { initReactI18next } from "react-i18next"
import EN from "./en/texts.json"
import JA from "./ja/texts.json"
i18n.use(initReactI18next).init({
resources: {
en: { translation: EN },
ja: { translation: JA },
},
lng: "ja",
fallbackLng: ["ja"],
interpolation: {
escapeValue: false,
},
compatibilityJSON: "v3",
})
export default i18n
上記は、日本語 (ja) と英語 (en) を対応している場合の設定です。後ほど設定するJSONファイルに用意した訳を読み込んでいます。
lngはデフォルトの言語で、fallbackLngは訳が定義されていない単語が指定された場合にフォールバックする先の言語です。
先ほどreact-native-localizeで取得したアプリの言語設定を読み込みましょう。react-native-localizeを入れない場合は、"ja"を直書きでもいいですし、状態管理と組み合わせた言語設定画面を作ってもいいです。
依存配列にlanguageCodeを含めていますが、アプリの設定を変更したら自動的にアプリが再起動するので、必要ないかもしれません。
import { useEffect } from "react"
import { useTranslation } from "react-i18next"
import { findBestLanguageTag, getLocales } from "react-native-localize"
import { Text } from "react-native"
export default function App() {
// 1. `react-native-localize` で言語情報取得
const locales = getLocales()
const bestLanguage = findBestLanguageTag(locales.map(locale => locale.languageTag))
const languageCode = bestLanguage?.languageTag.split("-")[0]
// 2. `react-i18next` に言語情報を反映
const { i18n } = useTranslation()
useEffect(() => {
if (languageCode) {
i18n.changeLanguage(languageCode)
}
}, [languageCode, i18n.changeLanguage])
...
}
対応言語の訳の定義
やっとここまで来ました。対応したい言語の訳をJSONファイルで定義しましょう。
キーとなる単語と、対応言語の訳を値として定義します。
src/texts/en/texts.jsonなどに定義しましょう
{
"ホーム": "Home",
"投稿": "Post",
"プロフィール": "Profile",
}
日本語訳は空でいいです。
src/texts/ja/texts.json
{}
コンポーネントでの呼び出し
useTranslationで先ほど定義した多言語の訳を取得できるオブジェクトtを呼び出します。これをTextコンポーネント内で呼び出すことで、表示上は多言語対応されます。
以下のコードはイメージです。適切にスタイルを定義しましょう。
import { useTranslation } from "react-i18next"
import { Pressable, Text, View } from "react-native"
import { HomeIcon, PostIcon, ProfileIcon } from "hoge"
export const NavigationTab = () => {
const { t } = useTranslation()
return (
<View>
<Pressable>
<HomeIcon />
<Text>{t("ホーム")}</Text>
</Pressable>
<Pressable>
<PostIcon />
<Text>{t("投稿")}</Text>
</Pressable>
<Pressable>
<ProfileIcon />
<Text>{t("プロフィール")}</Text>
</Pressable>
</View>
)
}
いい感じにスタイルを定義すると、以下のようになります。
const { t } = useTranslation()は、多言語対応するコンポーネントすべてで呼び出す必要が出てきます。そのため個人的には、以下の文字のコンポーネントを作成するのをお勧めします。こっちの方がすっきりします。
import { useTranslation } from "react-i18next"
import { Text } froom "react-native"
interface TxProps extends TextProps {
children: React.ReactNode
}
export const Tx: React.FC<TxProps> = ({children, ...props}) => {
const { t } = useTranslation()
const content = typeof children === "string" ? t(children) : children
return <Text {...props} >{content}</Text>
}
おすすめの設定方法
個人的におすすめの設定なのですが、日本語訳はあえて定義せず、キー名を日本語にしておきます。(上記で説明した方法です)
そうすると、先ほどのフォールバックの設定で自動的に日本語がそのまま表示されます。
これは後述の複数のメリットがあると思います。
一方、公式に書かれている方法では以下のように定義していきます。
英語用の設定
{
"tabs" : {
"home": "Home",
"post": "Post",
"profile": "Profile",
},
"users": {
"followers": "Followers",
"settings": "Settings",
...
},
"settings": {
"title": "Settings",
"description": "In this page, you can change the app settings",
...
},
....
}
日本語用の設定
{
"tabs" : {
"home": "ホーム",
"post": "投稿",
"profile": "プロフィール",
},
"users": {
"followers": "フォロワー",
"settings": "設定",
...
},
"settings": {
"title": "設定",
"description": "この画面では、アプリの設定を変更することができます。",
...
},
...
}
これが同じ設定を以下で定義できます。日本語訳の設定も不要です。
{
"ホーム": "Home",
"投稿": "Post",
"プロフィール": "Profile",
"フォロワー": "Followers",
"設定": "Settings",
"この画面では、アプリの設定を変更することができます。": "In this page, you can change the app settings",
}
呼び出すときはこんな感じです。
import { Tx } from "@/components/tx"
export default function HomeScreen() {
...
return (
...
<Tx>ホーム</Tx>
...
)
}
1. キー名を管理する必要がなくなる。
公式の方法にすることで構造的に管理ができるというメリットはあります。
一方、コンポーネントで呼び出すキー名が分かりにくい、ということが起きます。
毎回設定ファイルを見ないと、必要な訳を呼び出せない、というのはかなりのストレスです。
また、重複するものが出てきた場合、その都度定義する必要が出てきます。よく出てくる単語の場合 (上記の例での "設定") 、訳は同じです。何回も定義する必要はないです。
キー名を考えるのも一苦労です。上記の例でいう、settings.descriptionなんて、ちゃんとこれで分かりやすいか考えて、他に説明の訳を定義する必要が出てきたときに別のキー名にして、それを呼び出している場所をすべて変えて、...なんてやっていると大変です。
2. 用意する訳が一つ減る
これも重要です。
特にとりあえず英語だけ導入したい場合など、日本語と英語の両方を定義するより、英語だけ定義する方が圧倒的に楽です。
3. 日本人が開発するのであれば、キー名は日本語の方が圧倒的に分かりやすい
これはとてもシンプルですね。
この記事を読んでる人は英語より日本語が得意だと思うので、コンポーネントで呼び出す訳は日本語のほうがいいに決まっています。
4. 最悪、訳を定義し忘れても問題なく表示はできる
これは英語だけの対応なら、動作確認して気づけますが、対応する言語が増えてくると、対応忘れなどが発生する可能性が上がります。そのような場合でも最悪、何らかの言語が表示されるのはUI上大事です。
プラスα
言語設定のDBへの保存
DBに言語設定を保存しておくと、プッシュ通知の多言語対応が可能になります
起動の度にサーバーと通信するのが嫌なら、react-native-async-storage/async-storageのstorageに保存しておいて、アプリ言語設定が変わったなど、保存した設定と差分があるときのみ、サーバーと通信するのをお勧めします。
1端末1アカウントとは限らない
スマホアプリのユーザーは、1端末1アカウントとは限らないと思います。
例えば、同じ端末のアプリ上でログアウトしてから別のアカウントにログインする場合、1端末複数アカウントになります。また、1つのアカウントだけであっても、機種変更などをしてしまえば、複数端末1アカウントとなります。
データベース設計もこれに対応できるように行う必要があります。多対多ってやつです。
以下例です。ついでに、端末情報に保存しておくと使える情報も書いています。
type User = {
id: number
name: string
}
type Device = {
id: number
platform: "iOS" | "Android"
languageCode: string
pushToken: string
}
type UserDevice = {
id: number
user_id: number
device_id: number
}
最後に
最後まで読んでいただきありがとうございます。今回紹介した方法はあくまで1つの方法ということでご理解いただけますと幸いです。
私 konishi は、Tuneeというアプリを開発しています。自分の好きな音楽を共有できるSNS的なやつです。まだiOS版のみですが、興味があればぜひ使ってみてください!
Tunee (app store)
以下URLもしくは、app storeで「tunee」と検索する表示されるサジェストから選択
X (Twitter)



