Deterministic, incremental, build-time translation compiler using LLMs
Most translation tools are either:
- Runtime systems with performance and cost overhead
- SaaS platforms that own your data and workflow
- Non-deterministic LLM wrappers that retranslate everything
translatronx treats translations like source code:
- Compile once at build time
- Only retranslate what changed
- Preserve manual edits permanently
- Ship static files with zero runtime cost
translatronx is not another translation management system. It is a translation compiler β it treats language like code: build once β ship everywhere β zero runtime cost.
- π Incremental & Deterministic - Only translates new/changed strings, same input = same output
- π° Cost-Efficient - Deduplication and intelligent batching minimize LLM API costs
- π Developer Authority - Manual edits are detected and protected forever (unless forced)
- π― Provider-Agnostic - OpenAI, Anthropic, Groq, Azure OpenAI, OpenRouter, or local models
- β‘ CI/CD Ready - Integrates seamlessly into GitHub Actions, GitLab CI, CircleCI, etc.
- π State-Aware - SQLite ledger tracks changes without storing actual translations
- π‘οΈ Production-Ready - Atomic writes, transaction safety, zero data loss on interruption
- π¨ Customizable Prompts - Fine-tune translation quality with formatting, glossaries, and brand voice
- π Context Files - Provide LLMs with context for each translation key to improve quality
- π Comprehensive Reporting - Detailed statistics, cost estimates, and audit trails
- Installation
- Quick Start
- Tutorial: Step-by-Step Guide
- Configuration Reference
- CLI Commands
- Context Files
- Advanced Usage
- Best Practices
- API Reference
- Node.js >= 20.0.0
- npm, yarn, or pnpm
npm install --save-dev translatronx
# or
yarn add --dev translatronx
# or
pnpm add -D translatronxnpm install -g translatronxnpx translatronx initThis creates a translatronx.config.ts file in your project root.
# For OpenAI
export OPENAI_API_KEY=your-api-key
# For Anthropic
export ANTHROPIC_API_KEY=your-api-key
# For Groq
export GROQ_API_KEY=your-api-keyCreate ./locales/en.json:
{
"welcome": "Welcome to our app!",
"greeting": "Hello, {name}!",
"auth": {
"login": "Log in",
"logout": "Log out",
"signup": "Sign up"
}
}npx translatronx syncOutput:
π Syncing translations...
β
Translation sync complete!
Statistics:
Total strings: 5
Translated: 15
Failed: 0
Skipped: 0
Tokens used: 245 (input) + 312 (output)
Duration: 3.42s
This generates:
./locales/fr.json./locales/de.json./locales/es.json
# Create a new directory for your translations
mkdir my-app-i18n
cd my-app-i18n
# Initialize npm project
npm init -y
# Install translatronx
npm install --save-dev translatronx
# Initialize configuration
npx translatronx initEdit translatronx.config.ts:
import { defineConfig } from 'translatronx';
export default defineConfig({
sourceLanguage: 'en',
targetLanguages: [
{ language: 'French', shortCode: 'fr' },
{ language: 'German', shortCode: 'de' },
{ language: 'Spanish', shortCode: 'es' }
],
extractors: [
{
type: 'json',
pattern: './locales/en.json'
}
],
providers: [
{
name: 'openai',
type: 'openai',
model: 'gpt-4o-mini',
temperature: 0.3
}
],
output: {
dir: './locales',
format: 'json',
fileNaming: '{shortCode}.json'
}
});Create ./locales/en.json:
{
"app": {
"title": "My Awesome App",
"description": "The best app you'll ever use"
},
"navigation": {
"home": "Home",
"about": "About",
"contact": "Contact Us"
},
"auth": {
"login": "Log in",
"logout": "Log out",
"signup": "Sign up",
"forgotPassword": "Forgot Password?"
},
"messages": {
"welcome": "Welcome, {username}!",
"goodbye": "See you soon, {username}!",
"error": "An error occurred. Please try again."
}
}# Set your API key
export OPENAI_API_KEY=your-api-key-here
# Run translation sync
npx translatronx syncWhat happens:
- translatronx reads your source file (
en.json) - Extracts all translatable strings
- Batches them efficiently for the LLM
- Generates translations for all target languages
- Writes output files (
fr.json,de.json,es.json) - Stores state in
.translatronx/ledger.sqlite
npx translatronx statusOutput:
π Translation Coverage Report
βββββββββββββββ¬βββββββββ¬ββββββββββββ¬βββββββββββ¬βββββββββββ
β Language β Total β Clean β Dirty β Coverage β
βββββββββββββββΌβββββββββΌββββββββββββΌβββββββββββΌβββββββββββ€
β French (fr) β 12 β 12 β 0 β 100.0% β
β German (de) β 12 β 12 β 0 β 100.0% β
β Spanish (es)β 12 β 12 β 0 β 100.0% β
βββββββββββββββ΄βββββββββ΄ββββββββββββ΄βββββββββββ΄βββββββββββ
Add a new string to en.json:
{
"app": {
"title": "My Awesome App",
"description": "The best app you'll ever use",
"tagline": "Built with love β€οΈ" // NEW!
},
// ... rest of the file
}Run sync again:
npx translatronx syncOutput:
β
Translation sync complete!
Statistics:
Total strings: 13
Translated: 3 β Only the new string!
Failed: 0
Skipped: 10 β Existing translations skipped
This is the power of incremental translation! Only new/changed strings are translated, saving you time and money.
Context files allow you to provide additional information to the LLM about each translation key, resulting in more accurate and contextually appropriate translations.
npx translatronx context generate --source ./locales/en.jsonOutput:
π Generating context file template...
Source: /path/to/locales/en.json
Output: /path/to/locales/en.context.json
Merge: No
β
Context file generated successfully!
π‘ Tip: Edit locales/en.context.json to add context for each translation key
This creates ./locales/en.context.json:
{
"app": {
"title": {
"value": "My Awesome App",
"context": ""
},
"description": {
"value": "The best app you'll ever use",
"context": ""
},
"tagline": {
"value": "Built with love β€οΈ",
"context": ""
}
},
"navigation": {
"home": {
"value": "Home",
"context": ""
},
// ... etc
}
}Edit en.context.json to add helpful context:
{
"app": {
"title": {
"value": "My Awesome App",
"context": "Main application title shown in the header and browser tab"
},
"description": {
"value": "The best app you'll ever use",
"context": "Marketing tagline shown on the landing page. Should be enthusiastic and engaging."
},
"tagline": {
"value": "Built with love β€οΈ",
"context": "Footer tagline. Keep the heart emoji in all translations."
}
},
"auth": {
"login": {
"value": "Log in",
"context": "Button text for user authentication. Should be concise and action-oriented."
},
"logout": {
"value": "Log out",
"context": "Button text for ending user session."
},
"forgotPassword": {
"value": "Forgot Password?",
"context": "Link text for password recovery. Should be phrased as a question."
}
},
"messages": {
"welcome": {
"value": "Welcome, {username}!",
"context": "Greeting shown when user logs in. {username} is the user's display name."
}
}
}npx translatronx context validateOutput:
π Validating context file...
Source: /path/to/locales/en.json
Context: /path/to/locales/en.context.json
β
Context file is valid!
Update translatronx.config.ts:
export default defineConfig({
// ... other config
extractors: [
{
type: 'json',
pattern: './locales/en.json',
contextFile: {
enabled: true,
pattern: './locales/en.context.json',
autoGenerate: false,
autoUpdate: false
}
}
],
// ... rest of config
});To see the improvement, let's force a retranslation:
# Delete existing translations
rm ./locales/fr.json ./locales/de.json ./locales/es.json
# Clear the ledger to force retranslation
rm -rf .translatronx
# Run sync with context
npx translatronx syncThe LLM now receives context for each string, resulting in better translations!
For example, without context:
- "Log in" might be translated as "Connexion" (noun) in French
With context ("Button text for user authentication"):
- "Log in" is translated as "Se connecter" (verb/action) in French
If you already have translations and want to add context files without affecting them:
npx translatronx context import --source ./locales/en.jsonOutput:
π₯ Importing context from source...
Source: /path/to/locales/en.json
Output: /path/to/locales/en.context.json
Merge: Yes
β
Context imported from source successfully!
β’ Existing context preserved
β’ New keys from source added
β’ Source values updated to match current source
β’ Your existing translations are safe
π‘ Tip: Your existing translations will NOT be affected.
Context files only improve future translations.
Key Points:
- β
Your existing translation files (
fr.json,de.json, etc.) are never touched - β Existing context you've written is preserved
- β New keys from source are added with empty context
- β Only future translations will benefit from context
When you add/remove keys in your source file:
npx translatronx context syncOutput:
π Syncing context file with source...
Source: /path/to/locales/en.json
Context: /path/to/locales/en.context.json
β
Context file synced successfully!
β’ New keys added
β’ Deleted keys removed
β’ Existing context preserved
Improve translation quality by customizing the prompts sent to the LLM.
export default defineConfig({
// ... other config
prompts: {
customContext: 'This is a mobile banking app. Use financial terminology and maintain a professional tone.',
}
});export default defineConfig({
// ... other config
prompts: {
glossary: {
'Dashboard': 'Tableau de bord',
'Account': 'Compte',
'Transaction': 'Transaction',
'Balance': 'Solde'
}
}
});export default defineConfig({
// ... other config
prompts: {
formatting: 'formal', // 'formal' | 'casual' | 'technical'
brandVoice: 'Professional, trustworthy, and user-friendly'
}
});export default defineConfig({
// ... other config
prompts: {
userPrompt: [
'Translate the following strings for a mobile banking application.',
'Maintain consistency with previous translations.',
'Use formal language and financial terminology.',
'Preserve all placeholders like {username} exactly as they appear.'
]
}
});Create .github/workflows/translations.yml:
name: Update Translations
on:
push:
paths:
- 'locales/en.json'
- 'locales/en.context.json'
branches:
- main
jobs:
translate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Run translations
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: npx translatronx sync
- name: Commit translations
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add locales/*.json
git diff --quiet && git diff --staged --quiet || git commit -m "chore: update translations"
git pushCreate .gitlab-ci.yml:
translate:
image: node:20
stage: build
only:
changes:
- locales/en.json
- locales/en.context.json
script:
- npm ci
- npx translatronx sync
- git config user.email "ci@gitlab.com"
- git config user.name "GitLab CI"
- git add locales/*.json
- git diff --quiet && git diff --staged --quiet || (git commit -m "chore: update translations" && git push)
variables:
OPENAI_API_KEY: $OPENAI_API_KEYimport { defineConfig } from 'translatronx';
export default defineConfig({
// Source language code (ISO 639-1)
sourceLanguage: 'en',
// Target languages with full names and short codes
targetLanguages: [
{ language: 'French', shortCode: 'fr' },
{ language: 'German (Formal)', shortCode: 'de-formal' },
{ language: 'Simplified Chinese', shortCode: 'zh-Hans' }
],
// Extractors define how to find translatable strings
extractors: [
{
type: 'json', // 'json' | 'typescript' | 'custom'
pattern: './locales/en.json', // Glob pattern(s)
keyPrefix: 'app', // Optional: prefix for all keys
exclude: ['**/node_modules/**'], // Optional: exclude patterns
contextFile: { // Optional: context file configuration
enabled: true,
pattern: './locales/en.context.json',
autoGenerate: false,
autoUpdate: false
}
}
],
// LLM providers configuration
providers: [
{
name: 'openai-primary',
type: 'openai',
model: 'gpt-4o-mini',
temperature: 0.3,
maxRetries: 3,
apiKey: process.env.OPENAI_API_KEY, // Optional: defaults to env var
fallback: 'anthropic-backup' // Optional: fallback provider
},
{
name: 'anthropic-backup',
type: 'anthropic',
model: 'claude-3-5-sonnet-20241022',
temperature: 0.3,
maxRetries: 2
}
],
// Validation rules
validation: {
preservePlaceholders: true, // Ensure {var} placeholders are preserved
maxLengthRatio: 3, // Max target/source length ratio
preventSourceLeakage: true, // Prevent untranslated source text
brandNames: ['Acme', 'Widget'], // Protected brand names
customRules: [] // Custom validation functions
},
// Output configuration
output: {
dir: './locales',
format: 'json', // 'json' | 'yaml' | 'typescript'
flat: false, // Flatten nested objects
indent: 2, // JSON indentation
fileNaming: '{shortCode}.json', // File naming pattern
allowSameFolder: false // Allow source & target in same dir
},
// Prompt customization
prompts: {
customContext: 'This is a mobile banking app. Use financial terminology.',
formatting: 'formal', // 'formal' | 'casual' | 'technical'
glossary: {
'Dashboard': 'Tableau de bord',
'Settings': 'Paramètres'
},
brandVoice: 'Professional, trustworthy, and user-friendly',
userPrompt: [ // Custom user prompt (optional)
'Translate the following strings for a mobile app.',
'Maintain consistency with previous translations.'
]
},
// Advanced settings
advanced: {
batchSize: 20, // Strings per LLM call
concurrency: 3, // Parallel LLM requests
cacheDir: './.translatronx', // State directory
ledgerPath: './.translatronx/ledger.sqlite',
verbose: false // Enable verbose logging
}
});- Type:
string - Required: Yes
- Description: ISO 639-1 language code for source language
- Type:
Array<{ language: string, shortCode: string }> - Required: Yes
- Description: Array of target languages with full names and codes
- Type:
Array<ExtractorConfig> - Required: Yes
- Description: Configuration for extracting translatable strings
enabled: Enable context file supportpattern: Path to context file (defaults to{source}.context.json)autoGenerate: Auto-generate context file if missingautoUpdate: Auto-update context file when source changes
- Type:
Array<ProviderConfig> - Required: Yes
- Description: LLM provider configuration
Supported providers:
openai- OpenAI (GPT-4, GPT-3.5, etc.)anthropic- Anthropic (Claude)groq- Groqazure-openai- Azure OpenAIopenrouter- OpenRouter
- Type:
ValidationConfig - Required: No
- Description: Translation validation rules
- Type:
OutputConfig - Required: Yes
- Description: Output file configuration
- Type:
PromptConfig - Required: No
- Description: Prompt customization options
- Type:
AdvancedConfig - Required: No
- Description: Advanced performance and behavior settings
Synchronize translations (incremental processing).
npx translatronx sync [options]Options:
-f, --force- Force regeneration of manual overrides-v, --verbose- Enable verbose output
Example:
npx translatronx sync --verboseDisplay coverage statistics and system state.
npx translatronx statusOutput:
π Translation Coverage Report
βββββββββββββββ¬βββββββββ¬ββββββββββββ¬βββββββββββ¬βββββββββββ
β Language β Total β Clean β Dirty β Coverage β
βββββββββββββββΌβββββββββΌββββββββββββΌβββββββββββΌβββββββββββ€
β French (fr) β 12 β 12 β 0 β 100.0% β
β German (de) β 12 β 11 β 1 β 91.7% β
βββββββββββββββ΄βββββββββ΄ββββββββββββ΄βββββββββββ΄βββββββββββ
Retry failed translation batches.
npx translatronx retry [options]Options:
--batch <id>- Specific batch ID to retry--lang <code>- Specific language to retry--dry-run- Show what would be retried without making changes
Example:
npx translatronx retry --lang fr --dry-runInitialize translatronx configuration.
npx translatronx initCreates a translatronx.config.ts file in your project root.
Generate context file template from source file.
npx translatronx context generate [options]Options:
--source <path>- Source file path (overrides config)--output <path>- Context file output path--merge- Merge with existing context file--dry-run- Preview changes without writing
Example:
npx translatronx context generate --source ./locales/en.jsonValidate context file matches source file.
npx translatronx context validate [options]Options:
--source <path>- Source file path (overrides config)--context <path>- Context file path (overrides config)
Example:
npx translatronx context validateSync context file with source (add new keys, remove deleted keys).
npx translatronx context sync [options]Options:
--source <path>- Source file path (overrides config)--context <path>- Context file path (overrides config)--dry-run- Preview changes without writing
Example:
npx translatronx context sync --dry-runImport/generate context file from source JSON (preserves existing translations).
npx translatronx context import [options]Options:
--source <path>- Source JSON file to generate context from--output <path>- Output context file path--merge- Merge with existing context file (default: true)--dry-run- Preview changes without writing
Example:
npx translatronx context import --source ./locales/en.jsonUse Case: Perfect for users who already have translations and want to add context files without affecting existing translations.
Context files provide additional information to the LLM about each translation key, resulting in more accurate and contextually appropriate translations.
Context files mirror your source file structure:
Source File (en.json):
{
"greeting": "Hello, {name}!",
"auth": {
"login": "Sign in",
"logout": "Sign out"
}
}Context File (en.context.json):
{
"greeting": {
"value": "Hello, {name}!",
"context": "Greeting shown when user logs in. {name} is the user's display name.",
"notes": "Keep it friendly and welcoming",
"maxLength": 50,
"tone": "casual"
},
"auth": {
"login": {
"value": "Sign in",
"context": "Button text for user authentication. Should be concise and action-oriented."
},
"logout": {
"value": "Sign out",
"context": "Button text for ending user session."
}
}
}Each key in a context file can have:
value(required): The source text (for validation)context(optional): Descriptive text for the LLMnotes(optional): Additional notesmaxLength(optional): Maximum character lengthtone(optional): Desired tone (e.g., "formal", "casual", "technical")
-
Generate template:
npx translatronx context generate
-
Add context to keys: Edit the generated
en.context.jsonfile -
Validate:
npx translatronx context validate
-
Enable in config:
extractors: [{ type: 'json', pattern: './locales/en.json', contextFile: { enabled: true, pattern: './locales/en.context.json' } }]
-
Run sync:
npx translatronx sync
- Better Translation Quality - LLMs understand the purpose and usage of each string
- Consistency - Maintain consistent terminology across your app
- Tone Control - Specify formal vs. casual tone per string
- Length Constraints - Ensure translations fit UI constraints
- Optional - Works with or without context files
- Backward Compatible - No breaking changes to existing workflows
translatronx automatically detects and protects manual edits:
-
Translate your strings:
npx translatronx sync
-
Manually edit a translation in
fr.json:{ "welcome": "Bienvenue chez nous!" // Manual edit } -
Run sync again:
npx translatronx sync
-
Your manual edit is preserved! The ledger marks it as
MANUALstatus.
To force retranslation of manual edits:
npx translatronx sync --forceexport default defineConfig({
providers: [
{
name: 'primary',
type: 'openai',
model: 'gpt-4o-mini',
fallback: 'backup'
},
{
name: 'backup',
type: 'anthropic',
model: 'claude-3-5-sonnet-20241022'
}
]
});If the primary provider fails, translatronx automatically uses the backup.
export default defineConfig({
validation: {
customRules: [
(source, target) => {
// Ensure translations don't exceed source length by more than 50%
if (target.length > source.length * 1.5) {
return { isValid: false, error: 'Translation too long' };
}
return { isValid: true };
}
]
}
});If you have existing translations you want to import:
npx translatronx import --source ./old-translations/fr.json --target frThis imports your existing translations and marks them as MANUAL in the ledger, protecting them from being overwritten.
Add context to strings where:
- The meaning is ambiguous (e.g., "Bank" - financial institution or river bank?)
- Tone matters (e.g., error messages should be helpful, not scary)
- Length constraints exist (e.g., button text must be short)
- Cultural nuances matter (e.g., greetings, politeness levels)
- Use clear, descriptive keys:
auth.loginButtonnotbtn1 - Avoid abbreviations in source text
- Use consistent placeholder format:
{variable}not{{variable}}or$variable
Create a glossary for:
- Brand names
- Product names
- Technical terms
- Domain-specific terminology
Automate translations on source file changes:
- Ensures translations are always up-to-date
- Catches issues early
- Reduces manual work
While LLMs are good, they're not perfect:
- Review critical strings (legal, security, payments)
- Have native speakers spot-check
- Use the
statuscommand to track coverage
Don't retranslate everything:
- Let translatronx track changes
- Only force retranslation when necessary
- Trust the ledger system
import { TranslationCompiler, loadConfig } from 'translatronx';
async function main() {
// Load configuration
const config = await loadConfig();
// Create compiler
const compiler = new TranslationCompiler(config);
// Run sync
const stats = await compiler.sync({ verbose: true });
console.log(`Translated ${stats.translatedUnits} strings`);
console.log(`Cost: $${stats.costEstimateUsd.toFixed(4)}`);
// Close compiler
compiler.close();
}
main();import { defineConfig } from 'translatronx';
export default defineConfig({
// Type-safe configuration
sourceLanguage: 'en',
targetLanguages: [
{ language: 'French', shortCode: 'fr' }
],
// ... rest of config
});import { type Extractor, type SourceUnit } from 'translatronx';
class CustomExtractor implements Extractor {
async extract(sourceFiles: string[], config: ExtractorConfig): Promise<SourceUnit[]> {
// Your custom extraction logic
return [];
}
}We welcome contributions! Please see CONTRIBUTING.md for details.
# Clone repository
git clone https://github.com/msalways/translatronx.git
cd translatronx
# Install dependencies
npm install
# Run tests
npm test
# Build
npm run build
# Lint
npm run lintMIT Β© Shanthosh
- Inspired by the TypeScript compiler philosophy
- Built with OpenAI, Anthropic, and Groq
- Uses better-sqlite3 for state management
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Email: shanthubolt@gmail.com
Made with β€οΈ by developers, for developers