Admin-API
Die Erweiterung registriert achtzehn Routen unter /api/_action/nice-translate/. Innerhalb der Erweiterung haben sie einen Konsumenten, das Administrationsmodul. Alles, was das Modul tut, erreichen Sie über dieselben Routen auch aus eigenen Werkzeugen.
Achtung
Behandeln Sie diese Routen als intern. Die Payloads und die Antwortstrukturen können sich zwischen Minor-Versionen ändern, ohne Deprecation-Zyklus, und für keine davon gilt eine Kompatibilitätszusage. Fixieren Sie die Version der Erweiterung, wenn Sie darauf aufbauen.
Authentifizierung
Jede Route deklariert den Route-Scope der Admin-API (PlatformRequest::ATTRIBUTE_ROUTE_SCOPE => [ApiRouteScope::ID]). Die Routen sind deshalb nur über die Admin-API von Shopware erreichbar, und nur mit einem gültigen Admin-API-Access-Token. Es gibt keine Store-API-Oberfläche und kein separates Authentifizierungsverfahren. Holen Sie sich ein Token über den Standard-OAuth-Endpunkt der Admin-API von Shopware (POST /api/oauth/token), und senden Sie es als Authorization: Bearer <token>.
Berechtigungen
Jede Route deklariert die benötigte Berechtigung über PlatformRequest::ATTRIBUTE_ACL. Fehlt der Integration oder dem Benutzer eines Tokens diese Berechtigung, weist Shopware die Anfrage ab, bevor der Controller läuft. Berechtigungen (ACL) beschreibt die beiden Rollen.
Die Berechtigung folgt nicht immer der HTTP-Methode, schlagen Sie sie deshalb je Route nach. POST /estimate berechnet lediglich eine Schätzung und braucht trotzdem nice_translate.editor. GET|POST /provider/{providerId}/models braucht für beide Methoden nur nice_translate.viewer. Aus diesem Grund hat jede Routentabelle auf dieser Seite eine eigene Spalte für die Berechtigung.
| Berechtigung | Routen |
|---|---|
nice_translate.viewer | GET /entities, GET /coverage, GET /providers, GET|POST /provider/{providerId}/models, GET /usage, GET /subscription, GET /glossary/export |
nice_translate.editor | POST /estimate, POST /job, POST /job/{jobId}/cancel, POST /job/{jobId}/retry, POST /job/{jobId}/revert, POST /provider/{providerId}/test, POST /preview, POST /subscription/refresh, POST /glossary/import, POST /history/revert, POST /history/reapply |
Ein vollständiges Beispiel
Das Beispiel schätzt einen Lauf, bevor es den Auftrag anlegt. Es nutzt dafür dieselben zwei Aufrufe wie der Assistent:
curl -sS -X POST "https://shop.example/api/_action/nice-translate/estimate" -H "Authorization: Bearer $SW_TOKEN" -H "Content-Type: application/json" -d '{"config":{"entities":[{"name":"product","scope":"missing"}],"targetLanguageIds":["a1b2c3d4e5f60718293a4b5c6d7e8f90"],"providerId":"deepl","options":{"mode":"missing"}}}'{
"items": 128,
"characters": 41230,
"exact": true,
"cost": { "amount": 1.0308, "currency": "USD" },
"credits": null,
"serviceTier": null,
"multiplier": null,
"quota": null,
"warnings": []
}Wenn die Schätzung passt, legen Sie den Auftrag mit demselben config-Objekt an:
curl -sS -X POST "https://shop.example/api/_action/nice-translate/job" -H "Authorization: Bearer $SW_TOKEN" -H "Content-Type: application/json" -d '{"config":{"entities":[{"name":"product","scope":"missing"}],"targetLanguageIds":["a1b2c3d4e5f60718293a4b5c6d7e8f90"],"providerId":"deepl","options":{"mode":"missing"}}}'{ "jobId": "018f2c1a8f7c73f2b0a4a1b7d1e5c904" }Ihre Queue-Worker verarbeiten den Auftrag danach. Seinen Status fragen Sie über die DAL-Entität nice_translate_job ab (siehe Entitäten über die DAL-API).
Das JobConfig-Objekt
POST /estimate und POST /job erwarten beide ein einzelnes config-Objekt, und JobConfig::fromArray() normalisiert es. Unbekannte Schlüssel bleiben unverändert. Den ungültigen Wert ersetzt der Normalisierer durch den dokumentierten Standard.
{
"entities": [{ "name": "product", "scope": "all|missing|selection", "ids": null }],
"sourceLanguageId": null,
"targetLanguageIds": ["<uuid>"],
"providerId": "deepl",
"options": {
"mode": "missing",
"protectManualEdits": true,
"skipUnchanged": true,
"fields": null,
"includeCustomFields": true,
"tone": "default",
"customPrompt": null,
"glossary": true,
"seoTruncate": true,
"serviceTier": "balanced"
}
}| Feld | Normalisierung |
|---|---|
entities[].name | Eine reine Zeichenkette als Entität wird zu {"name": …} |
entities[].scope | all, missing oder selection. Ein ungültiger Wert wird zu selection, wenn ids vorhanden sind, und sonst zu all |
entities[].ids | Der Normalisierer verwirft die Nicht-String-Einträge und die leeren Einträge und dedupliziert die Liste. Eine leere Liste wird zu null. Bei scope: "selection" muss jeder Eintrag eine gültige UUID sein |
sourceLanguageId | Eine leere Zeichenkette wird zu null, und die Erweiterung löst die Quelle dann je Zielsprache auf |
targetLanguageIds | Der Normalisierer verwirft die leeren Einträge und dedupliziert die Liste |
options.mode | all, andernfalls missing |
options.tone | default, formal oder informal. Jeder andere Wert wird zu default |
options.serviceTier | speed, balanced oder quality. Jeder andere Wert wird zu balanced |
options.customPrompt | Wird getrimmt. Ein leerer Wert wird zu null |
options.fields | {"<entityName>": ["<property>", …]}. Ein leeres Ergebnis wird zu null |
options.protectManualEdits, skipUnchanged, includeCustomFields, glossary, seoTruncate | Werden als Boolean geparst |
serviceTier erreicht den Anbieter nur auf dem Managed-Weg. Übersetzungsassistent und Schutzmechanismen beschreiben die Wirkung der übrigen Optionen auf den Lauf.
Aufträge
| Methode | Pfad | Berechtigung |
|---|---|---|
POST | /api/_action/nice-translate/estimate | nice_translate.editor |
POST | /api/_action/nice-translate/job | nice_translate.editor |
POST | /api/_action/nice-translate/job/{jobId}/cancel | nice_translate.editor |
POST | /api/_action/nice-translate/job/{jobId}/retry | nice_translate.editor |
POST | /api/_action/nice-translate/job/{jobId}/revert | nice_translate.editor |
POST /estimate
Anfrage: {"config": <JobConfig>}. Felder der Antwort:
| Feld | Typ | Bedeutung |
|---|---|---|
items | int | Datensätze, die verarbeitet würden |
characters | int | Zeichen, die gesendet würden |
exact | bool | false, sobald mindestens eine Entität nur näherungsweise bestimmt werden konnte |
cost | {amount, currency} | Schätzung des Anbieters, auf vier Nachkommastellen gerundet |
credits | int | null | Nur beim Anbieter managed gesetzt |
serviceTier | string | null | Nur beim Anbieter managed gesetzt |
multiplier | int | null | Guthaben-Multiplikator der aufgelösten Stufe |
quota | object | null | Momentaufnahme des Managed-Kontingents |
warnings | list | Einträge der Form {code, params, message} |
Die Schätzung ist unverbindlich. Der Platzhalterschutz und die Prüfungen des Zielzustands laufen später und können den gesendeten Text und die abgerechnete Menge verändern. Ein Anbieter, dessen eigene Schätzung eine Exception wirft, steuert 0.0 USD bei und lässt den Aufruf nicht scheitern.
Die Route liefert genau diese elf Warncodes: unknown_provider, provider_not_configured, target_language_missing, source_equals_target, locale_unmappable, google_english_collapse, entity_unsupported, estimate_failed, snippet_source_set_missing, snippet_target_set_missing, managed_quota_exceeded. Kosten & Verbrauch listet den Meldungstext jedes Codes und seine Bedeutung für den Händler.
Fehler: 400 {"message": "Missing \"config\" object in the request body."} oder 400 mit der Validierungsmeldung, wenn die Konfiguration ungültig ist.
POST /job
Anfrage: {"config": <JobConfig>}. Antwort: {"jobId": "<hex uuid>"}.
Fehler: 400 bei fehlendem config, und sonst 400 mit der Validierungsmeldung.
Hinweis
Wenn der Versand in die Queue fehlschlägt, liefert der Aufruf dennoch 200 mit der Auftrags-ID zurück. Die committete queued-Zeile ist die dauerhafte Startmarkierung, und die geplante Wiederherstellung stellt die Nachricht nachträglich zu. Siehe Architektur.
POST /job/{jobId}/cancel
Kein Body. Der Controller wandelt {jobId} in Kleinbuchstaben um und validiert die ID als UUID. Antwort: {"status": "cancelled"}, also der Status, den der Controller nach dem Versuch erneut gelesen hat. Nur ein queued-Auftrag und ein running-Auftrag wechseln den Status.
Fehler: 400 {"message": "Invalid job id."}, 404 {"message": "Translation job not found."}.
POST /job/{jobId}/retry
Antwort: {"retried": <int>}. Die Route erwartet keinen Body.
Nur ein completed-Auftrag und ein completed_with_errors-Auftrag können wiederholen. Jeder andere Auftrag liefert 400 {"message": "Only completed jobs can retry failed items."}. Der erneute Lauf klammert die Fehlerzeilen der teilweise übersetzten Datensätze aus. Bereits geschriebene Inhalte bleiben unverändert.
POST /job/{jobId}/revert
Kein Body. Antwort: {"queued": true, "items": <int>}. Das Zurücknehmen versendet zusätzlich eine RevertJobMessage.
Fehler: 400 bei ungültiger ID; 404 bei unbekanntem Auftrag; 400 {"message": "The job is still queued or running and cannot be reverted yet."}; 400 {"message": "The job has no revertible history rows."}, wenn der Auftrag keine Verlaufszeile im Status applied hat.
Anbieter
| Methode | Pfad | Berechtigung |
|---|---|---|
GET | /api/_action/nice-translate/providers | nice_translate.viewer |
GET, POST | /api/_action/nice-translate/provider/{providerId}/models | nice_translate.viewer |
POST | /api/_action/nice-translate/provider/{providerId}/test | nice_translate.editor |
POST | /api/_action/nice-translate/preview | nice_translate.editor |
GET /providers
Keine Parameter. Die Route liefert jeden Service mit dem Tag nice_translate.provider, auch Ihren eigenen Service:
{
"providers": [
{
"id": "deepl",
"label": "DeepL",
"configured": true,
"models": [],
"supports": { "html": true, "formality": true, "tone": false, "glossary": false },
"viaSubscription": false
}
]
}models ist die kuratierte Liste des Anbieters, und sie ist bei einem reinen maschinellen Übersetzer leer. Jeder Eintrag hat id, name, tier, pricing und source. viaSubscription ist genau dann true, wenn die Anbieter-ID managed ist.
GET | POST /provider/{providerId}/models
Optionaler POST-Body: {"apiKey": "…"}, für einen Schlüssel, den jemand in der Administration eingegeben, aber nicht gespeichert hat. Einen leeren Wert und einen Wert aus reinen Leerzeichen normalisiert der Controller zu null.
Wenn der Anbieter SupportsLiveModelsInterface implementiert und die Anfrage einen Schlüssel übergeben hat oder der Anbieter bereits konfiguriert ist, versucht der Controller eine Live-Abfrage. Jeder Fehler fällt still auf die kuratierte Liste zurück.
Antwort: {"models": [...], "source": "live"} oder {"models": [...], "source": "curated"}.
Fehler: 404 {"message": "Unknown translation provider \"x\"."}.
POST /provider/{providerId}/test
Body: {"apiKey": "…"} (optional; ohne Angabe nutzt der Controller den gespeicherten Schlüssel). Die Antwort ist immer 200:
{ "valid": true, "message": "API key is valid.", "quota": { "used": 0, "limit": null } }quota ist null, wenn der Anbieter kein Kontingent meldet, und quota.limit ist null bei einem unbegrenzten Tarif. Der Controller überführt eine ProviderException in {"valid": false, "message": "<user-safe message>", "quota": null}. Er gibt den Schlüssel nie zurück.
Fehler: 404 bei unbekanntem Anbieter.
POST /preview
Die Route übersetzt einen einzelnen Beispieltext. Sie baut die Anfrage immer mit format: "text" und ohne Glossarbegriffe auf. Keine Oberfläche der Administration ruft diese Route auf. Sie existiert für API-Clients.
{
"providerId": "openai",
"text": "Sample sentence.",
"sourceLanguageId": "<uuid>",
"targetLanguageId": "<uuid>",
"tone": "formal",
"customPrompt": null
}providerId, ein nicht leerer text, sourceLanguageId und targetLanguageId sind Pflicht. tone akzeptiert der Controller nur als formal oder informal, und jeden anderen Wert behandelt er als nicht gesetzt.
Antwort: {"translation": "…", "characters": <int>, "cost": {"amount": <float>, "currency": "USD"}}.
Fehler: 400 {"message": "The fields \"providerId\", \"text\", \"sourceLanguageId\" and \"targetLanguageId\" are required."}; 400 {"message": "Invalid language id."}; 400 {"message": "The source or target language could not be resolved to a locale."}; 400 mit der anwenderfreundlichen Meldung des Anbieters, wenn dieser eine Exception wirft; 404 bei unbekanntem Anbieter.
Katalog
| Methode | Pfad | Berechtigung |
|---|---|---|
GET | /api/_action/nice-translate/entities | nice_translate.viewer |
GET | /api/_action/nice-translate/coverage | nice_translate.viewer |
GET /entities
Keine Parameter. Die Route listet die übersetzbaren Inhaltstypen mit ihren Bezeichnungen, der Gesamtzahl der Datensätze und den aufgelösten Feld-Specs:
{
"entities": [
{
"name": "product",
"label": "Products",
"total": 1234,
"fields": [{ "property": "name", "type": "text", "maxLength": 255 }]
}
]
}fields[].type ist text, html, custom_fields, string_list oder structure. Die Entitäten, die der Installation fehlen, lässt die Route weg. Die Liste endet immer mit der Pseudo-Entität snippet. Unterstützte Inhalte beschreibt, welche Inhaltstypen erscheinen und warum.
GET /coverage
| Query-Parameter | Bedeutung | Standard, wenn nicht angegeben |
|---|---|---|
languageIds | Kommagetrennte Sprach-UUIDs | Alle Sprachen außer der Systemsprache, nach Namen sortiert |
entities | Kommagetrennte Entitätsnamen | product,category,cms_page,snippet |
{
"rows": [
{ "entity": "product", "languageId": "<hex>", "total": 100, "translated": 40, "percent": 40.0 }
]
}Fehler: 400 {"message": "Invalid language id \"…\"."}; 400 {"message": "Unsupported entities: ….."}.
Die Abdeckung zählt die Datensätze mit einer eigenen Übersetzung der primären Textspalte ihrer Entität. Damit eignet sie sich, um Abweichungen zu finden. Die Auflösung endet beim Datensatz: Ein Datensatz gilt auch dann als übersetzt, wenn einzelne seiner Felder noch leer sind. Siehe Abdeckungsbericht.
Verbrauch und Abonnement
| Methode | Pfad | Berechtigung |
|---|---|---|
GET | /api/_action/nice-translate/usage | nice_translate.viewer |
GET | /api/_action/nice-translate/subscription | nice_translate.viewer |
POST | /api/_action/nice-translate/subscription/refresh | nice_translate.editor |
GET /usage
Query-Parameter months (numerisch, begrenzt auf 1 bis 36, Standard 6). Die Route liefert den neuesten Zeitraum zuerst.
{
"months": [
{
"period": "2026-07",
"providers": {
"deepl": {
"characters": 41230,
"inputTokens": 0,
"outputTokens": 0,
"requests": 12,
"cost": { "amount": 1.0308, "currency": "USD" }
}
}
}
]
}Diese Beträge sind die eigenen Aufzeichnungen der Erweiterung. Maßgeblich bleibt die Rechnung Ihres Anbieters. Siehe Kosten & Verbrauch.
GET /subscription
Keine Parameter.
{
"available": false,
"active": true,
"state": "active",
"plan": "scale",
"identifier": "NiceTranslateAllInOneScale",
"plans": [
{ "plan": "enterprise", "identifier": "NiceTranslateAllInOneEnterprise", "credits": 96000000 },
{ "plan": "scale", "identifier": "NiceTranslateAllInOneScale", "credits": 32000000 },
{ "plan": "growth", "identifier": "NiceTranslateAllInOneGrowth", "credits": 12000000 },
{ "plan": "starter", "identifier": "NiceTranslateAllInOneStarter", "credits": 4000000 }
],
"quota": null,
"reachable": true
}state ist active, available, expired oder disconnected. available ist genau dann true, wenn state === "available" gilt. modernice All-in-One beschreibt die Bedeutung der Zustände für einen Händler.
POST /subscription/refresh
Kein Body. Die Route stößt den Updater für In-App-Käufe von Shopware an und leitet danach dieselbe Statusstruktur ab wie GET /subscription. Die Administration fragt diese Route nach einem Kauf ab. Ohne sie würde ein neuer Kauf erst mit der täglichen geplanten Aktualisierung erscheinen.
Fehler:
{
"errors": [
{
"code": "MO_TRANSLATE__STORE_DISCONNECTED",
"detail": "Connect this administration user to the Shopware Store before refreshing purchases."
}
]
}Die Route liefert das mit 412, wenn der Administrationsbenutzer nicht am Shopware Store angemeldet ist, und
{
"errors": [
{
"code": "MO_TRANSLATE__SUBSCRIPTION_REFRESH_FAILED",
"detail": "The Shopware in-app purchase state could not be refreshed."
}
]
}mit 502, wenn der Updater eine Exception wirft.
Glossar
| Methode | Pfad | Berechtigung |
|---|---|---|
POST | /api/_action/nice-translate/glossary/import | nice_translate.editor |
GET | /api/_action/nice-translate/glossary/export | nice_translate.viewer |
POST /glossary/import
Body: {"csv": "…", "delimiter": ";"}. delimiter ist optional und muss genau ein Zeichen lang sein. Ein UTF-8-BOM am Anfang von csv entfernt der Controller.
Die Kopfzeile muss mit term, mode und caseSensitive beginnen (Vergleich ohne Beachtung der Groß-/Kleinschreibung), danach folgt eine Spalte je Locale-Code:
term;mode;caseSensitive;de-DE;fr-FR
modernice;keep;1;;
Delivery time;replace;0;Lieferzeit;Délai de livraisonBehandlung der Zeilen: Einen leeren term überspringt der Controller. Ein leerer mode wird zu keep. Ein mode außerhalb von keep und replace überspringt die Zeile. caseSensitive ist true bei 1, true oder yes. Der Controller dedupliziert die Zeilen über den kleingeschriebenen Begriff, aktualisiert die vorhandenen Begriffe und legt die neuen Begriffe als aktiv an.
Antwort: {"imported": <int>, "created": <int>, "updated": <int>, "skipped": <int>}.
Fehler: 400 {"message": "Missing \"csv\" content in the request body."}; 400 {"message": "The delimiter must be a single character."}; 400 {"message": "The CSV content is empty."}; 400 {"message": "Invalid CSV header. Expected columns: term;mode;caseSensitive;<localeCode>;..."}; 400 {"message": "Unknown locale code(s) in CSV header: ….."}.
GET /glossary/export
Keine Parameter. Die Route liefert text/csv; charset=utf-8 mit UTF-8-BOM und Content-Disposition: attachment; filename=nice-translate-glossary.csv. Das Trennzeichen ist ;. Die Spalten sind term;mode;caseSensitive, danach folgt eine Spalte je vorkommendem Locale-Code, sortiert ohne Beachtung der Groß-/Kleinschreibung. caseSensitive gibt die Route als 1 oder 0 aus. Der Export enthält immer das vollständige Glossar, und Sie importieren ihn erneut, um die passenden Begriffe zu aktualisieren.
Verlauf
| Methode | Pfad | Berechtigung |
|---|---|---|
POST | /api/_action/nice-translate/history/revert | nice_translate.editor |
POST | /api/_action/nice-translate/history/reapply | nice_translate.editor |
Beide Routen erwarten {"ids": ["<uuid>", …]} und liefern dieselbe Struktur:
{ "done": 3, "skipped": [{ "id": "<uuid>", "reason": "…" }] }Beide Richtungen vergleichen vor dem Schreiben den aktuellen direkten Zielwert mit dem erwarteten Wert aus dem Verlauf. Einen Datensatz, den jemand zwischenzeitlich geändert hat oder der im falschen Status ist, melden die Routen in skipped und lassen ihn unverändert. Siehe Übersetzungsverlauf.
Fehler: 400 {"message": "Missing \"ids\" array in the request body."}; 400 {"message": "A maximum of 200 ids can be processed per request."}.
Entitäten über die DAL-API
Die Auftragsliste und das Auftragsdetail, die Auftragsfehler, das Glossar-CRUD, die Nutzungszeilen und die Verlaufszeilen haben keine eigenen Routen. Sie sind registrierte DAL-Entitäten und nutzen die Repository-Endpunkte, die Shopware erzeugt:
| Entität | Leseberechtigung | Schreibberechtigungen |
|---|---|---|
nice_translate_job | nice_translate_job:read | nice_translate_job:create, :update, :delete |
nice_translate_job_error | nice_translate_job_error:read | — |
nice_translate_glossary | nice_translate_glossary:read | nice_translate_glossary:create, :update, :delete |
nice_translate_usage | nice_translate_usage:read | — |
nice_translate_history | nice_translate_history:read | nice_translate_history:update |
Jedes Feld dieser Entitäten ist als ApiAware(AdminApiSource::class) deklariert. Die Entitäten sind deshalb nur über die Admin-API lesbar, und in der Store-API erscheinen sie nie. Die übrigen sechs Tabellen der Erweiterung sind reine DBAL-Aufzeichnungen ohne Definition, ohne Repository und ohne API-Ressource. Siehe Architektur.