Internationalisierung

Produktdaten übersetzen, ohne Daten zu verlieren

Wer Produkte automatisiert übersetzt, schreibt Felder zurück, die er vorher gelesen hat. Genau daran scheitern Integrationen nach einem Versionswechsel — Shopware macht Felder zwischendurch schreibgeschützt.

Pixup MediaVeröffentlicht am 9 Min. Lesezeit

Automatisierte Produktübersetzung sieht einfach aus: Produkt lesen, Texte durch die Übersetzung ersetzen, Produkt zurückschreiben. In dieser Reihenfolge funktioniert sie auch — bis zum nächsten Minor-Update.

Der Fehler, der dann auftritt, hat nichts mit der Übersetzung zu tun. Er entsteht durch ein Feld, das die Integration gar nicht übersetzen wollte, sondern nur mitgeschickt hat, weil es beim Lesen dabei war.

Was sich zwischen 6.6 und 6.7 geändert hat

Wir betreiben beide Linien parallel und vergleichen die Entitätsdefinitionen, wenn eine Erweiterung auf die neue Version soll. Für die Produktübersetzung ergibt der Vergleich einen neuen Eintrag.

Shopware 6.6.10.6 — das Feld description_teaser existiert in ProductTranslationDefinition nicht.

Shopware 6.7.13.1 — es existiert, mit dieser Definition:

(new StringField('description_teaser', 'descriptionTeaser', 512))
    ->addFlags(new ApiAware(), new WriteProtected(Context::SYSTEM_SCOPE))
    ->setDescription('Read-only, HTML-stripped excerpt of the description, derived on write.')

Drei Angaben stecken darin:

TeilBedeutung
ApiAwareDas Feld wird über die API ausgeliefert — es taucht in jeder Produktantwort auf
WriteProtected(Context::SYSTEM_SCOPE)Es darf nur im Systemkontext geschrieben werden, also von Shopware selbst
„derived on write"Shopware berechnet es beim Schreiben aus der Beschreibung, HTML entfernt

Die Kombination aus den ersten beiden ist der Kern des Problems: Das Feld kommt beim Lesen mit, darf beim Schreiben aber nicht dabei sein.

Warum genau das Integrationen trifft

Der übliche Ablauf einer Übersetzungs- oder PIM-Anbindung sieht so aus:

  1. Produkt über die Admin-API lesen, inklusive Übersetzungen
  2. Die zu übersetzenden Felder ersetzen
  3. Das Ergebnis zurückschreiben

In Schritt 1 liefert Shopware seit 6.7 descriptionTeaser mit. In Schritt 3 schickt eine Integration, die den gelesenen Datensatz als Grundlage nimmt, ihn wieder mit — auch wenn sie ihn nie angefasst hat.

Shopware prüft beim Aufbau des Schreibbefehls, ob ein geschütztes Feld explizit gesetzt wurde. Der WriteCommandExtractor macht das so:

if ($field->is(WriteProtected::class) && !$kvPair->isDefault()) {
    $this->validateContextHasPermission($field, $kvPair, $parameters);
}

Fehlt die Berechtigung, bricht der Schreibvorgang mit einer WriteFieldException ab — bevor etwas in der Datenbank landet. Die Übersetzung, die eigentlich geschrieben werden sollte, geht mit verloren.

Das ist die unangenehme Variante. Es gibt eine unangenehmere: Wenn die Integration den Fehler nicht auswertet und im Batch weiterläuft, verbraucht sie Übersetzungskontingent für Ergebnisse, die nirgends ankommen. Der Zähler läuft, der Shop bleibt einsprachig.

Die Regel, die daraus folgt

Schreiben Sie nur, was Sie ändern wollen. Nicht den gelesenen Datensatz zurückschicken, sondern ein Änderungsobjekt mit id und den tatsächlich geänderten Feldern aufbauen.

Statt:

{ "id": "…", "name": "…", "description": "…", "descriptionTeaser": "…",
  "available": true, "availableStock": 42, "ratingAverage": 4.5, … }

Besser:

{ "id": "…", "translations": { "en-GB": { "name": "…", "description": "…" } } }

Das ist nicht nur robuster gegen geschützte Felder — es ist auch schneller und erzeugt kleinere Änderungsprotokolle.

Die weiteren geschützten Felder am Produkt

descriptionTeaser ist nicht das einzige. In 6.7.13.1 tragen am Produkt selbst diese Felder ebenfalls WriteProtected:

FeldWas es ist
availableob das Produkt verfügbar ist — abgeleitet
availableStockBestand minus offene Bestellungen
displayGroupLaufzeitfeld für die Variantendarstellung
ratingAverageDurchschnitt aller Bewertungen
categoryTreeinterner Kategoriebaum
salesVerkaufshäufigkeit

Alle sechs haben dieselbe Eigenschaft: Shopware pflegt sie selbst. Wer sie schreibt, würde einen abgeleiteten Wert überschreiben — deshalb dürfen es nur die Stellen, die die Ableitung kennen.

Diese Liste ändert sich mit jeder Minor-Version. Sie ist kein Wissen zum Auswendiglernen, sondern etwas, das man vor einem Versionswechsel nachschlägt.

Prüfliste vor dem Update auf eine neue Minor-Version

  1. Feldliste vergleichen. Die Entitätsdefinition der alten und der neuen Version nebeneinanderlegen. Neue Felder mit WriteProtected sind die Kandidaten.
  2. Schreibpfad der Integration ansehen. Wird ein gelesener Datensatz zurückgeschrieben oder ein Änderungsobjekt aufgebaut? Nur Letzteres ist sicher.
  3. Auf einer Kopie testen, nicht in Production. Ein Testsystem mit echtem Datenbestand zeigt in zehn Minuten, was ein Batchlauf sonst über Stunden verteilt kaputtmacht.
  4. Fehler im Batch sichtbar machen. Ein Übersetzungslauf, der Fehler verschluckt, verbraucht Budget ohne Wirkung. Der Lauf muss melden, nicht still weiterlaufen — dasselbe Prinzip wie bei Automatisierungen, siehe Fehlerbehandlung in Make.
  5. Nach dem Lauf gegenprüfen. Nicht die Anzahl der Aufrufe zählen, sondern im Shop nachsehen, ob die Übersetzung angekommen ist. Ein 204 ohne Wirkung sieht aus wie ein Erfolg.

Der letzte Punkt ist der wichtigste. Die API bestätigt einen Schreibvorgang auch dann, wenn der Feldname nicht zur Definition passt — sie ignoriert, was sie nicht kennt. Wer nur Statuscodes zählt, hält einen wirkungslosen Lauf für gelungen.

Wo das in der Praxis auffällt

Wir entwickeln und betreiben ein Übersetzungs-Plugin für Shopware und pflegen es über Versionswechsel hinweg. Befunde wie dieser entstehen bei der Kompatibilitätsprüfung: dieselbe Erweiterung, zwei Runtimes, Verhalten vergleichen. Ein verwandter Fall aus derselben Arbeit betrifft nicht Felder, sondern Routen — wenn der Shopware-Kern eine Plugin-Route verdrängt.

Was das Übersetzen selbst angeht: Wie wir es aufsetzen, welche Felder wir anfassen und welche nicht, steht auf AI Translator. Wenn Sie eine bestehende Integration haben, die nach einem Update Fehler wirft, ist die Feldliste oben der erste Ort zum Nachsehen.

Häufige Fragen

Was bedeutet WriteProtected mit SYSTEM_SCOPE genau?
Das Feld darf nur geschrieben werden, wenn der Schreibvorgang im Systemkontext läuft — also von Shopware selbst. Ein Schreibversuch aus dem normalen API-Kontext wird beim Aufbau des Schreibbefehls abgewiesen, bevor irgendetwas in der Datenbank landet.
Warum scheitert meine Integration erst nach dem Update?
Weil das Feld vorher nicht existierte. Wer ein Produkt liest, verändert und vollständig zurückschreibt, sendet nach dem Update ein Feld mit, das es vorher nicht gab — und das jetzt geschützt ist.
Kann ich den Teaser gar nicht mehr beeinflussen?
Doch, indirekt. Er wird beim Schreiben aus der Beschreibung abgeleitet. Wer die übersetzte Beschreibung schreibt, bekommt den passenden Teaser automatisch.
Welche anderen Produktfelder sind schreibgeschützt?
Am Produkt selbst unter anderem available, availableStock, displayGroup, ratingAverage, categoryTree und sales. Alle sind abgeleitete Werte, die Shopware selbst pflegt.
WeiterführendPixup AI Translator