Frappe_Claude_Skill_Package frappe-core-translation
install
source · Clone the upstream repo
git clone https://github.com/OpenAEC-Foundation/Frappe_Claude_Skill_Package
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/OpenAEC-Foundation/Frappe_Claude_Skill_Package "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/source/core/frappe-core-translation" ~/.claude/skills/openaec-foundation-frappe-claude-skill-package-frappe-core-translation && rm -rf "$T"
manifest:
skills/source/core/frappe-core-translation/SKILL.mdsource content
Frappe Translation / i18n
Deterministic patterns for translating Frappe apps across v14, v15, and v16.
Quick Reference
| Task | Python | JavaScript |
|---|---|---|
| Translate string | | |
| With substitution | | |
| With context | | |
| Lazy (module-level) | [v15+] | N/A |
| Check 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 () | Per-app [v15+] |
| 3 | CSV files () | Per-app |
| 4 | Parent language (e.g., for ) | Fallback |
Version Differences
| Feature | v14 | v15 | v16 |
|---|---|---|---|
/ | Yes | Yes | Yes |
lazy translation | No | Yes | Yes |
| CSV translations | Yes | Yes (legacy) | Yes (legacy) |
| PO/MO (gettext) | No | Yes | Yes |
| 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 |
|---|---|---|
| Babel (AST) | , calls |
| Babel tokenizer [v15+] / regex [v14] | calls |
| Regex | in Jinja |
| Same as JS | in script/template |
| DocType parser | Labels, descriptions, options |
CRITICAL: Extractors work on the AST/tokens. They CANNOT extract dynamically constructed strings. See Anti-Patterns.
Anti-Patterns (NEVER Do These)
| Pattern | Why It Breaks | Correct Form |
|---|---|---|
| f-string not extractable | |
| Concatenation fragments | |
| Old-style not extractable | |
| Template literal not extractable | |
| Leading/trailing spaces trimmed | |
| Ternary inside _() | |
| Variable not extractable | |
Full anti-pattern catalog with code examples: references/anti-patterns.md
CSV Translation File Format
Location:
apps/{app}/{app}/translations/{lang}.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
directorytranslations/
PO/MO Files [v15+]
Location:
apps/{app}/{app}/locale/{lang}/LC_MESSAGES/{app}.po
# 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 |
|---|---|---|
| All | Export untranslated strings |
| All | Import translations |
| v15+ | Generate .pot template |
| v15+ | Convert CSV to PO format |
| v15+ | Compile PO to binary MO |
RTL Support
Hardcoded RTL languages:
ar (Arabic), he (Hebrew), fa (Persian/Farsi), ps (Pashto)
# Python if frappe.utils.is_rtl(): # Apply RTL-specific logic
// JavaScript if (frappe.utils.is_rtl()) { // Apply RTL-specific logic }
- Frappe auto-applies
to thedir="rtl"
element<html> - ALWAYS use logical CSS properties (
notmargin-inline-start
) for RTL compatibilitymargin-left - Bootstrap RTL stylesheet is auto-loaded when RTL language is active
Custom App Translation Workflow
Adding translations to your custom app:
- Write translatable strings using
/_()
with positional placeholders__() - Extract untranslated strings:
- v14:
bench --site {site} get-untranslated {lang} untranslated.csv - v15+:
bench generate-pot-file --app {app}
- v14:
- Translate the extracted strings (manually or via PO editor)
- Place translations:
- CSV:
apps/{app}/{app}/translations/{lang}.csv - PO:
apps/{app}/{app}/locale/{lang}/LC_MESSAGES/{app}.po
- CSV:
- Compile (v15+ PO only):
bench compile-po-to-mo --app {app} - Clear cache:
bench --site {site} clear-cache
Reference Files
| File | Contents |
|---|---|
| references/api-reference.md | Full Python _() and JS __() API with all signatures and edge cases |
| references/csv-and-bench.md | CSV format spec, bench commands, PO/MO workflow, custom app setup |
| references/anti-patterns.md | Complete anti-pattern catalog with failing and corrected examples |