スクリーンショットを多言語対応しよう
00要約Overview
01物語Story
02解決Solution
Criteria
- Mac を持たずに成立すること
- 大量の言語をカバーできること
- Apple の厳密なサイズ要件を満たせること
Answer
一つの手法で全部やろうとせず、4 つの選択肢を「全部使う」ことにした。
- UI をできるだけ言語に依存しない設計にする — 画像内の文字を減らせば、そのぶん翻訳も生成も要らなくなる。
- 主要言語 (日本語・英語など) は実機で撮る — 言語を切り替えて、実機のスクリーンショットを撮影する。
- その他の言語は Flow で一括変換する — 「この画像を○○語版にして」と指示し、まとめて生成する。
- 英語版に説明を描き込み、他言語でも流用する — イラストや注釈を加えておけば、細部まで各言語で作り直さずに済む。
生成した画像は ZIP でまとめて Git にアップロードし、Claude で展開・配置したうえで、GitHub Actions から App Store Connect へアップロードする (分割・トリミング・リサイズを通し、ストアの API 経由で送る)。
Reason
組み合わせでしか、Mac なしの制約を越えられない。 全部を実機で撮れば言語数分の手間で潰れ、全部を AI 生成にすれば細部が崩れてサイズ要件も外れる。
どれか一つでは詰む。
けれど、組み合わせれば越えられる。
とりわけ Flow は、一度に大量の画像を生成して ZIP でまとめて落とせるうえ、生成を待つ間に次の生成を指示できるので、作業効率がとても高い。
Options
- 全部を実機で撮影する — 忠実だが、言語数だけ撮影の手間がかかり、現実的でない。
- 全部を AI で生成する — 速いが、細部が崩れやすく、翻訳も正確ではない。
- Claude で画像置換してもらう — Claude の弱いところとして、画像生成エンジンを持っていない。酷いものが出てきたので諦めた。
03結果Result
Good
Mac なしで、多言語のスクリーンショットが用意できた。UI の言語非依存設計と英語版の説明流用のおかげで、生成すべき量そのものも抑えられた。地味に大量だった作業が、AI との組み合わせで回るようになった。Flow は、無料枠で結構画像生成ができる。それを ZIP で DL できる点も大きい。
Bad
コードを画像生成 (翻訳時) に入れていないので、精度が悪い。何度も画像生成 AI に修正をかけていくと、文字が画像なのでどんどん劣化していく。改善の余地は色々ありそう。
Follow-up
これで、アプリ本体・申請情報・スクリーンショットの 3 つすべてが多言語化でき、世界公開の準備が整った。狭いニッチでも、世界を母集団にすれば市場になる — その狙いを定める話は 3.1.2 STP に続く。
■ 再現できる方法
この構成を自分で再現したい人向けのメモ。読み物ではないので、指示文は数行だけ表示してスクロールにしてある。
まずは、仕組みを作成。下記を 1 回のコピペで貼り付けて実行 (プロンプト + Gemfile + workflow + Fastfile をまとめて渡す)。
App Store Connect のスクリーンショットを GitHub Actions から fastlane
(spaceship 直叩き)で自動アップロードする仕組みを作りたい。
前提:
・ASC の API キーは GitHub Secrets に APPLE_API_KEY_ID /
APPLE_API_KEY_ISSUER_ID / APPLE_API_KEY_P8 の3つで登録済み
・アプリは SKU=<自分のSKU>、Bundle ID=<自分のBundleID> で ASC 登録済み
作ってほしいもの:
1. Gemfile … fastlane 2.228 系を固定
2. fastlane/Fastfile に upload_screenshots_direct レーンを spaceship 直叩きで実装
・apps/<SKU>/screenshots/<locale>/*.png を読む
・iPhone ファイルは APP_IPHONE_67、iPad ファイルは APP_IPAD_PRO_3GEN_129
のスクショセットに入れ替えアップロード
・ローカルに画像が無いロケールはスキップ(= 別経路で上げ済みを消さない)
3. .github/workflows/ に screenshots 用の workflow_dispatch ワークフローを1つ。
env に APP_SKU / APP_BUNDLE_ID と3つの Secret を渡す(timeout は長めに)
4. apps/<SKU>/screenshots/<locale>/ を50ロケール分(.gitkeep 付き)
さらに、apps/<SKU>/ に置いた ZIP を読んで、ファイル名の言語タグで
ロケールを判定し、iPhone は縦3分割・iPad は縦2分割 → 中央トリミング →
Apple 推奨サイズ(iPhone 1320×2868 / iPad 2752×2064)へリサイズして
apps/<SKU>/screenshots/<locale>/ に書き出す Python スクリプトも作って。
実行前に「どのファイルが→どのロケールに行くか」の対応表を出す作りにして。
source "https://rubygems.org"
gem "fastlane", "~> 2.228.0"
name: <SKU> — screenshots
on:
workflow_dispatch:
jobs:
upload:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.3"
bundler-cache: true
- name: Run fastlane upload_screenshots_direct
env:
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_KEY_ISSUER_ID: ${{ secrets.APPLE_API_KEY_ISSUER_ID }}
APPLE_API_KEY_P8: ${{ secrets.APPLE_API_KEY_P8 }}
APP_SKU: <SKU>
APP_BUNDLE_ID: <BundleID>
FASTLANE_SKIP_UPDATE_CHECK: "1"
run: bundle exec fastlane ios upload_screenshots_direct
default_platform(:ios)
platform :ios do
desc "Upload per-locale screenshots via spaceship (bypasses deliver's broken screenshot upload)"
lane :upload_screenshots_direct do |options|
sku = options[:sku] || ENV["APP_SKU"]
bundle_id = options[:bundle_id] || ENV["APP_BUNDLE_ID"]
UI.user_error!("sku is required") if sku.nil? || sku.empty?
UI.user_error!("bundle_id is required") if bundle_id.nil? || bundle_id.empty?
screenshots_path = File.expand_path("../apps/#{sku}/screenshots", __dir__)
UI.user_error!("screenshots not found: #{screenshots_path}") unless Dir.exist?(screenshots_path)
require "spaceship"
Spaceship::ConnectAPI.token = Spaceship::ConnectAPI::Token.create(
key_id: ENV.fetch("APPLE_API_KEY_ID"),
issuer_id: ENV.fetch("APPLE_API_KEY_ISSUER_ID"),
key: ENV.fetch("APPLE_API_KEY_P8"),
)
app = Spaceship::ConnectAPI::App.find(bundle_id)
UI.user_error!("app not found for bundle_id=#{bundle_id}") if app.nil?
version = app.get_edit_app_store_version
UI.user_error!("no edit version found on App Store Connect") if version.nil?
UI.message("Writing screenshots to version #{version.version_string} (#{version.app_store_state})")
# 出力ファイル名 -> ASC のスクショ表示タイプ。
# "iPhone" ファイルは 1320x2868 (iPhone 6.9")、Apple が 6.7"/6.9" 共用にする枠へ。
# "iPad" ファイルは 2752x2064、iPad Pro 12.9" 3rd gen 横向き。
device_of = ->(filename) {
case filename
when /iPhone/i then "APP_IPHONE_67"
when /iPad/i then "APP_IPAD_PRO_3GEN_129"
else nil
end
}
summary_lines = ["## Direct screenshot upload to #{version.version_string}", ""]
updated = 0
version.get_app_store_version_localizations.sort_by(&:locale).each do |loc|
locale_dir = File.join(screenshots_path, loc.locale)
unless Dir.exist?(locale_dir)
summary_lines << "- **#{loc.locale}**: skipped (no local directory)"
next
end
pngs = Dir.children(locale_dir).select { |f| f.downcase.end_with?(".png") }.sort
if pngs.empty?
summary_lines << "- **#{loc.locale}**: skipped (no PNGs)"
next
end
groups = pngs.group_by { |f| device_of.call(f) }.reject { |k, _| k.nil? }
if groups.empty?
summary_lines << "- **#{loc.locale}**: skipped (no iPhone/iPad files)"
next
end
existing_sets = loc.get_app_screenshot_sets.group_by(&:screenshot_display_type)
groups.each do |display_type, files|
set = existing_sets[display_type]&.first
if set
# 既存スクショを消してから新しいものに入れ替える。
set.app_screenshots.each do |shot|
shot.delete!
end
else
set = loc.create_app_screenshot_set(attributes: {
screenshotDisplayType: display_type,
})
end
files.each do |filename|
path = File.join(locale_dir, filename)
UI.message(" #{loc.locale}/#{filename} -> #{display_type}")
set.upload_screenshot(path: path, wait_for_processing: true)
end
end
summary_lines << "- **#{loc.locale}**: uploaded #{pngs.length} screenshot(s)"
updated += 1
end
summary_lines << ""
summary_lines << "Updated #{updated} locale(s)."
report = summary_lines.join("\n")
UI.message(report)
step_summary = ENV["GITHUB_STEP_SUMMARY"]
File.write(step_summary, report) if step_summary && !step_summary.empty?
end
end
次に、各言語のスクショ画像を詰めた ZIP を apps/<アプリ名>/ に置いてから、下記をコピペして依頼。
apps/<アプリ名>/ に、各言語のスクショ画像を詰めた ZIP を置いた。 これを App Store 用に処理して。 ・ファイル名の言語タグ(ja_01.png / en-US_… / 韓国語.png など)でロケール判定 ・iPhone は縦3分割・iPad は縦2分割、中央トリミングで Apple 推奨サイズにリサイズ (iPhone 6.9" = 1320×2868 / iPad 13" = 2752×2064) ・en-US → en-AU / en-CA / en-GB のような家族コピーも展開 ・「その他言語」画像は未指定ロケール全部に自動展開 実行前に「どのファイルが → どのロケールに行くか」の対応表を出して確認させて。 OK を出したら apps/<アプリ名>/screenshots/<locale>/ に生成して。 <アプリ名> のスクショをアップする前にチェックして。 screenshots フォルダを全ロケール分ならべて、各ロケールの枚数と ファイル名を一覧で出して。触ってほしくないロケールを消さないか確認したい。
よく分からなかったら「何してるの?」と聞けば教えてくれるし、うまくいったら「スキルにして」と頼んだらスキルにしてくれる。スキル化するまで /clear や新しいセッションで再開しないこと。記憶が無くなるので、Claude の記憶 (同じセッション内) でスキル化させると、作業が安定する。
無理にスクショを多言語にする必要はない。デフォルト言語の画像が共通で使われるので、ゲームなど分かりやすいものであれば、無理して複数作らなくても、イラストで分かればリリースはできる。