Zum Hauptinhalt springen

Linguist (Übersetzungs-Sync)

Die UI-Übersetzungen (lang/de.json, lang/en.json) werden über den verwalteten Übersetzungs-Service Linguist gepflegt und mit dem Repo synchronisiert. Übersetzer arbeiten in der Linguist-Oberfläche; ein Deploy-Command holt die Übersetzungen kontrolliert ins Repo zurück.

Der Connector (hyperlinkgroup/linguist) round-trippt natives Laravel-lang-JSON — Übersetzungs-Keys und Platzhalter bleiben unverändert, der App-Code (__()) bleibt unangetastet.

Architektur in Kürze

EigenschaftWert
Verwaltete Dateienlang/de.json, lang/en.json (Top-Level JSON-String-Keys)
Nicht verwaltetlang/de/*.php, lang/en/*.php (auth/validation/…), der KI-Content-Übersetzer (DB-Inhalte)
Quell-/LeitspracheDeutsch (DE) — die __()-Schlüssel sind deutsch, also ist Deutsch die Referenz
ZielspracheEnglisch (EN) — die Übersetzung; Linguist zeigt fehlende englische Werte in der Oberfläche
LaufzeitLinguist läuft nie im Request-Pfad; __() liest die committeten lang/*.json. Laufzeit, Deploy und CI brauchen kein Token zur Laufzeit
Datenbankkeine — der Connector ist ein reiner Datei- + REST-API-Client

Variablen-Round-Trip

Beim Upload werden Laravel-Platzhalter :var in das Linguist-Format {{ var }} gewandelt; beim Download (Export mit ?prefix=:) wieder zurück nach :var. Damit bleiben Platzhalter wie :count, :reason und das Social-Handle-Präfix @:profileHandle byte-genau erhalten. Auch Keys mit Punkten (z. B. 1 – 3 Min.) bleiben flache Keys (kein Nesting).

Konfiguration (.env)

# ─────────────────────────────────────────────────────────────────────────────
# Linguist (UI-Übersetzungs-Connector)
# ─────────────────────────────────────────────────────────────────────────────
# Basis-URL der Linguist-API (Cloud-Default). Self-hosted: eigene Instanz mit /vN-Suffix.
LINGUIST_URL=https://api.linguist.eu/v2
# Projekt-Slug in Linguist. Pflicht ab dem ersten Sync.
LINGUIST_PROJECT=postbox
# Bearer-API-Token (SECRET!). Nur in der .env, niemals committen.
LINGUIST_TOKEN=
# Optionale URL zur Token-Verwaltung (vom Setup-Wizard genutzt).
LINGUIST_API_TOKENS_URL=https://app.linguist.eu/settings/api-tokens
# Pull-Format: false => pretty-printed JSON (Git-freundlich), true => minified.
LINGUIST_PULL_MINIFIED=false

Das Token ist projekt-scoped; ein Wechsel auf eine self-hosted Instanz ändert nur LINGUIST_URL.

Commands

translations:sync — deploy-sicherer Sync

Standard-Ablauf: guarded Push → Pull → Gates → Empty-Strip → Fail-safe.

php artisan translations:sync              # Push (nur neue Keys) + Pull + Gates + Empty-Strip
php artisan translations:sync --no-pull # nur Push (neue Keys registrieren), lokale Dateien unangetastet
php artisan translations:sync --pull-only # nur Pull (Keys müssen bereits remote sein)
php artisan translations:sync --strict # bei Gate-Verletzung Exit 1 (Deploy abbrechen) statt Fail-safe
php artisan translations:sync --overwrite # bestehende Remote-Werte mit den lokalen überschreiben (local wins) — nur für einmalige Re-Seeds/Korrekturen
php artisan translations:sync --dry-run # nur lokale vs. remote Key-Zahlen melden

Sicherheits-Eigenschaften:

  • Push ist nicht-destruktiv: lädt nur Keys hoch, die remote noch fehlen (bestehende Übersetzungen werden nie überschrieben). Transiente Fehler (z. B. ein HTTP 500 auf einem Batch) werden idempotent erneut versucht (--push-attempts, Default 3) — der erneute Push überspringt bereits vorhandene Keys.
  • Pull läuft nur bei sauberem Push. Sonst würde der vollständige Datei-Overwrite lokale Keys verlieren, die nicht hochgeladen wurden.
  • Empty-Strip: noch nicht übersetzte Keys (leerer Wert) werden aus der gepullten Datei entfernt und bleiben damit absent → sie fallen sauber auf die Quellsprache zurück, statt leer zu rendern.
  • Gates: kein Re-Nesting (Dot-Keys bleiben flach), keine un-reversierten Platzhalter-Klammern, kein nicht-leerer Baseline-Key verschwindet.
  • Fail-safe: schlägt ein Gate fehl, wird der committete Stand byte-genau wiederhergestellt und der Deploy läuft mit der Baseline weiter (mit --strict bricht er stattdessen ab).

lang:extract — CI-Guard für neue Keys

Linguist erfährt von einem neuen __('…') erst, wenn der Key in lang/<locale>.json steht und der Push ihn hochlädt — der Connector scannt keinen Code. lang:extract schließt diese Lücke:

php artisan lang:extract           # meldet Keys, die im Code stehen, aber in en.json fehlen (Exit 1 = CI-Gate)
php artisan lang:extract --add # ergänzt die fehlenden Keys in en.json (Wert = Key)
php artisan lang:extract --locale=de

Erfasst werden einfach-gequotete statische Literale in __(), @lang(), trans() und trans_choice(). Dynamische Keys (__($var)) und doppelt-gequotete Strings werden bewusst nicht erfasst — diese wenigen Fälle bleiben manuell.

Workflow

Neue Übersetzungs-Keys aus dem Code

  1. Entwickler fügt __('Neuer String') ein und trägt den Key in lang/en.json ein (englischer Wert) — oder lässt lang:extract --add ihn ergänzen.
  2. Beim Deploy registriert der Push den neuen Key in Linguist (nur neue Keys).
  3. Übersetzer setzen den englischen Wert in der Linguist-Oberfläche.
  4. Der nächste Deploy holt die Übersetzung per Pull nach lang/en.json.

Übersetzer-Änderungen

Übersetzer pflegen ausschließlich in Linguist. Lokale Wert-Änderungen an lang/*.json sollten nicht erfolgen, da der Pull sie überschreibt — die Quelle der Übersetzungen ist Linguist, der committete Git-Stand ist Baseline/Fallback.

Deployment (Laravel Forge)

translations:sync gehört in das Forge-Deploy-Script — nach composer install und den Migrationen, vor dem atomaren current-Symlink-Flip:

$FORGE_PHP artisan migrate --force
$FORGE_PHP artisan translations:sync
$FORGE_PHP artisan config:cache

Wichtig bei Zero-Downtime-Deployments (Releases + current-Symlink):

  • lang/ ist release-lokaler Code und darf nicht als „Shared/Persistent Directory" verlinkt werden (geteilt sind nur storage/ und .env). Der Pull schreibt ins neue Release, das erst beim Symlink-Flip live geht — zero-downtime-sicher.
  • Das Token kommt aus der geteilten .env.
  • Production-lang/*.json (nach Pull + Empty-Strip) können vom Git-Stand abweichen (Git = Baseline, Linguist = live). Das ist gewollt. Wer den Git-Stand nachziehen will, führt den Sync lokal aus und committet bewusst.

Ein automatischer, unbeaufsichtigter Pull außerhalb des Deployments (z. B. per Cron) wird nicht empfohlen, da der Pull die Dateien vollständig überschreibt.

Laravel-13-Kompatibilität

Ab hyperlinkgroup/linguist 2.0.0 wird Laravel 13 nativ unterstützt (illuminate/contracts ^12|^13, php ^8.3) — es genügt ein normales composer require "hyperlinkgroup/linguist:^2.0", kein Bypass/Repository-Eintrag nötig.

Historie: Die ältere 1.0-Reihe deklarierte nur ^10|^11|^12; L13 wurde dort übergangsweise per Composer-package-Repository (Constraint-Override in der Root- composer.json) überbrückt. Mit 2.0 entfällt das komplett.

Fehlerbehandlung

  • Ist Linguist nicht konfiguriert (LINGUIST_PROJECT/LINGUIST_TOKEN leer), wird der Sync übersprungen (Exit 0) und lang/* bleibt unverändert.
  • Bei Push-Fehlern wird der Pull übersprungen; bei Gate-Verletzungen greift der Fail-safe. In beiden Fällen läuft der Deploy mit der Baseline weiter (sofern nicht --strict).
  • Erfolg/Fehler werden geloggt; der Erfolgs-Check ist „alle Sprachen erfolgreich und keine Fehler", nicht eine reine Key-Zahl.