Skip to content
AI & Agents
Skill

/frappe-core-translation

Use 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

From plugin
frappe-claude-skill-package
17861 skills
Install
$ npx -y skills add Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-core-translation --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/frappe-core-translation

Context preview

The summary Claude sees to decide when to auto-load this skill.

Use 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

SKILL.md

frappe-core-translation.SKILL.md
name: frappe-core-translation
description: >
  Use 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 with f-strings, concatenation, and template
  literals that break string extraction.
  Keywords: translation, i18n, _(), __(), _lt(), CSV, PO, gettext,, translate my app, multi-language, text not translated, wrong language, how to add translation.
  bench get-untranslated, RTL, localization.
license: MIT
compatibility: "Claude Code, Claude.ai Projects, Claude API. Frappe v14-v16."
metadata:
  author: OpenAEC-Foundation
  version: "3.0"

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`

"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`

# 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 |

Read more
Ships withfrappe-claude-skill-package

60 deterministic Claude AI skills for Frappe Framework & ERPNext v14-v16 development and operations

Get the whole plugin
Stats
178
Stars
53
Forks
Maintained
Maintenance
Python
Language
2mo ago
Last commit
8mo ago
Created
13d ago
Added

Repo: Impertio-Studio/Frappe_Claude_Skill_Package