Alle Beispiele

Mehrsprachige Preisliste · Sprache und Format im Frame-Header wählen

Die Sprache lässt sich direkt im Header der Vorschau umschalten. Report-Texte kommen aus dem Modul-lang-Verzeichnis, Datum und Zahlen folgen der Format-Locale; die Währung bleibt CHF.

Flow Sprache Aggregate /reports/multilanguage/run
PDF

Was hier typisch ist

  • Sprachwahl im Header des Report-Frames
  • Modul-eigene Übersetzungen unter app/Reports/Multilanguage/lang
  • Produkt-, Kategorie- und Status-Texte kommen als Sprach-Arrays aus der Source (wie Spatie Translatable)
  • Datum und Zahlen folgen der Format-Locale (de_CH vs. en_US)
  • Die Währung bleibt unabhängig von der Sprache CHF
  • Der Wechsel wirkt auf HTML-Vorschau und PDF

Modul-Struktur

app/Reports/Multilanguage/
├── Report.php              # locale-aware report
├── Source.php              # language-neutral demo rows
├── lang/
│   ├── de/report.php
│   └── en/report.php
└── views/
    ├── report-start.blade.php
    ├── grid-head.blade.php
    ├── row.blade.php
    └── report-end.blade.php

Dateien

<?php

declare(strict_types=1);

namespace App\Reports\Multilanguage;

use Guggach\Reports\Aggregates\Count;
use Guggach\Reports\Aggregates\Sum;
use Guggach\Reports\Definition\ReportBuilder;
use Guggach\Reports\Report as BaseReport;
use Guggach\Reports\Sources\ReportSource;

/**
 * Flow report with module translations and locale-aware dates/numbers.
 * The language selector in the preview frame changes the run locale; this
 * report then renders all labels, dates and amounts in that language.
 */
final class Report extends BaseReport
{
    public function key(): string
    {
        return 'multilanguage';
    }

    public function name(): string
    {
        return __('multilanguage::report.name');
    }

    public function layout(): string
    {
        return 'default';
    }

    public function currency(): string
    {
        return 'CHF';
    }

    public function source(): ReportSource
    {
        return new Source;
    }

    public function define(ReportBuilder $r): void
    {
        $r->reportStart(view: 'report-start');
        $r->gridHeader(view: 'grid-head');
        $r->detail(view: 'row');
        $r->reportEnd(view: 'report-end');

        $r->aggregate('total', Sum::class, field: 'price', scope: 'report');
        $r->aggregate('positions', Count::class, field: 'product', scope: 'report');
    }
}
<?php

declare(strict_types=1);

namespace App\Reports\Multilanguage;

use Guggach\Reports\Localization\ReportLocale;
use Guggach\Reports\Sources\ReportField;
use Guggach\Reports\Sources\ReportSource;

/**
 * Language-neutral demo data. In a real application product, category and
 * status would come from Eloquent models with spatie/laravel-translatable;
 * the reports engine sets app()->getLocale() before the source is queried.
 * This fixture demonstrates the same idea with language arrays.
 */
final class Source extends ReportSource
{
    public function records(): iterable
    {
        $language = $this->language();

        $records = [
            [
                'product' => ['de' => 'Studio Miete', 'en' => 'Studio rent'],
                'category' => ['de' => 'Miete', 'en' => 'Rent'],
                'price' => 2400.00,
                'valid_from' => '2026-09-18',
                'status' => ['de' => 'Aktiv', 'en' => 'Active'],
                'status_key' => 'active',
            ],
            [
                'product' => ['de' => 'Proberaum Service', 'en' => 'Rehearsal room service'],
                'category' => ['de' => 'Service', 'en' => 'Service'],
                'price' => 860.00,
                'valid_from' => '2026-09-22',
                'status' => ['de' => 'Aktiv', 'en' => 'Active'],
                'status_key' => 'active',
            ],
            [
                'product' => ['de' => 'Bühnenlicht Paket', 'en' => 'Stage lighting package'],
                'category' => ['de' => 'Verkauf', 'en' => 'Sale'],
                'price' => 12500.00,
                'valid_from' => '2026-10-02',
                'status' => ['de' => 'Aktiv', 'en' => 'Active'],
                'status_key' => 'active',
            ],
            [
                'product' => ['de' => 'Künstler-Workshop', 'en' => 'Artist workshop'],
                'category' => ['de' => 'Service', 'en' => 'Service'],
                'price' => 1450.00,
                'valid_from' => '2026-10-05',
                'status' => ['de' => 'Entwurf', 'en' => 'Draft'],
                'status_key' => 'draft',
            ],
            [
                'product' => ['de' => 'Tanzboden Miete', 'en' => 'Dance floor rent'],
                'category' => ['de' => 'Miete', 'en' => 'Rent'],
                'price' => 1800.00,
                'valid_from' => '2026-10-08',
                'status' => ['de' => 'Aktiv', 'en' => 'Active'],
                'status_key' => 'active',
            ],
            [
                'product' => ['de' => 'Requisiten Verkauf', 'en' => 'Props sale'],
                'category' => ['de' => 'Verkauf', 'en' => 'Sale'],
                'price' => 7600.00,
                'valid_from' => '2026-10-12',
                'status' => ['de' => 'Entwurf', 'en' => 'Draft'],
                'status_key' => 'draft',
            ],
        ];

        return array_map(function (array $record) use ($language): array {
            foreach (['product', 'category', 'status'] as $key) {
                $record[$key] = $this->translated($record[$key], $language);
            }

            return $record;
        }, $records);
    }

    /**
     * @return list<ReportField>
     */
    public function fields(): array
    {
        return [
            new ReportField('product', __('multilanguage::report.field.product'), filterable: false),
            new ReportField('category', __('multilanguage::report.field.category'), type: 'enum', filterable: false),
            new ReportField('price', __('multilanguage::report.field.price'), type: 'number', filterable: false, aggregatable: true, format: 'currency'),
            new ReportField('valid_from', __('multilanguage::report.field.valid_from'), type: 'date', filterable: false, format: 'date'),
            new ReportField('status', __('multilanguage::report.field.status'), type: 'enum', filterable: false),
        ];
    }

    private function language(): string
    {
        $locale = $this->locale() ?? app()->getLocale();

        return explode('_', ReportLocale::normalize($locale))[0];
    }

    /**
     * @param  array<string, string>  $values
     */
    private function translated(array $values, string $language): string
    {
        $value = $values[$language] ?? $values['en'] ?? reset($values);

        return is_string($value) ? $value : '';
    }
}
<?php

declare(strict_types=1);

return [
    'name' => 'Mehrsprachige Preisliste',
    'title' => 'Preisliste',
    'intro' => 'Sprache und Formatierung folgen der Auswahl im Header. Die Währung bleibt unabhängig davon CHF.',
    'language_label' => 'Sprache',
    'format_label' => 'Format',
    'columns' => [
        'product' => 'Produkt',
        'category' => 'Kategorie',
        'valid_from' => 'Gültig ab',
        'status' => 'Status',
        'price' => 'Preis',
    ],
    'summary' => [
        'positions' => 'Positionen',
        'total' => 'Gesamttotal',
    ],
    'field' => [
        'product' => 'Produkt',
        'category' => 'Kategorie',
        'price' => 'Preis',
        'valid_from' => 'Gültig ab',
        'status' => 'Status',
    ],
];
<?php

declare(strict_types=1);

return [
    'name' => 'Multilingual price list',
    'title' => 'Price list',
    'intro' => 'Language and formatting follow the selection in the header. The currency stays CHF independently of the locale.',
    'language_label' => 'Language',
    'format_label' => 'Format',
    'columns' => [
        'product' => 'Product',
        'category' => 'Category',
        'valid_from' => 'Valid from',
        'status' => 'Status',
        'price' => 'Price',
    ],
    'summary' => [
        'positions' => 'Positions',
        'total' => 'Total',
    ],
    'field' => [
        'product' => 'Product',
        'category' => 'Category',
        'price' => 'Price',
        'valid_from' => 'Valid from',
        'status' => 'Status',
    ],
];
<style>
    .ml-head {
        display: flex;
        justify-content: space-between;
        align-items: flex-start;
        gap: 8mm;
        border-bottom: 3px solid #1d4ed8;
        padding-bottom: 4mm;
        margin-bottom: 5mm;
    }
    .ml-head h1 { margin: 0 0 1mm; font-size: 22px; color: #0f172a; }
    .ml-head p { margin: 0; font-size: 10px; color: #64748b; max-width: 120mm; }
    .ml-locale {
        display: flex;
        gap: 4mm;
        padding: 2mm 3mm;
        border-radius: 2mm;
        background: #dbeafe;
        color: #1e40af;
        font-size: 9px;
        white-space: nowrap;
    }
    .ml-locale strong { display: block; font-size: 11px; }
    .ml-grid-head,
    .ml-row {
        display: flex;
        align-items: center;
        gap: 3mm;
        box-sizing: border-box;
    }
    .ml-grid-head {
        height: 9mm;
        border-bottom: 2px solid #1d4ed8;
        font-size: 8px;
        font-weight: 700;
        color: #0f172a;
    }
    .ml-row {
        min-height: 9mm;
        border-bottom: 1px solid #e2e8f0;
        font-size: 9px;
        color: #334155;
    }
    .ml-col-product { flex: 1 1 auto; font-weight: 600; color: #0f172a; }
    .ml-col-category { flex: 0 0 24mm; }
    .ml-col-valid { flex: 0 0 28mm; color: #64748b; }
    .ml-col-status { flex: 0 0 22mm; }
    .ml-col-price { flex: 0 0 28mm; text-align: right; font-variant-numeric: tabular-nums; }
    .ml-status {
        display: inline-block;
        padding: 0.8mm 1.6mm;
        border-radius: 1mm;
        font-size: 8px;
        font-weight: 600;
    }
    .ml-status--active { background: #dcfce7; color: #166534; }
    .ml-status--draft { background: #fef3c7; color: #92400e; }
    .ml-summary {
        display: flex;
        justify-content: flex-end;
        gap: 10mm;
        margin-top: 4mm;
        padding-top: 3mm;
        border-top: 2px solid #0f172a;
        font-size: 11px;
        font-weight: 700;
    }
</style>

<div class="ml-head">
    <div>
        <h1>{{ __('multilanguage::report.title') }}</h1>
        <p>{{ __('multilanguage::report.intro') }}</p>
    </div>
    <div class="ml-locale">
        <span>
            {{ __('multilanguage::report.language_label') }}
            <strong>{{ $ctx->locale }}</strong>
        </span>
        <span>
            {{ __('multilanguage::report.format_label') }}
            <strong>{{ $ctx->formatLocale }}</strong>
        </span>
    </div>
</div>
<div class="ml-grid-head">
    <span class="ml-col-product">{{ __('multilanguage::report.columns.product') }}</span>
    <span class="ml-col-category">{{ __('multilanguage::report.columns.category') }}</span>
    <span class="ml-col-valid">{{ __('multilanguage::report.columns.valid_from') }}</span>
    <span class="ml-col-status">{{ __('multilanguage::report.columns.status') }}</span>
    <span class="ml-col-price">{{ __('multilanguage::report.columns.price') }}</span>
</div>
<div class="ml-row">
    <span class="ml-col-product">{{ $ctx->product }}</span>
    <span class="ml-col-category">{{ $ctx->category }}</span>
    <span class="ml-col-valid">{{ $ctx->date($ctx->valid_from) }}</span>
    <span class="ml-col-status">
        <span class="ml-status ml-status--{{ $ctx->status_key }}">
            {{ $ctx->status }}
        </span>
    </span>
    <span class="ml-col-price">{{ $ctx->currency($ctx->price) }}</span>
</div>
<div class="ml-summary">
    <span>{{ $ctx->aggregates['positions'] }} {{ __('multilanguage::report.summary.positions') }}</span>
    <span>{{ __('multilanguage::report.summary.total') }} {{ $ctx->currency($ctx->aggregates['total']) }}</span>
</div>