320장의 스크린샷을 터미널 한 줄로 올린 이야기
20개 언어 × 4개 디바이스 = 320장. App Store 스크린샷을 Xcode 스크린샷 모드와 fastlane deliver로 자동화한 과정.
320장을 손으로?
FinPal은 20개 언어를 지원한다. App Store에 올려야 하는 스크린샷은 언어마다 4개 디바이스 사이즈 × 4장. 계산하면 이렇다.
iPhone 5.8 · 6.3 · 6.5 · 6.9 인치 — 4개 해상도
App Store Connect 웹 UI에서 이걸 수동으로 올린다고 상상해보면 — 언어 하나 선택하고, 사이즈 탭 열고, 드래그 앤 드롭으로 4장 올리고, 다음 사이즈 탭, 다시 4장… 이걸 80번 반복해야 한다. 한 세트에 30초면 빨리 한다고 쳐도 40분. 근데 중간에 잘못 올리거나 순서가 틀리면? 처음부터 다시.
혼자 앱을 만들면서 이런 데 시간을 쓸 수는 없었다. 자동화가 필요했다.
3단계 파이프라인
전체 흐름은 간단하다. 캡처하고, 폴더 정리하고, 업로드하면 끝.
20개 언어 × 4개 디바이스를 시뮬레이터에서 촬영
Xcode 내보내기 구조 → fastlane이 인식하는 구조로 변환
터미널 한 줄로 App Store Connect에 전부 업로드
Step 1 — Xcode 스크린샷 모드
Xcode에는 앱 스크린샷을 찍을 수 있는 내보내기 기능이 있다. 시뮬레이터에서 원하는 화면을 띄워놓고, 각 언어와 디바이스 사이즈별로 캡처하면 이런 폴더 구조로 내보내진다.
핵심은 앱 코드에 스크린샷 모드를 미리 심어두는 것이다. FinPal에서는 DependencyContainer.isScreenshotMode 플래그를 추가해서, 스크린샷 모드일 때 Firebase를 최소 초기화하고 목업 유저로 바로 메인 화면에 진입하도록 만들었다. 런치 인수로 탭 자동 전환도 가능하게 해둬서, 나중에 UI 테스트 기반 완전 자동화로 확장할 수 있다.
Step 2 — 폴더 구조 변환
문제가 하나 있다. Xcode는 {디바이스}/{언어}/ 구조로 내보내는데, fastlane deliver는 {언어}/ 구조를 기대한다. 그리고 같은 언어 폴더 안에 여러 디바이스의 스크린샷이 섞여야 하는데, fastlane은 이미지 해상도로 디바이스를 자동 감지한다.
변환 스크립트의 핵심은 두 가지다. 언어 코드 매핑과 디바이스 프리픽스 부여.
# 언어 코드 매핑: Xcode 폴더명 → App Store Connect locale
declare -A LOCALE_MAP=(
["en"]="en-US" ["ko"]="ko" ["ja"]="ja"
["ar"]="ar-SA" ["da"]="da" ["de"]="de-DE"
["es"]="es-ES" ["fr"]="fr-FR" ["id"]="id"
["it"]="it" ["nb"]="no" ["nl"]="nl-NL"
["pl"]="pl" ["pt-BR"]="pt-BR" ["sv"]="sv"
["th"]="th" ["tr"]="tr" ["vi"]="vi"
["zh-Hans"]="zh-Hans" ["zh-Hant"]="zh-Hant"
)
# 디바이스 사이즈 → 파일명 프리픽스
declare -A DEVICE_PREFIX=(
["6.1"]="iPhone_5.8" ["6.3"]="iPhone_6.3"
["6.5"]="iPhone_6.5" ["6.9"]="iPhone_6.9"
)
en이 en-US로, nb(노르웨이어)가 no로 바뀌는 식이다. 이 매핑만 맞으면 나머지는 단순 복사.
for size_dir in "$SOURCE_DIR"/*/; do
size=$(basename "$size_dir")
prefix="${DEVICE_PREFIX[$size]:-$size}"
for lang_dir in "$size_dir"*/; do
lang=$(basename "$lang_dir")
locale="${LOCALE_MAP[$lang]:-$lang}"
dest="$TARGET_DIR/$locale"
mkdir -p "$dest"
for screenshot in "$lang_dir"*.png; do
[ -f "$screenshot" ] || continue
filename=$(basename "$screenshot")
cp "$screenshot" "$dest/${prefix}_${filename}"
done
done
done
스크립트를 실행하면 320장이 20개 locale 폴더로 정리된다.
Step 3 — fastlane deliver
fastlane 설정은 놀라울 정도로 간단하다. 파일 두 개면 된다.
# fastlane/Appfile
app_identifier "com.universe.finpal"
apple_id "your@email.com"
itc_team_id "127342735"
# fastlane/Deliverfile
skip_metadata true # 메타데이터는 건드리지 않음
skip_binary_upload true # 바이너리 업로드 안 함
overwrite_screenshots true # 기존 스크린샷 덮어쓰기
screenshots_path "./fastlane/screenshots"
force false # 업로드 전 확인 프롬프트
submit_for_review false # 심사 제출은 수동으로
overwrite_screenshots: true로 설정해두면 스크린샷을 수정하고 다시 실행해도 안전하게 덮어쓰기된다. 실수로 잘못 올렸을 때 부담 없이 재실행할 수 있다.
그리고 실행.
# 1. 폴더 변환
./scripts/prepare_fastlane_screenshots.sh
# 2. App Store Connect에 업로드
fastlane deliver
Apple ID 비밀번호를 입력하고 2FA를 통과하면, fastlane이 320장을 알아서 올려준다. 각 이미지의 해상도를 읽어서 어떤 디바이스인지 자동으로 매칭하기 때문에, 폴더 구조만 맞으면 신경 쓸 게 없다.
마케팅 프레임 — app-store-screenshots
시뮬레이터에서 캡처한 원본 스크린샷은 날것이다. App Store에 올리려면 폰 목업 프레임을 씌우고, 마케팅 카피를 얹어야 한다. 이걸 Figma에서 320장 일일이 작업한다고 상상해보면 — 말이 안 된다.
여기서 사용한 도구가 app-store-screenshots다. 오픈소스 Next.js 기반 스크린샷 생성기인데, 핵심 아이디어가 깔끔하다: 스크린샷을 코드로 디자인한다.
단일 page.tsx 파일로 구성된 스크린샷 에디터
내장 폰 베젤 프레임 + 정밀 스크린 영역 매핑
로케일별 헤드라인·라벨을 코드에 내장
6.9"/6.5"/6.3"/6.1" Apple 필수 사이즈 지원
Headless Chrome으로 전체 조합을 자동 렌더링
토큰 기반 테마 시스템으로 슬라이드별 다른 분위기
사용 방법
생성기를 로컬에서 띄우고, 브라우저에서 언어 탭과 사이즈를 선택하면 실시간 미리보기가 뜬다. 마음에 들면 개별 내보내기도 되고, Puppeteer 스크립트로 전체 조합을 한 번에 뽑을 수도 있다.
# 생성기 서버 시작
cd appstore_screen/generator && npm run dev -- -p 3333
# 전체 320장 일괄 내보내기 (20언어 × 4사이즈 × 4슬라이드)
npm run export
# 주요 언어만 빠르게 (en/ko/ja — 48장)
npm run export:main
# 특정 조합만
node scripts/export-all.mjs --locales en,ko --sizes 0,1
Next.js 앱을 localhost:3333에서 실행
브라우저에서 언어·사이즈별 실시간 미리보기
Headless Chrome이 20언어 × 4사이즈 × 4슬라이드를 자동 렌더링
exported/{사이즈}/{언어}/ 폴더에 320장 저장
FinPal에 맞춘 커스터마이징
기본 도구를 그대로 쓴 건 아니다. FinPal의 모노크롬 디자인에 맞춰 몇 가지를 조정했다.
슬라이드 4장의 디자인 구성은 이렇다: 대시보드(순백 배경), 지출 분석(다크), 영수증(오프화이트), 구독 관리(딥블랙). 밝고 어두운 배경이 교대로 나오면서 App Store에서 스크롤할 때 시각적 리듬이 생긴다.
실제 결과물
같은 대시보드 화면인데, 로케일에 따라 통화, 금액, UI 텍스트, 마케팅 카피가 전부 바뀐다.
단순히 UI 번역만 바뀌는 게 아니라 금액이 실제 환율 기반으로 변환되기 때문에 각 로케일에서 자연스럽게 보인다. 가게 이름도 로케일에 맞춰 바뀐다.
결과
가장 큰 이점은 "다음번"에 있다. 스크린샷을 새로 찍어야 할 때 — 디자인이 바뀌거나, 새 기능이 추가되거나 — 같은 스크립트를 돌리고 fastlane deliver만 치면 된다. 처음 세팅에 1시간 정도 들었지만, 앞으로의 모든 업데이트에서 그 시간을 회수한다.
다음은
이번에는 스크린샷만 자동화했지만, fastlane deliver는 앱 설명, 키워드, 릴리스 노트 같은 메타데이터도 관리할 수 있다. 20개 언어의 앱 설명을 JSON으로 관리하고 한 번에 올리는 것. 그게 다음 목표다.
FinPal 시리즈의 이전 글도 있다:
관련 글
20만엔 사고 이후: FinPal 아키텍처 전면 개편기
API 키 탈취 사고 이후, FinPal의 AI 호출 구조를 클라이언트 직접 호출에서 서버 사이드 프록시로 전면 개편한 과정을 기록했다.
사고 도구에 AI가 꼭 필요한가
모든 프로덕트에 AI를 붙이는 시대. 하지만 Dimension은 AI 없이도 작동하는 사고 도구를 지향한다. 왜 그런 선택을 했는지, 그리고 이건 노트 앱과 어떻게 다른지.