This is a nasty bug, as sites will have carts that can't proceed to checkout if they are translating the site.
If anyone encounters this, you can stop the bleeding momentarilly by setting notranslate on the class for each element.
You can add this in a custom module in a hook_form_alter.
if (strpos($form_id, 'views_form_commerce_cart_form_') === 0) { // Google Translate fix: Add notranslate class to all submit buttons on // Commerce cart form to prevent Google Translate from changing submit value, // which makes Drupal form no longer know what button was clicked because the action changed. $form['actions']['submit']['#attributes']['class'][] = 'notranslate'; $form['actions']['checkout']['#attributes']['class'][] = 'notranslate'; $form['actions']['empty_cart']['#attributes']['class'][] = 'notranslate'; // Also for the remove buttons. foreach (Element::children($form['remove_button']) as $key) { $form['remove_button'][$key]['#attributes']['class'][] = 'notranslate'; } }
This is a poor solution, but at least customers can checkout...
- π―π΅Japan magaki
The cause seems that the triggered element refers to a different than the actually pressed element.
A
#submit
callbacks will be set to$form_state
in theFormBuilder::doBuildForm()
, line:1111-1113.if (isset($triggering_element['#submit'])) { $form_state->setSubmitHandlers($triggering_element['#submit']); }
This
$triggering_element
is the value got from$form_state->getTriggeringElement();
, and the triggered element will be set forform_state
in theFormBuilder::handleInputElement()
, line:1300-1305.$buttons = $form_state->getButtons(); $buttons[] = $element; $form_state->setButtons($buttons); if ($this->buttonWasClicked($element, $form_state)) { $form_state->setTriggeringElement($element); }
A triggered buttons is determined using the
FormBuilder::buttonWasClicked()
.
The function determines whether the element's#value
value exists in theinput
of$form_state
.
However, if a button that is translated by the browser is pressed,input
has the translated value, sobuttonWasClicked
returnsFALSE
.protected function buttonWasClicked($element, FormStateInterface &$form_state) { // First detect normal 'vanilla' button clicks. Traditionally, all standard // buttons on a form share the same name (usually 'op'), and the specific // return value is used to determine which was clicked. This ONLY works as // long as $form['#name'] puts the value at the top level of the tree of // \Drupal::request()->request data. $input = $form_state->getUserInput(); // The input value attribute is treated as CDATA by browsers. This means // that they replace character entities with characters. Therefore, we need // to decode the value in $element['#value']. For more details see // http://www.w3.org/TR/html401/types.html#type-cdata. if (isset($input[$element['#name']]) && $input[$element['#name']] == Html::decodeEntities($element['#value'])) { return TRUE; } // When image buttons are clicked, browsers do NOT pass the form element // value in \Drupal::request()->Request. Instead they pass an integer // representing the coordinates of the click on the button image. This means // that image buttons MUST have unique $form['#name'] values, but the // details of their \Drupal::request()->request data should be ignored. elseif (!empty($element['#has_garbage_value']) && isset($element['#value']) && $element['#value'] !== '') { return TRUE; } return FALSE; }
If the button was not found in the above process, the first button among the elements registered as buttons will be set as the triggered element in the
FormBuilder::doBuildForm()
.$buttons = $form_state->getButtons(); if (!$form_state->isProgrammed() && !$form_state->getTriggeringElement() && !empty($buttons)) { $form_state->setTriggeringElement($buttons[0]); }
As a result, when a translated button is pressed,
- if there are one or more buttons, it will refer to the first button and set in
$form_state
, and the#submit
will be executed.
- if there is no button, nothing happens.As a solution, since
#element[#value]
will be rewritten by the browser, it may be possible to solve the problem by defining any identifiable values and using them to determine the triggered element.I checked the work using the following Form class.
Normally, pressing buttons 1, 2, and 3 each show a different message.
However, pressing buttons 2, and 3 will show the message for button 1 when the element has been translated by the browser.<?php namespace Drupal\sandbox\Form; use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormStateInterface; class SandboxForm extends FormBase { public function getFormId(): string { return 'sandbox_form'; } public function buildForm(array $form, FormStateInterface $form_state): array { $form['button1'] = [ '#type' => 'submit', '#value' => 'This is button 1', '#submit' => [ '::callback1', ], ]; $form['button2'] = [ '#type' => 'submit', '#value' => 'This is button 2', '#submit' => [ '::callback2', ], ]; $form['button3'] = [ '#type' => 'submit', '#value' => 'This is button 3', '#submit' => [ '::callback3', ], ]; return $form; } public function submitForm(array &$form, FormStateInterface $form_state): array { return $form; } public function callback1(array $form, FormStateInterface $form_state): void { \Drupal::service('messenger')->addStatus('Button1 was pressed (callback1)'); } public function callback2(array $form, FormStateInterface $form_state): void { \Drupal::service('messenger')->addStatus('Button2 was pressed (callback2)'); } public function callback3(array $form, FormStateInterface $form_state): void { \Drupal::service('messenger')->addStatus('Button3 was pressed (callback3)'); } }
- π©πͺGermany Anybody Porta Westfalica
I can confirm this issue. Just happend to a client of us, translating the UI from English to German. Indeed the technical reasons is, that the value of the Submit button is being translated in the UI and therefore doesn't match the form save!
What would you think about - at least for now - forbidding translation on the entity forms?
https://stackoverflow.com/questions/12238396/how-to-disable-google-trans...
by setting:
<html translate="no">
Edit: I think #10 is even better?
Otherwise, we'd need to change a lot of core logic, I think? And having a solution, that doesn't show the button translated (as kind of bug) is surely better than the current bug.The most critical thing currently is, that the user doesn't recognize that his inputs were not saved! The page just reloads, but only experienced users know, that they should see a confirmation instead!
Another option would be to show an error, if the submit button value is unknown.
Ideas?