Englische Fassung der Markenseiten: Merkmale, Leistungen, Abschnitte, Showroom, Bildunterschriften

Diese Markenfelder waren nur einsprachig gespeichert und erschienen auf der englischen Seite
deutsch. Neue Spalte cms_brands.field_translations (additiv) trägt die Übersetzungen je Sprache,
die bisherigen Spalten bleiben die deutsche Fassung; ohne Übersetzung gilt weiter Deutsch.
Marken-Editor bearbeitet die Felder über den DE/EN-Umschalter (Bilder und Rubriken bleiben
sprachübergreifend). BrandTranslationsSeeder spielt die englischen Texte der vier Kernmarken
ein, ohne gepflegte Übersetzungen zu überschreiben. php artisan cms:untranslated listet
Inhalte, die in einer Sprache noch fehlen oder deutsch sind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-10-09 10:08:03 +00:00
parent dc31cdb401
commit 8ddc85422e
8 changed files with 982 additions and 34 deletions

View file

@ -95,6 +95,25 @@ $loadLocaleFields = function (CmsBrand $brand, string $locale) {
$this->originCity = $brand->getTranslation('origin_city', $locale) ?? '';
$this->originDistance = $brand->getTranslation('origin_distance', $locale) ?? '';
$this->originRole = $brand->getTranslation('origin_role', $locale) ?? '';
// Merkmale, Leistungen, Abschnitte, Showroom und Bildunterschriften: Deutsch aus den Spalten,
// andere Sprachen aus `field_translations` – ohne Übersetzung steht der deutsche Text als Vorlage da.
$this->showroom = (string) ($brand->localized('showroom', $locale) ?? '');
$this->highlights = $brand->localized('highlights', $locale) ?: ['', '', ''];
$this->services = $brand->localized('services', $locale) ?: [['title' => '', 'description' => '']];
$this->gallery = $brand->localizedGallery($locale);
// Absätze im Formular als Textblock: eine Leerzeile trennt sie.
$this->sections = collect(CmsBrand::normalizeSections($brand->localized('sections', $locale)))
->map(fn (array $s) => [
'title' => $s['title'],
'paragraphs' => implode("\n\n", $s['paragraphs']),
'claim' => $s['claim'],
'quote' => $s['quote'],
'quote_author' => $s['quote_author'],
'detail_title' => $s['detail']['title'] ?? '',
'detail_paragraphs' => implode("\n\n", $s['detail']['paragraphs'] ?? []),
])
->all();
};
$openEdit = function (int $id) {
@ -109,24 +128,8 @@ $openEdit = function (int $id) {
$this->logo = $brand->logo ?? '';
$this->image = $brand->image ?? '';
$this->website_url = $brand->website_url ?? '';
$this->showroom = $brand->showroom ?? '';
$this->is_published = $brand->is_published;
$this->order = $brand->order ?? 0;
$this->highlights = $brand->highlights ?: ['', '', ''];
$this->services = $brand->services ?: [['title' => '', 'description' => '']];
$this->gallery = CmsBrand::normalizeGallery($brand->gallery);
// Absätze im Formular als Textblock: eine Leerzeile trennt sie.
$this->sections = collect(CmsBrand::normalizeSections($brand->sections))
->map(fn (array $s) => [
'title' => $s['title'],
'paragraphs' => implode("\n\n", $s['paragraphs']),
'claim' => $s['claim'],
'quote' => $s['quote'],
'quote_author' => $s['quote_author'],
'detail_title' => $s['detail']['title'] ?? '',
'detail_paragraphs' => implode("\n\n", $s['detail']['paragraphs'] ?? []),
])
->all();
$this->loadLocaleFields($brand, $this->editLocale);
$this->showForm = true;
@ -248,20 +251,30 @@ $save = function () {
$brand->logo = $this->logo ?: null;
$brand->image = $this->image ?: null;
$brand->website_url = $validated['website_url'] ?: null;
$brand->showroom = $validated['showroom'] ?: null;
$brand->setLocalized('showroom', $this->editLocale, $validated['showroom'] ?: null);
$brand->highlights = collect($this->highlights)
$brand->setLocalized('highlights', $this->editLocale, collect($this->highlights)
->map(fn ($h) => trim((string) $h))
->filter()
->values()
->toArray();
->toArray());
$brand->services = collect($this->services)
$brand->setLocalized('services', $this->editLocale, collect($this->services)
->filter(fn ($s) => ! empty($s['title']) || ! empty($s['description']))
->values()
->toArray();
->toArray());
$brand->gallery = CmsBrand::normalizeGallery($this->gallery);
// Bilder und Rubriken gelten für alle Sprachen; die Unterschrift gehört zur bearbeiteten Sprache.
$gallery = CmsBrand::normalizeGallery($this->gallery);
$captions = collect($gallery)->mapWithKeys(fn (array $item) => [$item['image'] => $item['caption']])->all();
if ($this->editLocale !== CmsBrand::BASE_LOCALE) {
$baseCaptions = $brand->localized('gallery_captions', CmsBrand::BASE_LOCALE);
$gallery = array_map(fn (array $item) => [...$item, 'caption' => (string) ($baseCaptions[$item['image']] ?? '')], $gallery);
}
$brand->gallery = $gallery;
$brand->setLocalized('gallery_captions', $this->editLocale, $captions);
$brand->setTranslation('widget_label', $this->editLocale, trim((string) $this->widgetLabel));
$brand->setTranslation('cta_title', $this->editLocale, trim((string) $this->ctaTitle));
@ -273,7 +286,7 @@ $save = function () {
$brand->setTranslation('origin_role', $this->editLocale, trim((string) ($validated['originRole'] ?? '')));
// normalizeSections() nimmt die Textblöcke entgegen und trennt sie an den Leerzeilen.
$brand->sections = CmsBrand::normalizeSections(collect($this->sections)->map(fn (array $s) => [
$brand->setLocalized('sections', $this->editLocale, CmsBrand::normalizeSections(collect($this->sections)->map(fn (array $s) => [
'title' => $s['title'] ?? '',
'paragraphs' => $s['paragraphs'] ?? '',
'claim' => $s['claim'] ?? '',
@ -283,7 +296,7 @@ $save = function () {
'title' => $s['detail_title'] ?? '',
'paragraphs' => $s['detail_paragraphs'] ?? '',
],
])->all());
])->all()));
$brand->is_published = $validated['is_published'];
$brand->order = $validated['order'];
@ -367,7 +380,7 @@ $cancelForm = function () {
placeholder="Einbauschränke nach Maß." />
</div>
<flux:input wire:model="showroom" label="Showroom (optional)" placeholder="Showroom Bielefeld" />
<flux:input wire:model="showroom" label="Showroom ({{ strtoupper($editLocale) }}, optional)" placeholder="Showroom Bielefeld" />
<flux:input wire:model="website_url" label="Herstellerseite (optional)" placeholder="https://..." />
@error('website_url') <span class="text-sm text-red-600">{{ $message }}</span> @enderror
@ -403,7 +416,7 @@ $cancelForm = function () {
{{-- Drei Merkmale für die Übersichtsseite --}}
<div class="mt-6">
<div class="mb-2 flex items-center justify-between">
<flux:heading size="sm">Merkmale auf /marken</flux:heading>
<flux:heading size="sm">Merkmale auf /marken ({{ strtoupper($editLocale) }})</flux:heading>
<flux:button size="xs" variant="ghost" icon="plus" wire:click="addHighlight">Merkmal hinzufügen</flux:button>
</div>
<div class="space-y-2">
@ -461,7 +474,7 @@ $cancelForm = function () {
{{-- Leistungen --}}
<div class="mt-6">
<div class="mb-2 flex items-center justify-between">
<flux:heading size="sm">Leistungen und Besonderheiten</flux:heading>
<flux:heading size="sm">Leistungen und Besonderheiten ({{ strtoupper($editLocale) }})</flux:heading>
<flux:button size="xs" variant="ghost" icon="plus" wire:click="addService">Punkt hinzufügen</flux:button>
</div>
<div class="space-y-4">
@ -485,7 +498,7 @@ $cancelForm = function () {
{{-- Abschnitte der Markenseite --}}
<div class="mt-6">
<div class="mb-2 flex items-center justify-between">
<flux:heading size="sm">Abschnitte der Markenseite</flux:heading>
<flux:heading size="sm">Abschnitte der Markenseite ({{ strtoupper($editLocale) }})</flux:heading>
<flux:button size="xs" variant="ghost" icon="plus" wire:click="addSection">Abschnitt hinzufügen</flux:button>
</div>
@ -596,7 +609,7 @@ $cancelForm = function () {
</flux:select>
<flux:input wire:model="gallery.{{ $i }}.caption" size="sm" class="mb-2"
placeholder="Bildunterschrift (optional)" />
placeholder="Bildunterschrift {{ strtoupper($editLocale) }} (optional)" />
<div class="flex items-center justify-between gap-2">
<livewire:admin.cms.media-picker :value="null" field="brand_gallery:{{ $i }}"