Problem/Motivation
1. Configure node type article with multiple body field and enable the content_translation module for it.
2. Add a second and a third language to the site e.g. french and german.
3. Create a node of type article and and translate it to french.
4. Disable java script in the browser.
5. Go to edit of the newly english translation.
6.1. Change the value of the language field widget from english go german.
6.2. execute an ajax call by clicking on "Add new item" for the body field.
7. Watch how the form is rebuild and newly returned to the browser with a form language "German".
Step 7 you can see explicitly if you have the content translation module activated and enabled for the node type article - in this case the title of the form will change to '@title [%language translation]' after the ajax call.
Attaching screenshots before and after the ajax call with disabled java script in the browser.
Before the ajax call:

After the ajax call:

The form language code, which is stored in the form state, indicates the language used, for which the form has been requested and generated. As shown on the screenshots there are also core modules such as content_translation, which are relying on the form language code stored in the form state and based on it alter the form array. If the form language code changes during an ajax callback it means that the form array that will be generated during the rebuild will differ from the original one and if you use the site with java script disabled and trigger a form rebuild (e.g. "add new item" or "image upload") then the new generated form based on the updated form language code will be delivered to the user and surprisingly for her it will/might differ completely just because of the changed value in the language widget in the current case.
If using the site with java script enabled the form will be generated based on the the new language and something might go wrong as well but the user will not see it because we would only replace the field for which we use ajax and not the whole form, but on the server side it will/might be completely wrong now.
That is why the form language code should not be changed during form rebuilds.
Proposed resolution
Flag the ContentEntityForm::updateFormLangcode entity builder as deprecated in 8.1.4, which will be removed in 9.0.0. and empty the body of the function, so that the form language is not changed during an ajax call.
Remaining tasks
Confirm this is still a problem and update the steps to reproduce
Update patch
Review
Commit
User interface changes
none
API changes
ContentEntityForm::updateFormLangcode has an empty body now, so that it does not change the form language during ajax calls and is deprecated as of 8.1.4 and will be removed in 9.0.0.
Data model changes
none
Change record: https://www.drupal.org/node/2758653.
| Comment | File | Size | Author |
|---|---|---|---|
| #70 | test-only.patch | 4.16 KB | quietone |
| #46 | 2757003-46.patch | 5.84 KB | hchonov |
| after_ajax_call.png | 124.6 KB | hchonov | |
| before_ajax_call.png | 90.1 KB | hchonov |
Comments
Comment #2
hchonovMarking as blocker as this issue caused reverting the patch committed in #2675010: Cloned entity will point to the same field objects if the clone was created after an entity translation has been initialized and it should be fixed before committing #2675010: Cloned entity will point to the same field objects if the clone was created after an entity translation has been initialized again.
Comment #3
hchonovHere a test proving that the form lang code is being changed during an ajax call and also the fix, which removes the entity builder that is causing the problem.
Comment #4
gábor hojtsyComment #6
johnchqueCan we also assert that when save the node its language is updated?
Comment #7
hchonov@yongt9412 https://www.drupal.org/node/2675010#comment-11351235 explains what exactly happens when changing the form language during ajax call. So there is no need for saving the node entity during the tests here.
Comment #8
johnchqueyes, but IMHO is also good and useful to check that the node language has changed when saving, just to be sure.
Comment #9
hchonovNot really sure that this is important but adding it to the test, if that is your wish.
Comment #10
gábor hojtsyReviewing. Interesting because the code says it allows modules to act before and after the form language is updated. But the updateFormLangcode is the one updating it itself. So I guess the intention was to override in extensions this method to do things before/after the default behavior. Did you do any archeology to see why was this introduced in the first place? Is the form langcode not supposed to reflect what language are we editing of the entity? What should happen on preview for example (if you are not going into Ajax, change the language and then preview?).
Concrete patch review:
I don't think its possible to remove a public method and claim we have backwards compatility :)
I also think it should be left called from the form entity builder (even if empty in the base implementation) as this is a base class that others expect the same behavior (if they override the updateFormLanguage for example).
Minor: spacing issues on empty lines.
Comment #12
hchonov@Gábor Hojtsy you are right, so I am flagging the function with
@deprecated in Drupal 8.1.4, will be removed before Drupal 9.0.0.and also emptying its body.This change was first introduced in #2230637-136: Create a Language field widget and the related formatter. However I am not sure why it was made. But changing the form language during an ajax call is false, because changing the value of the language widget still does not change the language of the form, because when rebuilding the form still the original entity shall be used and afterwards the user input applied on top of it, it is just how the form builder works.
The value of the language widget acts still as a normal field value. And while displaying the form in one language we could not simply make it in the next ajax call be in different language. It just does not sound right. The entity will have the selected language in the language widget first after the form is submitted.
Fixed the spacing issues on empty lines.
Comment #13
hchonovComment #15
gábor hojtsyI don't think you addressed the preview question. What happens there? Also for the ajax interaction, I think people change the langcode on the form and then start editing entity references, they expect that entities in that language will show up instead of the prior language. I know that is an "inconsistent" expectation given that form elements that are not AJAX would not yet work like that. But (again) what about preview before/after this patch. Would after preview all elements work with the new language?
Since the method is named updateFormLangcode() but no updating of the form langcode happens here or elsewhere, should it document why?
Comment #16
hchonovFrom IRC:
And also about the comment of the entity builder:
A new patch with updated documentation.
Comment #17
gábor hojtsySeems to be fine with me based on the history digging and lack of side effect discovery work done above :)
Comment #18
hchonovCreated a change record -> https://www.drupal.org/node/2758653, which can be published, when the patch here is committed.
Comment #20
gábor hojtsySent for a retest. Update tests fail with config schema errors, so it completely looks unrelated:
... etc.
Comment #22
gábor hojtsySame random fail as detailed in #20.
Comment #23
berdirwe have a trait for this, or you could create the field using the API, need you don't need field_ui module, which should make the test faster.
I don't really see an explanation in the issue summary *why* this change is wrong and a bug and has to be changed. We *are* making a behavior change, and just because core doesn't fail doesn't mean that contrib/custom code won't have a problem with it.
I am not saying that the change here is wrong. I'm just saying we need to a better job at explaining why the current behavior is wrong. That might include documenting what the meaning of the langcode in $form_state actually is and why changing it is wrong. Otherwise I'm not sure we can get this into 8.1 as a major bugfix.
That's also visible in the test, which is basically "self-fullfilling". It doesn't expose an actual bug/problem with the current behavior (like, something being saved in the wrong language or so), it just asserts for the new behavior.
Comment #24
hchonov@berdir:
I've adjusted the patch as you suggested and I amended the issue summary as requested by you. I hope that it makes it easier for you to understand now what actually is the problem and how significant it might be or already is.
Comment #28
hchonovIt has been a random test failure, so putting back to RTBC.
Comment #29
gábor hojtsyI think it would be important to get @Berdir's updated feedback on this since he did not have a chance for that since the update.
Comment #30
berdirLooks like my comment here didn't make it.
No, I don't think that's needed. I didn't ask for those updates for me, at least not the issue summary updates, that's for those that will need to decide about committing this and against which versions. (Bugfix would imply fixing this in 8.1 too, but I'm not sure about that..)
Comment #32
gábor hojtsyRandom fails on 8.1.x, the retest is already running.
Comment #33
alexpottSo we've just released 8.1.8 - imho the comments shouldn't reference release apart from the @deprecated one. Also I don't think the issue #2757003 should be referenced - we should just say why updating form the the langcode is wrong. Also I think maybe the correct BC behaviour is to not add the entity builder but leave the method functionally the same. And just document that it is no longer used and has caused problems with AJAX.
Also I'm still not convinced that the proper archaeology has been done to understand why it exists in the first place.
Comment #34
hchonov@alexpott:
I've updated the patch according to your review.
In #12 I've mentioned, that this change has been introduced in #2230637-136: Create a Language field widget and the related formatter. The change has been made by @plach with the comment :
Previously the function used to be called inside ContentEntityForm::validate by reading the langcode from the form state values and putting it into the form state storage. And before that it has been used in submit with the function name being "submitEntityLanguage", which has been introduced in #1188388-19: Entity translation UI in core , however without any explanation why this was added. It used to look like this :
Unfortunately I do not how to contact plach and ask him about his intention at this place.
Comment #36
plachI just heard about this, sorry, I'll try to have a look to it tomorrow...
Comment #37
plachSo, I picked up my whip and did some more archaeology:
CEFI::updateFormLangcode()is a far descendant ofentity_translation_entity_form_language_update, in fact I suspect it was added as part of the initial port, when it probably made sense, since we had no (Content) Entity Translation API at the time (and so we couldn't rely onContentEntityInterface::language()for the active language).This is the function body:
As you can see, it explicitly mentions AJAX requests and the second comment is encouraging, as it describes an issue similar to the one we are addressing here. The first comment makes me think this logic is no longer needed in D8, since we have a different way to initialize the form language.
Additionally, I couldn't find any explicit usage of the form language after form submission both in D8 core and D7
entity_translationandtitlecode bases. However, in D7 form language may still be important after form submission because the coreentity_language()function may rely on it:Given all that, I strongly suspect
CEFI::updateFormLangcode()is no longer needed. If it weren't for the PHP doc mentioning use cases, I would be pretty sure about that. OTOH, maybe I didn't have any specific use case in mind, I was just thinking one may want to act before or after form language has been updated, and that's all.To be safe, I'd suggest to keep the function around and working as it currently does and just make sure the initial form language is preserved when rebuilding the form (see the attached draft). I think this should be the least disruptive change and we may want to change the deprecation note to state that the function will be removed in D9 (not before).
Looking at the code, my main remark is about the test itself: we already have
EntityTranslationFormTest, so I guess this should be a new method on that class and deal with the test entity and not nodes, for consistency. Also, I agree with Berdir that the test should outline the consequences of this misbehavior, for instance that submitting the form will create a new translation instead of updating the default language (if that's actually the case).Comment #38
plachOops, I forgot the patch draft
Comment #39
hchonov@plach the init function (from within the initFormLangcodes is called) is called only twice - the first time the form is requested and the second time when the user triggers an ajax call, after this step the form state is cached and init does not run anymore an all the subsequent ajax calls. Which means that the form language in the form state will still be updated and will not get reset. This happens because of EntityForm::buildForm :
Beside that I am not really sure that it is fine to have the updateFormLangcode function and then somewhere else reseting what the functions has done. If a committer says this is fine then I would introduce a new entity builder which runs exactly after the updateFormLangcode one and resets what the updateFormLangcode has done.
Comment #40
plachAre you sure? I tested that code and it seemed to be working. Well, at least the form was rebuilt with the proper language...
Comment #41
plachWhat you are proposing is not the same of what I coded: the form language is reset only when rebuilding the form, but validation and submission handlers will always find the updated form language code if the form is actually being submitted.
Comment #42
hchonov@plach, yes I am sure. Forms with ajax work always like this:
1. The form is requested for the first time and completely rebuilt.
2. The user triggers an ajax call.
3. The form is completely rebuilt like in 1.
4. If the ajax submit function requested form rebuild the form will be rebuilt once more.
5. The form state is cached from now on.
From now on the form state is cached as well as its storage, which means that the check in EntityForm::buildForm !$form_state->has('entity_form_initialized') will always evaluate to FALSE for all subsequent rebuilds. If the language is reset then this is not because EntityForm::init is called on subsequent ajax calls, but because some of the functions such as ContentEntityForm::getFormLangcode are being executed which then call ::initFormLangcodes and if we want such a solution we should not rely on that, that the other functions are called and then ::initFormLangcodes is called again, because the intention is that this happens in ::init.
@plach I do not think that it is ok, that in the submit and validate functions (ajax or not ajax one as well) the form language is the new one. I still think we do not have to update the form language at all. Why do you need the new language selected in the language widget updated in the form state? The form should always be rebuilt for the original language and the new selected one is just a value of a widget.
When you need the new language, then you have two options to get it -> from the values of the language widget or from the entity itself.
Comment #43
berdirChanging to entity system as it is not a bug in the form component.
Comment #44
tstoecklerFor sake of full disclosure: @hchonov and I work for the same employer.
I fully agree with @hchonov's position and I disagree with @plach's last comment and patch.
My thoughts on this:
We can never change the default language code of the entity, which means that when you are changing the value of the language field in a form, all other form fields will be updated in the translation in the language that you just selected. Therefore, I can understand the impulse to want to provide a proper translation form (thus, updating the form language code) when rebuilding the form, to properly distinguish in the user interface the fact that - as explained in the previous sentence - you are editing .
However, operations in forms are not atomic. When submitting a form (Ajax or not) you are never just updating a single value. So there will always be inconsistent states as values might have been updated before changing the language code, while changing the language code (i.e. in the same submission) or after changing it. So there is no way that we can communicate to the user what is happening in a reliable and sane way. Our current solution of just changing the language during form validation/submission from underneath your feet is not sufficient as can be easily seen from the issue summary.
So while it is a behavior change, I think it is the only sensible thing to remove the
updateFormLangcode()call. Solving this would involve much bigger changes, if it is even possible at all. I am thinking that we would need to get rid of the simple select element for the language and provide a dedicated button or something, but even then it is not exactly trivial to define a proper and intuitive behavior for the user. As I said, that's clearly out of scope here.So it is a bug fix (again see the issue summary), but fixing it requires a behavior change, so I think we should only get this into 8.3.x. Modules that do rely on the new entity language can access that through the form state already, and they can in fact do that in a way that will work with and without this patch. So if we get this in to 8.3.x soon modules will have ~6 months to be fixed to properly fetch the language code.
Would love to get some more thoughts on this by @plach and @Berdir, though.
Edit: I didn't know that Content Translation prevents you from editing translations by just switching the language code (which is great!). The described problem still applies to adding translations, though.
Comment #45
tstoecklerComment #46
hchonovI've rerolled the patch from #34 and changed the comment that the entity builder function for updating the form language code is deprecated in 8.3.0, as it is a bug fix and a behaviour change at the same time.
Comment #47
plach@tstoeckler: @hchonov:
Let me try to clarify my position: as stated in #37, theoretically I agree that we can remove the
::updateFormLangcode()method, I'm just unsure whether we are allowed to do that by the current BC policies. Hence I was trying to find a solution that would not imply a change in the current behavior, even if the current behavior does not make much sense to me.I'll talk to the committers to figure out a way forward.
Comment #48
plachDiscussed this with @catch, @Berdir, @hchonov in IRC: we agreed to wait for @Berdir to try an alternative fix not implying a behavior change. If there's no way to achieve that we will move on with the current approach, that got @catch's approval, if there's no other way forward.
Btw, during the discussion @catch introduced the new
Comment #49
plachMy remark on the test from #37 still stands, unless @Berdir provides an alternative patch.
In both cases needs work ;)
Comment #50
tstoecklerJust a note, that I was under a wrong impression regarding what actually happens when you change the value of the language selector on the form. So I agree that we should spend more time trying to understand this problem space and am more or less on one page with @Berdir.
Comment #51
berdir#2675010: Cloned entity will point to the same field objects if the clone was created after an entity translation has been initialized now has a patch that works without this change. Not everything is 100% clear over there yet, but I would suggest we either close this as won't fix or move to 9.x if we agree on my approach in the other issue.
Comment #52
hchonovI have my concerns with the patch that is provided in the other issue and I am not convinced it is the proper way and think it might cause a lot of troubles. I've posted my thoughts there.
Comment #53
tstoecklerSo at the meeting we did not agree on a solution, but did agree that it's major, so that's something ;-)
Comment #54
tstoecklerHopefully won't be long, but let's mark this postponed on #2675010: Cloned entity will point to the same field objects if the clone was created after an entity translation has been initialized.
Comment #60
berdir> tstoeckler commented 11 January 2017 at 15:30
> Hopefully won't be long, but let's mark this postponed on #2675010: Cloned entity will point to the same field objects if the clone was created after an entity translation has been initialized.
Wellllll...
Setting this to active again, but I would need to catch up on the long discussion to figure out if this is still an issue.. apparently it at least wasn't a priority anymore for either of you ;)
Comment #62
joseph.olstad***EDIT*** my test environment is too dirty, I have to rebase. Followup later if there's a need.
Ok guys, I have a use case where I need this api function or something similar to it. (updateFormLangcode)
I'm currently helping out with the entity_translation_unified_form module, which is very similar to the multi form display module ( mfd ) , the idea is to render node edit forms with all languages content on one page. ETUF for short (entity_translation_unified_form) module does work in D8 with the exception of image fields precisely I am pretty sure due to ajax calls being made in only the form language. So, what I would need to figure out or maybe an api for, is a way to say to the form element to run in the intended language , keeping in mind there could be 2 or 3 languages, so in this case, the image widget for uploading images (translateable enabled) so that the ajax call is posted in the correct language.
Now, I didn't know about this api until now and I haven't tried it, but ya see my issue description and screenshot and see the contrib module we're working onall this worked with the D7 version of this module, but having problems getting the D8 version to co-operate fully.so there's two contrib modules that can do this, but they both have the same problem according to my test environment anyway.It's for managed files upload, the image upload widget.
Contrib module 1)https://www.drupal.org/project/mfdContrib module 2)https://www.drupal.org/project/entity_translation_unified_form
see a screenshot for a quick illustration:multilingual form display (mfd) module screenshot (similar to the entity_translation_unified_form module (ETUF)out of these two modules, the ETUF approach is probably the simplest , I just have to resolve this ajax issue.***END EDIT***
Comment #63
joseph.olstadThis core issue turned out not to make a difference in our case, sorry for the noise. I found a workaround for my use case, although it looks like some sort of a core bug but not what I had originally suspected, not sure where the core bug is in my use case but my contrib fix is a workaround to a core glitch. I fixed the symptom.
Please disregard my previous comment above.
Comment #69
quietone commentedI tested this on Drupal 9.5.x, standard install, with Italian and Spanish instead of French and German, but I was unable to follow the steps in the issue summary at step 5. Step 5 is "Go to edit of the newly english translation." except the last translation created was in Italian. Does this mean to add an english translation of the Italian. I played around with this but wasn't able to reproduce the problem.
I then looked at the patch and there is a test, so I got that to run in Drupal 9.5. and it fails. So, if the test is correct there is something wrong here.
I think the next step is to confirm that this is a problem. I have updated the IS.
Can anyone confirm this is still an issue?
Thanks!
Comment #70
quietone commentedI meant to upload the test patch.
Comment #73
smustgrave commentedSince there hasn't been a follow up in a year going to close out for now. If still a valid bug though please reopen, maybe updating issue summary with additional steps to trigger.
Thanks all!