The Lingo.dev CLI and localization API support two patterns for email localization: translate template files at build time to ship per-locale templates, or translate content at runtime before sending. Both run through a configured localization engine with glossary rules, brand voice, and model selection applied automatically.
Choose Your Approach#
| Approach | Best for | How it works |
|---|---|---|
| Build-time (CLI) | Template files - react-email, MJML, HTML | Translate files in your repository, deploy per-locale templates |
| Runtime (API) | Dynamic content, ESP-rendered templates | Call the localization API before sending, pass translated content to your email provider |
Which approach?
If your email templates live in your repository as files (HTML, MJML, or React components), use the build-time approach. If your email content is generated dynamically or stored in your email service provider, use the runtime approach.
Prerequisites#
Every translation runs through a localization engine - the configuration that determines which LLM model, glossary, brand voice, and instructions apply. Create one in the Lingo.dev dashboard and generate an API key.
Build-Time Localization#
The CLI translates email template files directly. Configure a bucket that matches your template format, run the CLI, and get per-locale template files alongside your source templates.
react-email templates are React components that render to HTML. Extract translatable strings into JSON resource files using an i18n library like react-i18next, then translate the JSON files with the CLI.
{
"$schema": "https://lingo.dev/schema/i18n.json",
"version": "1.15",
"locale": {
"source": "en",
"targets": ["es", "fr", "de", "ja"]
},
"buckets": {
"json": {
"include": ["emails/locales/[locale].json"]
}
}
}At render time, pass the locale to your email component and load the corresponding JSON file. The react-email render() function produces locale-specific HTML ready to send.
Run translations with:
npx lingo.dev@latest runRuntime Localization#
When email content is dynamic - personalized notifications, user-generated content summaries, or marketing copy stored in a CMS - translate it at runtime before sending. This builds on the pattern described in the Translation API guide.
async function sendLocalizedEmail(userId, templateId, content) {
const user = await db.users.findById(userId);
const response = await fetch("https://api.lingo.dev/process/localize", {
method: "POST",
headers: {
"X-API-Key": process.env.LINGODOTDEV_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
engineId: "eng_abc123",
sourceLocale: "en",
targetLocale: user.locale,
data: {
subject: content.subject,
preheader: content.preheader,
body: content.body,
},
}),
});
const { data } = await response.json();
await emailProvider.send({
to: user.email,
subject: data.subject,
html: renderTemplate(templateId, data),
});
}Best Practices#
| Area | Recommendation |
|---|---|
| Subject lines | Keep under 50 characters. Use a glossary to lock brand names from translation. |
| Preview text | Translate separately from the body - email clients display it independently. |
| Brand voice | Configure per-locale tone in the localization engine. Marketing emails in Japanese need a different register than German. |
| RTL languages | Test rendered output in email clients for Arabic, Hebrew, and Persian. HTML dir="rtl" handling varies across clients. |
| Key locking | Use locked keys for URLs, product names, and legal identifiers that should not be translated. |
