Problem/Motivation

This module's central promise is strict language selection: a language the client did not select or accept is never served. Primary data honours it everywhere: an individual read misses with 404/406, a collection excludes items with no acceptable translation, and a /related/ read omits and reports excluded targets (#3612537).

The included member breaks the promise. Core's IncludeResolver resolves every ?include= target through the swapped EntityAccessChecker::getAccessCheckedResourceObject(), and its getEntityTranslation() returned the untranslated entity when the selected language has no translation. The in-code justification ("a harmless no-op") reasons only about individuals, which the controller already resolved, and collection items, which were excluded at query time. It is false for includes, which have no earlier guard: an included resource with no translation in an acceptable language is silently emitted in its default language.

So GET /jsonapi/node/article/{uuid}?include=field_ref&langCode=fr returns the primary node strictly in fr, next to an included resource in en. The same happens under Accept-Language, and for config entities reached through include. The client cannot tell a translated resource from a fallback, and the behaviour is an undocumented divergence from the rest of the module.

Steps to reproduce

  1. Languages en (default) and fr. Article A has en and fr translations; article B has only en. A references B via an entity-reference field field_ref.
  2. GET /jsonapi/node/article/{A}?include=field_ref&langCode=fr. Expected: no en resource anywhere in the response. Actual: primary data is fr, but included carries B in en.
  3. Same URL with header Accept-Language: fr instead of langCode: same wrong result.

Proposed resolution

Omit and report, mirroring the related endpoint: an included resource with no translation in any acceptable language is omitted from included and reported under meta.omitted (core's standard omission channel, already used for inaccessible includes), with a link to the resource and a per-item detail: "No translation of the included resource matches the request's language selector. Available translations: ...". The client can distinguish "omitted for language" from "absent" and re-fetch with another selector.

This completes one consistent mental model: a primary to-one miss hard-fails (404/406); list-shaped members (collections, to-many related targets, included) exclude and report.

  • The implementation stays in the access checker seam: getEntityTranslation() signals a strict miss (a langCode the entity has no translation of, or a failed strict Accept-Language negotiation), and getAccessCheckedResourceObject() converts the miss into the same EntityAccessDeniedHttpException-shaped omission the related endpoint builds. Core's IncludeResolver and the normalizers render meta.omitted with no further changes, and any future caller reaching the checker with an unresolvable language gets the strict behaviour by default.
  • Config entities follow the same rule, with availability defined by their base language plus configured overrides.
  • Content-Language keeps its fallback: it accompanies writes and may target a translation that is being created.
  • Cacheability: the omission entry carries the omitted entity's cacheability, so the report is invalidated when the entity gains the missing translation, plus the language cache contexts.
  • Reads without a language selector keep core's include behaviour unchanged.
  • Alternatives rejected: documenting the silent fallback as a divergence (contradicts the module's promise and makes included semantically unreliable), and failing the whole request on any include miss (disproportionate; mirrors neither collections nor related).
  • The behaviour carries a DIVERGENCE(core): marker: core #3199697 does not translate includes at all; propose upstream or drop at handoff.
Command icon 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

lauriii created an issue. See original summary.

lauriii’s picture

Issue summary: View changes

  • lauriii committed ebd026e5 on 1.0.x
    fix: #3613728 Included resources ignore the language selector and fall...
lauriii’s picture

Status: Active » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.