Skip to main content
Add a locale to a Template and Courier picks it per recipient at send time.

How it works

When message.to includes a locale, Courier replaces default property values with that locale’s translations. Elements with no matching locale definition keep their defaults. The locale interface:

Text node resolution

Text elements accept two content formats: content (a plain or markdown string) and elements (a structured array of inline nodes like string, link, and img). Locales handle every combination:
If a locale translation is missing for an element, Courier falls back to the default property value. Notifications still render when translations are incomplete.
When a text node uses the elements format and a locale provides only a content string, Courier wraps the string into a single-element array ([{ type: "string", content: "..." }]). Rendering survives, but any inline formatting (bold, italic, links) in the original elements is lost. Provide elements in your locale translations to keep formatting.

Why use locales

  • Single template, multiple languages: One structure and logic, with translated text per locale
  • Automatic selection: Courier uses the recipient’s locale from their profile or the message.to.locale field
  • Fallback support: If a translation is missing, Courier uses the default content

Supported elements and properties

Which properties accept a locale override depends on the element type:
  • content: Text content in text, action, quote, and html elements
  • title: Title in meta elements (email subject lines, push notification titles)
  • href: URLs in action and image elements
  • src: Image source URLs in image elements
  • raw: Provider-native channel overrides in channel elements, such as a translated subject or an entire translated html body
  • elements: Nested elements. The accepted shape varies by type:
    • inline nodes (string, link, img) in text and list-item
    • full nodes in channel, group, and column
    • column nodes in columns
    • list-item nodes in list
divider, jsonnet, partial, and comment elements reject any locale override. Sending a property that an element type does not support returns a 400 naming the field.

Add translations

1

Add a locale in the designer

Open the template, add a locale (like fr or pt-BR), and enter the translated content for each element. Courier stores per-locale content on the template, so the structure stays shared and only the text differs.
2

Or set it via the API

Write a locale’s content with or the put-locale CLI verb. Use this to keep translations in your own system and sync them in.
3

Translate with AI

Courier’s AI translation generates a locale’s content from your base content, which you then review and edit. This is a console feature. Run it per template when you add or update a locale.AI translation needs the Business or Enterprise plan. Each translation costs 2.5 AI credits plus token overages, drawn from the same balance as . With no credits the console reports that translation requires them.
4

Send to a localized recipient

Set a test user’s profile.locale to a locale you localized and send the template. Confirm the localized content renders in . Then send to a user without that locale and confirm the base content renders.

Workspace translation strings

Workspace translations hold short strings reused across templates, like button labels and common phrases. They are keyed by locale and rendered with the {{t}} Handlebars helper. Manage them with and reference a string with {{t "welcome_headline"}}. Use this for a shared glossary, and per-locale template content for the body of a notification. Three details decide how you integrate it:
  • The payload is a .po file, not JSON. That is the gettext format most translation tooling already exports, so a vendor’s output usually needs no conversion.
  • domain only accepts default today. It exists for future namespacing, and any other value fails.
  • Read them back with , which returns the stored .po content. Useful for diffing what is live against what your translation system holds.
Translations are per workspace, not per tenant. A cannot override a string, so a customer who needs different wording needs a different template rather than a different translation.

Basic example

Localize a text element:
When the recipient’s locale is "fr", "Hello" becomes "Bonjour". When it is "es", it becomes "Hola". Any other locale, or none, renders the default "Hello".

Localizing multiple elements

Localize several element types in one template:
The example covers:
  • Meta element: Localizing the email subject line (title property)
  • Text element: Localizing body content with Handlebars variables
  • Action element: Localizing both button text (content) and URL (href)

Structured elements

When a text node uses the elements array (inline string, link, and img nodes), provide locale translations as elements arrays too. That preserves bold, italic, and inline links across languages.

Mixed locale formats

You can mix content and elements across locales in the same text node. Use it when some translations need formatting and others can be plain strings:
The French locale uses a content string, which Courier wraps into a single text element at render time. The Spanish locale keeps the structured elements with an inline link.

When Both content and elements Are Present

If a locale entry includes both content and elements, only elements is used. Choose one format per locale entry. See the resolution table for all combinations.

Locale sources

Courier resolves the recipient’s locale in this order:
  1. message.to.locale: Set in the to object of the send request (highest priority)
  2. User profile locale: Stored in the user’s profile via the
  3. Default fallback: If no locale is found, the default content is used
message.to.locale takes precedence over every other source. Courier merges it into the profile object during processing, so it overrides any locale stored in the user’s profile.
Two places to set it:
Set the locale in the user’s profile (via the Profiles API) and every notification for that user is localized, with no locale in the send request. message.to.locale still overrides it.

Best practices

Use consistent locale codes

Use standard locale codes (en-US, es-ES, fr-FR) or simple language codes (en, es, fr) consistently across your templates. Courier supports any locale string format, but consistency makes maintenance easier.

Provide default content

Always provide default content for each element. Notifications then render even when:
  • A user’s locale isn’t supported
  • A translation is missing
  • The locale field is omitted

Localize URLs when needed

For action buttons and images, localize the href and src properties to point at localized versions of your website or app:

Combine with channel customization

Combine locales with for localized, per-channel content:

Test all locales

Before deploying, test your templates with every supported locale and check:
  • All translations are present
  • Handlebars variables work correctly in all languages
  • URLs and links are properly localized
  • Text fits within UI constraints (button sizes, email widths, etc.)