Skip to content

Commit a8eb281

Browse files
authored
Correct the pipeline docs against the actual release lanes (#25752)
Fixes claims the code disproves: - download_localized_strings runs with each beta and at finalize_release — never at complete_code_freeze; the forward upload also re-runs with each beta - the GlotPress download action logs empty values as errors but never raises - hu, not pl, is the sparse-locale example (~160 of ~4,280 keys; pl is ~complete) - fold anchors include machine cells kept from prior folds, not just human forms - re-runs do re-query the model for English-fallback cells and en-* variants; kept cells are what make re-runs cheap - Localizable.xcstrings is generated only transiently in CI (gitignored), never committed - the eyeball command needs bundle exec to reliably find the anthropic gem
1 parent aa42eeb commit a8eb281

1 file changed

Lines changed: 9 additions & 9 deletions

File tree

docs/localization-pipeline.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ How user-facing strings get from English source into every shipped locale. This
88

99
Strings make two trips, both driven from fastlane.
1010

11-
### Forward (code freeze) — English → GlotPress
11+
### Forward — English → GlotPress
1212

13-
Run as part of `complete_code_freeze` (`generate_strings_file_for_glotpress`):
13+
Runs at code freeze and again with each beta (`generate_strings_file_for_glotpress`):
1414

1515
- **Regular strings** are extracted from source (`ios_generate_strings_file_from_code`, i.e. `genstrings` over `NSLocalizedString` / `AppLocalizedString`) into `WordPress/Resources/en.lproj/Localizable.strings`, then the manually-maintained `.strings` files are merged in. These English originals are uploaded to the [apps/ios GlotPress project](https://translate.wordpress.org/projects/apps/ios/dev/).
1616
- **Plurals** are authored in `WordPress/Classes/Plurals.xcstrings` (English `one`/`other`). The forward lane (`generate_plural_strings_for_glotpress`) flattens each plural form into an independent string keyed `<key>|==|plural.<cldr-category>` and merges those originals into the same `Localizable.strings`, so they ride the same GlotPress project as everything else.
@@ -19,9 +19,9 @@ Translators then do their work in GlotPress.
1919

2020
### Reverse (release prep) — GlotPress → app
2121

22-
`download_localized_strings` (called by `complete_code_freeze` / `finalize_release`) runs, in order:
22+
`download_localized_strings` runs with each beta and at release finalization — never at code freeze, when translators haven't yet seen the new originals. It runs, in order:
2323

24-
1. **Download** each locale's `Localizable.strings` from GlotPress (`ios_download_strings_files_from_glotpress`) into `WordPress/Resources/<locale>.lproj/`, and commit. The export filter is `status: current`, so **only translated strings come back** — untranslated ones are *omitted entirely* (not emitted as empty values; the action even errors if it finds an empty value). This is why `pl` ships ~1,650 of ~4,280 keys while `fr` ships ~all of them.
24+
1. **Download** each locale's `Localizable.strings` from GlotPress (`ios_download_strings_files_from_glotpress`) into `WordPress/Resources/<locale>.lproj/`, and commit. The export filter is `status: current`, so **only translated strings come back** — untranslated ones are *omitted entirely* (not emitted as empty valuesthe download action checks for empties and flags them in the log as a GlotPress bug). This is why `hu`'s file carries only ~160 of the ~4,280 keys while `fr`'s carries ~4,220.
2525
2. **Re-dispatch** the relevant subset back to the manually-maintained `.strings` files (`ios_extract_keys_from_strings_files`), and commit.
2626
3. **Plural fold** (`download_localized_plurals`): pull the flat plural translations back out of the downloaded `Localizable.strings`, fold them into `Plurals.xcstrings`, and fill the gaps with the AI tier (below).
2727

@@ -39,15 +39,15 @@ The reusable primitives live in `fastlane/lanes/`: `AITranslator` (prompt buildi
3939

4040
## What's wired today: plurals
4141

42-
The plural reverse-fold (`PluralStrings.fold_translations!`) fills each `(key, locale)` cell of `Plurals.xcstrings` as `human ?? AI ?? English` — human ⇒ `translated`; AI / English ⇒ `needs_review`. The AI tier is called **once per `(key, locale)` form-set** (`AITranslator#translate_plural`), not per cell, with the already-human-translated forms passed as **anchors**. Translating the whole set in one request keeps a single consistent stem across the forms — a per-category call lets the model drift between synonyms (Polish `słowo``wyrazy``słów`), which it structurally can't prevent.
42+
The plural reverse-fold (`PluralStrings.fold_translations!`) fills each `(key, locale)` cell of `Plurals.xcstrings` as `human ?? AI ?? English` — human ⇒ `translated`; AI / English ⇒ `needs_review`. The AI tier is called **once per `(key, locale)` form-set** (`AITranslator#translate_plural`), not per cell, with the forms already in hand — human translations plus machine cells kept from a prior fold — passed as **anchors**. Translating the whole set in one request keeps a single consistent stem across the forms — a per-category call lets the model drift between synonyms (Polish `słowo``wyrazy``słów`), which it structurally can't prevent.
4343

44-
**`Plurals.xcstrings` is a String Catalog, which is why this works**: the catalog carries a real `needs_review` state, so a machine cell is recorded as machine output, a human translation supersedes it on the next download, and an unchanged machine cell is carried over as-is on a re-fold rather than re-translated. The fold is idempotent — a re-run with the same input neither re-spends on the model nor churns the staged text, and a cell that fell back to English (the AI declined) is retried next time.
44+
**`Plurals.xcstrings` is a String Catalog, which is why this works**: the catalog carries a real `needs_review` state, so a machine cell is recorded as machine output, a human translation supersedes it on the next download, and an unchanged machine cell is carried over as-is on a re-fold rather than re-translated. The fold is idempotent — necessary, since the reverse runs with every beta: a re-run with the same input never churns the staged text, and kept machine cells aren't re-translated. Two kinds of cell do re-query the model each run: those that previously fell back to English (deliberate — a transient failure gets retried next time), and the `en-*` variant locales, where a correct translation is indistinguishable from the English fallback and so can never be kept.
4545

46-
> **This does not ship machine translations yet.** `Plurals.xcstrings` is built into the app but **not consumed at runtime**no code reads from it; 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.
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.
4747
4848
## What's deferred: regular strings
4949

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 generated as the future backing store but isn't the runtime store yet. 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 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.
5151

5252
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.
5353

@@ -65,7 +65,7 @@ When that's built, two facts established here will carry over:
6565
## Operational notes
6666

6767
- **Eyeball one string against the live model** (needs `ANTHROPIC_API_KEY` + `bundle install`):
68-
`ruby fastlane/lanes/ai_translator.rb fr "You have %1$d new posts" "Notification text. %1$d is the count."`
68+
`bundle exec ruby fastlane/lanes/ai_translator.rb fr "You have %1$d new posts" "Notification text. %1$d is the count."`
6969
- **Tests** are pure stdlib minitest and run in CI (`.buildkite/commands/test-localization-tooling.sh`): `ruby fastlane/lanes/*_test.rb`.
7070

7171
## Code map

0 commit comments

Comments
 (0)