diff --git a/paragraphs.module b/paragraphs.module index a6a75af..0148fc8 100644 --- a/paragraphs.module +++ b/paragraphs.module @@ -125,7 +125,7 @@ function paragraphs_form_field_storage_config_edit_form_alter(&$form, \Drupal\Co /** * Implements hook_form_FORM_ID_alter(). * - * Indicate unsupported multilingual paragraphs field configuration. + * Indicates not recommended multilingual paragraphs field configuration. */ function paragraphs_form_field_config_edit_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) { $field = $form_state->getFormObject()->getEntity(); @@ -144,24 +144,18 @@ function paragraphs_form_field_config_edit_form_alter(&$form, \Drupal\Core\Form } // This is a translatable ERR field pointing to a paragraph. - $message_display = 'warning'; - $message_text = t('Paragraphs fields do not support translation. See the online documentation.', [ + $message_text = t('The recommended multilingual configuration is to not enable translation on Paragraph fields. See the online documentation.', [ ':documentation' => Url::fromUri('https://www.drupal.org/node/2735121') ->toString() ]); - if ($form['translatable']['#default_value'] == TRUE) { - $message_display = 'error'; - } - - $form['paragraphs_message'] = array( - '#type' => 'container', - '#markup' => $message_text, - '#attributes' => array( - 'class' => array('messages messages--' . $message_display), - ), + $form['paragraphs_message'] = [ + '#theme' => 'status_messages', '#weight' => 0, - ); + '#message_list' => [ + 'warning' => [$message_text], + ], + ]; } /** @@ -185,10 +179,7 @@ function paragraphs_module_implements_alter(&$implementations, $hook) { /** * Implements hook_form_FORM_ID_alter(). * - * Indicate unsupported multilingual paragraphs field configuration. - * - * Add a warning that paragraph fields can not be translated. - * Switch to error if a paragraph field is marked as translatable. + * Indicates not recommended multilingual paragraphs field configuration. */ function paragraphs_form_language_content_settings_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) { // Without it Paragraphs message are meaningless. @@ -197,15 +188,11 @@ function paragraphs_form_language_content_settings_form_alter(&$form, \Drupal\Co } $content_translation_manager = \Drupal::service('content_translation.manager'); - $message_display = 'warning'; - $message_text = t('(* unsupported) Paragraphs fields do not support translation. See the online documentation.', [ + $message_text = t('(* not recommended) The recommended multilingual configuration is to not enable translation on Paragraph fields. See the online documentation.', [ ':documentation' => Url::fromUri('https://www.drupal.org/node/2735121') ->toString()]); $map = \Drupal::service('entity_field.manager')->getFieldMapByFieldType('entity_reference_revisions'); foreach ($map as $entity_type_id => $info) { - if (!$content_translation_manager->isEnabled($entity_type_id)) { - continue; - } $field_storage_definitions = \Drupal::service('entity_field.manager')->getFieldStorageDefinitions($entity_type_id); /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition */ @@ -223,30 +210,23 @@ function paragraphs_form_language_content_settings_form_alter(&$form, \Drupal\Co $bundles = Element::children($form['settings'][$entity_type_id]); } foreach($bundles as $bundle) { - if (!$content_translation_manager->isEnabled($entity_type_id, $bundle)) { - continue; - } // Update the label and if the paragraph field is translatable, // display an error message instead of just a warning. if (isset($form['settings'][$entity_type_id][$bundle]['fields'][$name]['#label'])) { - $form['settings'][$entity_type_id][$bundle]['fields'][$name]['#label'] = t('@field_label (* unsupported)', ['@field_label' => $form['settings'][$entity_type_id][$bundle]['fields'][$name]['#label']]); - } - if (!empty($form['settings'][$entity_type_id][$bundle]['fields'][$name]['#default_value'])) { - $message_display = 'error'; + $form['settings'][$entity_type_id][$bundle]['fields'][$name]['#label'] = t('@field_label (* not recommended)', ['@field_label' => $form['settings'][$entity_type_id][$bundle]['fields'][$name]['#label']]); } } } } } - $form['settings']['paragraphs_message'] = array( - '#type' => 'container', - '#markup' => $message_text, - '#attributes' => array( - 'class' => array('messages messages--' . $message_display), - ), + $form['settings']['paragraphs_message'] = [ + '#theme' => 'status_messages', '#weight' => 0, - ); + '#message_list' => [ + 'warning' => [$message_text], + ], + ]; } /** diff --git a/src/Plugin/Field/FieldWidget/ParagraphsWidget.php b/src/Plugin/Field/FieldWidget/ParagraphsWidget.php index 45f33cc..0800d29 100644 --- a/src/Plugin/Field/FieldWidget/ParagraphsWidget.php +++ b/src/Plugin/Field/FieldWidget/ParagraphsWidget.php @@ -16,6 +16,7 @@ use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Form\SubformState; use Drupal\Core\Render\Element; use Drupal\paragraphs\ParagraphInterface; +use Drupal\paragraphs\Entity\Paragraph; use Drupal\paragraphs\Plugin\EntityReferenceSelection\ParagraphSelection; use Symfony\Component\Validator\ConstraintViolationInterface; use Symfony\Component\Validator\ConstraintViolationListInterface; @@ -385,54 +386,7 @@ class ParagraphsWidget extends WidgetBase { } if ($paragraphs_entity) { - // Detect if we are translating. - $this->initIsTranslating($form_state, $host); - $langcode = $form_state->get('langcode'); - - if (!$this->isTranslating) { - // Set the langcode if we are not translating. - $langcode_key = $paragraphs_entity->getEntityType()->getKey('langcode'); - if ($paragraphs_entity->get($langcode_key)->value != $langcode) { - // If a translation in the given language already exists, switch to - // that. If there is none yet, update the language. - if ($paragraphs_entity->hasTranslation($langcode)) { - $paragraphs_entity = $paragraphs_entity->getTranslation($langcode); - } - else { - $paragraphs_entity->set($langcode_key, $langcode); - } - } - } - else { - // Add translation if missing for the target language. - if (!$paragraphs_entity->hasTranslation($langcode)) { - // Get the selected translation of the paragraph entity. - $entity_langcode = $paragraphs_entity->language()->getId(); - $source = $form_state->get(['content_translation', 'source']); - $source_langcode = $source ? $source->getId() : $entity_langcode; - // Make sure the source language version is used if available. It is a - // the host and fetching the translation without this check could lead - // valid scenario to have no paragraphs items in the source version of - // to an exception. - if ($paragraphs_entity->hasTranslation($source_langcode)) { - $paragraphs_entity = $paragraphs_entity->getTranslation($source_langcode); - } - // The paragraphs entity has no content translation source field if - // no paragraph entity field is translatable, even if the host is. - if ($paragraphs_entity->hasField('content_translation_source')) { - // Initialise the translation with source language values. - $paragraphs_entity->addTranslation($langcode, $paragraphs_entity->toArray()); - $translation = $paragraphs_entity->getTranslation($langcode); - $manager = \Drupal::service('content_translation.manager'); - $manager->getTranslationMetadata($translation)->setSource($paragraphs_entity->language()->getId()); - } - } - // If any paragraphs type is translatable do not switch. - if ($paragraphs_entity->hasField('content_translation_source')) { - // Switch the paragraph to the translation. - $paragraphs_entity = $paragraphs_entity->getTranslation($langcode); - } - } + $paragraphs_entity = $this->prepareEntity($paragraphs_entity, $items, $form_state); $element_parents = $parents; $element_parents[] = $field_name; @@ -2161,6 +2115,89 @@ class ParagraphsWidget extends WidgetBase { return $values; } + +/** + * Prepares the paragraph entity for translation. + * + * @param \Drupal\paragraphs\Entity\Paragraph $entity + * The paragraph entity. + * @param \Drupal\Core\Field\FieldItemListInterface $items + * The field items list that hosts this paragraph. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The form state. + * + * @return \Drupal\paragraphs\Entity\Paragraph + * The prepared paragraph. + * + * @see \Drupal\Core\Entity\ContentEntityForm::initFormLangcodes(). + */ + protected function prepareEntity(Paragraph $entity, FieldItemListInterface $items, FormStateInterface $form_state) { + // Detect if we are translating. + $this->initIsTranslating($form_state, $items->getEntity()); + $langcode = $form_state->get('langcode'); + + if (!$this->isTranslating) { + // Set the langcode if we are not translating. + $langcode_key = $entity->getEntityType()->getKey('langcode'); + if ($entity->get($langcode_key)->value != $langcode) { + // If a translation in the given language already exists, switch to + // that. If there is none yet, update the language. + if ($entity->hasTranslation($langcode)) { + $entity = $entity->getTranslation($langcode); + } + else { + $entity->set($langcode_key, $langcode); + } + } + } + + // Localised Paragraphs. + // If the parent field is marked as translatable, assume paragraphs + // to be localized (host entity expects different paragraphs for + // different languages) + elseif ($items->getFieldDefinition()->isTranslatable()) { + if (!empty($form_state->get('content_translation'))) { + $entity = $this->createDuplicateWithSingleLanguage($entity, $langcode); + } + if ($entity->hasTranslation($langcode)) { + $entity = $entity->getTranslation($langcode); + } + } + + // Translated Paragraphs + // If the parent field is not translatable, assume the paragraph + // entity itself (rather the fields within it) are marked as + // translatable. (host entity expects same paragraphs in different + // languages). + else { + // Add translation if missing for the target language. + if (!$entity->hasTranslation($langcode)) { + // Get the selected translation of the paragraph entity. + $entity_langcode = $entity->language()->getId(); + $source = $form_state->get(['content_translation', 'source']); + $source_langcode = $source ? $source->getId() : $entity_langcode; + $entity = $entity->getTranslation($source_langcode); + // The paragraphs entity has no content translation source field if + // no paragraph entity field is translatable, even if the host is. + if ($entity->hasField('content_translation_source')) { + // Initialise the translation with source language values. + $entity->addTranslation($langcode, $entity->toArray()); + $translation = $entity->getTranslation($langcode); + $manager = \Drupal::service('content_translation.manager'); + $manager->getTranslationMetadata($translation) + ->setSource($entity->language()->getId()); + } + } + // If any paragraphs type is translatable do not switch. + if ($entity->hasField('content_translation_source')) { + // Switch the paragraph to the translation. + $entity = $entity->getTranslation($langcode); + } + } + + return $entity; + } + /** * {@inheritdoc} */ @@ -2221,6 +2258,51 @@ class ParagraphsWidget extends WidgetBase { } } + + /** + * Clones a paragraph recursively. + * + * Also, in case of a translatable paragraph, updates its original language + * and removes all other translations. + * + * @param \Drupal\paragraphs\ParagraphInterface $paragraph + * The paragraph entity to clone. + * @param string $langcode + * Language code for all the clone entities created. + * + * @return \Drupal\paragraphs\ParagraphInterface + * New paragraph object with the data from the original paragraph. Not + * saved. All sub-paragraphs are clones as well. + */ + protected function createDuplicateWithSingleLanguage(ParagraphInterface $paragraph, $langcode) { + $duplicate = $paragraph->createDuplicate(); + + // Clone all sub-paragraphs recursively. + foreach ($duplicate->getFields(FALSE) as $field) { + // @todo: should we support field collections as well? + if ($field->getFieldDefinition()->getType() == 'entity_reference_revisions' && $field->getFieldDefinition()->getTargetEntityTypeId() == 'paragraph') { + foreach ($field as $item) { + $item->entity = $this->createDuplicateWithSingleLanguage($item->entity, $langcode); + } + } + } + + // Change the original language and remove possible translations. + if ($duplicate->isTranslatable()) { + $duplicate->set('langcode', $langcode); + foreach ($duplicate->getTranslationLanguages(FALSE) as $language) { + try { + $duplicate->removeTranslation($language->getId()); + } + catch (\InvalidArgumentException $e) { + // Should never happen. + } + } + } + + return $duplicate; + } + /** * After-build callback for removing the translatability clue from the widget. * @@ -2507,7 +2589,7 @@ class ParagraphsWidget extends WidgetBase { * TRUE if we can allow reference changes, otherwise FALSE. */ protected function allowReferenceChanges() { - return !$this->isTranslating; + return !$this->isTranslating || $this->fieldDefinition->isTranslatable(); } /**