異なる言語を使い、異なる文化圏にいるユーザーにも使いやすくするには、アプリをローカライズします。端末のロケールに合わせて、ユーザーになじみのある言語や通貨を表示できるようになります。
Expoの
expo-localization↗を使うと、
i18n-js↗などの翻訳ライブラリを使う前に、端末の言語設定を簡単に取得できます。世界向けに提供する場合、多言語対応は欠かせません。
従来のi18nベースのライブラリとExpoのSDKを組み合わせれば、さらに実装しやすくなります。
expo-localizationとreact-i18nextをインストールする 🔗
次のコマンドで expo-localization、react-i18next、i18next、@react-native-async-storage/async-storage をインストールします。
npx expo install expo-localization react-i18next i18next @react-native-async-storage/async-storage
その後、expo-localization をインポートし、端末の現在の言語設定を取得できます。
/app/index.tsximport React from "react";
import { View, Text } from "react-native";
import { getLocales } from "expo-localization";
const App = () => {
const deviceLanguage = getLocales()[0].languageCode;
return (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
}}
>
<Text style={{ fontSize: 20 }}>현재 기기 언어: {deviceLanguage}</Text>
</View>
);
};
export default App;
上のコードの
getLocales() は、端末の
システム設定
に基づいて現在のロケール情報を返します。アプリは、ユーザーが希望する言語を自動で判定できます。
👨💻
新しいAndroidやiOSでは、アプリごとに言語設定を変えられることがあります。アプリ内に言語切り替えボタンが必要か、検討してください。
/app/index.tsのコード全体を表示する
/app/index.tsimport React from "react";
import { View, Text, Button } from "react-native";
import { initReactI18next, useTranslation } from "react-i18next";
import { getLocales } from "expo-localization";
import i18n from "i18next";
const resources = {
en: {
translation: {
welcome: "Welcome to the app!",
},
},
ko: {
translation: {
welcome: "앱에 오신 것을 환영합니다!",
},
},
ar: {
translation: {
welcome: "مرحبًا بك في التطبيق!",
},
},
};
i18n.use(initReactI18next).init({
resources,
lng: getLocales()[0].languageCode || "ko",
fallbackLng: {
"en-*": ["en"],
"ko-*": ["ko"],
"ar-*": ["ar"],
default: ["en"],
},
interpolation: {
escapeValue: false,
},
react: {
useSuspense: false,
},
});
const App = () => {
const { t, i18n } = useTranslation();
return (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
gap: 20,
}}
>
<Text>{t("welcome")}</Text>
<Button title="English" onPress={() => i18n.changeLanguage("en")} />
<Button title="Korean" onPress={() => i18n.changeLanguage("ko")} />
<Button title="Arabic" onPress={() => i18n.changeLanguage("ar")} />
</View>
);
};
export default App;
アプリで対応する言語と、翻訳データを定義します。
/app/index.ts// 컴포넌트 밖에 번역 데이터 정의
const resources = {
en: {
translation: {
welcome: "Welcome to the app!",
},
},
ko: {
translation: {
welcome: "앱에 오신 것을 환영합니다!",
},
},
ar: {
translation: {
welcome: "مرحبًا بك في التطبيق!",
},
},
};
// ...
i18nを初期化するコードを書きます。
getLocales()[0].languageCode を使い、端末の言語を初期値にします。
fallbackLngで、使用する言語が指定されていない場合の代替言語を設定できます。
/app/index.tsimport i18n from "i18next";
import { initReactI18next } from "react-i18next";
i18n.use(initReactI18next).init({
resources,
lng: getLocales()[0].languageCode || "ko",
fallbackLng: {
"en-*": ["en"],
"ko-*": ["ko"],
"ar-*": ["ar"],
default: ["en"],
},
interpolation: {
escapeValue: false,
},
react: {
useSuspense: false,
},
});
最後に翻訳を表示し、言語を切り替えるボタンを追加します。useTranslation フックで翻訳を使えます。 `
/app/index.tsimport React from "react";
import { View, Text, Button } from "react-native";
import { initReactI18next, useTranslation } from "react-i18next";
import { getLocales } from "expo-localization";
import i18n from "i18next";
//... i18n 초기화 코드
const App = () => {
const { t, i18n } = useTranslation();
return (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
gap: 20,
}}
>
<Text>{t("welcome")}</Text>
<Button title="English" onPress={() => i18n.changeLanguage("en")} />
<Button title="Korean" onPress={() => i18n.changeLanguage("ko")} />
<Button title="Arabic" onPress={() => i18n.changeLanguage("ar")} />
</View>
);
};
export default App;
各ボタンを押すと、対応する言語に表示が切り替わります。
言語パックとi18nの初期化を別ファイルに分ける 🔗
上のコードでは、コンポーネントの外側に翻訳データを変数として定義しました。翻訳が増えるとコードが長くなり、管理しにくくなります。ここでは翻訳データを別ファイルに分けます。
ターミナルで次のコマンドを実行し、保存用のディレクトリとファイルを作ります。
mkdir -p src/i18n/locales/{en-US,ko-KR,ar-SA} && \
touch src/i18n/index.ts \
src/i18n/locales/en-US/translations.json \
src/i18n/locales/ko-KR/translations.json \
src/i18n/locales/ar-SA/translations.json
次のような構成になります。
📦 src
┣ 📂 i18n
┃ ┣ 📂 locales
┃ ┃ ┣ 📂 ar-SA
┃ ┃ ┃ ┗ 📜 translations.json
┃ ┃ ┣ 📂 en-US
┃ ┃ ┃ ┗ 📜 translations.json
┃ ┃ ┗ 📂 ko-KR
┃ ┃ ┃ ┗ 📜 translations.json
┗ ┗ 📜 index.ts
各JSONファイルに翻訳データを書きます。
/src/i18n/locales/en-US/translations.json{
"welcome": "Welcome to the app!"
}
/src/i18n/locales/ko-KR/translations.json{
"welcome": "앱에 오신 것을 환영합니다!"
}
/src/i18n/locales/ar-SA/translations.json{
"welcome": "مرحبًا بك في التطبيق!"
}
src/i18n/index.ts に、翻訳データを読み込むコードを書きます。
/src/i18n/index.tsimport i18n from "i18next";
import { initReactI18next } from "react-i18next";
import { getLocales } from "expo-localization";
import translationEn from "./locales/en-US/translations.json";
import translationKo from "./locales/ko-KR/translations.json";
import translationAr from "./locales/ar-SA/translations.json";
const resources = {
en: { translation: translationEn },
ko: { translation: translationKo },
ar: { translation: translationAr },
};
const initI18n = async () => {
i18n.use(initReactI18next).init({
resources,
lng: getLocales()[0].languageCode || "ko",
fallbackLng: {
"en-*": ["en"],
"ko-*": ["ko"],
"ar-*": ["ar"],
default: ["en"],
},
interpolation: {
escapeValue: false,
},
react: {
useSuspense: false,
},
});
};
initI18n();
export default i18n;
最後に src/i18n/index.ts をインポートして使います。これで翻訳データを管理しやすくなりました。/app/_layout.tsx に適用すると、プロジェクト全体に反映できます。
/app/index.tsimport "@/src/i18n";
// ... App 컴포넌트 밖의 i18n 초기화 코드 제거
変更した言語をアプリの再起動後も保つには、
AsyncStorage に保存します。
AsyncStorage は、アプリのデータを非同期で保存し、読み込めます。
/src/i18n/index.ts で
AsyncStorage を使い、言語設定を保存、取得します。
- AsyncStorageに保存済みの言語設定を確認します。
- 設定がなければ、端末の言語を取得して適用します。
- 設定があれば、その言語を適用します。
- 言語設定をAsyncStorageに保存します。
言語コードは {言語コード}-{国コード} の形式で扱います。例えば韓国語は ko-KR、英語は en-US です。ただし、今回の翻訳データのキーは ko、en、ar なので、端末の設定から言語コード部分だけを取り出して保存します。問題が起きた場合に備え、既定の言語を en にします。
/src/i18n/index.tsのコード全体
/src/i18n/index.tsimport i18n from "i18next";
import { initReactI18next } from "react-i18next";
import * as Localization from "expo-localization";
import AsyncStorage from "@react-native-async-storage/async-storage";
import translationEn from "./locales/en-US/translations.json";
import translationKo from "./locales/ko-KR/translations.json";
import translationAr from "./locales/ar-SA/translations.json";
const resources = {
en: { translation: translationEn },
ko: { translation: translationKo },
ar: { translation: translationAr },
};
const LANGUAGE_KEY = "@app_language";
const initI18n = async () => {
try {
const savedLanguage = await AsyncStorage.getItem(LANGUAGE_KEY);
let selectedLanguage = savedLanguage;
if (!selectedLanguage) {
const deviceLocales = Localization.getLocales();
const deviceLocale = deviceLocales[0]?.languageTag || "en-US";
const languageCode = deviceLocale.split("-")[0];
if (languageCode in resources) {
selectedLanguage = languageCode;
} else {
selectedLanguage = "en";
}
}
await i18n.use(initReactI18next).init({
resources,
lng: selectedLanguage,
fallbackLng: {
"en-*": ["en"],
"ko-*": ["ko"],
"ar-*": ["ar"],
default: ["en"],
},
interpolation: {
escapeValue: false,
},
react: {
useSuspense: false,
},
});
if (!savedLanguage) {
await AsyncStorage.setItem(LANGUAGE_KEY, selectedLanguage);
}
} catch (error) {
await i18n.use(initReactI18next).init({
resources,
lng: "en",
fallbackLng: "en",
interpolation: {
escapeValue: false,
},
react: {
useSuspense: false,
},
});
}
};
initI18n();
export default i18n;
/app/index.tsx で言語切り替えボタンを押すたび、AsyncStorageに言語設定を保存します。
/app/index.tsのコード全体
/app/index.tsimport React from "react";
import "@/src/i18n";
import { View, Text, Button } from "react-native";
import { useTranslation } from "react-i18next";
import AsyncStorage from "@react-native-async-storage/async-storage";
const App = () => {
const { t, i18n } = useTranslation();
const changeLanguage = async (language: "en" | "ko" | "ar") => {
i18n.changeLanguage(language);
await AsyncStorage.setItem("@app_language", language);
};
return (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
gap: 20,
}}
>
<Text>{t("welcome")}</Text>
<Button title="English" onPress={() => changeLanguage("en")} />
<Button title="Korean" onPress={() => changeLanguage("ko")} />
<Button title="Arabic" onPress={() => changeLanguage("ar")} />
</View>
);
};
export default App;
これで、アプリを終了しても設定が残ります。言語を切り替え、アプリを閉じてから起動し直し、確認してください。
Cannot find native module 'ExpoLocalization' 🔗
開発モードで、設定がネイティブコードに反映されていないと発生します。次のコマンドを実行して対処できます。
cd ios && pod install && cd ..
npx expo run:ios
今回は、Expoで言語パックを使って多言語対応する方法を学びました。
expo-localization で端末の設定を取得し、
i18next で翻訳を実装しました。
さらに、翻訳データを別ファイルで管理し、AsyncStorageで言語設定を保存する方法も学びました。世界に向けてアプリを提供する場合は多言語対応が欠かせません。ユーザーが使いやすいよう、言語パックの導入を検討してみてください。