Problem/Motivation
Language-neutral content (langcode und, "Not specified", or zxx, "Not applicable") belongs to no specific language and, by Drupal convention, therefore to all of them. Core renders it in any language context, and this module already says so itself: the jsonapi_menu_items decorator treats neutral entities as matching every request, and the ?include= and /related/ paths serve them because both short-circuit on isTranslatable(), which is FALSE for a locked language.
Strict langCode reads disagree. An entity whose langcode is und answers ?langCode=fr with 404 and silently disappears from every ?langCode-selected collection, because both sites test for an actual translation in the requested language.
The result is that the same entity behaves differently depending on which endpoint reaches it: served when included from another resource, absent from the collection that should list it. "Not specified" is a standard option on every langcode field, and it is what content predating content_translation typically carries, so this is reachable on ordinary sites.
Steps to reproduce
- Site with
en(default) andfrconfigured, and a translatable article bundle. - Create an article whose language is "Not specified".
GET /jsonapi/node/article/{uuid}?langCode=fr→404.GET /jsonapi/node/article?langCode=fr→ the node is absent from the collection.GET /jsonapi/node/article/{other-uuid}?include=field_ref&langCode=fr, wherefield_refpoints at the neutral node → the node is included, showing the inconsistency.
Proposed resolution
Make language-neutral content satisfy every langCode selector on reads, matching what the include, related and menu paths already do.
- Individual reads: the strict-miss branch serves the entity when it is language-neutral instead of throwing the 404.
- Collections: the strict langcode condition becomes an OR group matching the requested langcode or the two neutral langcodes. A row matches at most one side, so no item is duplicated.
- Fallback mode is untouched, and is already correct: core's entity repository resolves neutral entities without help. The new test pins that as a regression rather than changing it.
The rule is deliberately read-only, and that distinction is the subtle part of this change. A write's langCode names the translation being written to; satisfying that selector with a language-neutral entity would let a PATCH land on the entity while the client believes it addressed a translation, which is exactly the failure mode #3614200 exists to prevent. The shared translation resolver used by the write paths is therefore left byte-identical, and the neutral rule lives in the read branch only.
Write behavior was characterized against unmodified code and is pinned by a regression test, so a future refactor of the shared resolver cannot quietly change it. For a language-neutral entity with ?langCode=fr:
PATCH→404, "The "fr" translation of the specified resource does not exist."- translation
POST→422, "Translation is not enabled for the specified resource." This one is not obvious:checkEntityTranslatability()callsisTranslatable(), which isFALSEfor a lockedund/zxxlanguage, so the write is rejected before translation creation is ever reached. DELETE→404, same detail asPATCH.
The regression test asserts those exact responses and also that nothing changed: the title is untouched and the translation count stays at one.
Issue fork jsonapi_multilingual-3614388
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #4
lauriii