Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

Veyrnox store listings — 44 locales

Machine-translated App Store + Google Play listing metadata for every language in src/i18n/SUPPORTED_LANGUAGES.

What's here

  • _schema.json — JSON Schema for one <locale>.json file.
  • _source-apple.md, _source-play.md — the copies extracted from docs/play-launch/app-store-submission-copy.md + docs/play-launch/store-listing.md (archived here for provenance).
  • en.jsonsource of truth. Any edit to store copy starts here.
  • 43 sibling <locale>.json files — machine-translated from en.json.
  • ../scripts/upload-store-listings.mjs — uploads all of the above to Apple + Google via their publishing APIs, except play.releaseNotes.
  • ../scripts/play-release-notes.mjs — emits play.releaseNotes as the whatsnew-<play-lang> file format that r0adkll/upload-google-play reads.

Release notes: Apple and Play are separate strings

apple.whatsNew and play.releaseNotes say the same thing and are not interchangeable:

Apple Play
cap 4000 chars 500 chars
attaches to the app store version a track release, not the listing
uploaded by upload-store-listings.mjs nothing yet — see below

Play's 500-character cap is the reason play.releaseNotes is its own shorter copy rather than a reuse of apple.whatsNew. Pasting the Apple string across would fit Apple's cap and blow Play's, so src/__tests__/play-release-notes.test.js pins that the two are never equal, alongside the cap itself.

Because Play attaches release notes to a track release (edits.tracksreleases[].releaseNotes) rather than to a listing, uploading them needs a target track and versionCode. On this repo the Play upload in ci.yml already owns the release, so play-release-notes.mjs only writes files:

node scripts/play-release-notes.mjs --check     # validate, write nothing
node scripts/play-release-notes.mjs             # write dist-whatsnew/

Nothing consumes the output yet, deliberately. The CI upload step carries an explicit comment that it sends no release notes because closed testers see the versionCode; switching that on changes what every tester and user is shown on each release, which is an owner decision. To wire it, run the script before the upload step and pass whatsNewDirectory: dist-whatsnew.

Reviewed vs unreviewed

| Reviewed (reviewed: true) | 1 | | Unreviewed (reviewed: false) | 43 |

This was 6 reviewed / 38 unreviewed until 2026-09-19 (the table said 5 while listing 6 languages — that count was wrong before this edit, not changed by it). The reviewed set was en, pt-BR, es-419, es, fr, it — the same set whose in-app security.json was native-reviewer-approved in PR #1507.

apple.whatsNew was rewritten for the 1.0.2 release and machine-translated into all 43 siblings, so the five non-English reviewed locales now contain one string no native reviewer has seen. reviewed is file-level and --require-reviewed gates on it, so leaving those five at true would have uploaded unvetted copy under a reviewed banner. They were flipped to false. Each one's notes records that its other four Apple fields are unchanged and still vouched for, so a re-review only has to look at whatsNew.

en stays true: it is the source every sibling is translated from, so there is no translation fidelity to vouch for.

The flag doesn't change what gets uploaded by default, but the upload script supports --require-reviewed if you want to gate strictly.

Uploading

The script authenticates with two things:

  • App Store Connect: an API key .p8 file
  • Google Play Publishing: a service-account JSON

Both live outside this repo. Set env vars and run:

export ASC_KEY_ID=XXXXXXXXXX
export ASC_ISSUER_ID=2d4c5bd7-1de3-4953-b203-a92e788c2d7c
export ASC_KEY_PATH=~/.appstoreconnect/private_keys/AuthKey_XXXXXXXXXX.p8
export ASC_APP_ID=<numeric-app-id-from-App-Store-Connect>
export PLAY_PACKAGE_NAME=<package-name-e.g.-io.veyrnox.wallet>
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/play-publishing-sa.json

# Dry-run — prints what would happen, doesn't touch stores
node scripts/upload-store-listings.mjs

# Restrict to specific locales (comma-separated)
node scripts/upload-store-listings.mjs --only=pt-BR,es-419,fr

# Only upload reviewed languages
node scripts/upload-store-listings.mjs --require-reviewed

# For real (uploads)
node scripts/upload-store-listings.mjs --live

# Apple only / Play only
node scripts/upload-store-listings.mjs --live --apple-only
node scripts/upload-store-listings.mjs --live --play-only

The script fails LOUDLY on character-limit overflow before touching any API — if a field is 1 char over its limit, the store would reject the whole submission, so it's better to catch here.

What DOESN'T upload

  • Screenshots. Every locale needs its own set of screenshots (5 × iPhone, optionally 5 × iPad on Apple; 2–8 × phone, 2–8 × 7-inch tablet, 2–8 × 10-inch tablet on Play). Not automated here. Follow-up: screenshot capture workflow using Fastlane snapshot (iOS) + screengrab (Android), driving the app in a device farm per-locale.
  • In-App Purchase / Subscription copy. Safety Plus subscription localisation is separate — App Store Connect → In-App Purchases → Safety Plus → Localizations, and Play Console → Monetize → Products → Subscriptions → each base plan. Follow-up if the current $5.99/month + $49.99/year Store-side copy needs per-language translation.
  • App Review Notes. Currently English-only (see _source-apple.md section). Reviewers work in English on both stores, so this is fine.

Native reviewer signoff pattern

When a native speaker approves a <locale>.json for shipping:

  1. In <locale>.json, set "reviewed": true.
  2. Log the reviewer + date in the file's notes field.
  3. If they edited any values, the diff is the review.

No separate approval file — the JSON is self-documenting.

Character-limit summary (all locales pass)

Apple's tight fields are subtitle (30), keywords (100). Play's tight field is shortDescription (80). Every <locale>.json in this directory has been checked against the schema-defined limits; the upload script re-checks before every run.