Skip to content

Localization: wire AI plural translation into the GlotPress reverse fold#25710

Draft
jkmassel wants to merge 2 commits into
jkmassel/claude-string-translationfrom
jkmassel/wire-ai-translation-pipeline
Draft

Localization: wire AI plural translation into the GlotPress reverse fold#25710
jkmassel wants to merge 2 commits into
jkmassel/claude-string-translationfrom
jkmassel/wire-ai-translation-pipeline

Conversation

@jkmassel

@jkmassel jkmassel commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Wires the AI translation primitives from #25705 into the plural reverse fold — the live tier behind the human ?? AI ?? English floor — filling the ai_translate_plural stub #25688 left open.

Stacked on #25705 (its primitives aren't on trunk yet); the diff here is wiring + tests + docs only, and the base retargets to trunk once #25705 merges. Draft to match #25705 and because the live fold wants a real download run to validate.

Scope: plurals only

Plurals fold into Plurals.xcstrings — a String Catalog with a needs_review state — which is built into the app but not consumed at runtime yet (no code reads it; plurals still render the legacy way). So the AI tier pre-populates the catalog for the plural cutover without shipping any machine-translated copy today.

Regular strings are deliberately out of scope: they still ship from the legacy Localizable.strings, where a machine translation would go live immediately — and we don't want machine-translated copy shipping before the catalog cutover. Regular-string MT waits for a catalog reverse-fold (same shape as plurals), where it can stage as needs_review until cutover. The rationale is captured in docs/localization-pipeline.md so it doesn't need re-explaining.

Summary

  • download_localized_plurals now drives AITranslator instead of the nil stub — gated on ANTHROPIC_API_KEY (absent ⇒ unchanged English-fallback behavior) and run as a non-fatal step, so the AI tier can never break a release.
  • The reverse fold calls the model once per (key, locale) form-set (translate_plural), with the human-translated forms as anchors — one consistent stem across the forms, not per-cell drift.
  • New pure, stdlib-only plural_strings_helper_test.rb (picked up by Localization: AI translation primitives #25705's existing Localization Tooling Unit Tests CI job); nokogiri made lazy so the suite needs no gems.
  • docs/localization-pipeline.md documents the GlotPress + AI round trip end to end and why regular-string MT waits for the catalog.

Form-set, not per-cell

PluralStrings.fold_translations!'s ai_translator contract changed from per-cell to a per-(key, locale) form-set callable:

ai_translator.call(english_forms:, categories:, locale:, note:, anchors:) => { <cat> => translation }

AITranslator#translate_plural implements it directly — ai_translator: translator.method(:translate_plural). This is deliberately not the per-cell for_plural adapter #25705's notes floated: translating the whole set in one request is the fix for the synonym drift a per-category call structurally can't prevent (Polish słowowyrazysłów). A per-set API failure degrades to English (needs_review) rather than aborting the fold.

Test plan

  • ruby fastlane/lanes/plural_strings_helper_test.rb — 10 (fold provenance + the form-set/anchors contract)
  • Full fastlane/lanes/*_test.rb suite green — 60 runs
  • rubocop clean on all changed files
  • Live plural fold against the API (needs ANTHROPIC_API_KEY + bundle install) on a real download — confirm quality before the plural cutover

Related

@dangermattic

dangermattic commented Jun 26, 2026

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

Generated by 🚫 Danger

@jkmassel jkmassel force-pushed the jkmassel/wire-ai-translation-pipeline branch from a65c33b to fd62b7f Compare June 26, 2026 20:55
jkmassel added 2 commits June 26, 2026 15:08
Replace the ai_translate_plural -> nil stub with the AI tier. download_localized_plurals
now builds AITranslator.with_anthropic once (gated on ANTHROPIC_API_KEY; absent => the
prior English-fallback behavior, unchanged) and PluralStrings.fold_translations! invokes
it once per (key, locale) with the whole form-set via translate_plural — keeping one
consistent stem across forms — passing the human-translated forms as anchors.

The fold's ai_translator contract changes from per-cell to per-form-set; a per-set API
failure degrades to English (needs_review) instead of aborting the fold. Adds
plural_strings_helper_test.rb covering provenance and the form-set/anchors contract; the
nokogiri require is made lazy so that pure suite needs no gems (matches the no-bundle CI job).
Adds docs/localization-pipeline.md: the GlotPress + AI round trip, the human ?? AI ?? English
floor, the AI tier (gating, placeholder gate, form-set plurals), and why regular-string MT is
deferred to the String Catalog cutover — machine translations only ship from a state-bearing
store (the catalog's needs_review), never from the live legacy .strings. Linked from AGENTS.md.
@jkmassel jkmassel changed the title Localization: wire the AI translation tier into the GlotPress pipeline Localization: wire AI plural translation into the GlotPress reverse fold Jun 26, 2026
@jkmassel jkmassel force-pushed the jkmassel/wire-ai-translation-pipeline branch from fd62b7f to 068fe82 Compare June 26, 2026 21:10
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