Skill125 repo starsupdated 2mo ago
frappe-core-translation
>
Install in Claude Code
Copygit clone --depth 1 https://github.com/Impertio-Studio/Frappe_Claude_Skill_Package /tmp/frappe-core-translation && cp -r /tmp/frappe-core-translation/skills/source/core/frappe-core-translation ~/.claude/skills/frappe-core-translationThen start a new Claude Code session; the skill loads automatically.
Definition
SKILL.md
# Frappe Translation / i18n
> Deterministic patterns for translating Frappe apps across v14, v15, and v16.
---
## Quick Reference
| Task | Python | JavaScript |
|------|--------|------------|
| Translate string | `_("Hello")` | `__("Hello")` |
| With substitution | `_("Hello {0}").format(name)` | `__("Hello {0}", [name])` |
| With context | `_("Change", context="Coins")` | `__("Change", null, "Coins")` |
| Lazy (module-level) | `_lt("Pending")` [v15+] | N/A |
| Check RTL | `frappe.utils.is_rtl()` | `frappe.utils.is_rtl()` |
---
## Decision Tree
```
Need to translate a string?
├── In Python (.py)?
│ ├── Inside a function/method → _("text {0}").format(val)
│ ├── Module-level constant [v15+] → _lt("text")
│ └── Module-level constant [v14] → define inside function or use lazy
├── In JavaScript (.js)?
│ └── ALWAYS → __("text {0}", [val])
├── In Jinja template (.html)?
│ └── {{ _("text") }}
├── In Vue (.vue)?
│ └── __("text") in <script>, {{ __("text") }} in <template>
└── DocType label/description/option?
└── Auto-extracted — no _() needed
Where do translations live?
├── v14 → apps/{app}/{app}/translations/{lang}.csv
├── v15+ → apps/{app}/{app}/locale/{lang}/LC_MESSAGES/{app}.po
└── User overrides → Translation DocType (highest priority)
Need to extract untranslated strings?
├── v14 → bench --site {site} get-untranslated {lang} {output}
└── v15+ → bench generate-pot-file --app {app}
```
---
## Translation Priority (Highest First)
| Priority | Source | Scope |
|----------|--------|-------|
| 1 | **Translation DocType** (user overrides) | Per-site |
| 2 | **MO files** (`locale/{lang}/.../{app}.mo`) | Per-app [v15+] |
| 3 | **CSV files** (`translations/{lang}.csv`) | Per-app |
| 4 | **Parent language** (e.g., `pt` for `pt-BR`) | Fallback |
---
## Version Differences
| Feature | v14 | v15 | v16 |
|---------|-----|-----|-----|
| `_()` / `__()` | Yes | Yes | Yes |
| `_lt()` lazy translation | No | Yes | Yes |
| CSV translations | Yes | Yes (legacy) | Yes (legacy) |
| PO/MO (gettext) | No | Yes | Yes |
| `bench generate-pot-file` | No | Yes | Yes |
| Babel JS extractor | No | Yes | Yes |
| Type hints on `_()` | No | No | Yes |
---
## Auto-Extracted Strings (No _() Needed)
These are extracted automatically by the framework:
- DocType **labels** and **descriptions**
- Select field **options** (each option line)
- Workflow **states** and **actions**
- Print Format **labels**
- Report **column labels**
- Notification **subjects** (not body)
- Dashboard chart **labels**
---
## String Extraction Rules
| File Type | Extractor | What It Finds |
|-----------|-----------|---------------|
| `.py` | Babel (AST) | `_("...")`, `_lt("...")` calls |
| `.js` | Babel tokenizer [v15+] / regex [v14] | `__("...")` calls |
| `.html` | Regex | `{{ _("...") }}` in Jinja |
| `.vue` | Same as JS | `__("...")` in script/template |
| `.json` | DocType parser | Labels, descriptions, options |
**CRITICAL**: Extractors work on the AST/tokens. They CANNOT extract dynamically constructed strings. See [Anti-Patterns](references/anti-patterns.md).
---
## Anti-Patterns (NEVER Do These)
| Pattern | Why It Breaks | Correct Form |
|---------|---------------|--------------|
| `_(f"Hello {name}")` | f-string not extractable | `_("Hello {0}").format(name)` |
| `_("Hello " + name)` | Concatenation fragments | `_("Hello {0}").format(name)` |
| `_("Welcome %s") % name` | Old-style not extractable | `_("Welcome {0}").format(name)` |
| `` __(`Hello ${name}`) `` | Template literal not extractable | `__("Hello {0}", [name])` |
| `_(" Hello ")` | Leading/trailing spaces trimmed | `_("Hello")` |
| `_("item" if x else "items")` | Ternary inside _() | `_("item") if x else _("items")` |
| `_(variable)` | Variable not extractable | `_("Known String")` |
> Full anti-pattern catalog with code examples: [references/anti-patterns.md](references/anti-patterns.md)
---
## CSV Translation File Format
**Location**: `apps/{app}/{app}/translations/{lang}.csv`
```csv
"source","translation","context"
"Hello","Hallo",""
"Change","Wisselgeld","Coins"
"Change","Wijziging","Amendment"
```
- ALWAYS use UTF-8 encoding (no BOM)
- ALWAYS quote all fields with double quotes
- Context column is optional but MUST be present (empty string if unused)
- No hooks registration needed — auto-discovered from `translations/` directory
---
## PO/MO Files [v15+]
**Location**: `apps/{app}/{app}/locale/{lang}/LC_MESSAGES/{app}.po`
```bash
# Generate POT template
bench generate-pot-file --app {app}
# Migrate existing CSV to PO
bench migrate-csv-to-po --app {app}
# Compile PO to MO (required for runtime)
bench compile-po-to-mo --app {app}
```
PO files follow standard GNU gettext format. Use any PO editor (Poedit, Weblate, Transifex).
---
## Bench Commands
| Command | Version | Purpose |
|---------|---------|---------|
| `bench --site {site} get-untranslated {lang} {output.csv}` | All | Export untranslated strings |
| `bench update-translations {lang} {untranslated.csv} {translated.csv}` | All | Import translations |
| `bench generate-pot-file --app {app}` | v15+ | Generate .pot template |
| `bench migrate-csv-to-po --app {app}` | v15+ | Convert CSV to PO format |
| `bench compile-po-to-mo --app {app}` | v15+ | Compile PO to binary MO |
---
## RTL Support
**Hardcoded RTL languages**: `ar` (Arabic), `he` (Hebrew), `fa` (Persian/Farsi), `ps` (Pashto)
```python
# Python
if frappe.utils.is_rtl():
# Apply RTL-specific logic
```
```javascript
// JavaScript
if (frappe.utils.is_rtl()) {
// Apply RTL-specific logic
}
```
- Frappe auto-applies `dir="rtl"` to the `<html>` element
- ALWAYS use logical CSS properties (`margin-inline-start` not `margin-left`) for RTL compatibility
- Bootstrap RTL stylesheet is auto-loaded when RTL language is active
---
## Custom App Translation Workflow
### Adding translations to your custom app:
1. **Write translatable strings** using `_()` / `__()` with positional placeholders
2. **Extract unt