AI-powered translation CLI for Frappe apps using the Claude CLI.
Massively parallelizes translation by assembling rich context (source code snippets, term glossaries, comments) and translating into all target languages simultaneously.
With Claude Code Max, this can translate around 500 strings into 30 languages per 4-hour token limit. However, the 4-hour token limit is already exhausted after 1 hour. So we're effectively doing 4 translations per second.
- Python 3.10+
- Claude CLI with an active subscription (Pro/Max)
- A Frappe bench directory with apps containing
locale/main.potfiles
uv tool install https://github.com/alyf-de/frappe-translator.gitgit clone https://github.com/alyf-de/frappe-translator
cd frappe-translator
uv sync
uv tool install -e .frappe-translator translate /path/to/bench --mode fill-missingfrappe-translator translate /path/to/bench \
--app frappe --app erpnext \
--language de --language fr \
--model sonnetfrappe-translator translate /path/to/bench --skip-glossaryfrappe-translator status /path/to/benchfrappe-translator translate /path/to/bench --dry-runfrappe-translator clear-progress /path/to/bench-
Pass 1 (term extraction): Batches all translatable strings and sends them to Claude to extract key domain-specific terms (e.g., "Invoice", "Purchase Order", "DocType"). Existing translations for these terms are looked up across all PO files to build a glossary. This pass is optional (
--skip-glossary) and cached across runs. -
Pass 2 (translation): Strings are batched (default 50 per call) and sent to Claude with full context:
- Source code snippets (up to 3 per string, from diverse files)
- Term glossary with existing translations
- Extractor comments from the POT file
- Per-language style instructions (formality, address form)
The LLM translates each string into all target languages at once. Failed batches are retried individually.
- All languages at once: Context assembly is 80% of the work. Once rich context is assembled, translating into multiple languages simultaneously is efficient and produces more consistent results.
- Dependency ordering: Apps are processed in order (frappe, erpnext, hrms, ...) so earlier apps' translations inform downstream glossaries.
- Incremental writes: Translations are written to PO files every 50 entries. Re-runs skip already-translated entries.
- Per-language resume: If translation fails for one language (e.g., placeholder mismatch), only that language is retried on the next run.
| Mode | Description |
|---|---|
fill-missing |
Only translate entries with empty msgstr in any target locale (default) |
review-existing |
Re-translate entries that already have translations |
full-correct |
Re-translate all entries regardless of existing translations |
Create a frappe-translator.toml file:
[general]
concurrency = 5
batch_size = 50
timeout = 120
model = "sonnet"
[style.default]
formality = "formal"
notes = "Use formal address. Preserve technical terms commonly used in ERP software."
[style.de]
formality = "formal"
address = "Sie"
notes = "Use 'Sie' form. German compound nouns preferred over English loanwords."
[style.fr]
formality = "formal"
address = "vous"
[terminology.de]
"Workspace" = "Arbeitsbereich"
[apps.order]
priority = ["frappe", "erpnext", "hrms"]Pass it with --config frappe-translator.toml.
uv sync
uv run ruff check src/ tests/
uv run ty check src/
uv run pytestMIT