fix(Internal HTTP API, Translation): #3591713 Fix Canvas page preview for translated pages when the URL prefix differs from the langcode

Closes #3591713

What this MR does

The Canvas UI requested a translated preview by prepending the langcode as a URL path prefix (/<langcode>/canvas/api/v0/layout/…). That only works when the langcode happens to equal the language's configured URL prefix, so as soon as the prefix differs (or the site negotiates language by something other than the URL), the request matched no route, returned an HTML error page, and the UI surfaced it as a "syntax error".

The UI now sends the langcode as a ?language=<langcode> hint on the layout GET routes, and a request subscriber (redirectCanvasApiToPreviewLanguage) resolves it through the active language negotiation chain rather than assuming any one method: it redirects to the URL negotiation builds for that language (e.g. the configured prefix), or does nothing when the negotiator already honors the incoming request (e.g. a session/query negotiator). A hint naming a non-existent language returns a 404 JSON:API error.

Testing steps

  • On a multilingual site, translate a Canvas page into a second language (e.g. French).
  • At admin/config/regional/language/detection/url, change that language's path prefix so it differs from its langcode (e.g. langcode fr → prefix francais).
  • Open the translation in Canvas and click Preview → the translated preview loads (previously: syntax error).
  • Automated: ddev xb-phpunit tests/src/Functional/TranslationTest.php --filter=testCanvasPreviewLanguageNegotiation (covers prefix = langcode, prefix ≠ langcode, and a session/query negotiator).
  • Automated: ddev xb-phpunit tests/src/Functional/TranslationTest.php --filter=testCanvasDevTranslationLayoutApi (content-template preview resolves the example entity in the requested language).

Notes for reviewers

  • The subscriber runs at KernelEvents::REQUEST priority 30 — after the router (so the matched route and raw params are available to build the redirect, and so a 404 for an unknown language is converted to JSON by ApiExceptionSubscriber), and before the OpenAPI request validator.
  • It is scoped to the two layout GET routes (canvas.api.layout.get and canvas.api.layout.get.content_template).
  • Known follow-up (kept as a @todo): assumes interface and content languages negotiate to the same language; divergent interface/content negotiation is out of scope here.

AI usage

This change was developed with AI assistance (Claude Opus 4.8), reviewed and verified by a human, per Drupal.org's policy on the use of AI when contributing.

Merge request reports

Loading