You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Stage regular-string translations into Localizable.xcstrings
Adds the localize half of the String Catalog pipeline: a manual (non-CI) lane that folds current GlotPress translations for regular, non-plural strings into the existing Localizable.xcstrings as `human ?? AI ?? English`. The catalog isn't the runtime store yet (the app still ships Localizable.strings), so this only pre-populates it for the eventual cutover — nothing users see changes.
- `localize_catalog` lane: download current GlotPress translations into a throwaway dir, fold them in (human => `translated`), then AI-fill the rest (=> `needs_review`). Manual only — it calls the translation API (cost) and commits a large catalog; never wired into `download_localized_strings` or CI.
- `CatalogStrings.fold_translations!`: pure, reuse-aware fold. An existing valid cell is kept, so re-runs only translate genuinely-new gaps — the catalog's `needs_review` state IS the persistence (no side-store). A human translation from GlotPress supersedes a kept cell on the next fold.
- Immutable-key enforcement replaces `reconcile_changed_sources`: reworded English in place is now a hard error, since `xcstringstool sync` silently keeps the old key's translations and would ship stale text. Rewording requires a new key.
- `translate_all` keeps completed batches when one fails mid-run, so a partial API failure degrades to English for the remainder instead of dropping the lot.
- docs/localization-pipeline.md documents the regular-string fold and the `human ?? AI ?? English` floor — any translation, human or machine, that fails the placeholder gate falls through to the next rung.
Copy file name to clipboardExpand all lines: docs/localization-pipeline.md
+13-6Lines changed: 13 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,7 @@
2
2
3
3
How user-facing strings get from English source into every shipped locale. This is the **release/tooling** view (the fastlane lanes under `fastlane/lanes/`); for how to *write* localizable strings in app code, see [localization.md](./localization.md).
4
4
5
-
> The contract for every shipped string is **`human ?? AI ?? English`**: a human (GlotPress) translation if one exists, otherwise a machine translation, otherwise the English source. Nothing ships a broken placeholder — machine output that fails the format-specifier gate falls back to English.
5
+
> The contract for every shipped string is **`human ?? AI ?? English`**: a human (GlotPress) translation if one exists, otherwise a machine translation, otherwise the English source. Nothing ships a broken placeholder — **any** translation, human or machine, that fails the format-specifier gate is rejected and falls through to the next rung.
6
6
7
7
## The round trip
8
8
@@ -45,13 +45,20 @@ The plural reverse-fold (`PluralStrings.fold_translations!`) fills each `(key, l
45
45
46
46
> **This does not ship machine translations yet.**`Plurals.xcstrings` is built into the app but **not consumed at runtime** — nothing reads the catalog, and nothing references its keys yet; the app still renders plurals the legacy way. The fold *pre-populates* the catalog so it's ready when plurals cut over to it. Until that cutover, the AI plural translations sit in the catalog unused.
47
47
48
-
## What's deferred: regular strings
48
+
## What's staged, not shipped: regular strings
49
49
50
-
Regular (non-plural) strings are **not** machine-translated, by design. The app still ships the legacy `WordPress/Resources/<locale>.lproj/Localizable.strings` for them — `Localizable.xcstrings` (`generate_strings_catalog`) is the designated future backing store, but today it's only generated transiently in CI as a coverage check — the file is gitignored, not committed, and nothing ships from it. A machine translation written into the legacy `.strings`would be **live immediately**, and we don't want machine-translated regular strings shipping before the catalog cutover.
50
+
Regular (non-plural) strings still ship the legacy way — from `WordPress/Resources/<locale>.lproj/Localizable.strings`, with no machine translation. A machine translation written there would be **live immediately**, and we don't want machine-translated regular strings shipping before the catalog cutover. `Localizable.xcstrings` (`generate_strings_catalog`) is the designated future backing store; it's gitignored and not a build member, so nothing ships from it.
51
51
52
-
So regular-string MT waits for the same shape as plurals: once `Localizable.xcstrings`becomes the runtime store, a regular-string **catalog reverse-fold** folds the human translations in and AI-fills the `needs_review` gaps, staged in the catalog (not shipped) until cutover — exactly as the plural fold does today.
52
+
The tooling to **stage**regular-string translations into that catalog now exists, the same shape as the plural fold. `CatalogStrings.fold_translations!` fills each `(key, locale)` cell of `Localizable.xcstrings`as `human ?? AI ?? English` — human ⇒ `translated`, machine / English ⇒ `needs_review` — and is reuse-aware in the same way: a kept machine cell isn't re-translated, and a human translation supersedes it on the next fold. It runs as two manual lanes: `generate_strings_catalog` (extract the English source) and `localize_catalog` (download GlotPress into a throwaway temp dir, fold the humans in, AI-fill the rest, commit the catalog). The download is a fresh temp dir every run, so no stale/partial translation state is ever carried between runs.
53
53
54
-
When that's built, two facts established here will carry over:
54
+
Two things keep it from shipping anything today, and both set it apart from the plural fold:
55
+
56
+
-**Manual, not in the release path.** These lanes aren't wired into `download_localized_strings` or any beta/release step — a run extracts strings, calls the API (cost), and commits a large catalog, so it's run on demand. Only the unit tests run in CI.
57
+
-**Staged, not shipped.**`Localizable.xcstrings` still isn't the runtime store, so the folded translations sit in the catalog unused until the cutover — exactly like the plural catalog.
58
+
59
+
**Keys are immutable.**`generate_strings_catalog` hard-fails if an explicit-key string's English is reworded in place — `xcstringstool sync` would silently keep that key's now-stale translations, and the fold can't tell a translation of the old English from a current one. Rewording requires a **new key**. Key-as-source strings are exempt: rewording one changes the key, which sync handles as new/stale. (Enforcement today fires where the catalog persists; extending it to the transient-catalog CI path is a follow-up — a lint on the committed English `.strings`.)
60
+
61
+
Two facts the fold relies on, both established by the reverse download:
55
62
56
63
-**"Undefined by GlotPress" = absent**, not empty. The export omits untranslated strings (`status: current`; verified no empty-valued entries), so absence is the untranslated signal.
57
64
-**Humans always supersede MT**, and machine output never returns to GlotPress — so there's no translation-memory pollution and no manual reconciliation, as long as MT lives in a state-bearing store (the catalog's `needs_review`).
@@ -77,5 +84,5 @@ When that's built, two facts established here will carry over:
0 commit comments