Best Practices
Recommended patterns and workflows for @lingo.dev/compiler.
Development Workflow
Use Pseudotranslator by Default
Do:
{
dev: {
usePseudotranslator: true, // Fast, free, instant feedback
}
}
Why:
- Instant feedback—no API latency
- Zero cost—no API credits consumed
- Visual markers show what gets translated
- Tests UI with varying text lengths
Disable only when reviewing actual translation quality.
Separate Development, CI, and Production
Development:
{
buildMode: "translate",
dev: {
usePseudotranslator: true,
}
}
CI:
{
buildMode: "translate",
dev: {
usePseudotranslator: false,
}
}
Production:
{
buildMode: "cache-only",
}
This workflow:
- Keeps development fast and cheap
- Generates real translations in CI once per deployment
- Makes production builds deterministic and fast
Translation Strategy
Let AI Handle Most Translations
Do:
<p>Welcome to our application</p>
Don't:
<p data-lingo-override={{ es: "...", de: "...", fr: "..." }}>
Welcome to our application
</p>
Use overrides sparingly—only for:
- Brand names
- Technical terms requiring specific translations
- Legal text requiring certification
- Marketing copy needing human review
Use Overrides Consistently
Do:
// Consistent brand name across app
<h1 data-lingo-override={{ es: "Lingo.dev", de: "Lingo.dev" }}>
Lingo.dev
</h1>
<p>
Welcome to <span data-lingo-override={{ es: "Lingo.dev", de: "Lingo.dev" }}>
Lingo.dev
</span>
</p>
Don't:
<h1 data-lingo-override={{ es: "Lingo.dev" }}>Lingo.dev</h1>
<p>Welcome to Lingo.dev</p> // Missing override—inconsistent
Configuration
Start Simple
Do:
{
sourceLocale: "en",
targetLocales: ["es", "de"],
models: "lingo.dev",
}
Don't:
{
sourceLocale: "en",
targetLocales: ["es", "de", "fr", "pt", "it", "ja", "zh", "ar", "ru", "ko"],
models: {
"en:es": "groq:...",
"en:de": "google:...",
// Complex mappings for 10 locales
},
prompt: "Long custom prompt...",
pluralization: { enabled: false },
}
Start with 2-3 target locales. Add more as needed. Avoid premature optimization.
Use Lingo.dev Engine
Do:
{
models: "lingo.dev" // Simple, optimized, supports all features
}
Don't:
{
models: {
"*:*": "groq:...", // Requires manual model selection
}
}
Lingo.dev Engine provides:
- Automatic model selection
- Fallback handling
- Translation memory
- Glossary support
Use direct LLM providers only if you need full control or cost optimization.
Locale Detection
Use Default Cookie-Based Persistence
Do:
{
localePersistence: {
type: "cookie",
config: {
name: "locale",
maxAge: 31536000,
},
},
}
When to customize:
- Need localStorage (SPA preference)
- URL-based routing (
/en/about) - Subdomain routing (
es.example.com) - Database-backed user preferences
Only implement Custom Locale Resolvers when the default doesn't fit.
Version Control
Commit .lingo/ Directory
Do:
git add .lingo/
git commit -m "chore: update translations"
git push
Why:
- Version control tracks translation changes
- Team shares translations
- CI/CD uses committed translations
- Production builds don't need API keys
Commit After CI Runs
Do (in CI):
- name: Generate translations
run: npm run build
- name: Commit translations
run: |
git add .lingo/
git commit -m "chore: update translations" || exit 0
git push
This ensures production builds always have up-to-date translations.
CI/CD
Generate Translations in CI
Do:
# GitHub Actions
- name: Generate translations
env:
LINGODOTDEV_API_KEY: ${{ secrets.LINGODOTDEV_API_KEY }}
run: npm run build
Don't:
# Production build without API key
- name: Build
run: npm run build # Fails if translations missing
Generate translations in CI where you have API keys. Production builds use cached translations.
Use cache-only in Production
Do:
# Production build
LINGO_BUILD_MODE=cache-only npm run build
Don't:
# Production build with translate mode
LINGO_BUILD_MODE=translate npm run build # Non-deterministic, requires API keys
Performance
Enable Pluralization Selectively
Do (if you use plural forms):
{
pluralization: {
enabled: true,
}
}
Do (if you don't use plurals):
{
pluralization: {
enabled: false, // Skip plural detection—faster builds
}
}
Pluralization adds small overhead (one LLM call per text with numbers). Disable if not needed.
Use Fast Models for Pluralization
Do:
{
pluralization: {
enabled: true,
model: "groq:llama-3.1-8b-instant", // Fast, cheap
}
}
Don't:
{
pluralization: {
model: "openai:gpt-4o", // Expensive overkill for plural detection
}
}
Optimize Locale-Pair Mapping
Do (cost optimization):
{
models: {
"en:es": "groq:llama-3.3-70b-versatile", // Fast & cheap
"en:ja": "openai:gpt-4o", // High quality for complex language
"*:*": "lingo.dev", // Fallback
}
}
Use fast/cheap models for similar languages (Romance, Germanic). Use high-quality models for complex languages (CJK, Arabic).
Testing
Test with Pseudotranslator First
Do:
- Enable pseudotranslator
- Test all UI components
- Fix layout issues (overflow, truncation)
- Then generate real translations
Why:
- Pseudotranslations are instant
- Reveals layout problems early
- Saves API costs
Test All Target Locales
Do:
// Test with locale switcher
<LanguageSwitcher /> // Switch between all locales
// Or manually test
setLocale("es"); // Spanish
setLocale("de"); // German
setLocale("fr"); // French
Verify each locale:
- Translations appear correctly
- Layout accommodates text length
- No untranslated text
- RTL languages render correctly (if applicable)
Error Handling
Handle Missing Translations Gracefully
The compiler fails build if translations are missing. This is intentional—better to catch missing translations early than ship broken UI.
If build fails:
- Run with
buildMode: "translate"to generate missing translations - Commit
.lingo/metadata.json - Retry production build with
buildMode: "cache-only"
Monitor Translation Failures
In CI, check for translation errors:
- name: Generate translations
run: npm run build 2>&1 | tee build.log
- name: Check for translation errors
run: |
if grep -q "Failed to generate translation" build.log; then
echo "Translation generation failed"
exit 1
fi
Maintenance
Regular Cleanup
Periodically remove unused translations:
# Backup first
cp .lingo/metadata.json .lingo/metadata.backup.json
# Manual: Search for each hash in codebase, remove if not found
# Automated (coming soon):
npx @lingo.dev/compiler clean
Monitor File Size
.lingo/metadata.json grows with your app. If it becomes large (>5 MB):
- Consider splitting into multiple apps
- Archive old translations
- Use automated cleanup
Common Anti-Patterns
Don't Over-Use Overrides
Bad:
<p data-lingo-override={{ es: "...", de: "...", fr: "..." }}>
Welcome to our app
</p>
Let AI handle regular text. Overrides are for exceptions.
Don't Commit API Keys
Bad:
// next.config.ts
{
models: "lingo.dev",
apiKey: "your-api-key-here", // NEVER commit API keys
}
Good:
# .env (not committed)
LINGODOTDEV_API_KEY=your_key_here
Don't Use translate Mode in Production
Bad:
// production config
{
buildMode: "translate", // Non-deterministic, requires API keys
}
Good:
{
buildMode: "cache-only", // Deterministic, no API keys
}
Don't Skip Version Control
Bad:
# .gitignore
.lingo/ # DON'T ignore translations
Good:
# .gitignore
.env # Ignore API keys only
Migration Strategy
Gradual Rollout
When adding compiler to existing app:
- Start with 1-2 locales
- Enable pseudotranslator
- Test all pages
- Fix layout issues
- Add more locales
- Generate real translations
- Deploy
Don't try to translate 20 locales on day one.
Incremental Adoption
You don't need to translate entire app at once:
{
useDirective: true, // Opt-in per file
}
Add 'use i18n' directive to files you want to translate:
'use i18n'; // This file gets translated
export function HomePage() {
return <h1>Welcome</h1>;
}
Other files remain untranslated until you opt them in.
Next Steps
- Migration Guide — Migrate from old compiler
- Troubleshooting — Common issues and solutions
- Configuration Reference — All options explained