Skip to main content

Auto-localize a template into per-locale carrier-approved variants

One source template, many languages: Orbit translates a template into up to 25 target locales in a single call, keeps your {{token}} placeholders intact, and (for WhatsApp and RCS) submits each translated variant for carrier approval exactly as if you had authored it by hand. This guide walks both halves of the feature, the dashboard and API flows, the limits and failure codes, and what happens at send time when a recipient’s language is matched against your variants. Auto-localization rewrites copy only. It never chooses a route or sends anything by itself; every variant still goes through your normal template, campaign, and approval flows.

1. The two halves: generate vs commit

The feature is two endpoints that solve different problems. Know which half you need before you call either. Generate is for drafting: you hand it copy, it hands back translations for you to review, edit, and then commit onto a campaign or template through the regular write endpoints. The campaign composer’s translate-locales dialog is a client of this endpoint. Commit is for the template library: it reads one canonical template (its body and, for rich WhatsApp templates, its button labels), translates it, and bulk-creates one real template row per locale. Each created row is indistinguishable from a hand-authored variant. It shares the source template’s channel and name, carries its own language, and is submitted to the carrier for approval on creation. A useful rule of thumb: campaign copy that you review before a specific send → generate; a canonical WhatsApp/RCS template you want available in every market → commit.

2. Dashboard walkthrough: the translate-locales dialog

In the campaign composer (Campaigns → Create), the Translate locales dialog runs the generate half against your campaign’s current message:
  1. Open the dialog from the message step of the composer. It reads the body (and subject, for email) you have drafted so far as the source, with its locale as the source locale.
  2. Pick your target locales (BCP-47 tags such as fr, de, pt-BR). Up to 25 per run.
  3. Run the translation. The dialog shows one editable variant per locale, plus the source locale as the default variant. Edit any variant’s body or subject in place if a translation needs a brand-specific tweak. The response also names any locale the model failed to return (missingLocales), so you author that one manually instead of silently shipping fewer variants.
  4. Commit the variant set to the campaign. From then on, each recipient receives the variant matching their stored language (section 6).
Nothing is persisted server-side by the translation call itself. What the campaign sends is the variant set you reviewed and committed, so the review step is where quality control happens. For per-channel authoring norms (SMS segment budget, WhatsApp positional tokens, RCS cards) that apply to every variant you review, see the template + variable personalization walkthrough.

3. API walkthrough

All calls are authenticated the same way as the rest of the API; the commit endpoint requires the templates:write scope and accepts an Idempotency-Key header.

3.1 Generate: translate a body for review

The response is one variant per locale, with the source locale included as the default so the set is directly usable:
If you pass a subject, each variant carries a translated subject too. missingLocales lists any requested locale the model did not return, so you can re-run just those or author them by hand. The same call from JavaScript:

3.2 Commit: bulk-create per-locale template rows

Give the commit endpoint a stored template id and the locales you want. It translates the template’s body (and button labels for rich WhatsApp templates) and creates one row per locale:
sourceLocale is optional; when omitted, the source template row’s own language is used. The response reports one outcome per locale, each with its own status:
One bad locale never aborts the batch: a locale the model dropped is skipped with model_returned_no_variant, a locale whose row already exists is skipped with variant_already_exists (re-running localize is safe), and a per-row create failure is failed with create_failed while the rest still land. templateStatus is the status at insert time; the carrier-approval state settles afterwards and is authoritative via the tracker endpoint in section 5.

4. Limits and behavior

  • 1–25 target locales per call, on both endpoints. For more markets, call again with the next batch; the commit endpoint’s skip-if-exists behavior makes repeat runs harmless.
  • Source locale handling. Generate always includes the source locale as the default variant and drops it from the translation targets; commit never re-translates the source row.
  • 30 s translation ceiling. The whole translation round-trip is capped at 30 seconds. If it expires you get 504 with error.code: LLM_TIMEOUT.
  • Upstream failure of the translation provider returns 502 with LLM_UPSTREAM_ERROR; both are retryable.
  • Unparseable model output returns 422 with AI_PARSE_FAILED. Retry, or retry with a smaller locale batch.
  • No AI provider configured for your organization returns 503 (LOCALIZATION_UNAVAILABLE on commit). Author per-locale variants manually instead.
  • Validation errors (empty body, more than 25 locales, malformed tags) return 400.
  • Billing. Translation runs on your organization’s AI tier; usage is attributed to your organization, not to individual users.
  • Placeholder preservation. The translation contract requires the model to copy every placeholder through exactly as written: named tags ({{first_name}}, {{company}}) and WhatsApp positional tokens ({{1}}, {{2}}) are never translated, their braces are never reordered, and no spaces are inserted inside them. URLs, email addresses, and phone numbers are likewise copied verbatim into every variant.
  • Channel length norms. The model is instructed per channel: SMS bodies stay within 160 characters, push within 120, and WhatsApp/RCS bodies within the carrier body limit (1024 characters) with button labels within 25.

5. Per-locale carrier approval

Every WhatsApp/RCS row the commit endpoint creates goes through the same carrier-approval path as a hand-authored template: same submission, same queue, same status lifecycle on the row. There is no separate “localized” approval state; per-locale approval is just each row’s status. Poll all locales of one template with the tracker endpoint. The id may be any variant’s id; it resolves the shared (channel, name) and returns every sibling variant with its approval status, carrier external id, and any submission error:
Expect one entry per language, each settling independently: fr approved while pt-BR is still pending or comes back rejected. When one locale is rejected and the others are approved: treat the rejected row exactly like any rejected template. Edit and resubmit that one variant, or delete it; the approved siblings are unaffected and keep sending. Until the rejected locale is approved, recipients whose language points at it are served by the send-time fallback chain below rather than by an unapproved template. Re-running localize for that locale only recreates the row if you deleted it; an existing row reports skipped / variant_already_exists. Approval authoring norms per channel live in Create and approve WhatsApp templates and the RCS templates tab.

6. How a recipient’s language picks a variant at send time

The input is the contact’s language attribute: a BCP-47-ish tag (en, fr, pt-BR) stored on the contact, set through contact imports and the contacts API, defaulting to en. At send time the resolver (the resolveLocaleVariant fallback chain in the template-localization module) picks a variant per recipient in this order, first hit wins:
  1. Exact tag — a variant whose tag equals the recipient’s, case-insensitively (pt-br matches PT_br).
  2. Primary subtag — a variant sharing the language part. A bare-primary variant (pt) is preferred over a regional one (pt-BR), so a pt-PT recipient lands on neutral pt before an unrelated region.
  3. Your fallback chain — the optional fallback_languages order you set on the template (for example ["es", "en"]), consulted after the recipient’s own language fails to match and before the default. This lets a multi-market sender say “no variant for this language? prefer Spanish, then English.”
  4. The default locale — the variant matching the set’s declared source locale.
  5. First variant — last resort, and a sign the default-locale variant was dropped from the set.
Matching is case-insensitive and treats _ and - as equivalent, so messy CRM values like " PT_br " still resolve. A recipient with no stored language falls through to the default locale. For variable interpolation inside the chosen variant, see the personalization walkthrough; for reading translation quality back out of your traffic, see the language quality guide.

7. Troubleshooting

8. See also