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)
- Install fresh copy of Drupal 8
- Install and enable restui contrib module
drush dl restui ; drush en restui - Add REST export display on admin/structure/views/view/frontpage
- Enable the Content Translation module
- Create a new language other than English ie zh-hant /admin/config/regional/language
- Create a piece of content, like an article.
- Translate article
- Call web service to get the results ie
curl -H 'Accept: application/json' --request GET http://d8/zh-hant/node - JSON results are in English
- Expected language Chinese
Comments
Comment #1
Gábor Hojtsy CreditAttribution: Gábor Hojtsy commentedComment #2
Greg Sims CreditAttribution: Greg Sims commentedComment #3
Gábor Hojtsy CreditAttribution: Gábor Hojtsy commentedAs #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.
Comment #4
Berdir CreditAttribution: Berdir commentedDepends 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.
Comment #5
klausi CreditAttribution: klausi commentedIdealy 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.
Comment #6
dawehner CreditAttribution: dawehner commented#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.
Comment #7
Berdir CreditAttribution: Berdir commentedWe already have language negotiation, that should also run and work for REST requests, why would you want to add something custom?
Comment #8
Crell CreditAttribution: Crell commentedThe 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.
Comment #9
Gábor Hojtsy CreditAttribution: Gábor Hojtsy commentedSo 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).
Comment #10
Crell CreditAttribution: Crell commentedEr, 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.
Comment #11
Gábor Hojtsy CreditAttribution: Gábor Hojtsy commented@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"?
Comment #12
Crell CreditAttribution: Crell commented#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.
Comment #13
Gábor Hojtsy CreditAttribution: Gábor Hojtsy commented@Crell: that is *surprising*. #2331919-47: Implement StackNegotiation for an extended answer.
Comment #14
Gábor Hojtsy CreditAttribution: Gábor Hojtsy commentedFeeding back info from there, no StackNegotiation will not be useful to negotiate a language yet, the Drupal implementation is much more battle tested.
Comment #15
zippydoug CreditAttribution: zippydoug commentedI believe the child issue mentioned is related.
Comment #16
YesCT CreditAttribution: YesCT commented#2359323: Language results for JSON REST services not correct
Comment #17
Crell CreditAttribution: Crell commentedGetting 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,
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:
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. :-(
Comment #18
Greg Boggs CreditAttribution: Greg Boggs commentedAssuming 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?
Comment #19
Crell CreditAttribution: Crell commentedOption 2 would say that results in either a 404 or a 406 error, I believe.
Comment #20
Gábor Hojtsy CreditAttribution: Gábor Hojtsy commentedSpaces 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?
Comment #21
Crell CreditAttribution: Crell commentedFrom 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?
Comment #22
dixon_ CreditAttribution: dixon_ commentedCompletely agree. We should not go ways to support languages in the domain/path in any way. That's going against restful principles.
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/Languageheaders are available, send/receive the entity object as is.If
Accept-Language/Languageheaders are NOT available, send/receivemultipart/related(see MIME#Related on Wikipedia) withTransfer-Encoding: chunked.A simplified GET request would be:
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.
Comment #23
Gábor Hojtsy CreditAttribution: Gábor Hojtsy commented@dixon_: I think that makes total sense :)
Comment #24
dixon_ CreditAttribution: dixon_ commentedI noticed that neither Symfony HttpFoundation or Guzzle had support for
multipart/*requests and responses (exceptmultipart/form-datawhich 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.
Comment #25
Gábor Hojtsy CreditAttribution: Gábor Hojtsy commentedThat'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.
Comment #26
clemens.tolboom CreditAttribution: clemens.tolboom commentedAdded steps to repro from #2359323: Language results for JSON REST services not correct
Comment #27
Wim Leers CreditAttribution: Wim Leers at Acquia commentedI 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.
Comment #28
vasi CreditAttribution: vasi at Evolving Web commentedThe 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?
Comment #30
Wim Leers CreditAttribution: Wim Leers at Acquia commentedComment #31
andypost CreditAttribution: andypost as a volunteer and at Skilld commented