Alpha
The Lingo.dev Compiler is in alpha. It is unstable, not recommended for production use, and APIs may change between releases.
These practices are based on patterns that produce reliable, cost-effective results with the Lingo.dev Compiler. They cover the build pipeline, code organization, translation quality, and testing.
Build pipeline#
Use the three-mode strategy#
Dev - pseudotranslator
Enable dev.usePseudotranslator: true for instant feedback. No API calls, no cost, immediate results. Pseudotranslations help you spot untranslated strings and test layout.
{
buildMode: "translate",
dev: { usePseudotranslator: true },
}CI - translate mode
Run with buildMode: "translate" and a real provider. Commit the updated .lingo/metadata.json after each CI run so translations are available for production.
LINGO_BUILD_MODE=translate npm run buildProduction - cache-only mode
Deploy with buildMode: "cache-only". No API keys needed in production. Builds are deterministic and fast.
LINGO_BUILD_MODE=cache-only npm run buildVersion control#
Commit .lingo/ to your repository#
The .lingo/metadata.json file is the source of truth for all cached translations. Production builds in cache-only mode depend on it.
# .gitignore - do NOT ignore .lingo/
node_modules/
dist/
.envIf .lingo/metadata.json is not committed, production builds fail because cache-only mode has no translations to read.
Review translation diffs#
When CI commits updated translations, review the .lingo/metadata.json diff in pull requests. This lets you catch translation issues before they reach production - similar to reviewing code changes.
Code organization#
Place translatable text directly in JSX#
The compiler scans JSX for translatable content. Text stored in JavaScript variables, constants, or external files is not detected:
// Good - compiler detects this text
export function Header() {
return <h1>Welcome to our app</h1>;
}
// Bad - compiler cannot detect text in a variable
const title = "Welcome to our app";
export function Header() {
return <h1>{title}</h1>;
}Use useDirective for large codebases#
In large projects, scanning every file increases build time. Enable useDirective: true and add 'use i18n' only to files that contain user-facing text:
{
useDirective: true,
}'use i18n';
// Only this file is scanned for translations
export function PublicPage() {
return <h1>Welcome</h1>;
}Keep sourceRoot narrow#
Set sourceRoot to the smallest directory that contains your translatable components. A broad sourceRoot scans unnecessary files:
| Project type | Recommended sourceRoot |
|---|---|
| Next.js App Router | "./app" |
| Vite + React | "src" |
| Monorepo (with useDirective) | "." |
Translation quality#
Use manual overrides for brand terms#
Brand names, product names, and legal text should use manual overrides rather than relying on AI translation:
<h1 data-lingo-override={{ es: "Motor de Localizacion", de: "Lokalisierungs-Engine" }}>
Localization Engine
</h1>Use locale-pair mapping for cost optimization#
Different models have different strengths and price points. Map expensive models to languages that need them and use cost-effective models elsewhere:
{
models: {
"*:*": "groq:llama-3.3-70b-versatile", // Fast, cost-effective default
"*:ja": "anthropic:claude-3-5-sonnet", // Higher quality for Japanese
"*:zh-Hans": "anthropic:claude-3-5-sonnet", // Higher quality for Chinese
},
}Use the Lingo.dev engine for glossary and brand voice#
When you need consistent terminology across your app, configure a localization engine on Lingo.dev with a glossary and brand voice. These apply automatically to every translation request.
Pluralization#
Disable pluralization if not needed#
If your app does not display numeric counts alongside text, disable pluralization to reduce build complexity:
{
pluralization: { enabled: false },
}Write count-dependent text naturally#
When pluralization is enabled, write text with numeric variables naturally. The compiler handles the ICU MessageFormat conversion:
// Good - the compiler detects and pluralizes this
<p>You have {count} items in your cart</p>
// Also good - works with any numeric expression
<p>{unreadCount} unread messages</p>Testing#
Test with pseudotranslator first#
Before generating real translations, run with the pseudotranslator to verify complete coverage:
- Enable
dev.usePseudotranslator: true - Navigate through every page and component
- Any text without
[!!! ... !!!]markers is not being translated - Fix text placement issues (move text into JSX, adjust
sourceRoot, add'use i18n'directives)
Catching untranslated strings with the pseudotranslator is faster and cheaper than discovering them after generating real translations.
Test with real translations before release#
Disable the pseudotranslator and generate real translations for at least one target locale before releasing:
{
dev: { usePseudotranslator: false },
}Check for layout overflow, text truncation, and bidirectional text issues that pseudotranslations cannot reveal.
