Skip to content

Localization: stage regular-string translations into Localizable.xcstrings (manual)#25713

Draft
jkmassel wants to merge 6 commits into
jkmassel/claude-string-translationfrom
jkmassel/catalog-strings-translation
Draft

Localization: stage regular-string translations into Localizable.xcstrings (manual)#25713
jkmassel wants to merge 6 commits into
jkmassel/claude-string-translationfrom
jkmassel/catalog-strings-translation

Conversation

@jkmassel

@jkmassel jkmassel commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Stages regular-string translations into Localizable.xcstrings — a regular-string reverse fold (the catalog analogue of the plural fold), populating the future backing store with GlotPress human translations plus AI machine translations as human ?? existing-machine ?? AI ?? English.

Stacked on #25705 (its AITranslator primitives aren't on trunk yet); base retargets to trunk once #25705 merges. Sibling of #25710 (which does the same for plurals into Plurals.xcstrings). Draft — the live fold wants a real run to validate.

Staged, not shipped — and manual, not CI

Two deliberate constraints:

  • Staged, not shipped. Localizable.xcstrings is generated (Localization: String Catalog pipeline (plurals + catalog generation) #25688) but isn't the runtime store yet — the app still ships the legacy Localizable.strings for regular strings. This fold only pre-populates the catalog for the eventual cutover; it changes nothing users see. (This is why regular-string MT belongs in the catalog, not the live .strings: the catalog's needs_review state lets a machine translation be staged rather than shipped.)
  • Manual, not CI. download_localized_catalog is not wired into download_localized_strings or any pipeline step — it runs xcstringstool extraction, calls the translation API (cost), and commits a large catalog, so it's run on demand, not on every release. The pure unit suite still runs in CI (it's free).

So this PR makes the catalog fold possible to run, without putting it in CI's path. First manual run generates + commits the Localizable.xcstrings; later runs maintain it.

What's here

  • CatalogStrings.fold_translations! (pure, 12 tests) — per (key, locale): human ?? existing-machine ?? AI ?? English; human ⇒ translated, machine / English ⇒ needs_review. Reuse-aware: a valid existing needs_review machine cell is kept and not re-translated — the catalog's needs_review state is the persistence, so re-runs only translate genuinely-new gaps (no side-store), and a GlotPress human translation supersedes a machine cell automatically on the next fold. Handles key-as-source strings and shouldTranslate:false.
  • Three independent stages, each its own fastlane invocation (scoped by locales:fr,de): generate_strings_catalog scans the code into the English catalog (existing lane, no AI), download_catalog_strings pulls the current GlotPress translations into the *.lproj dirs, then localize_catalog folds those human translations in (→ translated) and AI-fills the rest (CatalogStrings, → needs_review) → commit. Scanning the code is therefore separate from the AI fill; localize_catalog neither scans nor downloads. Gated on ANTHROPIC_API_KEY (absent ⇒ human + English only); a per-locale API failure degrades to English.

Uploading the AI drafts back to GlotPress as needs-review (the eventual "step 4") is intentionally out of this PR. It's not blocked on tooling — it builds on the existing GlotPress import integration the project already uses for metadata source (gp_update_metadata_source) — it's just a separate step.

Test plan

  • ruby fastlane/lanes/catalog_strings_helper_test.rb — 12 (provenance, reuse vs. retry, human-supersedes-machine, key-as-source, shouldTranslate, batched per-locale call)
  • Full fastlane/lanes/*_test.rb suite green — 62 runs
  • rubocop clean on all changed files
  • Manual download_localized_catalog run (needs ANTHROPIC_API_KEY + bundle install) on real downloaded strings — eyeball the staged catalog + measure cost

Related

…lane)

Adds a regular-string reverse fold — the catalog analogue of the plural fold — that
populates Localizable.xcstrings (the future regular-string backing store) with GlotPress
human translations plus AI machine translations, as human ?? existing-machine ?? AI ??
English (human => translated; machine / English => needs_review).

- CatalogStrings.fold_translations! (pure, 12 tests): reuse-aware — a valid existing
  needs_review machine cell is kept, so the catalog's needs_review state is the persistence
  (no side-store) and re-runs only translate genuinely-new gaps. Humans supersede machine
  cells on the next fold.
- download_localized_catalog lane: generate the English catalog, fold the downloaded
  .strings humans + AI-fill (translate_all), commit. Gated on ANTHROPIC_API_KEY.

STAGED, NOT SHIPPED: the catalog isn't the runtime store yet, so this changes nothing users
see. MANUAL ONLY: deliberately not wired into download_localized_strings or CI — it runs
xcstringstool extraction, calls the API, and commits a large catalog, so it's run on demand.
@dangermattic

Copy link
Copy Markdown
Collaborator
1 Message
📖 This PR is still a Draft: some checks will be skipped.

Generated by 🚫 Danger

jkmassel added 5 commits June 26, 2026 15:54
… -> AI-fill

Make the lane mirror the pipeline order: (1) generate_strings_catalog scans the code, (2) it
now downloads the current GlotPress translations itself (ios_download_strings_files_from_glotpress)
and folds them in, (3) CatalogStrings AI-fills the rest. Adds a locales:fr,de scope (and
skip_download:true) so a run can be kept cheap. Uploading the AI drafts back to GlotPress
(the eventual step 4) stays out — it builds on the existing GlotPress import integration
(gp_update_metadata_source), not done here.
Run a download then a localize as separate fastlane invocations:
- download_catalog_strings — pull GlotPress translations into the *.lproj dirs (scoped by locales:)
- localize_catalog — scan + fold the downloaded strings + AI-fill into Localizable.xcstrings

Replaces the single download_localized_catalog lane (and its skip_download flag): download
once, re-localize as many times as you like without re-downloading.
localize_catalog no longer re-runs generate_strings_catalog — scanning the code is its own
lane (generate_strings_catalog), so you can refresh the English catalog without ever invoking
the AI. The three stages are now independent fastlane invocations:
  generate_strings_catalog (scan) -> download_catalog_strings (download) -> localize_catalog (fold + AI-fill)
localize_catalog errors if the catalog hasn't been generated yet.
The extract/sync sh calls echo enormous commands (up to 400 file paths per extract batch; a
--stringsdata <path> pair per file on sync), burying the output. Pass log: false and emit
concise progress lines instead. No fastlane/release-toolkit action wraps xcstringstool, so sh
is the invocation; log: false is its clean-output option.
fastlane's sh treats each call as an action: even with log: false it prints a
'--- Step: shell command ---' banner per call and lists it in the run summary. extract is
chunked into ~7 calls and sync runs twice, so that wrapped the real progress in a wall of
banners. Run xcstringstool through Open3.capture2e instead (argv — safe for paths with spaces;
captured output surfaced only on failure), via a run_xcstringstool helper. The scan now prints
just its own progress lines.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants