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.