Workflows

Ship every locale

Caption files in your repo, locale variants, explicit readiness for missing or inherited cells, and one screenshot bundle for every locale.

View as Markdown

Tidebook ships in en-US and de-DE. One bundle carries both, fastlane deliver uploads both in a single run, and the German panels say German words — but only because you passed them. Nothing about a locale code makes copy appear.

Render the approved Tidebook strip in en-US and de-DE, then package both locale folders in one screenshot bundle.

Expected result: both locale previews use their own words, and the final zip contains one numbered screenshot folder for each locale.

#Caption files in your repo

ShotOps never stores or looks up your shipping copy. The words are per-render input and they come from the caller, which in practice means a file in your own repository, one per locale, keyed by panel ordinal — 0-based, in strip order, so the file survives every re-render.

// captions.de-DE.json
{
  "0": { "headline": "Die Gezeiten kennen, bevor du losfährst" },
  "1": { "headline": "Sieben Tage Swell" },
  "2": { "headline": "Jeder Spot, den du surfst" },
  "3": { "headline": "Dein eigenes Session-Log" },
  "4": [{ "text": "NEU" }, { "text": "Beim richtigen Swell aufwachen" }]
}

A panel's entry is either one caption (headline, optional subtitle) or an array of layers stacked in order — use the array when a panel needs more than a headline and a subtitle.

To render a locale, read the file, map it onto style.captions[] in the same order (index i → panel i, null for a panel with no caption), and pass locale alongside so the render and the bundle are labelled and routed consistently.

#Locale screenshot variants

German captions over English pixels is a legitimate place to be for a week. When you do have localized screens, any screenshots entry can carry per-locale variants instead of one image:

{
  "screenshots": [
    { "locales": { "en-US": { "ref": "…01_today_en…" }, "de-DE": { "ref": "…01_today_de…" } } },
    { "ref": "…02_forecast…" },
    { "ref": "…03_spots…" },
    { "ref": "…04_log…" },
    { "locales": { "en-US": { "ref": "…05_alerts_en…" }, "de-DE": { "ref": "…05_alerts_de…" } } }
  ]
}

A plain entry serves every locale. render({ locale: "de-DE", … }) picks the de-DE variant of each entry that has one, and echoes the declared locales back as screenshotLocales.

On the hosted door, request_screenshot_upload({ count, names, family?, locale? }) returns a variant on every slot. Pass it through beside the ref when you render a project: it names the device-family and locale cell the upload overrides, not a bookkeeping tag. The name chooses which shot; the variant chooses which of that shot's cells.

#What happens when a cell is missing

A missing coordinate first tries the documented fallback chain. When a fallback exists, nothing is skipped and the selected source is specific enough to predict.

For an entry you pass on the wire, resolution is: the requested locale's variant, else the base locale's variant (en-US unless the project says otherwise), else the first variant declared. An entry whose locales map is empty is a caller error and is refused with a message saying so.

For a project's stored screenshots, cells inherit along two different precedence orders, because pixels and words fail differently:

PrecedenceChain for German on a non-base device family
PixelsDevice family dominates, strictlythat family's German cell → that family's base cell → the base cell
WordsLocale dominates, over the full latticethat family's German → any-family German → that family's base → the base cell

The strictness on the pixel side is the point: the chain never reaches another family's German screenshots, because iPhone pixels under an Android listing are a store-policy problem rather than an aesthetic one. On the caption side, generic German copy beats device-specific English copy every time.

Both read_project and a project render report the outcome per cell rather than leaving you to guess. read_project returns screenshots.cells[] — for each shot × family × locale, the variant asked for, the resolvedAt cell actually used, and an inherited array naming which axes fell back. A project render reports the same resolvedAt and inherited per shot in its shots array. A doubly-inherited cell — reflowed pixels under untranslated captions — is the panel most likely to ship wrong, and it is exactly the one those fields name.

If the chain reaches no screenshot at all, preview can still show the incomplete panel and reports the gap. A render can remain incomplete while the strip is being assembled; a bundle refuses an empty panel source before it creates the store artifact. Locale or device fallback is different: final output requires the authenticated owner to accept that exact readiness finding, or to supply the missing cell.

#Render per locale

Put each locale's words into the project, then preview that locale:

{
  "project": "Tidebook 2.4 launch",
  "captions": [
    { "frame": 1, "locale": "de-DE", "text": "Kenne die Tide, bevor du losgehst" },
    { "frame": 2, "locale": "de-DE", "text": "Sieben Tage Dünung" }
  ]
}

That is an edit_project call; a locale the project does not have yet is added. It saves only once every caption clears the devices, and says which frame to fix when one cannot. Then render({ "project": "Tidebook 2.4 launch", "locale": "de-DE" }) previews the German strip.

Preview each locale first. A German headline is routinely a third longer than its English original, and the length is what decides whether the panel wants standard or bleed.

#One bundle, every locale

One call renders every locale with its own words out of the project and zips them:

{
  "project": "Tidebook 2.4 launch",
  "output": "bundle",
  "bundleId": "com.tidebook.app",
  "locales": ["en-US", "de-DE"]
}

The zip carries fastlane/screenshots/en-US/ and fastlane/screenshots/de-DE/, each with its panels numbered in strip order, and fastlane deliver uploads both in one run. It is one full-resolution render per locale, per device the project targets, so budget the time. A full-resolution Hosted call may return an operation id first; poll it with production_operation and use action: "result" after success.

Supplied screenshots with locales work too, but every locale then carries the same style.captions. For localized words, save the project first.

Stage four: a screenshot bundle for the weather app, shaped for fastlane with one folder per locale and numbered screenshots.
One zip, one folder per locale, uploaded by your own fastlane.