Events & Flow Builder
Die Erweiterung versendet genau ein eigenes Event: eine Meldung über einen abgeschlossenen Auftrag, auf die Sie im Flow Builder oder in PHP reagieren. Außerdem registriert sie zwei geplante Aufgaben und zwei Subscriber.
Flow-Builder-Trigger
Nice\Translate\Job\Event\TranslationJobFinishedEvent ist ein registriertes Business Event, deshalb erscheint es in der Trigger-Liste des Flow Builders.
| Aspekt | Wert |
|---|---|
| Technischer Name | nice_translate.job.finished |
| Bezeichnung in der Administration | Übersetzungsauftrag abgeschlossen |
| Trigger-Pfad | KI-Übersetzung → Auftrag → Abgeschlossen |
| Klasse | Nice\Translate\Job\Event\TranslationJobFinishedEvent |
| Basisklasse | Symfony\Contracts\EventDispatcher\Event |
Es implementiert drei Aware-Interfaces:
| Interface | Auswirkung |
|---|---|
Shopware\Core\Framework\Event\FlowEventAware | Das Event kann überhaupt ein Flow-Trigger sein |
Shopware\Core\Content\Flow\Dispatching\Aware\ScalarValuesAware | Die Erweiterung speichert die sechs Payload-Werte mit der Flow-Sequenz, deshalb hat auch eine gespeicherte, verzögerte Flow-Ausführung sie noch |
Shopware\Core\Framework\Event\MailAware | Die Mail-Aktion von Shopware adressiert den Ersteller des Auftrags, und Sie konfigurieren keinen Empfänger |
getSalesChannelId() liefert immer null. Übersetzungsaufträge sind Arbeit auf Administrationsebene und gehören zu keinem Verkaufskanal.

Payload
getAvailableData() stellt genau diese sechs Skalare in dieser Reihenfolge bereit. getValues() liefert dieselben sechs Schlüssel, und dadurch stehen sie auch einer verzögerten Flow-Ausführung und einer E-Mail-Vorlage zur Verfügung.
| Eigenschaft | Typ | Bedeutung |
|---|---|---|
jobId | string | Die Hex-UUID der Zeile in nice_translate_job |
title | string | Der Auftragstitel, wie ihn die Auftragsliste zeigt |
status | string | Der Endstatus, den der Auftrag erreicht hat |
processedItems | int | Die Datensätze, die die Erweiterung übersetzt hat |
failedItems | int | Die Datensätze, die fehlgeschlagen sind |
skippedItems | int | Die Datensätze, die die Schutzprüfungen übersprungen haben |
Die Erweiterung schließt einen Auftrag ab, sobald processedItems + failedItems + skippedItems die geplante Gesamtzahl erreicht. Die drei Zähler decken zusammen deshalb den gesamten Lauf ab. Ein Datensatz gilt als übersprungen, wenn eine Schutzprüfung sein Überschreiben abgelehnt hat. Siehe Schutzmechanismen.
Wann es auslöst
Das Event trägt einen von drei Status:
| Status | Wird erreicht, wenn |
|---|---|
completed | Die Erweiterung jeden Datensatz verarbeitet hat und keiner fehlgeschlagen ist |
completed_with_errors | Der Auftrag mit mindestens einem fehlgeschlagenen Datensatz endete |
failed | Die Vorbereitung eines wartenden Auftrags fehlschlug, bevor ein Batch lief |
Der Finalizer der Batches erzeugt nur die ersten beiden Status. failed entsteht ausschließlich über JobProgressUpdater::failQueued(), der seinen eigenen Snapshot in dieselbe Outbox schreibt.
Für einen cancelled-Auftrag löst das Event nicht aus. Die Automatisierung beim Speichern löst es ebenfalls nicht aus, denn sie übersetzt ohne einen Auftrag. Siehe Automatisierung.
Der Finalizer schreibt den Abschluss-Snapshot in die Outbox nice_translate_finished_event, in derselben Transaktion, die den Auftrag abschließt, und er versendet das Event nie selbst. JobFinishedEventRelay nimmt den Snapshot nach diesem Commit auf. Die Zustellung erfolgt deshalb mindestens einmal: Ein Versand, der eine Exception wirft, behält seinen Lease, und die geplante Wiederherstellung versucht ihn nach Ablauf des Stale-Fensters erneut. Wenn Ihr Listener Seiteneffekte hat, deduplizieren Sie über jobId.
E-Mail-Empfänger
Das Relay leitet den Empfänger vom Ersteller des Auftrags ab, also von dem Shopware-Benutzer, auf den nice_translate_job.created_by_id verweist. Diese Adresse reist mit dem Event, und deshalb erreicht eine Mail-Aktion im Flow den Ersteller ohne weitere Konfiguration.
Achtung
Die geplante Aufgabe legt ihre Aufträge in einem Systemkontext an, und diese Aufträge haben keinen Ersteller. Die Empfängerliste bleibt für sie deshalb leer. Konfigurieren Sie in Ihrer Mail-Aktion einen festen Empfänger, wenn Sie eine Benachrichtigung über die geplanten Läufe möchten.
Einen Flow erstellen
Diese Schritte bauen einen Flow, der die Aufträge meldet, die mit Fehlern abgeschlossen wurden:
- Öffnen Sie in der Administration den Flow Builder und legen Sie einen neuen Flow an. Geben Sie ihm einen Namen wie Übersetzungsauftrag abgeschlossen.
- Wählen Sie als Trigger KI-Übersetzung → Auftrag → Abgeschlossen aus. Die Liste führt ihn als Übersetzungsauftrag abgeschlossen.
- Fügen Sie dem Flow eine Mail-Aktion hinzu. Das Event ist
MailAware, deshalb adressiert die Aktion den Ersteller des Auftrags. Sie akzeptiert auch alle Administratoren oder eine feste Adresse. - Verzweigen Sie in der E-Mail-Vorlage über das Payload, damit ein sauberer Lauf keine E-Mail versendet:
{% if failedItems > 0 %}
Übersetzungsauftrag "{{ title }}" wurde mit Status {{ status }} beendet.
{{ processedItems }} verarbeitet, {{ failedItems }} fehlgeschlagen, {{ skippedItems }} übersprungen.
Fehlerliste in der Auftragsdetailansicht öffnen: Auftrags-ID {{ jobId }}.
{% endif %}Der Trigger löst für jeden Auftrag aus, der einen Endstatus erreicht, und er hat keinen eigenen Filter. Die Bedingung gehört deshalb in die Vorlage oder in einen PHP-Listener.
Geplante Aufgaben
Die Erweiterung registriert zwei geplante Aufgaben mit dem Tag shopware.scheduled.task. Beide brauchen einen laufenden bin/console scheduled-task:run-Worker.
| Aufgabenklasse | Aufgabenname | Standardintervall |
|---|---|---|
AutoTranslateTask | nice_translate.auto_translate | 3600 s (1 Stunde) |
BatchDispatchTask | nice_translate.dispatch_batches | 60 s (1 Minute) |
nice_translate.auto_translate
Die Aufgabe läuft stündlich und übersetzt höchstens einmal pro scheduledIntervalHours. AutoTranslateTaskHandler::run() führt bei jedem Durchlauf diese Schritte der Reihe nach aus:
- Verlauf aufräumen. Der Handler löscht die Zeilen in
nice_translate_history, die älter alshistoryRetentionDayssind. Das geschieht bei jedem Durchlauf, mit eingeschalteter oder ausgeschalteter geplanter Übersetzung. Bei einem konfigurierten Wert unter 1 gilt eine Aufbewahrung von 30 Tagen. - Abbrechen, wenn
scheduledEnabledaus ist. Nichts unterhalb dieses Punktes läuft dann. - Abbrechen, wenn seit
scheduledLastRundas konfigurierte Intervall nicht verstrichen ist. Bei einem konfigurierten Wert unter 1 gilt fürscheduledIntervalHoursder Wert 24. - Den Lauf vermerken und abbrechen, wenn
scheduledEntitiesoderautoTranslateLanguageIdsleer ist. - Abbrechen, ohne den Lauf zu vermerken, wenn noch ein
nice_translate_jobden Statusqueuedoderrunninghat. Der Lauf bleibt fällig, und der nächste Durchlauf prüft dasselbe erneut. - Die Zielsprachen nach ihrem aufgelösten Anbieter gruppieren und je Anbietergruppe einen Auftrag anlegen. Ein Auftrag je Anbieter hält Validierung, Abrechnung und Auftragsbezeichnungen eindeutig. Die Aufträge nutzen den Scope
missing, den Modusmissing,skipUnchangedundprotectManualEdits, und ihr Titel beginnt mitScheduled (<provider label>):. scheduledLastRunals ISO-8601-Zeitstempel schreiben, nachdem der Handler mindestens einen Auftrag angelegt hat. Ein fälliger Lauf ohne angelegten Auftrag lässt den Zeitstempel unverändert, deshalb versucht es der nächste Durchlauf erneut.
Die Entitätenliste dieser Aufgabe ist scheduledEntities, eine eigene Einstellung neben den autoTranslateEntities, die die Automatisierung beim Speichern liest. Automatisierung und die Einstellungsreferenz dokumentieren beide Einstellungen.
nice_translate.dispatch_batches
BatchDispatchTaskHandler::run() ruft JobBatchDispatcher::recover() auf, danach JobFinishedEventRelay::recover(). Jeder Aufruf hat sein eigenes try/catch, das den Fehler protokolliert, deshalb blockiert ein fehlschlagender Wiederherstellungslauf den anderen Lauf nicht.
Diese Aufgabe macht die Ausfallsicherheitsschicht wirksam. Sie stellt die committeten Aufträge erneut in die Queue, deren Startnachricht den Transport nie erreicht hat, versendet die Batches mit unvollständigem Versand erneut, wiederholt die abgelaufenen Ansprüche auf Abschluss-Events und räumt die abgelaufenen Quittungen auf. Sie ist Infrastruktur und hat keine Einstellungen.
Subscriber
src/Job/Subscriber/ enthält zwei Subscriber, und beide sind als kernel.event_subscriber registriert.
| Klasse | Reagiert auf |
|---|---|
BusinessEventCollectorSubscriber | BusinessEventCollectorEvent::NAME, Priorität 1000 |
EntityWrittenSubscriber | product.written, category.written, product_manufacturer.written, cms_page.written |
BusinessEventCollectorSubscriber definiert TranslationJobFinishedEvent und nimmt es in die Sammlung auf. Dadurch erscheint der Trigger im Flow Builder.
EntityWrittenSubscriber setzt die Automatisierung beim Speichern um. Bevor er etwas versendet, durchläuft er in dieser Reihenfolge eine Kette von Prüfungen:
autoTranslateEnabledmusstruesein.- Der Kontext darf nicht den State
nice_translate.writingtragen. Das ist der Schleifenschutz. Er hält die eigenen Schreibvorgänge der Erweiterung von einem zweiten Auslösen ab. - Der Schreibvorgang muss in der Standardsprache des Systems erfolgt sein.
- Der Schreibvorgang muss in der Live-Version erfolgt sein.
autoTranslateEntitiesmuss die Entität enthalten.autoTranslateLanguageIdsdarf nicht leer sein.- Der Subscriber verwirft die Ergebnisse von Löschvorgängen und dedupliziert die verbleibenden IDs.
Danach löst er je Zielsprache einen Anbieter auf, baut eine auftragslose JobConfig mit dem Scope selection, dem Modus missing, skipUnchanged und protectManualEdits, teilt die IDs anhand der konfigurierten Batch-Größe auf und registriert ein dauerhaftes Manifest vor dem Versand. Wenn er einen Anbieter nicht auflösen kann, protokolliert er das und überspringt diese Sprache. Einen fehlgeschlagenen Versand protokolliert er ebenfalls und überlässt ihn danach der Wiederherstellungsaufgabe.
TranslationJobFinishedEvent ist die einzige eigene Event-Klasse der Erweiterung, und sie versendet nichts anderes. Wenn Sie einen Hook brauchen, den es nicht gibt, ist das Service-Tag aus Eigene Anbieter meist die richtige Stelle.
In PHP darauf reagieren
Das Relay versendet mit einem ausdrücklichen Event-Namen:
$this->eventDispatcher->dispatch($event, TranslationJobFinishedEvent::EVENT_NAME);Registrieren Sie Ihren Subscriber auf diesen Namen. Ein Subscriber, den Sie auf die Klasse registrieren, erhält den Versand nicht:
<?php declare(strict_types=1);
namespace Acme\Translate\Subscriber;
use Nice\Translate\Job\Event\TranslationJobFinishedEvent;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
final class TranslationJobSubscriber implements EventSubscriberInterface
{
public function __construct(
private readonly LoggerInterface $logger,
) {
}
public static function getSubscribedEvents(): array
{
return [
TranslationJobFinishedEvent::EVENT_NAME => 'onJobFinished',
];
}
public function onJobFinished(TranslationJobFinishedEvent $event): void
{
if ($event->getFailedItems() === 0) {
return;
}
$this->logger->warning('Translation job finished with errors.', [
'jobId' => $event->getJobId(),
'title' => $event->getTitle(),
'status' => $event->getStatus(),
'processed' => $event->getProcessedItems(),
'failed' => $event->getFailedItems(),
'skipped' => $event->getSkippedItems(),
]);
}
}<service id="Acme\Translate\Subscriber\TranslationJobSubscriber">
<argument type="service" id="logger"/>
<tag name="kernel.event_subscriber"/>
</service>Zwei Eigenschaften des Versands sind für jeden Listener wichtig, den Sie schreiben:
- Er läuft in einem Queue-Worker. Das Relay versendet aus dem Prozess heraus, der den Auftrag abgeschlossen hat: aus einem Queue-Worker oder aus der geplanten Wiederherstellungsaufgabe. Ein Web-Request ist daran nicht beteiligt, und
getContext()liefert einen Kontext aus einer System-Source. Darin gibt es weder einen Administrationsbenutzer noch einen Verkaufskanal zu lesen. - Er kann für denselben Auftrag mehr als einmal laufen. Die Zustellung erfolgt mindestens einmal. Halten Sie den Listener idempotent, oder deduplizieren Sie über
jobId.
Wenn Sie mehr als die sechs Skalare brauchen, laden Sie die Entität nice_translate_job über ihr Repository anhand von jobId. Die Admin-API nennt die Liste der Entitäten und die nötigen Berechtigungen.