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 JSON strings | 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 translatable email copy lives in your repository as resource files, 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, then install and authenticate the CLI:
npm install -g @lingo.dev/cli
lingo loginThe CLI requires Node 22+. In CI, set LINGO_API_KEY instead of running lingo login.
Build-Time Localization#
The CLI translates email content from JSON resource files. Extract your translatable copy into JSON, point the CLI at it, and get per-locale files alongside your source.
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.
Run lingo init to scaffold the config and lingo link to attach your organization and engine. The resulting .lingo/config.json looks like this:
{
"orgId": "org_abc123",
"engineId": "eng_abc123",
"sourceLocale": "en",
"targetLocales": ["es", "fr", "de", "ja"],
"files": [{ "pattern": "emails/locales/en.json" }]
}The locale lives in the path: the CLI replaces the source locale in the pattern with each target, so emails/locales/en.json produces emails/locales/es.json, emails/locales/fr.json, and so on. Commit .lingo/config.json to your repository.
Translate all locales on the first run, then translate only what changed on later runs:
lingo push --backfill-missing # first run / new locale
lingo push # delta on later runsAt 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.
To retrieve results from the last push run at any point, use lingo pull. To verify translations are up to date without writing changes (for example in CI), use lingo check.
Runtime 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. |
