Resolve "Create Route for Delete Translation"
This currently covers only the translation deletion route for the canvas_page entity.
- Add DELETE /canvas/api/v0/content/canvas_page/{id}/translations/{language} REST endpoint gated behind canvas_dev_translation module.
- Add ApiContentTranslationControllers::delete() in the canvas module with guards: 400 for default language, 404 for non-existent translation, 403 via entity access check for missing permissions.
- Create canvas_dev_translation.routing.yml to gate the route behind the feature-flag module. Add content_translation + language as dependencies.
- Add testDeleteTranslation() functional test covering 204/400/404 cases.
Testing instructions
Prerequisites
-
A running DDEV site:
ddev start ddev site-install -
Add French as a content language:
- Go to Admin → Configuration → Regional and language → Languages
(
/admin/config/regional/language) - Click Add language, choose French, and save.
- Go to Admin → Configuration → Regional and language → Languages
(
-
Enable translation for the Page content type:
- Go to Admin → Configuration → Regional and language → Content language
and translation (
/admin/config/regional/content-language) - Expand Page, tick Enable translation, and save.
- Go to Admin → Configuration → Regional and language → Content language
and translation (
-
Create a Canvas page and add a French translation:
ddev drush php:eval " \$page = \Drupal\canvas\Entity\Page::load(1); \$fr = \$page->addTranslation('fr'); \$fr->set('title', 'Page de test'); \$fr->save(); echo 'French translation created for page ' . \$page->id() . PHP_EOL; " -
Note the page ID (used as
{id}below).
Obtain a session cookie
ddev drush uli
# Open that URL in a browser, then copy the SSESS... cookie from DevToolsCOOKIE="SSESS...=..."
PAGE_ID=1Scenario 1 — Delete an existing non-default translation → 204
The language is specified via the URL prefix (/fr/):
curl -X DELETE \
"https://canvas-dev.ddev.site/fr/canvas/api/v0/content/canvas_page/${PAGE_ID}/translations" \
-H "Cookie: ${COOKIE}" \
-v 2>&1 | grep "< HTTP"Expected: < HTTP/2 204 with an empty body.
Verify via Drush:
ddev drush php:eval "
\$p = \Drupal\canvas\Entity\Page::load(${PAGE_ID});
var_dump(\$p->hasTranslation('fr'), \$p->hasTranslation('en'));
"
## Expected: bool(false) bool(true)Scenario 2 — Attempt to delete the default language → 400
No language prefix means the request resolves to the default (English) translation:
curl -X DELETE \
"https://canvas-dev.ddev.site/canvas/api/v0/content/canvas_page/${PAGE_ID}/translations" \
-H "Cookie: ${COOKIE}" \
-v 2>&1 | grep "< HTTP"Expected: < HTTP/2 400 with a JSON body:
{
"message": "Cannot delete the default translation for canvas_page 1. Use the entity delete endpoint to remove the whole entity."
}Scenario 3 — Language prefix for a translation that no longer exists → 400
(Run after Scenario 1 so the French translation is gone.)
Drupal's language negotiation falls back to the default translation when the
requested language has no translation, so the endpoint returns 400 (default
translation guard):
curl -X DELETE \
"https://canvas-dev.ddev.site/fr/canvas/api/v0/content/canvas_page/${PAGE_ID}/translations" \
-H "Cookie: ${COOKIE}" \
-v 2>&1 | grep "< HTTP"Expected: < HTTP/2 400
Scenario 4 — Unauthenticated request → 401
curl -X DELETE \
"https://canvas-dev.ddev.site/fr/canvas/api/v0/content/canvas_page/${PAGE_ID}/translations" \
-v 2>&1 | grep "< HTTP"Expected: < HTTP/2 401
Scenario 5 — User without delete permission → 403
- Create a user without the Administer Canvas pages permission and log in.
- Copy that session cookie, then:
curl -X DELETE \
"https://canvas-dev.ddev.site/fr/canvas/api/v0/content/canvas_page/${PAGE_ID}/translations" \
-H "Cookie: ${LIMITED_COOKIE}" \
-v 2>&1 | grep "< HTTP"Expected: < HTTP/2 403
Closes #3591588