Zum Hauptinhalt springen
Schneespur
Dokumentation durchblättern

Module entwickeln

Übersetzungen

Wie ein Modul Übersetzungsdateien anlegt, Schlüssel statt Texte an Registries übergibt, marken-neutrale Texte schreibt und eigene UI-Sprachen über die LocaleRegistry registriert.

Der Kern bringt von Haus aus Deutsch (de) und Englisch (en) mit. Ihr Modul legt seine eigenen Texte daneben und kann — seit 1.1.3 — über die LocaleRegistry sogar eine ganz neue Oberflächensprache ergänzen, ohne den Kern anzufassen.

Übersetzungsdateien im Modul

Die Übersetzungen eines Moduls liegen in dessen lang/-Verzeichnis, nach Sprachcode getrennt:

modules/my-module/
  lang/
    de/
      messages.php
    en/
      messages.php

Dateiformat

Es sind gewöhnliche Laravel-Übersetzungs-Arrays — nichts Modul-Spezifisches. Wer Laravel kennt, kann sofort loslegen:

<?php
// lang/de/messages.php
return [
    'settings' => 'Einstellungen',
    'upload' => 'Hochladen',
    'document_types' => [
        'contract' => 'Vertrag',
        'invoice' => 'Rechnung',
        'other' => 'Sonstiges',
    ],
];
<?php
// lang/en/messages.php
return [
    'settings' => 'Settings',
    'upload' => 'Upload',
    'document_types' => [
        'contract' => 'Contract',
        'invoice' => 'Invoice',
        'other' => 'Other',
    ],
];

Übersetzungen verwenden

Jeder Modul-Text ist mit dem Modul-Slug namespaced — also {slug}::datei.schlüssel. Den Namespace registriert der ModuleManager während des Bootens automatisch; Sie müssen loadTranslationsFrom() für die normalen Modul-Texte nicht selbst aufrufen.

// In PHP
__('my-module::messages.settings');  // → 'Einstellungen' (wenn Locale de ist)

// Mit Parametern
__('my-module::messages.uploaded_by', ['name' => 'Max']);
{{-- In Blade --}}
{{ __('my-module::messages.settings') }}

@lang('my-module::messages.upload')

Nicht beim Booten übersetzen — Schlüssel an Registries übergeben

Das ist die wichtigste Regel auf dieser Seite. __() löst immer gegen die aktuelle Sprache auf — und zwar in dem Moment, in dem der Aufruf läuft. Während Ihr ServiceProvider::boot() ausgeführt wird, ist aber nur die Fallback-Sprache (de) aktiv. Die Sprache pro Benutzer oder pro Kunde wird erst später, je Request, gesetzt. Ein __() in boot() friert sein Ergebnis also auf Deutsch ein.

Das fällt überall dort auf die Füße, wo Sie einen Wert an eine Registry übergeben, den der Kern später rendert — zum Beispiel ein Menü-Label:

// ❌ Friert das Menü-Label auf Deutsch ein — der Seiteninhalt übersetzt, das Menü nicht.
$nav->addItem(label: __('my-module::messages.nav_label'), ...);

// ✅ Den rohen Schlüssel übergeben; die NavigationRegistry löst ihn pro Request auf.
$nav->addItem(label: 'my-module::messages.nav_label', ...);

Faustregel: In boot() geben Sie Übersetzungsschlüssel an Navigations- und Portal-Registries weiter — die lösen verzögert, zur Render-Zeit, auf. __() rufen Sie nur dort auf, wo der Wert direkt verbraucht wird: in Blade-Views, Controllern oder in Closures, die je Request laufen. Mehr dazu unter Navigation und Dashboard.

Verschachtelte Schlüssel

Verschachtelte Arrays sprechen Sie mit Punkt-Notation an:

__('my-module::messages.document_types.contract'); // → 'Vertrag'

Pluralformen

Für Mengenangaben nutzen Sie Laravels Plural-Syntax und trans_choice():

// lang/de/messages.php
return [
    'documents_count' => '{0} Keine Dokumente|{1} Ein Dokument|[2,*] :count Dokumente',
];

// Verwendung
trans_choice('my-module::messages.documents_count', 5); // → '5 Dokumente'

Marken-neutrale Texte mit brand()

Der Kern setzt einen BrandedTranslator ein, der Marken-Bezeichnungen automatisch ersetzt. Das ist der Grund, warum Sie den Produktnamen in Texten nicht fest verdrahten sollten: Schreiben Sie stattdessen den Platzhalter :brand, dann zeigt derselbe Text je nach Installation die richtige Marke an.

// Nicht so:
return ['welcome' => 'Willkommen bei Schneespur'];

// Sondern so:
return ['welcome' => 'Willkommen bei :brand'];

// Verwendung:
__('my-module::messages.welcome', ['brand' => brand()]);

Lokalisierte module.json

Auch die Felder name und description in der module.json lassen sich lokalisieren — nützlich, damit Ihr Modul in der Modul-Verwaltung in der passenden Sprache erscheint:

{
    "name": {
        "de": "Dokumentenverwaltung",
        "en": "Document Management"
    },
    "description": {
        "de": "Verwaltet Verträge und Dokumente für Kunden.",
        "en": "Manages contracts and documents for customers."
    }
}

Die Methode pickLocalized() des Module-Modells wählt die aktuelle App-Sprache aus und fällt auf Englisch zurück, wenn die gewünschte Sprache fehlt.

Eine eigene Sprache registrieren (Sprachpakete)

Seit 1.1.3 kann ein Modul eine neue Oberflächensprache ergänzen — über die LocaleRegistry (ein Singleton). Der Kern registriert de und en; Ihr Modul fügt weitere hinzu. So lässt sich etwa Tschechisch (cs) nachrüsten, ohne eine einzige Core-Datei zu ändern.

Schritt 1 — den Sprachcode registrieren, in Ihrem ServiceProvider::boot():

use App\Services\Extension\LocaleRegistry;

public function boot(): void
{
    app(LocaleRegistry::class)->add('cs', 'Čeština');

    // Die eigenen Texte des Moduls (namespaced):
    $this->loadTranslationsFrom(__DIR__ . '/../lang', 'my-module');
}

Sobald die Sprache registriert ist, wird cs überall akzeptiert, wo eine Sprache geprüft oder angewendet wird — bei der Kunden- und Benutzer-Validierung, in der EnsureCustomer/EnsureDriver-Middleware, in den Auswahllisten der Verwaltung und im Installer. Diese Stellen fragen LocaleRegistry::codes() bzw. has() ab, statt einer fest verdrahteten de,en-Liste. Genau deshalb genügt das eine add() — Sie müssen nicht an zehn Stellen nachtragen.

Schritt 2 — die Kern-Oberfläche übersetzen. Die Modul-namespaced Texte (my-module::…) decken nur die Bildschirme Ihres Moduls ab. Um auch die Anwendungs-Oberfläche (Verwaltung, Portal, Fahrer-Ansicht) in die neue Sprache zu bringen, müssen die Kern-Sprachdateien unter lang/<code>/ vorliegen — in denselben Namespaces wie lang/de/. Laravel löst Kern-Texte aus dem Wurzel-Namespace auf; ein Modul-Namespace überschreibt diese nicht. Dafür gibt es zwei Wege:

  1. Das Sprachpaket-Modul kopiert seine lang/cs/-Dateien bei der Aktivierung in das Kern-Verzeichnis lang/ der Installation.
  2. Die lang/cs/-Dateien werden direkt im Kern mitgeliefert.

Zum Boot-Zeitpunkt: Modul-Sprachen werden während ModuleManager::boot() registriert, das innerhalb des app()->booted()-Callbacks läuft. Die anwendungsweite default_locale wird daher nach dem Booten der Module angewendet — eine reine cs-Installation löst dadurch korrekt auf.

Die genaue API der LocaleRegistry steht unter Registries.

Sprache pro Benutzer, Fahrer und Kunde

Die Oberflächensprache lässt sich einem einzelnen Akteur zuweisen; er sieht die Anwendung nach dem Login in dieser Sprache.

AkteurSpalteWo gesetztWo angewendet
Kundecustomers.localePortal-Profil (Selbstbedienung) oder Kundenformular in der VerwaltungEnsureCustomer-Middleware (pro Request)
Fahrer / Verwalterusers.locale (nullable)Verwaltung → BenutzerformularEnsureDriver-Middleware (pro Request)

Ein null in users.locale bedeutet „die anwendungsweite Standardsprache verwenden”. Beide Middlewares wenden die Sprache nur an, wenn LocaleRegistry::has($code) zutrifft — eine nicht (mehr) registrierte Sprache fällt sicher auf den Standard zurück. Die <select>-Listen der Sprachauswahl werden aus LocaleRegistry::labels() gebaut, sodass jede registrierte Sprache automatisch erscheint.

Ein praktisches Beispiel: eine deutsche Schneespur-Installation mit einem tschechischen Fahrer. Der Betreiber installiert das tschechische Sprachpaket und setzt die Sprache dieses Fahrers auf cs. Der Fahrer meldet sich an und sieht die gesamte Fahrer-Oberfläche auf Tschechisch, während für alle anderen Deutsch bleibt.

Anwendungsweite Standardsprache

Die Standardsprache der Anwendung liegt in der Einstellung default_locale (Verwaltung → Unternehmenseinstellungen) und akzeptiert jede registrierte Sprache. Sie wird im app()->booted()-Callback des AppServiceProvider angewendet — nachdem die Module ihre Sprachen registriert haben, abgesichert durch LocaleRegistry::has().

Ist der Standard eine andere Sprache als de, löst die Marke zu Wintertrace auf (siehe das Zwei-Marken-Modell unter Architektur). Eine vollständig tschechische Installation wird dadurch automatisch als Wintertrace gebrandet.