diff --git a/README.txt b/README.txt index 562ce31..209b4e5 100644 --- a/README.txt +++ b/README.txt @@ -4,8 +4,44 @@ README.txt Paragraph is a module to create paragraphs in your content. You can create types(with own display and fields) as paragraph types. -When you use the Entity Reference Paragraphs widget + Entity Reference selection type on your node/entity, you can select the allowed types, and when using the widget, you can select a paragraph type from the allowed types to use different fields/display per paragraph. +When you use the Entity Reference Paragraphs widget + Entity Reference selection +type on your node/entity, you can select the allowed types, and when using the +widget, you can select a paragraph type from the allowed types to use +different fields/display per paragraph. * Different fields per paragraph type * Using different paragraph types in a single paragraph field * Displays per paragraph type + +CONFIGURATION +------------- + * Enable the Paragraph module. + + * Add new languages for the translation in Configuration » Languages. + + * Enable any custom content type with a paragraph field to be translatable in + Configuration » Content language + and translation: + + - Under Custom language settings check Content. + + - Under Content check the content type with a paragraph field. + + - Make sure that the content type with a paragraph field is set to NOT + translatable. + + - Set the fields of each paragraph type to translatable as required. + + * Check Paragraphs as the embedded reference in Configuration » Translation + Management settings. + + * Create a new content - Paragraphed article and translate it. + + +LIMITATION +------------- +For now, this module does not support switching entity reference revision field +of the paragraph itself into multilingual mode. This would raise complexity +significantly. +Check #2461695: Support translatable paragraph entity reference revision field +(https://www.drupal.org/node/2461695). diff --git a/modules/paragraphs_demo/config/install/core.entity_form_display.node.paragraphed_content_demo.default.yml b/modules/paragraphs_demo/config/install/core.entity_form_display.node.paragraphed_content_demo.default.yml index 315b72d..dc94f0c 100644 --- a/modules/paragraphs_demo/config/install/core.entity_form_display.node.paragraphed_content_demo.default.yml +++ b/modules/paragraphs_demo/config/install/core.entity_form_display.node.paragraphed_content_demo.default.yml @@ -6,7 +6,6 @@ dependencies: - node.type.paragraphed_content_demo module: - paragraphs - - path id: node.paragraphed_content_demo.default targetEntityType: node bundle: paragraphed_content_demo @@ -35,11 +34,6 @@ content: add_mode: dropdown form_display_mode: default third_party_settings: { } - path: - type: path - weight: 30 - settings: { } - third_party_settings: { } promote: type: boolean_checkbox weight: 15 diff --git a/modules/paragraphs_demo/config/install/node.type.paragraphed_content_demo.yml b/modules/paragraphs_demo/config/install/node.type.paragraphed_content_demo.yml index 73462f0..2a71756 100644 --- a/modules/paragraphs_demo/config/install/node.type.paragraphed_content_demo.yml +++ b/modules/paragraphs_demo/config/install/node.type.paragraphed_content_demo.yml @@ -1,13 +1,7 @@ langcode: en status: true -dependencies: - module: - - menu_ui -third_party_settings: - menu_ui: - available_menus: - - main - parent: 'main:' +dependencies: { } +third_party_settings: { } name: 'Paragraphed article' type: paragraphed_content_demo description: 'Article with paragraphs.' diff --git a/modules/paragraphs_demo/paragraphs_demo.info.yml b/modules/paragraphs_demo/paragraphs_demo.info.yml index d3c907d..5282294 100644 --- a/modules/paragraphs_demo/paragraphs_demo.info.yml +++ b/modules/paragraphs_demo/paragraphs_demo.info.yml @@ -2,6 +2,11 @@ description: Demo module for paragraphs. core: 8.x dependencies: - paragraphs + - field + - image + - field_ui + - entity_reference + - block hidden: false name: Paragraphs demo package: paragraphs diff --git a/paragraphs.routing.yml b/paragraphs.routing.yml index e255d5e..e892a22 100644 --- a/paragraphs.routing.yml +++ b/paragraphs.routing.yml @@ -28,4 +28,4 @@ entity.paragraphs_type.delete_form: _entity_form: 'paragraphs_type.delete' _title: 'Delete' requirements: - _permission: 'administer paragraphs types' \ No newline at end of file + _permission: 'administer paragraphs types' diff --git a/src/Entity/Paragraph.php b/src/Entity/Paragraph.php index 7be72a5..a463ed0 100644 --- a/src/Entity/Paragraph.php +++ b/src/Entity/Paragraph.php @@ -47,6 +47,7 @@ use Drupal\user\UserInterface; * bundle_entity_type = "paragraphs_type", * field_ui_base_route = "entity.paragraphs_type.edit_form", * common_reference_revisions_target = TRUE, + * content_translation_ui_skip = TRUE, * default_reference_revision_settings = { * "field_storage_config" = { * "cardinality" = -1, diff --git a/src/Plugin/Field/FieldWidget/InlineParagraphsWidget.php b/src/Plugin/Field/FieldWidget/InlineParagraphsWidget.php index 7debd12..f935440 100644 --- a/src/Plugin/Field/FieldWidget/InlineParagraphsWidget.php +++ b/src/Plugin/Field/FieldWidget/InlineParagraphsWidget.php @@ -201,6 +201,10 @@ class InlineParagraphsWidget extends WidgetBase { } if ($paragraphs_entity) { + + // Initiate the paragraph with the correct translation. + $paragraphs_entity = $paragraphs_entity->getTranslation($this->getCurrentLangcode($form_state, $items)); + $element_parents = $parents; $element_parents[] = $field_name; $element_parents[] = $delta; @@ -275,6 +279,7 @@ class InlineParagraphsWidget extends WidgetBase { ); } + $button_access = $paragraphs_entity->access('delete') && $paragraphs_entity->language()->getId() == $paragraphs_entity->getUntranslated()->language()->getId(); $links['remove_button'] = array( '#type' => 'submit', '#value' => t('Remove'), @@ -288,7 +293,7 @@ class InlineParagraphsWidget extends WidgetBase { 'wrapper' => $widget_state['ajax_wrapper_id'], 'effect' => 'fade', ), - '#access' => $paragraphs_entity->access('delete'), + '#access' => $button_access, '#prefix' => '
  • ', '#suffix' => '
  • ', ); @@ -711,9 +716,11 @@ class InlineParagraphsWidget extends WidgetBase { // Add 'add more' button, if not working with a programmed form. if (($real_item_count < $cardinality || $cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED) && !$form_state->isProgrammed()) { + $add_more_access = $this->getCurrentLangcode($form_state, $items) == $items->getEntity()->getUntranslated()->language()->getId(); $elements['add_more'] = array( '#type' => 'container', '#theme_wrappers' => array('paragraphs_dropbutton_wrapper'), + '#access' => $add_more_access, ); if (count($access_options)) { @@ -816,6 +823,21 @@ class InlineParagraphsWidget extends WidgetBase { } /** + * Gets current language code from the form state or item. + * + * Since the paragraph field is not set as translatable, the item language + * code is set to the source language. The intended translation language + * is only accessibly through the form state. + * + * @param \Drupal\Core\Form\FormStateInterface $form_state + * @param \Drupal\Core\Field\FieldItemListInterface $items + * @return string + */ + protected function getCurrentLangcode(FormStateInterface $form_state, FieldItemListInterface $items) { + return $form_state->get('langcode') ?: $items->getEntity()->language()->getId(); + } + + /** * {@inheritdoc} */ public static function addMoreAjax(array $form, FormStateInterface $form_state) { diff --git a/src/Tests/ParagraphsTranslationTest.php b/src/Tests/ParagraphsTranslationTest.php new file mode 100644 index 0000000..4448a79 --- /dev/null +++ b/src/Tests/ParagraphsTranslationTest.php @@ -0,0 +1,156 @@ +save(); + $language = ConfigurableLanguage::createFromLangcode('fr'); + $language->save(); + } + + /** + * Tests the paragraph translation. + */ + public function testParagraphTranslation() { + $admin_user = $this->drupalCreateUser(array( + 'administer site configuration', + 'administer nodes', + 'create paragraphed_content_demo content', + 'edit any paragraphed_content_demo content', + 'delete any paragraphed_content_demo content', + 'administer content translation', + 'translate any entity', + 'create content translations', + 'administer languages', + )); + + $this->drupalLogin($admin_user); + + $this->drupalGet('admin/config/regional/content-language'); + + // Enable translation for paragraphs and it's bundles. + $edit = array( + 'entity_types[node]' => TRUE, + 'entity_types[paragraph]' => TRUE, + 'settings[paragraph][images][translatable]' => TRUE, + 'settings[paragraph][image_text][translatable]' => TRUE, + 'settings[node][paragraphed_content_demo][settings][language][language_alterable]' => TRUE, + 'settings[paragraph][user][translatable]' => TRUE, + 'settings[paragraph][text_image][translatable]' => TRUE, + 'settings[paragraph][text_image][fields][field_text_demo]' => TRUE, + 'settings[node][paragraphed_content_demo][translatable]' => TRUE, + 'settings[node][paragraphed_content_demo][fields][title]' => TRUE, + 'settings[node][paragraphed_content_demo][fields][uid]' => TRUE, + 'settings[node][paragraphed_content_demo][fields][status]' => TRUE, + 'settings[node][paragraphed_content_demo][fields][created]' => TRUE, + 'settings[node][paragraphed_content_demo][fields][changed]' => TRUE, + 'settings[node][paragraphed_content_demo][fields][promote]' => TRUE, + 'settings[node][paragraphed_content_demo][fields][sticky]' => TRUE, + 'settings[node][paragraphed_content_demo][fields][revision_log]' => TRUE, + ); + $this->drupalPostForm(NULL, $edit, t('Save configuration')); + + // Clear cached bundles refer 2450251. + \Drupal::entityManager()->clearCachedBundles(); + + // Check the settings are saved correctly. + // @todo Uncomment these lines once core is fixed https://www.drupal.org/node/2449457 + // https://www.drupal.org/node/2473721 + // $this->assertFieldChecked('edit-entity-types-paragraph'); + $this->assertFieldChecked('edit-settings-node-paragraphed-content-demo-translatable'); + // $this->assertFieldChecked('edit-settings-paragraph-text-image-translatable'); + + // Add paragraphed content. + $this->drupalGet('node/add/paragraphed_content_demo'); + $this->drupalPostForm(NULL, NULL, t('Add Text + Image')); + $edit = array( + 'title[0][value]' => 'Title in english', + 'field_paragraphs_demo[0][subform][field_text_demo][0][value]' => 'Text in english', + ); + // The button to remove a paragraph is present. + $this->assertRaw(t('Remove')); + $this->drupalPostForm(NULL, $edit, t('Save and publish')); + $node = $this->drupalGetNodeByTitle('Title in english'); + // The text is present when editing again. + $this->clickLink(t('Edit')); + $this->assertText('Title in english'); + $this->assertText('Text in english'); + + // Add french translation. + $this->clickLink(t('Translate')); + $this->clickLink(t('Add')); + // Make sure the Add / Remove paragraph buttons are hidden. + $this->assertNoRaw(t('Remove')); + $this->assertNoRaw(t('Add Text + Image')); + $edit = array( + 'title[0][value]' => 'Title in french', + 'field_paragraphs_demo[0][subform][field_text_demo][0][value]' => 'Text in french', + ); + $this->drupalPostForm(NULL, $edit, t('Save and keep published (this translation)')); + + // Check the english translation. + $this->drupalGet('node/' . $node->id()); + $this->assertText('Title in english'); + $this->assertText('Text in english'); + $this->assertNoText('Title in french'); + $this->assertNoText('Text in french'); + + // Check the french translation. + $this->drupalGet('fr/node/' . $node->id()); + $this->assertText('Title in french'); + $this->assertText('Text in french'); + $this->assertNoText('Title in english'); + // The translation is still present when editing again. + $this->clickLink(t('Edit')); + $this->assertText('Title in french'); + $this->assertText('Text in french'); + $edit = array( + 'field_paragraphs_demo[0][subform][field_text_demo][0][value]' => 'New text in french', + ); + $this->drupalPostForm(NULL, $edit, t('Save and keep published (this translation)')); + $this->assertText('Title in french'); + $this->assertText('New text in french'); + + // Back to the source language. + $this->drupalGet('node/' . $node->id()); + $this->clickLink(t('Edit')); + $this->assertText('Title in english'); + $this->assertText('Text in english'); + } +}