初めてのiOSアプリが審査を通り、無事に公開されました。
喜んでストアページを眺めていたら、 「情報」セクションの言語が「英語」になっていました。
中身は全部日本語なのに・・・
原因を突き止めて直すまでの記録と、次に作るアプリで同じ失敗をしないための設定をまとめます。


対象:Expo SDK 54 / EAS Build所要:コード修正10分+審査1回Xcodeを開く必要:なし
この「言語」はどこから来ているのか
最初に困ったのは、App Store Connectの管理画面をいくら探しても、 この「言語」を編集する欄が見つからないことでした。
説明文もキーワードもスクリーンショットも自分で入力したのに、 ここだけ入力欄がない・・・
答えは単純で、この行は人間が入力するものではありません。
Appleがアップロードされた.ipaの中身を読み取って、自動的に表示しています。
つまりストア側の設定ミスではなく、アプリ本体が「私は英語のアプリです」と名乗っていたということです。
ここが分かると、切り分けるべき対象が2つあることも見えてきます。
同じ「英語になっている」でも、直す場所と手間がまるで違います。
| どこの話か | 症状 | 直し方 | 審査 |
|---|---|---|---|
| App Store Connectの 「主要言語」 | 説明文やスクリーンショットの見出しなど、ストアページ全体が英語扱いになる | Web上の設定を変えるだけ | 不要 |
| アプリ本体 (バイナリ)の対応言語 | 「情報」の中の「言語」の行だけが英語になる | コードを修正して再ビルド | 必要 |
今回は後者。ストアページの日本語は正しく出ていて、「言語」の1行だけが英語。 つまり再ビルドと再審査が必要だということになります。
証拠を確認する
Expoで作ったアプリでも、ビルド時には裏でネイティブのiOSプロジェクトが生成されています。
npx expo prebuild を実行すると ios/ フォルダが作られるので、その中身を見れば実態が分かる。
# ios/<プロジェクト名>/Info.plist
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> ← 既定値のまま = en
# ios/<プロジェクト名>.xcodeproj/project.pbxproj
knownRegions = (
en,
Base,
); ← 日本語がどこにもいない
さらに、日本語のリソースを入れるための ja.lproj というフォルダも、 対応言語を宣言する CFBundleLocalizations というキーも、どちらも存在しませんでした。
Appleから見れば、これは疑いようもなく英語だけのアプリです。
盲点だったこと。 アプリ内の文字を全部日本語で書いても、それは「日本語対応」の宣言にはなりません。
画面に表示される文字列はJavaScript側の話で、Appleが読むのはネイティブの設定ファイルのほう。この2つは完全に別レイヤーで、自動では繋がりません。
Xcodeは開かなくていい
調べ始めてまず混乱したのが、この件を扱う記事の多くが 「Xcodeを開いてPROJECT → Info → Localizations → + でJapaneseを追加」という手順を案内していることでした。
Xcodeでの操作は初心者にはハードルが高いし、そもそも普段のExpo開発では一度も開いていません。
結論から言うと、Expoで作っているなら開かなくていいのです。
ExpoにはCNG(Continuous Native Generation)という仕組みがあり、 ios/ フォルダは app.json からビルドのたびに自動生成される使い捨ての成果物として扱われます。
実際、このフォルダは .gitignore に入っていてGit管理の対象ですらありません。
Xcodeで手を加えても、次のビルドで生成し直されて消えてしまう。Xcodeでの操作を案内している記事は、ネイティブプロジェクトを自分で管理する構成(bare workflow)を前提にしています。 自分がどちらなのかは、ios/ フォルダがGitに入っているかどうかで判断できます。
# ターミナル — どちらの構成か確かめる
$ git ls-files ios | head -5
何も出てこなければCNG構成 → app.jsonを直す
ファイル名がずらっと出たらbare構成 → Xcodeで直す
実際に書いたコード
用意したファイルは2つだけだった。
① languages/ja.json を新規作成
日本語ロケール用の文字列をまとめるファイル。ビルド時にこれが ja.lproj/InfoPlist.strings というネイティブのファイルに変換されます。
つまりXcodeで手作業する代わりに、このJSONを置いておくということ。
# languages/ja.json
{
"ios": {
"CFBundleDisplayName": "3秒で今日のきぶん",
"CFBundleName": "3秒で今日のきぶん"
},
"android": {
"app_name": "3秒で今日のきぶん"
}
}
② app.json に追記
# app.json
{
"expo": {
"version": "1.0.1",
"ios": {
"bundleIdentifier": "com.example.myapp",
"infoPlist": {
"CFBundleDevelopmentRegion": "ja",
"CFBundleLocalizations": ["ja"],
"CFBundleAllowMixedLocalizations": true
}
},
"locales": {
"ja": "./languages/ja.json"
}
}
}
それぞれの役割はこうなっている。
| 設定 | 何をするか |
|---|---|
CFBundleLocalizations | App Storeの「言語」欄が直接読むのはここ。「このアプリは日本語対応です」という明示的な宣言 |
CFBundleDevelopmentRegion | アプリの既定言語。未指定だと en にフォールバックする |
CFBundleAllowMixedLocalizations | locales を使うときにExpo公式が必須としている設定 |
locales | ja.jsonを読んで ja.lproj を生成し、Xcodeプロジェクトにリソースとして登録する |
本当に効いているか確かめる
ここが今回いちばん大事だったところ。 App Storeへの反映は審査を通らないと確認できないので、間違っていたら審査1回分を無駄にします。
提出前に、手元で確実に確かめておくといいとおもいます。
ビルド前:生成されるファイルを見る
$ npx expo prebuild -p ios --no-install
$ ls ios/*/Supporting/ja.lproj/
InfoPlist.strings ← 生成されている
ビルド後:完成した.ipaを解剖する
.ipa はただのZIPなので、ダウンロードして展開すれば中身をそのまま確認できます。
Appleに送るのと同じものを直接見られるので、これ以上に確実な検証はありません。
# ターミナル — 提出するものそのものを検証する
$ unzip -q app.ipa -d x
$ APP=$(ls -d x/Payload/*.app)
$ find "$APP" -maxdepth 1 -name "*.lproj"
x/Payload/MyApp.app/ja.lproj ← ja.lprojだけが存在(en.lprojは無い)
$ plutil -extract CFBundleLocalizations json -o - "$APP/Info.plist"
["ja"]
$ plutil -extract CFBundleDevelopmentRegion raw -o - "$APP/Info.plist"
ja
修正前は en/日本語リソースなし。修正後は ja.lproj が入り、対応言語は ["ja"]。 Appleが読み取る対象が確かに切り替わっていることを、提出前に実物で確認できました。
反映されるまでの流れ
ストアの説明文を直したときのような即時反映ではありません。
この1行を変えるためだけに、 新しいバージョンを作って審査に出し直す必要があります。
- バージョン番号を上げる同じ番号のままでは新しいビルドを提出できない。1.0.0 → 1.0.1 に。ビルド番号のほうはEASが自動で採番してくれた
- ビルドする
eas build --platform ios --profile production。prebuildはEASのサーバー側で走るので、手元のios/フォルダの状態は関係ない - アップロードする
eas submit --platform ios --profile production。この後Appleの処理に5〜10分かかり、完了するとメールが届く - App Store Connectで新規バージョンを作成「このバージョンの最新情報」は必須項目。今回はユーザーから見える変更がないので、正直に「表示言語の設定を修正しました」と書いた
- 審査に提出して待つ公開されたタイミングで、ようやく「情報 → 言語」が日本語に変わる
新バージョンで引き継がれないものがあります。
新規バージョンを作ると、説明文やスクリーンショットは引き継がれるが、 「このバージョンの最新情報」は必ず空で、 App Review情報に添付したファイルも消えます。
不具合ではなく仕様なので、前回添付した審査用の動画などがあれば貼り直しておく。
最初にこれを置いておけば起きない
今回の作業は、コードを書いた時間より「なぜこうなるのか」を理解する時間のほうが圧倒的に長かったです。 そして修正内容そのものは、プロジェクトを作った初日にやっておけば10分で終わるものでした。 次からは最初のビルドの前に、この2ファイルを置くことにします。
languages/ja.json── 表示名と、使う予定の権限説明文を日本語でapp.json──localesとios.infoPlistの3キー
権限の説明文(カメラや写真ライブラリを使うときに出るあのダイアログの文章)も同じ仕組みで日本語にできます。 ここを英語のまま公開してしまうと、ユーザーは日本語アプリの中で突然英語の許可ダイアログを見ることになります。
ついでに揃えておきたい。languages/ja.json — 権限の説明文も入れておく
{
"ios": {
"CFBundleDisplayName": "アプリの日本語名",
"CFBundleName": "アプリの日本語名",
// 実際に使う権限だけを残すこと
"NSCameraUsageDescription": "写真を撮影して記録に添付するために使用します。",
"NSPhotoLibraryUsageDescription": "保存済みの写真を記録に添付するために使用します。"
},
"android": {
"app_name": "アプリの日本語名"
}
}
使っていない権限の説明文まで書いておくと、審査で「なぜこの権限が必要なのか」を問われる原因になります。
通知だけを使うアプリなら、権限文言の準備は要りません。 通知の許可ダイアログには NSxxxUsageDescription にあたるキーが存在せず、 文面はOS側が端末の言語に合わせて自動で出してくれます。
アプリ名を日本語にするときの落とし穴
ついでに、名前まわりでも一度つまずいた。app.json の name は Xcodeのターゲット名の元になるため、「3秒で〜」のように先頭が数字の日本語名を入れると、 変換後のターゲット名が 3 のような裸の数字になってビルドが壊れることがあります。
安全策として、name は常に半角英数にして、日本語の表示名は infoPlist と ja.json の側で与えるというルールにしておくと、 名前で悩まなくて済みます。
まとめ
- App Storeの「情報 → 言語」は入力欄がなく、アプリ本体から自動で読み取られる
- アプリ内の文字が日本語でも、それは日本語対応の宣言にはならない
- Expo(CNG構成)ならXcodeは開かない。
app.jsonとja.jsonの2ファイルで完結する - 提出前に
.ipaを展開して確かめれば、審査1回分を賭けずに済む - そして何より、最初のビルドの前にやっておけば、この記事は必要なかった
公開できた達成感のあとに来た、地味だけれど確実に効く後始末の話。 同じところでつまずいた人の検索に引っかかってくれたら嬉しいです。
この記事のカテゴリー
開発ブログ の記事をもっと見る →