App Store Connect の多言語対応を Expo の設定ファイルで管理する

App Store Connect で多言語対応するときは、GUIで国ごとにタイトルや説明文、プロモーションテキストを書かなければならず、けっこうな手間がかかります。

Expo のプロジェクトでは、 store.config.json を使うことで、これらの入力をかなり省力化できます。 store.config.json をプロジェクトのルートに配置しましょう。

shell
touch store.config.json

公式のサンプルでは以下のようになっています。

store.config.json
{
  "configVersion": 0,
  "apple": {
    "info": {
      "en-US": {
        "title": "Awesome App",
        "subtitle": "Your self-made awesome app",
        "description": "The most awesome app you've ever seen",
        "keywords": ["awesome", "app"],
        "marketingUrl": "https://example.com/en/promo",
        "supportUrl": "https://example.com/en/support",
        "privacyPolicyUrl": "https://example.com/en/privacy"
      }
    }
  }
}

Static store config

多言語化対応

まずは多言語化対応を行います。上の例だと en-US しかないので、Apple info languages を見ながら必要な言語を追加していきます。 "en-US": と同じ階層に言語設定をコピペで追加していけばOKです。

アプリの説明文は日本語で書いて、その他の言語への翻訳は機械翻訳よりも GPT-4 を使う方がいい感じにしてくれます。

ChatGPT
以下のようなJSONで、アプリの説明文とキーワードを書いています。

```
"ja": {
	"description": "習慣サプリは習慣づけを応援するアプリです。\\n習慣にしたいタスクについて、どれだけ時間を投入できたかを記録できます。\\n一つ一つのタスクはストップウォッチで時間を測ることができます。その時間を記録して、振り返ることができるので、モチベーションが維持しやすくなっています。",
	"keywords": ["habit", "todo", "note", "stopwatch", "成長", "習慣", "成功", "繰り返し", "勉強を習慣にする", "毎日勉強", "勉強時間の記録"],
}
```

この説明文をを多言語化したいです。
英語、ドイツ語、スペイン語、韓国語が対象です。

上記の日本語での説明を踏まえて、それぞれの言語で、わかりやすい文章で説明文を書いてください。また、キーワードの配列も作成してください。
出力はそれぞれの言語で json の形でお願いします。

GPT-4 か DeepL を使って、いい感じに翻訳してみてください。

カテゴリを選択する

Apple categories を見ながら、自分のアプリケーションのカテゴリを設定します。二重配列のうち、前半はプライマリカテゴリで、後半はサブカテゴリです。

store.config.json
{
  "configVersion": 0,
  "apple": {
    "categories": ["UTILITIES", "LIFESTYLE"],
  }
}

年齢制限の設定

Apple advisory を見ながら、年齢制限の設定を行います。ギャンブル的なコンテンツがあるかどうか、などを設定します。1度手動で App Store Connect で登録した経験がないとイメージが湧きづらいかもしれません。 apple.advisory に情報を配置します。

store.config.json
{
  "configVersion": 0,
  "apple": {
    "advisory": {
      "alcoholTobaccoOrDrugUseOrReferences": "NONE",
      "contests": "NONE",
      "gamblingSimulated": "NONE",
      "horrorOrFearThemes": "NONE",
      "matureOrSuggestiveThemes": "NONE",
      "medicalOrTreatmentInformation": "NONE",
      "profanityOrCrudeHumor": "NONE",
      "sexualContentGraphicAndNudity": "NONE",
      "sexualContentOrNudity": "NONE",
      "violenceCartoonOrFantasy": "NONE",
      "violenceRealistic": "NONE",
      "violenceRealisticProlongedGraphicOrSadistic": "NONE",
      "gambling": false,
      "unrestrictedWebAccess": false,
      "kidsAgeBand": null,
      "seventeenPlus": false
    }
  }
}

自動リリースの設定

手動リリース化自動リリースかを設定します。審査終了後に自動でリリースして良ければ、 "automaticRelease": true を指定します。

完成形のひながたは以下のとおりです。

store.config.json
{
  "configVersion": 1,
  "apple": {
    "advisory": {
      "alcoholTobaccoOrDrugUseOrReferences": "NONE",
      "contests": "NONE",
      "gamblingSimulated": "NONE",
      "horrorOrFearThemes": "NONE",
      "matureOrSuggestiveThemes": "NONE",
      "medicalOrTreatmentInformation": "NONE",
      "profanityOrCrudeHumor": "NONE",
      "sexualContentGraphicAndNudity": "NONE",
      "sexualContentOrNudity": "NONE",
      "violenceCartoonOrFantasy": "NONE",
      "violenceRealistic": "NONE",
      "violenceRealisticProlongedGraphicOrSadistic": "NONE",
      "gambling": false,
      "unrestrictedWebAccess": false,
      "kidsAgeBand": null,
      "seventeenPlus": false
    },
    "categories": [["LIFESTYLE"], "UTILITIES"],
    "release": {
      "automaticRelease": true
    },
    "review": {
      "firstName": "John",
      "lastName": "Doe",
      "email": "john@example.com",
      "phone": "+1 123 456 7890",
      "demoUsername": "john",
      "demoPassword": "applereview",
      "demoRequired": false,
      "notes": "This is an example app primarily used for educational purposes."
    },
    "info": {
      "en-US": {
        "title": "Awesome App",
        "subtitle": "Your self-made awesome app",
        "description": "The most awesome app you've ever seen",
        "keywords": ["awesome", "app"],
        "releaseNotes": "",
        "promoText": "",
        "marketingUrl": "https://example.com/en/promo",
        "supportUrl": "https://example.com/en/support",
        "privacyPolicyUrl": "https://example.com/en/privacy"
      },
      "en-AU": {
        "title": "Awesome App",
        "subtitle": "Your self-made awesome app",
        "description": "The most awesome app you've ever seen",
        "keywords": ["awesome", "app"],
        "marketingUrl": "https://example.com/en/promo",
        "supportUrl": "https://example.com/en/support",
        "privacyPolicyUrl": "https://example.com/en/privacy"
      },
      "en-CA": {
        "title": "Awesome App",
        "subtitle": "Your self-made awesome app",
        "description": "The most awesome app you've ever seen",
        "keywords": ["awesome", "app"],
        "marketingUrl": "https://example.com/en/promo",
        "supportUrl": "https://example.com/en/support",
        "privacyPolicyUrl": "https://example.com/en/privacy"
      },
      "en-GB": {
        "title": "Awesome App",
        "subtitle": "Your self-made awesome app",
        "description": "The most awesome app you've ever seen",
        "keywords": ["awesome", "app"],
        "marketingUrl": "https://example.com/en/promo",
        "supportUrl": "https://example.com/en/support",
        "privacyPolicyUrl": "https://example.com/en/privacy"
      },
      "ja": {
        "title": "Awesome App",
        "subtitle": "Your self-made awesome app",
        "description": "The most awesome app you've ever seen",
        "keywords": ["awesome", "app"],
        "marketingUrl": "https://example.com/en/promo",
        "supportUrl": "https://example.com/en/support",
        "privacyPolicyUrl": "https://example.com/en/privacy"
      },
      "es-MX": {
        "title": "Awesome App",
        "subtitle": "Your self-made awesome app",
        "description": "The most awesome app you've ever seen",
        "keywords": ["awesome", "app"],
        "marketingUrl": "https://example.com/en/promo",
        "supportUrl": "https://example.com/en/support",
        "privacyPolicyUrl": "https://example.com/en/privacy"
      },
      "es-ES": {
        "title": "Awesome App",
        "subtitle": "Your self-made awesome app",
        "description": "The most awesome app you've ever seen",
        "keywords": ["awesome", "app"],
        "marketingUrl": "https://example.com/en/promo",
        "supportUrl": "https://example.com/en/support",
        "privacyPolicyUrl": "https://example.com/en/privacy"
      },
      "ko": {
        "title": "Awesome App",
        "subtitle": "Your self-made awesome app",
        "description": "The most awesome app you've ever seen",
        "keywords": ["awesome", "app"],
        "marketingUrl": "https://example.com/en/promo",
        "supportUrl": "https://example.com/en/support",
        "privacyPolicyUrl": "https://example.com/en/privacy"
      }
    }
  }
}

アプリの表示名を場所ごとに切り替える

app.jsonexpo 直下の場所、つまり ios と同じ階層に locales を配置します。

app.json
{
  "expo": {
    "name": "Habit Motivator",
    "ios": {},
    "locales": {
      "ja": "./languages/ja.json",
      "en-US": "./languages/en-US.json",
      "en-GB": "./languages/en-GB.json",
      "en-CA": "./languages/en-CA.json",
      "en-AU": "./languages/en-AU.json",
      "es-MX": "./languages/es-MX.json",
      "es-ES": "./languages/es-ES.json",
      "de-DE": "./languages/de-DE.json",
      "ko": "./languages/ko.json"
    },

  }
}
shell
mkdir languages

languages 以下に移動して、以下のコマンドを打ちます。

shell
cd languages
shell
touch {en-GB,en-CA,en-AU,en-US,ja,es-MX,es-ES,de-DE,ko,zh-Hans,zh-Hant,ru}.json

languages/ja.json には以下のように書きます。

ja.json
{
  "CFBundleDisplayName": "習慣サプリ",
  "NSContactsUsageDescription": "習慣づけの成功を全力で応援するアプリ"
}

メタデータは以下のコマンドでプッシュできます。ただ、これは後述する eas submit の後で実行してください。

shell
eas metadata:push

ちなみに、アプリ名については機械翻訳するよりも ChatGPTを使った方がそれっぽくなります。

ChatGPT
日本語で、以下のような名前のアプリを作っています。以下のファイルは ja.json です。

```
{
  "CFBundleDisplayName": "習慣サプリ",
  "NSContactsUsageDescription": "習慣づけの成功を全力で応援するアプリ"
}
```

このアプリを多言語化したいです。
英語、ドイツ語、スペイン語、韓国語で、それぞれ日本語でのイメージに適した良いアプリ名を提案してくれませんか?
それぞれの言語で、できるだけ短く、魅力的な名前で提案してもらいたいです。

出力はそれぞれの言語で json の形でお願いします。

App Store Connect にビルドしたファイルをアップロードする

以下のコマンドを実行します。

shell
eas build:configure
eas build

# eas build を実行すると、 eas.json ファイルができるので、 store.config.json へのパスを設定する(下の項目を参照してください)

eas submit

App Store Connect に多言語情報をアップロードする

eas.json"submit" を編集して、 metadataPathstore.config.json を指定します。

eas.json
  "submit": {
    "production": {
      "ios": {
        "metadataPath": "./store.config.json"
      }
    }
  }

以下のコマンドを実行すると、メタデータが App Store Connect に push されます。

shell
eas metadata:push

言語コードの対応

それぞれのファイルの言語コードの対応を表にします。

言語app.json の localesstore.config.jsonのinfoi18nGOOGLETRANSLATE
日本語jajajaja
英語(アメリカ)enen-USenen
英語(オーストラリア)enen-AUenen
英語(カナダ)enen-CAenen
英語(イギリス)enen-GBenen
スペイン語(メキシコ)eses-MXeses
スペイン語(スペイン)eses-ESeses
ドイツ語dede-DEdede
韓国語kokokoko
言語コードの対応表

ISO 639-1 の 言語コード(2文字で表現する決まりがあるらしい)Codes arranged alphabetically by alpha-3/ISO 639-2 Code
app.json"locales" は 2文字にします。

Google Sheets の GOOGLETRANSLATE 関数の言語コード 言語サポート
まとめて翻訳するために Google Sheets の関数を使います。

翻訳するときは、いちいち DeepL にコピペするのは面倒なので、Google Sheets の関数 =GOOGLETRANSLATE($C3, "ja", "es") を使って、表計算シートでまとめて翻訳するのが。

エラー対応の記録

eas submit で already been used on a different account が出た場合

shell
App name "tabmemo" has already been used on a different account. If you have trademark rights to this name and would like it released for your use, submit a claim to Apple.
Visit https://appstoreconnect.apple.com and resolve any warnings, then try again.
Submission failed

app.jsonnameslug を変更します。 name は世界で一意でなければいけないので、誰かと被っていたら eas submit できません。

ただ、単純に slug を書き換えると、以下のようなエラーが出ます。

shell
Project config: Slug for project identified by "extra.eas.projectId" (xxx) does not match the "slug" field (xxx). Learn more: https://expo.fyi/eas-project-id

app.json の下の方に extra ができています。

app.json
    "extra": {
      "eas": {
        "projectId": "e4f13461-3cd9-4b0b-8cb5-2e5657ae4bab"
      }
    }

これは eas build で、 ? Would you like to automatically create an EAS project for xxx? と聞かれたときに自動で作成されるものです。

slugprojectId を紐付けられているため、 slug を変更したときは、一度 extra を削除して、再度 eas build しなければなりません。