Skip to main content
ClaudeWave
Skill125 estrellas del repoactualizado 2mo ago

frappe-core-translation

>

Instalar en Claude Code
Copiar
git 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-translation
Después abre una sesión nueva de Claude Code; el skill carga automáticamente.

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