<!-- AUTO-GENERATED by scripts/wi-claude-rules-sync.sh — do not edit. -->
<!-- bundle: 30-copy-i18n | sources: 4 .mdc files -->
---
paths:
  - "languages/**"
  - "readme.txt"
  - "inc/**"
  - "UI/**"
  - "assets/**"
---

<!-- source: .cursor/rules/wi-loco-all.mdc -->

# WI — `loco all` (translation batch on staging)


## Free vs Premium auf Staging (Pflicht — Tobias 2026-06-12)

Staging kann **`webinar-ignition`** (Free, wp.org-Slug) **oder** **`webinarignition-premium`** (Premium) aktiv haben — **nur das aktive Plugin** lädt `.mo` zur Laufzeit.

| Slug | Ordner | Wann |
|------|--------|------|
| `webinar-ignition` | `wp-content/plugins/webinar-ignition/` | Free aktiv (häufig längere QA-Phase) |
| `webinarignition-premium` | `wp-content/plugins/webinarignition-premium/` | Premium aktiv |

**Agent-Pflicht vor Deploy, loco all, Pull:**

1. **`wi_staging_resolve_active_plugin`** (`wi-dev/local/scripts/lib/wi-staging-active-plugin.sh`) — `wp plugin list`, welcher Slug `active` ist.
2. **Code-Deploy + Loco-Batch** nur in **`plugins/{aktiver-slug}/`** — nicht blind Premium.
3. **`wi-loco-pull-translations.sh`** pullt aus dem **aktiven** `languages/`-Ordner → Repo `languages/`.
4. **`wi-loco-batch`** (PHP): `WI_Loco_Batch_Plugin::get_bundle_id()` folgt dem aktiven Slug (nicht hardcoded Premium).
5. **Warnung ausgeben** wenn:
   - **beide** aktiv (`both_active`) — dann Premium für Batch, User hinweisen;
   - **`languages/` fehlt** am aktiven Slug (`missing_languages`) — oft **Plugin gerade gewechselt**, Deploy vergessen → **careless-switch**-Hinweis, nicht still weitermachen.
6. Override nur wenn nötig: `WI_STAGING_FORCE_PLUGIN_SLUG=webinar-ignition ./wi-dev/local/scripts/loco-all.sh`

**Version-Preflight:** `Version:` gegen **`plugins/{aktiver-slug}/webinarignition.php`** — nicht immer Premium.

Cross-Ref: Staging-Deploy allgemein → [`wi-staging-infra.mdc`](wi-staging-infra.mdc) (Agent deployt incremental in **aktiven** Slug).


## Trigger

User says **`loco all`** → full run: deploy `wi-loco-batch` on Cloudways staging, translate all author locales until `queued=0`, pull `languages/webinar-ignition-*.mo` + `webinar-ignition.pot` into the repo.

Script: `./scripts/loco-all.sh` (wraps `wi-loco-run-all-staging.sh`).

## Harte Kanon-Regel ab 2026-06-29

Es gibt nur noch **`loco all`** als gültigen Agent-Workflow. Teilbefehle sind nur interne Schritte des vollständigen Laufs und dürfen **nie** als erledigt gemeldet werden.

**Verboten als Abschluss / Ersatz:**

- nur `wp loco sync ...`
- nur `wp loco extract ...`
- nur `queued=0` aus einer alten Queue ablesen
- nur `.mo`/`.pot` pullen
- `--pull-only` nach neuen oder geänderten Source-Strings
- manuelle Gate-Overrides ohne danach vollständigen Queue-/Gate-Check

Wenn `./scripts/loco-all.sh` im Checkout fehlt oder blockiert ist, muss der Agent exakt denselben vollständigen Ablauf manuell und **in Reihenfolge** ausführen: Version-Preflight → aktiver Slug → POT Extract/Queue Refresh → Auto-Runner bis `queued=0` → Pull `.mo` + `.pot` → P26/Gate-Strings prüfen. Danach im Chat ausdrücklich melden, dass der Wrapper fehlte und der vollständige `loco all`-Ablauf manuell identisch ausgeführt wurde.

## Pflicht vor Start (Staging = latest local)

**Before** deploy batch or any Loco work:

1. **Compare version** — local `webinarignition.php` header `Version:` + `WEBINARIGNITION_VERSION` vs staging plugin file (SSH):
   ```bash
   grep -E '^\s*\* Version:|WEBINARIGNITION_VERSION' webinarignition.php | head -3
   ssh … "grep -E '^\s*\* Version:|WEBINARIGNITION_VERSION' public_html/wp-content/plugins/webinarignition-premium/webinarignition.php | head -3"
   ```
2. **Staging must match local** — translating on an old build wastes API runs and produces `.mo`/`.pot` out of sync with the code you ship.
3. **If staging is behind:**
   - **Fast path:** incremental SCP/rsync of changed plugin files + `webinarignition.php` (version bump), then `wp cache flush` — proceed with `loco all`.
   - **No fast upload** (many files, large diff, deploy blocked, no SSH, or not practical as incremental / single zip): **stop at the beginning of the task** and **ask Tobias to upload staging manually** — do not start the Loco batch until he confirms staging is updated.
4. After staging is confirmed latest → run `./scripts/loco-all.sh`.

## Agent-Ablauf (Kurz) — **POT zuerst, dann Sprachen**

**Falsch:** `queued=0` direkt nach Start = „nichts zu tun“ — oft **stale Queue** ohne frisches `WI_Loco_Queue::refresh()` (POT aus Plugin-Source neu bauen).

**Richtig (Pflicht-Reihenfolge):**

| Schritt | Aktion | Erfolg |
|--------|--------|--------|
| 0 | Version local = staging | `Version preflight OK:` |
| 1 | **POT sync** — `WI_Loco_Queue::refresh()` auf Staging (extrahiert Strings → `webinar-ignition.pot`) | `pot_total=` steigt ggü. altem Repo-POT |
| 2 | **Probe-Sprache** (z. B. `fr_FR`): `po_total < pot_total` **oder** `pending > 0` **oder** `status=queued` | Sonst **stoppen** — Batch würde nichts übersetzen |
| 3 | Batch `run_until_complete` starten + warten bis `queued=0` | `runner>0`, dann `done=N/N` |
| 4 | `./scripts/wi-loco-pull-translations.sh` — nur `.mo` + `.pot` + `index.php` (selective rsync); danach **`wi_loco_prune_repo_languages`** entfernt verbleibende `.po` / `.l10n.php` / Backups | mehrere `.mo` in `git status` wenn wirklich übersetzt |

Skript: Schritt 1–2 macht `wi_loco_refresh_queue` in `wi-loco-run-all-staging.sh` **vor** dem nohup-Runner (Race-Fix: `wi_loco_wait_for_runner`).

| Schritt | Aktion |
|--------|--------|
| 0 | Version local vs staging — wenn nicht gleich → deploy oder User bitten |
| 1 | `./scripts/loco-all.sh` vollständiger Lauf; falls Wrapper fehlt: manueller identischer Full-Run, keine Abkürzung |
| 2 | Verify: `git status languages/` — **mehrere** `.mo` + `webinar-ignition.pot` erwartet nach echtem Batch; **keine** `.po` / `.l10n.php` (sonst ZIP ~+24 MiB, Freemius-Limit 32 MiB) |
| 3 | Abschluss: Locales, `.pot`, `queued=0`, failed=0, Gate-Strings grün |


## Bekannter Fix — Batch-Start (2026-06-01)

| Symptom | Ursache | Fix (kanonisch) |
|--------|---------|-----------------|
| `unexpected EOF while looking for matching` direkt nach „Deployed wi-loco-batch" | Kaputtes **nested quoting** im alten `ssh … nohup bash -c '…'` One-liner | **`wi-loco-run-all-staging.sh`:** Batch via **SSH heredoc** starten (Zeile mit `bash -s <<'REMOTE'`) — **nicht** zurück auf One-liner |

Agent: Bei diesem Fehler **nicht** raten — heredoc-Pattern in `scripts/wi-loco-run-all-staging.sh` prüfen/beibehalten.

## Keine Schnellpfade mehr

Alte Schnellpfade wie `--pull-only` oder "nur Sync" sind abgeschafft. Auch wenn nur Dateien lokal nachgezogen werden sollen, heißt der Arbeitsmodus im Chat **`loco all`** und der Agent prüft mindestens Version, aktiven Slug, frische Queue/POT-Basis, `queued=0`, Pull und Gate-Strings. Wenn wirklich nur ein Pull ohne POT/Queue gewünscht ist, muss Tobias das ausdrücklich anders formulieren; Standard bleibt Full-Run.

**Auto-Preflight:** `wi-loco-run-all-staging.sh` ruft `wi_loco_version_preflight` auf — bricht ab wenn local `Version:` ≠ staging.

## Erfolgs-Check (Agent melden)

```
Version preflight OK: 4.12.xxx
POT sync OK: pot_total=2855 queued=24 … | fr_FR po=2849 pot=2855 pending=… status=queued
Batch runner active
[…] done=25/25 queued=0 failed=0
Pull complete → …/languages
files: 26
Done: batch complete + languages/ synced from staging.
```


## Plan-Abschluss

Wenn ein **go-ready-Plan** vollständig ist (alle Phasen + Tobias **`working ✓`** + Git push): Agent notiert im Handoff **`loco all`** — Tobias startet danach diesen Lauf (oder schreibt **`loco all`** im Chat). Siehe [`wi-plan-go-autonomous.mdc`](wi-plan-go-autonomous.mdc).

## Cross-Ref

- Staging deploy: `wi-staging-infra.mdc`
- i18n: EN source in code, Loco for `.mo` — `wi-agent-workflow.mdc` (i18n-Check)
- Pull only `.mo` + `.pot` + `index.php` (not `.po` / `.l10n.php`): `wi-loco-pull-translations.sh` + `lib/wi-loco-prune-repo-languages.sh`
- Freemius/rsync excludes: `languages/*.po`, `languages/*.l10n.php` in `scripts/lib/wi-deploy-rsync-excludes.txt`

---

<!-- source: .cursor/rules/wi-loco-translatable-text.mdc -->

# WI — All text Loco-translatable (global)

## Regel (hart)

**Jeder** sichtbare String im Plugin — Participant, Admin, Mail, AJAX, aria-label, Fehlermeldung, Einheit (`min`, `h`), Button — muss **Loco-übersetzbar** sein.

## Code (Pflicht)

| Erlaubt | Verboten |
|---------|----------|
| `__()`, `_e()`, `esc_html__()`, `esc_attr__()`, `_x()`, `sprintf( __( … ), … )` mit Domain **`webinar-ignition`** | Feste DE/FR/…-Texte im PHP/JS-Source |
| Englisch als **Source** im Repo | `.po`/`.mo` committen (Loco auf Staging/Live) |
| `_x( 'min', 'duration minute abbreviation', … )` für Abkürzungen | Hardcoded `' min'`, `'Duration:'` ohne Wrapper |

## JS

- Strings über `wp_localize_script` / `WEBINARIGNITION.translations` — **Englisch** im PHP-Array, Domain `webinar-ignition` wo WP-i18n greift.

## Agent-Check vor `working ✓`

- Neuer/geänderter sichtbarer Text → **nur EN** im Diff + `_x`-Context bei Abkürzungen.
- Abschluss: **`i18n: EN source only, Loco for DE/…`**
- Plan komplett + `working ✓` → Hinweis **`loco all`** ([`wi-loco-all.mdc`](wi-loco-all.mdc)).

## Cross-Ref

- [`wi-agent-workflow.mdc`](wi-agent-workflow.mdc) — i18n-Check
- [`wi-loco-all.mdc`](wi-loco-all.mdc) — Batch nach Release/Plan

---

<!-- source: .cursor/rules/wi-language-update.mdc -->

# WI Language Updates

- When syncing updated translations from Cloudways, import only `languages/*.pot`, `languages/*.mo`, and `languages/index.php`.
- Do not commit `.po`, `.l10n.php`, backup/original files (`*.po~`, `*.pot~`, `*.backup`, `*.original`), or ad-hoc language export artifacts.
- Keep the plugin package small: source translation editing can happen on Cloudways/local tooling, but Bitbucket receives only runtime `.mo`, the `.pot` template, and `index.php`.

## Agent — keine doppelten Locale-Dateien (hart, Tobias 2026-07-02)

**Erste Regel vor jeder i18n/Loco-Änderung:** prüfen, was **schon existiert** — nicht neu anlegen.

1. **Repo + Staging:** `languages/webinar-ignition-{locale}.mo` / Loco-Übersicht (Paket **und** System) lesen, bevor eine Datei erstellt oder deployed wird.
2. **`de_DE` ist vollständig** (~3200 Strings im Paket) — neue msgctxt-Strings (`admin_marketing`, `user_journey`) nur via **Loco Sync + `./scripts/loco-all.sh`**, **nie** zweite `de_DE`-Datei, **nie** Pilot-`.mo` unter `languages/loco/`.
3. **Keine Ad-hoc-Pilot-MO** zum „Beweisen“ von Übersetzungen — Preview/Smoke nutzt bestehende Loco-Datei nach Sync; Code-Änderung = POT/PO sync, dann Batch.
4. **`de_DE_formal` / `de_CH_informal`:** nur bewusst über Loco **„Neue Sprache“** anlegen, wenn freigegeben — **kein** Stub-`.mo` im Plugin-Ordner. Vorher prüfen, ob die Locale in Loco schon gelistet ist (Restore ≠ neu erfinden).
5. **`languages/loco/`** nur für offiziell gepullte Loco-Artefakte (`wi-loco-pull-translations.sh`), nicht für Agent-Piloten.

## msgctxt + Anrede (P40, Tobias 2026-07-02)

- **`admin_ui`:** neutral/unpersönlich (`__`) — Settings, Tabellen, Fehler, **Checklist-Labels + CTAs**
- **`admin_marketing`:** du+B in `de_DE`, Sie in `de_DE_formal` (`_x`) — Opt-in, Upgrade, **Checklist-hints only (Option A)**, Preflight-Ermunterung
- **`user_journey`:** du+B / Sie (`_x`) — Reg, TY, Mails, LP-Defaults
- **Checklist Option A (fix):** Label = `admin_ui`, hint = `admin_marketing` (warm, nicht kalt-neutral)
- Kanon: [`wi-i18n-msgctxt.mdc`](wi-i18n-msgctxt.mdc) · Loco-Prompt v2: `wi-dev/local/loco-openai-prompt-v2.txt`

Cross-Ref: msgctxt P39/P40, [`wi-loco-all.mdc`](wi-loco-all.mdc).

---

<!-- source: .cursor/rules/wi-i18n-msgctxt.mdc -->

# WI — i18n msgctxt Canon (P40)

## Drei Kontexte (Loco `context` / PHP `_x`)

| msgctxt | PHP | Ton EN source | de_DE | de_DE_formal |
|---------|-----|---------------|-------|--------------|
| **admin_ui** | `__()` / `esc_html__()` | neutral, kurz, unpersönlich | sachlich | sachlich (Sie ok) |
| **admin_marketing** | `_x( …, 'admin_marketing', … )` | SOG, Host, du in EN ok | **du** + Nutzen | **Sie** + Nutzen |
| **user_journey** | `_x( …, 'user_journey', … )` | warm, Teilnehmer, du in EN ok | **du** + Nutzen | **Sie** + Nutzen |

Formal vs. informal = **Locale** (`.mo`), nicht extra msgctxt.

Helfer: [`inc/wi-i18n-context.php`](../../inc/wi-i18n-context.php) · SOG: [`.cursor/wi-sog-texte.md`](../wi-sog-texte.md)

## Setup-Checklist (Luna/Nuna) — Option A (fix)

- **Label + CTA** → `admin_ui` (`__`)
- **hint** (+ spread/tease copy) → `admin_marketing` (`_x`), warm, wohlwollend, du in EN
- **hint_tip** (technisch, z. B. Rechtsklick Inkognito) → `admin_ui`

Opt-in, Upgrade, Freemius → durchgehend `admin_marketing` du+B.

## Loco

- Prompt: [`wi-dev/local/loco-openai-prompt-v2.txt`](../../wi-dev/local/loco-openai-prompt-v2.txt) · Modell **gpt-4.1**
- **Keine** zweite `de_DE`-Datei / keine Pilot-MO — siehe [`wi-language-update.mdc`](wi-language-update.mdc)
- Nach Code-Änderung: Sync POT → `./scripts/loco-all.sh` (de_DE + de_DE_formal)

## KI-Import

Brief: [`wi-dev/local/wi-ai-copy-brief-msgctxt.md`](../../wi-dev/local/wi-ai-copy-brief-msgctxt.md)

