Issues like #1892320: Add deserialize for JSON/AJAX made me think about how that will work with entities that have translations.

So I think to get started, we need (a lot) of tests for this, to identify possible problems.

Looking at that code, what I expect to happen for an entity that has default language en and a german translation, that GET will only return the active translation and the default langcode of the langcode field. Does language negotiation actually work there? Should it? If not, how do you get a different translation? Do you see the active language ($entity->language()->langcode) anywhere?

Then you change something and post it back. Will that update to the correct language? The denormalization code in the other issue would create the entity from scratch with only that translation, dropping any existing values. Is that only for POST? I know that PATCH does some tricks to figure out what changed, no idea what's going to happen there.

Steps to reproduce

(copied+edited from #2359323: Language results for JSON REST services not correct)

  1. Install fresh copy of Drupal 8
  2. Install and enable restui contrib module drush dl restui ; drush en restui
  3. Add REST export display on admin/structure/views/view/frontpage
  4. Enable the Content Translation module
  5. Create a new language other than English ie zh-hant /admin/config/regional/language
  6. Create a piece of content, like an article.
  7. Translate article
  8. Call web service to get the results ie curl -H 'Accept: application/json' --request GET http://d8/zh-hant/node
  9. JSON results are in English
  10. Expected language Chinese

Comments

Gábor Hojtsy’s picture

Title:Handling entities with translations» Handling entities with translations in REST
Issue tags:+D8MI, +language-content
Greg Sims’s picture

Gábor Hojtsy’s picture

As #2257293: RESTful Multi-Language Paths says, if you do a REST request to /node/1 on your French domain you expect the French translation, no? Not the Chinese original.

Berdir’s picture

Depends on how it is storing the values. That only works if saving/updating will only ever touch that translation.

REST is often used for content staging and synchronizing data across multiple sites. You don't want to repeat that process in every language you have?

hal currently exposes the language, didn't test it yet with multiple languages, though.

klausi’s picture

Idealy we would use the Accept-language and Content-language HTTP headers to indicate the language. Not sure what we should do if you want to patch multiple languages at once, but I guess multiple requests are fine for core.

dawehner’s picture

Idealy we would use the Accept-language and Content-language HTTP headers to indicate the language. Not sure what we should do if you want to patch multiple languages at once, but I guess multiple requests are fine for core.

#2331919: Implement StackNegotiation will certainly allow to get the Accept-Language and we already set the Content-language. So basically the language negotiators
have to take into account the language figured out by the negotiation middleware.

Berdir’s picture

We already have language negotiation, that should also run and work for REST requests, why would you want to add something custom?

Crell’s picture

The Drupal language negotiation process should take into account the request language. The request language can get negotiated by StackNegotiation prior to HttpKernel even firing. It doesn't replace Drupal's custom negotiation (which takes into account things like user preferences), but supports it.

Gábor Hojtsy’s picture

So the Drupal language negotiation currently does not have the "browser language" (AKA Accept-language based) negotiation turned on by default and even if it would be the default ordering makes others take precedence. Eg. if you access fr.example.com with an accept-language of 'de', it would still be French. Looks like you are saying for REST, all other language conditions should be ignored and only the accept-language should be taken? What if that is not provided? (Technically to implement such a setup, we would need a new locked language type for REST requests and use that to negotiate the language for the REST request).

Crell’s picture

Er, no? I think we're talking past each other. I'm saying that the HTTP language negotiation is easy to add, and we can leverage it or not (we should) in whatever way makes sense within the Drupal language negotiation process.

Gábor Hojtsy’s picture

@Crell: HTTP language negotiation is already implemented and supported. It is not enabled by default because without redirection built-in it causes SEO problems at least. Where do you envision it would be "easily added" and "leveraged or not"?

Crell’s picture

#2331919: Implement StackNegotiation makes it trivial to have the HTTP language negotiated and marked on the Request object before HttpKernel is even run. In fact I think the current patch would already do that. We can then remove our own HTTP language negotiation if we have any, or make it just wrap that value. What we do it with it beyond that is not my problem. :-)

But that's really just to explain dawehner's comment in #4, and doesn't necessarily address the original question which is how to represent the data payload.

Gábor Hojtsy’s picture

@Crell: that is *surprising*. #2331919-47: Implement StackNegotiation for an extended answer.

Gábor Hojtsy’s picture

Feeding back info from there, no StackNegotiation will not be useful to negotiate a language yet, the Drupal implementation is much more battle tested.

zippydoug’s picture

I believe the child issue mentioned is related.

YesCT’s picture

Crell’s picture

Getting back to the original discussion, there's two ways this could be done:

1) Say that for REST, you get back all translations of all fields. And then you PUT all translations of all fields. If you only bother reading some of them in the mean time, good for you.

2) Make REST Accept-Language aware for both GET and PUT. That is,

GET /node/1
Accept: application/hal+json
Accept-Language: de

Would return ONLY the German version of a node (give or take whatever rules we already have for non-translated fields and fields with a missing translation), and to write you'd have to do:

PUT /node/1
Content-Type: application/json
Language: de

{ ... }

From a REST perspective, actually, using out-of-band session information (like a user preference) would actually be incorrect; every request SHOULD contain all the information it needs in the request itself without any other server-side state. So it would be appropriate in this case to rely solely on the Accept-Language/Language headers and nothing else.

Option 2 is, I think, more RESTfully correct but probably also more work. :-(

Greg Boggs’s picture

Assuming you go with option 2, if you ask for zh-hans/node/1 in JSON without specifying a language, what language would the results be? Would there even be results at all?

Crell’s picture

Option 2 would say that results in either a 404 or a 406 error, I believe.

Gábor Hojtsy’s picture

Spaces and domain access will change some things I guess, if node/1 is only accessible on domain de.example.com and not en.example.com then no common REST endpoint will exist (as with path prefixes), but domain access is used anyway to make sites appear they are different sites, so different entry points for REST are not surprising.

As for option 1 or 2 I can imagine both having its uses. Is there a way we can satisfy both? For example if there was no Accept-language, we work with the whole entity and not some hardwired fallback language?

Crell’s picture

From a REST perspective, different domains => different resources. The amount of unholy things we do to support browsers is truly mind-boggling. :-(

I *think* the following logic would be REST-acceptable:

if (Accept-Language/Language header) {
use that and only that, ignoring all other configuration
}
else {
send/receive the whole object with all languages
}

We should probably verify that. That said, I am concerned about someone sending an object in just one language but without a Language header. That would probably get interpreted as "delete all languages but this one", which presumably we do not want.

Unless it were somehow a serialization error, because the language keys on fields are missing?

dixon_’s picture

From a REST perspective, different domains => different resources.

Completely agree. We should not go ways to support languages in the domain/path in any way. That's going against restful principles.

if (Accept-Language/Language header) {
use that and only that, ignoring all other configuration
}
else {
send/receive the whole object with all languages
}

The problem I see with this approach is that the structure of the entity fields would have to change to support send/receive all languages in one whole object. Although it might be restful it would complicate things for clients and consumers of the API having to handle different object structures.

So my take on this would be:

If Accept-Language/Language headers are available, send/receive the entity object as is.

If Accept-Language/Language headers are NOT available, send/receive multipart/related (see MIME#Related on Wikipedia) with Transfer-Encoding: chunked.



A simplified GET request would be:

GET /node/1
Accept: multipart/related



Response:

Content-Type: multipart/related; boundary="12345delimiter"
Transfer-Encoding: chunked

--12345delimiter
Content-Type: application/json
Language: en

{
  id: 1,
  title: [{value: "english title"}],
  body: [{value: "english text"}]
}
--12345delimiter
Content-Type: application/json
Language: se

{
  id: 1,
  title: [{value: "svensk titel"}],
  body: [{value: "svensk text"}]
}
--12345delimiter--



A simplified PUT request would be:

PUT /node/1
Accept: application/json
Content-Type: multipart/related; boundary="12345delimiter"

--12345delimiter
Content-Type: application/json
Accept-Language: en

{
  id: 1,
  title: [{value: "english title"}],
  body: [{value: "english text"}]
}
--12345delimiter
Content-Type: application/json
Accept-Language: se

{
  id: 1,
  title: [{value: "svensk titel"}],
  body: [{value: "svensk text"}]
}
--12345delimiter--



It may seem a bit complicated, but it's properly restful and most robust HTTP clients should support this type of responses nicely. Each "chunk" is simply a normal entity object translated in the language specified by the headers.

Gábor Hojtsy’s picture

@dixon_: I think that makes total sense :)

dixon_’s picture

I noticed that neither Symfony HttpFoundation or Guzzle had support for multipart/* requests and responses (except multipart/form-data which is not what we're looking for).

So I quickly created a set of components to deal with that, see:

This could serve as a demonstration of how we could implement support for multipart requests and responses in our REST implementation.

Gábor Hojtsy’s picture

That's unfortunate if we could not output or test those in core without more libraries :/ Sounds like this would be blocked for adding more libraries if we want to go that route.

clemens.tolboom’s picture

Wim Leers’s picture

Title:Handling entities with translations in REST» Supported entities with translations in REST
Priority:Normal» Major

I think this is at least major. AFAICT without this issue, Drupal 8 does not support working with translated entities via REST. Even though that's one of Drupal's strengths overall. That makes this a very important missing link.

vasi’s picture

The steps to reproduce match those for the issue I'm working on now: https://www.drupal.org/node/2664880 . I'm not sure about the other parts of this issue, so maybe 2664880 should be a sub-issue?

Version:8.0.x-dev» 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Wim Leers’s picture

Title:Supported entities with translations in REST» EntityResource: translations support
andypost’s picture

Version:8.1.x-dev» 8.2.x-dev