Mehrsprachige Reports

Sprache und Formatierung sind zwei getrennte Achsen: Die Sprache steuert Übersetzungen und app()->getLocale(), die Format-Locale steuert Datum, Zahlen und Trennzeichen. Die Währung bleibt davon unabhängig.

Sprache und Format-Locale

Die Übersetzungssprache ist immer ein Sprachcode. Die Region kommt aus der Format-Locale — nicht aus einem regionalen Übersetzungsordner.

language · de · en · fr Setzt App::setLocale(), steuert __(), die Modul-lang-Dateien und ReportSource::locale().
formatLocale · de_CH · en_US · en_GB Steuert NumberFormatter, IntlDateFormatter und Carbon. Darf sich von der Sprache unterscheiden.
lang/de/report.php Regionale Übersetzungsordner wie lang/de_CH sind nicht vorgesehen. Regionen sind Formatierung, nicht Sprache.
de → de_CH · en → en_US Ohne explizite Region füllt die Engine sie über config/reports.format.locales oder einen Host-Resolver.
app()->getLocale() Darf technisch de_CH sein. Die Engine normalisiert intern auf language = de und formatLocale = de_CH.

Übersetzungen im Report-Modul

Jeder Report bringt seine Texte selbst mit. Der ReportRegistry registriert das lang-Verzeichnis automatisch als Laravel-Namespace mit dem key() als Präfix.

<?php

return [
    'name' => 'Mehrsprachige Preisliste',
    'title' => 'Preisliste',
    'columns' => [
        'product' => 'Produkt',
        'price' => 'Preis',
    ],
];
<h1>{{ __('multilanguage::report.title') }}</h1>

<span>{{ __('multilanguage::report.language_label') }}: {{ $ctx->locale }}</span>
<span>{{ __('multilanguage::report.format_label') }}: {{ $ctx->formatLocale }}</span>

Modul-eigene Lang-Dateien und ihre Verwendung in den Blade-Bändern.

Der Namespace ist der Report-Key: multilanguage::report.name. Die Struktur ist lang/<language>/<group>.php; eine Datei lang/de.php auf oberster Ebene wird von Laravels Namespace-Loader nicht geladen.

Run-Locale und Frame-Selector

Die App-Sprache bleibt die Sprache des Users. Der Report-Output lässt sich pro Lauf überschreiben — über die Option, die Report-Klasse oder die Sprachwahl im Vorschau-Frame.

Reports::html($report, locale: 'en') Überschreibt die Sprache für diesen Lauf. Reports::pdf() nimmt denselben Parameter.
Report::locale() · Report::formatLocale() Reportweite Defaults, falls der Aufruf keine Locale übergibt.
LocaleScope Setzt App-, Carbon- und Number-Locale und stellt den vorherigen Zustand im finally wieder her — auch in Jobs und Queues.
?locale=en Der Frame-Selector setzt locale auf die Preview-URL; Dokument, iframe, PDF und Filterformular übernehmen den Wert.
config('reports.locales') Allow-List für die Auswahl. Bei nur einer Locale blendet der Frame den Selector aus.
use Guggach\Reports\Reports;

$html = Reports::html('multilanguage', locale: 'en');
$pdf = Reports::pdf('multilanguage', locale: 'en')->stream();
<x-reports::frame
    :url="$frameUrl"
    :pdf="$pdfUrl"
    :locales="$locales"
    :locale="$locale"
/>

Sprache pro Aufruf und die Sprachwahl im Frame.

Datum, Zahlen und Währung

Bänder formatieren über den RenderContext. Der Formatter kennt die Format-Locale des Laufs (oder des Datensatzes) und die explizite Währung des Reports.

$ctx->number(2500.4) Neutrale Zahl mit den Trennzeichen der Format-Locale, z. B. 2'500.40 in de_CH oder 2,500.40 in en_US.
$ctx->currency(2500.4) Formatiert mit expliziter Währung: Report::currency(), Feld-Metadatum oder zweiter Parameter.
$ctx->date($value) Default ist MEDIUM: 18.09.2026 in de_CH, Sep 18, 2026 in en_US.
date:short · date:long · date:iso SHORT nur für kompakte Tabellen, LONG für Verträge, ISO für technische Ausgabe.
$ctx->dateTime($value) · $ctx->time($value) Datum mit Zeit bzw. nur Zeit, ebenfalls in der Format-Locale.

Währung ist keine Locale-Eigenschaft

en_US bedeutet nicht USD, de_CH nicht CHF. Es gibt keinen globalen Währungs-Default und keine Ableitung aus der Locale. Ohne explizite Währung wirft currency() eine Exception; number() funktioniert immer. Multi-Währung kommt aus einem Datensatzfeld, ohne automatische Umrechnung.

Daten aus der Source

Die Engine setzt app()->getLocale() vor dem Laden der Daten. Eloquent-Modelle mit spatie/laravel-translatable liefern deshalb automatisch die richtige Sprache; sprachspaltige Tabellen filtern selbst.

use App\Models\Product;

public function records(): iterable
{
    // app()->getLocale() is already set to the run language.
    // spatie/laravel-translatable reads the ambient locale lazily.
    return Product::query()->get();
}
public function records(): iterable
{
    return DB::table('products')
        ->where('language', $this->locale())
        ->get();
}
use Guggach\Reports\Localization\ReportLocale;

public function records(): iterable
{
    $language = explode('_', ReportLocale::normalize(
        $this->locale() ?? app()->getLocale(),
    ))[0];

    $records = [
        ['product' => ['de' => 'Studio Miete', 'en' => 'Studio rent']],
    ];

    return array_map(function (array $record) use ($language): array {
        $record['product'] = $record['product'][$language]
            ?? $record['product']['en'];

        return $record;
    }, $records);
}

Drei Muster: translatable Models, Sprachspalten und Sprach-Arrays in der Demo-Source.

Gruppierungs- und Sortier-Keys sollten sprachneutral sein. Gruppendaten übersetzt man über enrichWith(); Gruppen selbst bleiben in der Run-Locale.

Locale pro Datensatz

Bei einem Lauf mit Rechnungen in verschiedenen Sprachen löst das Detailband die Locale pro Datensatz auf. Jede Formularinstanz rendert in ihrem eigenen LocaleScope.

->localeField('language') Liest die Sprache aus einem Source-Feld. Alternativ localeUsing(fn ($row) => …).
->formatLocaleField('format_locale') Optionale Format-Locale pro Datensatz; fehlt sie, wird sie aus der Sprache ergänzt.
$ctx->locale · $ctx->formatLocale Der RenderContext des Datensatzes enthält die aufgelösten Werte und einen passenden ReportFormatter.
Gruppen: Run-Locale Gruppenköpfe und -füsse bleiben in der Run-Locale. Übersetzte Gruppendaten kommen über enrichWith() aus Eloquent.
$r->freeform(view: 'invoice')
    ->localeField('language')
    ->formatLocaleField('format_locale');
return [
    ['invoice_id' => 1, 'language' => 'de', 'format_locale' => 'de_CH'],
    ['invoice_id' => 2, 'language' => 'en', 'format_locale' => 'en_US'],
];

Mehrere Rechnungen in einem Lauf, jede mit eigener Sprache und eigenem Format.