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

@ -0,0 +1,105 @@
<?php
namespace App\Console\Commands;
use App\Models\CmsArticle;
use App\Models\CmsBrand;
use FluxCms\Core\Models\CmsContent;
use Illuminate\Console\Command;
/**
* Findet Inhalte, die auf der englischen Seite deutsch erscheinen (09.10.2026): CMS-Sektionen,
* Marken (auch Merkmale, Leistungen, Abschnitte, Showroom, Bildunterschriften) und Magazin.
* Erkennung über typische deutsche Wörter – Eigennamen wie „Möbelservice“ allein schlagen nicht an.
*/
class CmsUntranslated extends Command
{
protected $signature = 'cms:untranslated {--locale=en : Zielsprache}';
protected $description = 'Listet CMS-Inhalte, die in der Zielsprache fehlen oder noch deutsch sind';
private const GermanWords = '/\b(und|der|die|das|für|mit|nicht|wir|Sie|Ihr|Ihre|Ihnen|ist|auf|ein|eine|vom|zum|zur|bei|aus|oder|auch|sich|werden|wird|unser|unsere|dem|den|des|im|sind|alle|mehr|über|wie|nach|nur|noch)\b/u';
public function handle(): int
{
$locale = (string) $this->option('locale');
$findings = [];
foreach (CmsContent::query()->orderBy('group')->orderBy('key')->get() as $row) {
$translations = $row->getTranslations('value');
$label = "CMS {$row->group}.{$row->key}";
if (! array_key_exists($locale, $translations)) {
$findings[] = [$label, 'keine Übersetzung'];
continue;
}
$this->collect($translations[$locale], $label, $findings);
}
foreach (CmsBrand::query()->ordered()->get() as $brand) {
foreach ($brand->translatable as $attribute) {
$translations = $brand->getTranslations($attribute);
if (filled($translations[CmsBrand::BASE_LOCALE] ?? null) && blank($translations[$locale] ?? null)) {
$findings[] = ["Marke {$brand->slug}.{$attribute}", 'keine Übersetzung'];
} else {
$this->collect($translations[$locale] ?? null, "Marke {$brand->slug}.{$attribute}", $findings);
}
}
foreach (CmsBrand::LOCALIZED_FIELDS as $field) {
$this->collect($brand->localized($field, $locale), "Marke {$brand->slug}.{$field}", $findings);
}
}
foreach (CmsArticle::query()->get() as $article) {
foreach ($article->translatable as $attribute) {
$translations = $article->getTranslations($attribute);
$label = "Magazin {$article->slug}.{$attribute}";
if (filled($translations[CmsBrand::BASE_LOCALE] ?? null) && blank($translations[$locale] ?? null)) {
$findings[] = [$label, 'keine Übersetzung'];
} else {
$this->collect($translations[$locale] ?? null, $label, $findings);
}
}
}
if ($findings === []) {
$this->info("Keine deutschen oder fehlenden Texte für „{$locale}“ gefunden.");
return self::SUCCESS;
}
$this->table(['Stelle', 'Text'], $findings);
$this->warn(count($findings).' Stellen.');
return self::FAILURE;
}
/**
* @param list<array{0: string, 1: string}> $findings
*/
private function collect(mixed $value, string $path, array &$findings): void
{
if (is_array($value)) {
foreach ($value as $key => $item) {
$this->collect($item, "{$path}.{$key}", $findings);
}
return;
}
if (! is_string($value)) {
return;
}
$text = trim(html_entity_decode(strip_tags($value)));
if ($text !== '' && preg_match_all(self::GermanWords, $text) >= 2) {
$findings[] = [$path, mb_strimwidth($text, 0, 90, '…')];
}
}
}