Problem/Motivation

When a translatable paragraph bundle is referenced through an Entity Reference Revisions field marked as non-translatable (translatable: false at the FieldConfig level) on a parent paragraph bundle, the ParagraphsWidget always shows — and overwrites — the original-language values of the child paragraph. The child paragraph's own translatable fields become impossible to translate.

The intent of a non-translatable ERR field is: all language versions of the host share the same structural reference (same child paragraph entity), but the child paragraph itself can still hold per-language translations of its own content fields. This is sometimes called the "shared structure, translated content" pattern.

Steps to reproduce

  1. Create two paragraph bundles:
    • wrapper with an ERR field field_items set to non-translatable (translatable: false), referencing bundle card.
    • card with content translation enabled and at least one translatable text field (e.g. field_title).
  2. Add a node type with an ERR field referencing wrapper. Enable content translation for both the node type and the card bundle.
  3. Create a node in the default language (e.g. English), add a wrapper with a card child. Save
  4. Translate the node to a second language (e.g. Italian). Open the translation form.

Expected behavior: The widget for field_items loads the existing translation of card in the current language, or — when none exists yet — pre-populates the subform with the source-language values so the user can translate them. On save, a translation of card in the current language is created or updated, while the source-language version is left untouched.

Actual behavior: The translatable fields of card cannot be translated at all. ParagraphsWidget::formElement() enters the !$this->isTranslating branch because the immediate host (wrapper) carries no translation (it was never required to have one, since its parent ERR field is non-translatable). Inside that branch, when no translation of card exists for the target language, the code falls through to:

<?php
$paragraphs_entity->set($langcode_key, $langcode);


This renames the original paragraph entity to the target language instead of creating a separate translation. The source-language version is destroyed, and no per-language translation is ever created. Editing the content in either language subsequently loads the same (now orphaned) entity.

Proposed resolution

In the !$this->isTranslating block, add an intermediate elseif before the destructive else:

<?php
if ($paragraphs_entity->hasTranslation($langcode)) {
    $paragraphs_entity = $paragraphs_entity->getTranslation($langcode);
}
elseif ($paragraphs_entity->hasField('content_translation_source')) {
    // The paragraph bundle supports content translation. Even when the
    // referencing ERR field is non-translatable (same entity shared across
    // all language versions of the host), the child needs a per-language
    // translation so its own translatable fields can be edited independently.
    // Create an in-memory translation here; ERR::preSave() will persist it.
    $paragraphs_entity->addTranslation($langcode, $paragraphs_entity->toArray());
    $paragraphs_translation = $paragraphs_entity->getTranslation($langcode);
    /** @var \Drupal\content_translation\ContentTranslationManagerInterface $manager */
    $manager = \Drupal::service('content_translation.manager');
    $manager->getTranslationMetadata($paragraphs_translation)
      ->setSource($paragraphs_entity->language()->getId());
    $paragraphs_entity = $paragraphs_translation;
}
else {
    $paragraphs_entity->set($langcode_key, $langcode);
}


A patch implementing this change is attached.

Comments

guizzard created an issue.