Commit d2292799 authored by Gábor Hojtsy's avatar Gábor Hojtsy

Issue #2878556 by plach, matsbla, vijaycs85, Gábor Hojtsy, catch, gabesullice,...

Issue #2878556 by plach, matsbla, vijaycs85, Gábor Hojtsy, catch, gabesullice, effulgentsia, hchonov, hass, amateescu, xjm: Ensure that changes to untranslatable fields affect only one translation in pending revisions

(cherry picked from commit 27c3b40e)
parent 9aaf79df
......@@ -20,6 +20,10 @@
*/
abstract class ContentEntityBase extends Entity implements \IteratorAggregate, ContentEntityInterface, TranslationStatusInterface {
use EntityChangesDetectionTrait {
getFieldsToSkipFromTranslationChangesCheck as traitGetFieldsToSkipFromTranslationChangesCheck;
}
/**
* The plain data values of the contained fields.
*
......@@ -1373,17 +1377,7 @@ public static function bundleFieldDefinitions(EntityTypeInterface $entity_type,
* An array of field names.
*/
protected function getFieldsToSkipFromTranslationChangesCheck() {
/** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
$entity_type = $this->getEntityType();
// A list of known revision metadata fields which should be skipped from
// the comparision.
$fields = [
$entity_type->getKey('revision'),
'revision_translation_affected',
];
$fields = array_merge($fields, array_values($entity_type->getRevisionMetadataKeys()));
return $fields;
return $this->traitGetFieldsToSkipFromTranslationChangesCheck($this);
}
/**
......@@ -1423,10 +1417,15 @@ public function hasTranslationChanges() {
// The list of fields to skip from the comparision.
$skip_fields = $this->getFieldsToSkipFromTranslationChangesCheck();
// We also check untranslatable fields, so that a change to those will mark
// all translations as affected, unless they are configured to only affect
// the default translation.
$skip_untranslatable_fields = !$this->isDefaultTranslation() && $this->isDefaultTranslationAffectedOnly();
foreach ($this->getFieldDefinitions() as $field_name => $definition) {
// @todo Avoid special-casing the following fields. See
// https://www.drupal.org/node/2329253.
if (in_array($field_name, $skip_fields, TRUE)) {
if (in_array($field_name, $skip_fields, TRUE) || ($skip_untranslatable_fields && !$definition->isTranslatable())) {
continue;
}
$field = $this->get($field_name);
......@@ -1447,4 +1446,14 @@ public function hasTranslationChanges() {
return FALSE;
}
/**
* {@inheritdoc}
*/
public function isDefaultTranslationAffectedOnly() {
$bundle_name = $this->bundle();
$bundle_info = \Drupal::service('entity_type.bundle.info')
->getBundleInfo($this->getEntityTypeId());
return !empty($bundle_info[$bundle_name]['untranslatable_fields.default_translation_affected']);
}
}
......@@ -227,13 +227,13 @@ public function createRevision(RevisionableInterface $entity, $default = TRUE, $
$active_langcode = $entity->language()->getId();
$skipped_field_names = array_flip($this->getRevisionTranslationMergeSkippedFieldNames());
// Default to preserving the untranslatable field values in the default
// revision, otherwise we may expose data that was not meant to be
// accessible.
// By default we copy untranslatable field values from the default
// revision, unless they are configured to affect only the default
// translation. This way we can ensure we always have only one affected
// translation in pending revisions. This constraint is enforced by
// EntityUntranslatableFieldsConstraintValidator.
if (!isset($keep_untranslatable_fields)) {
// @todo Implement a more complete default logic in
// https://www.drupal.org/project/drupal/issues/2878556.
$keep_untranslatable_fields = FALSE;
$keep_untranslatable_fields = $entity->isDefaultTranslation() && $entity->isDefaultTranslationAffectedOnly();
}
/** @var \Drupal\Core\Entity\ContentEntityInterface $default_revision */
......@@ -262,6 +262,13 @@ public function createRevision(RevisionableInterface $entity, $default = TRUE, $
// No need to copy untranslatable field values more than once.
$keep_untranslatable_fields = TRUE;
}
// The "original" property is used in various places to detect changes in
// field values with respect to the stored ones. If the property is not
// defined, the stored version is loaded explicitly. Since the merged
// revision generated here is not stored anywhere, we need to populate the
// "original" property manually, so that changes can be properly detected.
$new_revision->original = clone $new_revision;
}
// Eventually mark the new revision as such.
......
<?php
namespace Drupal\Core\Entity;
/**
* Provides helper methods to detect changes in an entity object.
*
* @internal This may be replaced by a proper entity comparison handler.
*/
trait EntityChangesDetectionTrait {
/**
* Returns an array of field names to skip when checking for changes.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* A content entity object.
*
* @return string[]
* An array of field names.
*/
protected function getFieldsToSkipFromTranslationChangesCheck(ContentEntityInterface $entity) {
/** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
$entity_type = $entity->getEntityType();
// A list of known revision metadata fields which should be skipped from
// the comparision.
$fields = [
$entity_type->getKey('revision'),
$entity_type->getKey('revision_translation_affected'),
];
$fields = array_merge($fields, array_values($entity_type->getRevisionMetadataKeys()));
return $fields;
}
}
......@@ -311,11 +311,16 @@ public function __construct($definition) {
$this->checkStorageClass($this->handlers['storage']);
}
// Automatically add the EntityChanged constraint if the entity type tracks
// the changed time.
// Automatically add the "EntityChanged" constraint if the entity type
// tracks the changed time.
if ($this->entityClassImplements(EntityChangedInterface::class)) {
$this->addConstraint('EntityChanged');
}
// Automatically add the "EntityUntranslatableFields" constraint if we have
// an entity type supporting translatable fields and pending revisions.
if ($this->entityClassImplements(ContentEntityInterface::class)) {
$this->addConstraint('EntityUntranslatableFields');
}
// Ensure a default list cache tag is set.
if (empty($this->list_cache_tags)) {
......
<?php
namespace Drupal\Core\Entity\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* Validation constraint for the entity changed timestamp.
*
* @Constraint(
* id = "EntityUntranslatableFields",
* label = @Translation("Entity untranslatable fields", context = "Validation"),
* type = {"entity"}
* )
*/
class EntityUntranslatableFieldsConstraint extends Constraint {
public $message = 'Non translatable fields can only be changed when updating the current revision or the original language.';
}
<?php
namespace Drupal\Core\Entity\Plugin\Validation\Constraint;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityChangesDetectionTrait;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\ChangedFieldItemList;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
* Validates the EntityChanged constraint.
*/
class EntityUntranslatableFieldsConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
use EntityChangesDetectionTrait;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs an EntityUntranslatableFieldsConstraintValidator object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.manager')
);
}
/**
* {@inheritdoc}
*/
public function validate($entity, Constraint $constraint) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
// Untranslatable field restrictions apply only to pending revisions of
// multilingual entities.
if ($entity->isNew() || $entity->isDefaultRevision() || !$entity->isTranslatable() || !$entity->getEntityType()->isRevisionable()) {
return;
}
// To avoid unintentional reverts and data losses, we forbid changes to
// untranslatable fields in pending revisions for multilingual entities. The
// only case where changes in pending revisions are acceptable is when
// untranslatable fields affect only the default translation, in which case
// a pending revision contains only one affected translation. Even in this
// case, multiple translations would be affected in a single revision, if we
// allowed changes to untranslatable fields while editing non-default
// translations, so that is forbidden too.
if ($this->hasUntranslatableFieldsChanges($entity)) {
if ($entity->isDefaultTranslationAffectedOnly()) {
foreach ($entity->getTranslationLanguages(FALSE) as $langcode => $language) {
if ($entity->getTranslation($langcode)->hasTranslationChanges()) {
$this->context->addViolation($constraint->message);
break;
}
}
}
else {
$this->context->addViolation($constraint->message);
}
}
}
/**
* Checks whether an entity has untranslatable field changes.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* A content entity object.
*
* @return bool
* TRUE if untranslatable fields have changes, FALSE otherwise.
*/
protected function hasUntranslatableFieldsChanges(ContentEntityInterface $entity) {
$skip_fields = $this->getFieldsToSkipFromTranslationChangesCheck($entity);
/** @var \Drupal\Core\Entity\ContentEntityInterface $original */
if (isset($entity->original)) {
$original = $entity->original;
}
else {
$original = $this->entityTypeManager
->getStorage($entity->getEntityTypeId())
->loadRevision($entity->getLoadedRevisionId());
}
foreach ($entity->getFieldDefinitions() as $field_name => $definition) {
if (in_array($field_name, $skip_fields, TRUE) || $definition->isTranslatable() || $definition->isComputed()) {
continue;
}
// When saving entities in the user interface, the changed timestamp is
// automatically incremented by ContentEntityForm::submitForm() even if
// nothing was actually changed. Thus, the changed time needs to be
// ignored when determining whether there are any actual changes in the
// entity.
$field = $entity->get($field_name);
if ($field instanceof ChangedFieldItemList) {
continue;
}
$items = $field->filterEmptyItems();
$original_items = $original->get($field_name)->filterEmptyItems();
if (!$items->equals($original_items)) {
return TRUE;
}
}
return FALSE;
}
}
......@@ -71,4 +71,13 @@ public function isRevisionTranslationAffectedEnforced();
*/
public function setRevisionTranslationAffectedEnforced($enforced);
/**
* Checks if untranslatable fields should affect only the default translation.
*
* @return bool
* TRUE if untranslatable fields should affect only the default translation,
* FALSE otherwise.
*/
public function isDefaultTranslationAffectedOnly();
}
......@@ -18,3 +18,9 @@ language.content_settings.*.*.third_party.content_translation:
enabled:
type: boolean
label: 'Content translation enabled'
bundle_settings:
type: sequence
label: 'Content translation bundle settings'
sequence:
type: string
label: 'Bundle settings values'
......@@ -5,6 +5,7 @@
* The content translation administration forms.
*/
use Drupal\content_translation\BundleTranslationSettingsInterface;
use Drupal\Core\Config\Entity\ThirdPartySettingsInterface;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityTypeInterface;
......@@ -83,6 +84,7 @@ function _content_translation_form_language_content_settings_form_alter(array &$
return;
}
/** @var \Drupal\content_translation\ContentTranslationManagerInterface $content_translation_manager */
$content_translation_manager = \Drupal::service('content_translation.manager');
$default = $form['entity_types']['#default_value'];
foreach ($default as $entity_type_id => $enabled) {
......@@ -110,6 +112,23 @@ function _content_translation_form_language_content_settings_form_alter(array &$
continue;
}
// Displayed the "shared fields widgets" toggle.
if ($content_translation_manager instanceof BundleTranslationSettingsInterface) {
$settings = $content_translation_manager->getBundleTranslationSettings($entity_type_id, $bundle);
$form['settings'][$entity_type_id][$bundle]['settings']['content_translation']['untranslatable_fields_hide'] = [
'#type' => 'checkbox',
'#title' => t('Hide non translatable fields on translation forms'),
'#default_value' => !empty($settings['untranslatable_fields_hide']),
'#states' => [
'visible' => [
':input[name="settings[' . $entity_type_id . '][' . $bundle . '][translatable]"]' => [
'checked' => TRUE,
],
],
],
];
}
$fields = $entity_manager->getFieldDefinitions($entity_type_id, $bundle);
if ($fields) {
foreach ($fields as $field_name => $definition) {
......@@ -317,6 +336,8 @@ function content_translation_form_language_content_settings_validate(array $form
* @see content_translation_admin_settings_form_validate()
*/
function content_translation_form_language_content_settings_submit(array $form, FormStateInterface $form_state) {
/** @var \Drupal\content_translation\ContentTranslationManagerInterface $content_translation_manager */
$content_translation_manager = \Drupal::service('content_translation.manager');
$entity_types = $form_state->getValue('entity_types');
$settings = &$form_state->getValue('settings');
......@@ -347,7 +368,12 @@ function content_translation_form_language_content_settings_submit(array $form,
}
if (isset($bundle_settings['translatable'])) {
// Store whether a bundle has translation enabled or not.
\Drupal::service('content_translation.manager')->setEnabled($entity_type_id, $bundle, $bundle_settings['translatable']);
$content_translation_manager->setEnabled($entity_type_id, $bundle, $bundle_settings['translatable']);
// Store any other bundle settings.
if ($content_translation_manager instanceof BundleTranslationSettingsInterface) {
$content_translation_manager->setBundleTranslationSettings($entity_type_id, $bundle, $bundle_settings['settings']['content_translation']);
}
// Save translation_sync settings.
if (!empty($bundle_settings['columns'])) {
......@@ -367,8 +393,8 @@ function content_translation_form_language_content_settings_submit(array $form,
}
}
}
// Ensure entity and menu router information are correctly rebuilt.
\Drupal::entityManager()->clearCachedDefinitions();
\Drupal::service('router.builder')->setRebuildNeeded();
}
......@@ -5,6 +5,7 @@
* Allows entities to be translated into different languages.
*/
use Drupal\content_translation\BundleTranslationSettingsInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\ContentEntityFormInterface;
use Drupal\Core\Entity\ContentEntityInterface;
......@@ -161,9 +162,15 @@ function content_translation_entity_type_alter(array &$entity_types) {
* Implements hook_entity_bundle_info_alter().
*/
function content_translation_entity_bundle_info_alter(&$bundles) {
foreach ($bundles as $entity_type => &$info) {
/** @var \Drupal\content_translation\ContentTranslationManagerInterface $content_translation_manager */
$content_translation_manager = \Drupal::service('content_translation.manager');
foreach ($bundles as $entity_type_id => &$info) {
foreach ($info as $bundle => &$bundle_info) {
$bundle_info['translatable'] = \Drupal::service('content_translation.manager')->isEnabled($entity_type, $bundle);
$bundle_info['translatable'] = $content_translation_manager->isEnabled($entity_type_id, $bundle);
if ($content_translation_manager instanceof BundleTranslationSettingsInterface) {
$settings = $content_translation_manager->getBundleTranslationSettings($entity_type_id, $bundle);
$bundle_info['untranslatable_fields.default_translation_affected'] = !empty($settings['untranslatable_fields_hide']);
}
}
}
}
......@@ -319,6 +326,11 @@ function content_translation_form_alter(array &$form, FormStateInterface $form_s
}
}
// The footer region, if defined, may contain multilingual widgets so we
// need to always display it.
if (isset($form['footer'])) {
$form['footer']['#multilingual'] = TRUE;
}
}
}
......
<?php
namespace Drupal\content_translation;
/**
* Interface providing support for content translation bundle settings.
*/
interface BundleTranslationSettingsInterface {
/**
* Returns translation settings for the specified bundle.
*
* @param string $entity_type_id
* The entity type identifier.
* @param string $bundle
* The bundle name.
*
* @return array
* An associative array of values keyed by setting name.
*/
public function getBundleTranslationSettings($entity_type_id, $bundle);
/**
* Sets translation settings for the specified bundle.
*
* @param string $entity_type_id
* The entity type identifier.
* @param string $bundle
* The bundle name.
* @param array $settings
* An associative array of values keyed by setting name.
*/
public function setBundleTranslationSettings($entity_type_id, $bundle, array $settings);
}
......@@ -5,6 +5,7 @@
use Drupal\Core\Access\AccessResult;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\Core\Entity\EntityChangesDetectionTrait;
use Drupal\Core\Entity\EntityHandlerInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
......@@ -13,8 +14,10 @@
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\user\Entity\User;
use Drupal\user\EntityOwnerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
......@@ -25,7 +28,10 @@
* @ingroup entity_api
*/
class ContentTranslationHandler implements ContentTranslationHandlerInterface, EntityHandlerInterface {
use EntityChangesDetectionTrait;
use DependencySerializationTrait;
use StringTranslationTrait;
/**
* The type of the entity being translated.
......@@ -70,6 +76,13 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
*/
protected $fieldStorageDefinitions;
/**
* The messenger service.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* Initializes an instance of the content translation controller.
*
......@@ -83,14 +96,17 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
* The entity manager.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger service.
*/
public function __construct(EntityTypeInterface $entity_type, LanguageManagerInterface $language_manager, ContentTranslationManagerInterface $manager, EntityManagerInterface $entity_manager, AccountInterface $current_user) {
public function __construct(EntityTypeInterface $entity_type, LanguageManagerInterface $language_manager, ContentTranslationManagerInterface $manager, EntityManagerInterface $entity_manager, AccountInterface $current_user, MessengerInterface $messenger) {
$this->entityTypeId = $entity_type->id();
$this->entityType = $entity_type;
$this->languageManager = $language_manager;
$this->manager = $manager;
$this->currentUser = $current_user;
$this->fieldStorageDefinitions = $entity_manager->getLastInstalledFieldStorageDefinitions($this->entityTypeId);
$this->messenger = $messenger;
}
/**
......@@ -102,7 +118,8 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
$container->get('language_manager'),
$container->get('content_translation.manager'),
$container->get('entity.manager'),
$container->get('current_user')
$container->get('current_user'),
$container->get('messenger')
);
}
......@@ -269,6 +286,8 @@ public function getSourceLangcode(FormStateInterface $form_state) {
* {@inheritdoc}
*/
public function entityFormAlter(array &$form, FormStateInterface $form_state, EntityInterface $entity) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$form_object = $form_state->getFormObject();
$form_langcode = $form_object->getFormLangcode($form_state);
$entity_langcode = $entity->getUntranslated()->language()->getId();
......@@ -512,6 +531,20 @@ public function entityFormSharedElements($element, FormStateInterface $form_stat
$ignored_types = array_flip(['actions', 'value', 'hidden', 'vertical_tabs', 'token', 'details']);
}
/** @var \Drupal\Core\Entity\ContentEntityForm $form_object */
$form_object = $form_state->getFormObject();
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $form_object->getEntity();
$display_translatability_clue = !$entity->isDefaultTranslationAffectedOnly();
$hide_untranslatable_fields = $entity->isDefaultTranslationAffectedOnly() && !$entity->isDefaultTranslation();
$translation_form = $form_state->get(['content_translation', 'translation_form']);
$display_warning = FALSE;
// We use field definitions to identify untranslatable field widgets to be
// hidden. Fields that are not involved in translation changes checks should
// not be affected by this logic (the "revision_log" field, for instance).
$field_definitions = array_diff_key($entity->getFieldDefinitions(), array_flip($this->getFieldsToSkipFromTranslationChangesCheck($entity)));
foreach (Element::children($element) as $key) {
if (!isset($element[$key]['#type'])) {
$this->entityFormSharedElements($element[$key], $form_state, $form);
......@@ -524,10 +557,17 @@ public function entityFormSharedElements($element, FormStateInterface $form_stat
// Elements are considered to be non multilingual by default.
if (empty($element[$key]['#multilingual'])) {
// If we are displaying a multilingual entity form we need to provide
// translatability clues, otherwise the shared form elements should be
// hidden.
if (!$form_state->get(['content_translation', 'translation_form'])) {
$this->addTranslatabilityClue($element[$key]);
// translatability clues, otherwise the non-multilingual form elements
// should be hidden.
if (!$translation_form) {
if ($display_translatability_clue) {
$this->addTranslatabilityClue($element[$key]);
}
// Hide widgets for untranslatable fields.
if ($hide_untranslatable_fields && isset($field_definitions[$key])) {
$element[$key]['#access'] = FALSE;
$display_warning = TRUE;
}
}
else {
$element[$key]['#access'] = FALSE;
......@@ -536,6 +576,11 @@ public function entityFormSharedElements($element, FormStateInterface $form_stat
}
}
if ($display_warning && !$form_state->isSubmitted() && !$form_state->isRebuilding()) {
$url = $entity->getUntranslated()->toUrl('edit-form')->toString();
$this->messenger->addWarning($this->t('Fields that apply to all languages are hidden to avoid conflicting changes. <a href=":url">Edit them on the original language form</a>.', [':url' => $url]));
}
return $element;
}
......
......@@ -8,7 +8,7 @@
/**
* Provides common functionality for content translation.
*/
class ContentTranslationManager implements ContentTranslationManagerInterface {
class ContentTranslationManager implements ContentTranslationManagerInterface, BundleTranslationSettingsInterface {
/**
* The entity type manager.
......@@ -105,6 +105,23 @@ public function isEnabled($entity_type_id, $bundle = NULL) {
return $enabled;
}
/**
* {@inheritdoc}
*/
public function setBundleTranslationSettings($entity_type_id, $bundle, array $settings) {
$config = $this->loadContentLanguageSettings($entity_type_id, $bundle);
$config->setThirdPartySetting('content_translation', 'bundle_settings', $settings)
->save();
}
/**
* {@inheritdoc}
*/
public function getBundleTranslationSettings($entity_type_id, $bundle) {
$config = $this->loadContentLanguageSettings($entity_type_id, $bundle);
return $config->getThirdPartySetting('content_translation', 'bundle_settings', []);
}
/**
* Loads a content language config entity based on the entity type and bundle.
*
......
......@@ -138,7 +138,7 @@ protected function getEditorPermissions() {
* Returns an array of permissions needed for the administrator.
*/
protected function getAdministratorPermissions() {
return array_merge($this->getEditorPermissions(), $this->getTranslatorPermissions(), ['administer content translation']);
return array_merge($this->getEditorPermissions(), $this->getTranslatorPermissions(), ['administer languages', 'administer content translation']);
}
/**
......
<?php
namespace Drupal\Tests\content_translation\Functional;
use Drupal\Core\Url;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests the untranslatable fields behaviors.
*
* @group content_translation
*/
class ContentTranslationUntranslatableFieldsTest extends ContentTranslationTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['language', 'content_translation', 'entity_test'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Configure one field as untranslatable.
$this->drupalLogin($this->administrator);
$edit = [
'settings[' . $this->entityTypeId . '][' . $this->bundle . '][fields][' . $this->fieldName . ']' => 0,
];
$this->drupalPostForm('admin/config/regional/content-language', $edit, 'Save configuration');
/** @var \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager */
$entity_field_manager = $this->container->get('entity_field.manager');
$entity_field_manager->clearCachedFieldDefinitions();
$definitions = $entity_field_manager->getFieldDefinitions($this->entityTypeId, $this->bundle);
$this->assertFalse($definitions[$this->fieldName]->isTranslatable());
}
/**
* {@inheritdoc}