$npx -y skills add Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-core-translationUse when implementing translations/i18n in Frappe v14-v16 apps. Covers _() in Python, __() in JavaScript, CSV translation files, bench commands, string extraction rules, lazy translation _lt(), PO/MO files [v15+], RTL support, and custom app translations. Prevents common mistakes
| 1 | # Frappe Translation / i18n |
| 2 | |
| 3 | > Deterministic patterns for translating Frappe apps across v14, v15, and v16. |
| 4 | |
| 5 | --- |
| 6 | |
| 7 | ## Quick Reference |
| 8 | |
| 9 | | Task | Python | JavaScript | |
| 10 | |------|--------|------------| |
| 11 | | Translate string | `_("Hello")` | `__("Hello")` | |
| 12 | | With substitution | `_("Hello {0}").format(name)` | `__("Hello {0}", [name])` | |
| 13 | | With context | `_("Change", context="Coins")` | `__("Change", null, "Coins")` | |
| 14 | | Lazy (module-level) | `_lt("Pending")` [v15+] | N/A | |
| 15 | | Check RTL | `frappe.utils.is_rtl()` | `frappe.utils.is_rtl()` | |
| 16 | |
| 17 | --- |
| 18 | |
| 19 | ## Decision Tree |
| 20 | |
| 21 | ``` |
| 22 | Need to translate a string? |
| 23 | ├── In Python (.py)? |
| 24 | │ ├── Inside a function/method → _("text {0}").format(val) |
| 25 | │ ├── Module-level constant [v15+] → _lt("text") |
| 26 | │ └── Module-level constant [v14] → define inside function or use lazy |
| 27 | ├── In JavaScript (.js)? |
| 28 | │ └── ALWAYS → __("text {0}", [val]) |
| 29 | ├── In Jinja template (.html)? |
| 30 | │ └── {{ _("text") }} |
| 31 | ├── In Vue (.vue)? |
| 32 | │ └── __("text") in <script>, {{ __("text") }} in <template> |
| 33 | └── DocType label/description/option? |
| 34 | └── Auto-extracted — no _() needed |
| 35 | |
| 36 | Where do translations live? |
| 37 | ├── v14 → apps/{app}/{app}/translations/{lang}.csv |
| 38 | ├── v15+ → apps/{app}/{app}/locale/{lang}/LC_MESSAGES/{app}.po |
| 39 | └── User overrides → Translation DocType (highest priority) |
| 40 | |
| 41 | Need to extract untranslated strings? |
| 42 | ├── v14 → bench --site {site} get-untranslated {lang} {output} |
| 43 | └── v15+ → bench generate-pot-file --app {app} |
| 44 | ``` |
| 45 | |
| 46 | --- |
| 47 | |
| 48 | ## Translation Priority (Highest First) |
| 49 | |
| 50 | | Priority | Source | Scope | |
| 51 | |----------|--------|-------| |
| 52 | | 1 | **Translation DocType** (user overrides) | Per-site | |
| 53 | | 2 | **MO files** (`locale/{lang}/.../{app}.mo`) | Per-app [v15+] | |
| 54 | | 3 | **CSV files** (`translations/{lang}.csv`) | Per-app | |
| 55 | | 4 | **Parent language** (e.g., `pt` for `pt-BR`) | Fallback | |
| 56 | |
| 57 | --- |
| 58 | |
| 59 | ## Version Differences |
| 60 | |
| 61 | | Feature | v14 | v15 | v16 | |
| 62 | |---------|-----|-----|-----| |
| 63 | | `_()` / `__()` | Yes | Yes | Yes | |
| 64 | | `_lt()` lazy translation | No | Yes | Yes | |
| 65 | | CSV translations | Yes | Yes (legacy) | Yes (legacy) | |
| 66 | | PO/MO (gettext) | No | Yes | Yes | |
| 67 | | `bench generate-pot-file` | No | Yes | Yes | |
| 68 | | Babel JS extractor | No | Yes | Yes | |
| 69 | | Type hints on `_()` | No | No | Yes | |
| 70 | |
| 71 | --- |
| 72 | |
| 73 | ## Auto-Extracted Strings (No _() Needed) |
| 74 | |
| 75 | These are extracted automatically by the framework: |
| 76 | |
| 77 | - DocType **labels** and **descriptions** |
| 78 | - Select field **options** (each option line) |
| 79 | - Workflow **states** and **actions** |
| 80 | - Print Format **labels** |
| 81 | - Report **column labels** |
| 82 | - Notification **subjects** (not body) |
| 83 | - Dashboard chart **labels** |
| 84 | |
| 85 | --- |
| 86 | |
| 87 | ## String Extraction Rules |
| 88 | |
| 89 | | File Type | Extractor | What It Finds | |
| 90 | |-----------|-----------|---------------| |
| 91 | | `.py` | Babel (AST) | `_("...")`, `_lt("...")` calls | |
| 92 | | `.js` | Babel tokenizer [v15+] / regex [v14] | `__("...")` calls | |
| 93 | | `.html` | Regex | `{{ _("...") }}` in Jinja | |
| 94 | | `.vue` | Same as JS | `__("...")` in script/template | |
| 95 | | `.json` | DocType parser | Labels, descriptions, options | |
| 96 | |
| 97 | **CRITICAL**: Extractors work on the AST/tokens. They CANNOT extract dynamically constructed strings. See [Anti-Patterns](references/anti-patterns.md). |
| 98 | |
| 99 | --- |
| 100 | |
| 101 | ## Anti-Patterns (NEVER Do These) |
| 102 | |
| 103 | | Pattern | Why It Breaks | Correct Form | |
| 104 | |---------|---------------|--------------| |
| 105 | | `_(f"Hello {name}")` | f-string not extractable | `_("Hello {0}").format(name)` | |
| 106 | | `_("Hello " + name)` | Concatenation fragments | `_("Hello {0}").format(name)` | |
| 107 | | `_("Welcome %s") % name` | Old-style not extractable | `_("Welcome {0}").format(name)` | |
| 108 | | `` __(`Hello ${name}`) `` | Template literal not extractable | `__("Hello {0}", [name])` | |
| 109 | | `_(" Hello ")` | Leading/trailing spaces trimmed | `_("Hello")` | |
| 110 | | `_("item" if x else "items")` | Ternary inside _() | `_("item") if x else _("items")` | |
| 111 | | `_(variable)` | Variable not extractable | `_("Known String")` | |
| 112 | |
| 113 | > Full anti-pattern catalog with code examples: [references/anti-patterns.md](references/anti-patterns.md) |
| 114 | |
| 115 | --- |
| 116 | |
| 117 | ## CSV Translation File Format |
| 118 | |
| 119 | **Location**: `apps/{app}/{app}/translations/{lang}.csv` |
| 120 | |
| 121 | ```csv |
| 122 | "source","translation","context" |
| 123 | "Hello","Hallo","" |
| 124 | "Change","Wisselgeld","Coins" |
| 125 | "Change","Wijziging","Amendment" |
| 126 | ``` |
| 127 | |
| 128 | - ALWAYS use UTF-8 encoding (no BOM) |
| 129 | - ALWAYS quote all fields with double quotes |
| 130 | - C |