React Native Expoアプリを多言語/多通貨対応する方法

React Native Expo アプリを多言語対応しました。日本語ユーザよりも英語ユーザの数の方が遥かに多いため、もし英語でも使えるアプリであれば多言語対応するに越したことはありません。

多言語化の手間はそこまで大きくないですが、アプリのテキスト量によっては時間が必要になります。また、数値と通貨についても対応する場合は結構手間がかかります。

ここでは、React Native Expo アプリを多言語対応する手順についてご説明します。

React Native Expo の Localization

ドキュメントでは i18n-js を使えば多言語対応できるという記載があります。サンプルコードも掲載されていました。

Localization - Expo Documentation

import * as React from "react";
import { Text } from "react-native";
import * as Localization from "expo-localization";
import i18n from "i18n-js";

// Set the key-value pairs for the different languages you want to support.
i18n.translations = {
  en: { welcome: "Hello", name: "Charlie" },
  ja: { welcome: "こんにちは" },
};
// Set the locale once at the beginning of your app.
i18n.locale = Localization.locale;
// When a value is missing from a language it'll fallback to another language with the key present.
i18n.fallbacks = true;

function App() {
  return (
    <Text>
      {i18n.t("welcome")} {i18n.t("name")}
    </Text>
  );
}

ひとつのファイルに多言語の文字列をすべて詰め込むのはつらいので、ja.tsxen.tsxを作成し、これをi18n.tsxでインポートするようにしました。

ja.tsx

export default {
  screenHomeTitle: "ホーム",
};

en.tsx

export default {
  screenHomeTitle: "Home",
};

i18n.tsx

import ja from "./ja";
import en from "./en";

i18n.translations = {
  ja,
  en,
};

この仕組みだけで大部分の多言語対応は可能です。あとは、jsx 中に直接書いているテキストをすべてi18n.t('hoge')で置き換えて、2 つの言語両方のテキストを用意するだけです。

ただし、年月日と通貨についても対応しようとすると色々検討する必要が出てきます。それから、iOS 版と Android 版でロケール情報の取得方法が違う点についても対応が必要になります。

年月日の多言語化

日本では年/月/日なのが、アメリカだと月/日/年だったり、イギリスだと日/月/年だったりします。これは言語だけではなく、どこの国・地域かというロケール情報が必要になります。今回の多言語対応ではロケールを考慮せず、言語ごとに固定の日付フォーマットとしました。

ja.tsx

  date: {
    formats: {
      short: '%Y/%-m',
      graph: '%Y/%-m',
    },
  },

en.tsx

  date: {
    formats: {
      short: '%B %Y',
      graph: '%-m/%Y',
    },
  },

使用例

export const getDate = (
  format: "short" | "graph",
  year: number,
  month: number
): string => {
  return i18n.l(
    "date.formats." + format,
    year + "-" + ("00" + month).slice(-2)
  );
};

数値/通貨の多言語化

数値/通貨は言語や地域によって表記が異なります。React Native Expo で通貨の情報を取得しようとしたのですが、下記のような全通貨のリストを取得する I/F しか無いようです。

Localization.isoCurrencyCodes A list of all the supported language ISO codes.

そこで、JavaScript の Intl を利用し、ロケールと通貨をマッピングして対応しました。詳細は次の記事「JavaScript の Intl で多言語・多通貨対応」を参照ください。

Android 版はロケールが非同期で読み込まれる

Android の場合、ロケール情報を Localization.getLocalizationAsync()で取得します。非同期なので、初期値がないとエラーになります。

i18n.locale = "ja-JP";
if (Platform.OS === "ios") {
  i18n.locale = Localization.locale;
} else {
  const { locale } = await Localization.getLocalizationAsync();
  i18n.locale = locale;
}

更に、i18n.t()によるリソース文字列の取得を関数にしておきます。関数で動的にすることで、ロケールの読み込みが完了したらリソース文字列が適切に再読み込みされるようになります。これで、アプリの起動中でも言語が切り替わったら違う言語のリソースを読み込むようになります。

const LIVING_COSTS = () => ({
  house: i18n.t('livingCostHouse'),
  food: i18n.t('livingCostFood'),
  energy: i18n.t('livingCostEnergy'),

以下のようにリソース文字列を静的に取得すると、非同期のロケール読み込みが完了してもリソース文字列が変わらず、初期値のロケールのままになってしまいます。

export default {
  LIVING_COSTS: {
    house: i18n.t("livingCostHouse"),
    food: i18n.t("livingCostFood"),
    energy: i18n.t("livingCostEnergy"),
  },
};

ここまででローカライズの大部分は完了です。残りは、iOS 版のアプリ名の多言語化と、プライマリ言語の変更だけです。

iOS のアプリ名を多言語化

アプリの下に表示されるアプリ名を多言語化するために、以下の手順で app.json を修正しました。

Deploying to App Stores - Expo Documentation

ここまででアプリの多言語対応は完了です。仮にアプリを日本語と英語に対応した場合は、アップルのアプリストアのプライマリ言語を変更しておきます。

英語をプライマリ言語に設定する

アプリストアのプライマリ言語に指定した言語は、アプリが対応していない言語のユーザに対してデフォルトで表示されます。日本語ユーザよりも英語ユーザの方が人数が多いですしメジャーなので、プライマリ言語としては英語の方が適しています。

プライマリ言語を変更しようとしたところ、以下のエラーが発生して先に進めませんでした。

プライマリロケールを保存できませんでした。最初にすべての必要なスクリーンショットを、この言語の各バージョンに追加する必要があります。

試行錯誤してもエラーを解消できません。しょうがないので多言語対応版を一旦リリース後に変更しようとしたところ、今度はプライマリロケールは無効になっていて変更自体できなくなりました。

その後、多言語対応版の次のバージョンのリリース準備を始めてからようやく変更できました。