Sub-paragraph fields cannot be translated

Created on 28 March 2017, about 8 years ago
Updated 11 September 2023, almost 2 years ago

Drupal 8.2.7
Paragraphs 8.x-1.1

Step to reproduce:

1. Create a paragraph type called "Accordion" with title and body text fields
2. Create a paragraph type called "Accordion group" with a paragraphs field of type "Accordion" and no other fields
3. Create a content type using an "Accordion group" field
4. Create an English node with an Accordion group containing an Accordion. Enter "English" in the title and body.
5. Translate the node to another language, change "English" to "translated".
6. Check the English node.

Expected result:

English node contains "English".

Actual result:

English node contains "translated".

Workaround:

Add a dummy "Dummy to fix translation" text field to the "Accordion group" paragraph type, and enable its translation. You can now translate the fields.

More info:

See thread beginning here, where the workaround comes from: https://www.drupal.org/node/2735121#comment-11925679

🐛 Bug report
Status

Needs work

Version

1.0

Component

Code

Created by

🇫🇮Finland hugovk

Live updates comments and jobs are added and updated live.
  • Needs tests

    The change is currently missing an automated test that fails when run with the original code, and succeeds when the bug has been fixed.

Sign in to follow issues

Merge Requests

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • leymannx Berlin

    Oh Jesus, workaround == life saver!! Found out, it's already enough if you simply select the published field of the parent paragraph bundle to be translatable under /admin/config/regional/content-language.

  • leymannx Berlin

    Shit, the "workaround" introduces another major downer: The first time after the workaround you add a new paragraph item in the original language all original language paragraph items get synced to the translation language again, replacing the translations. After that it's fine. It's just the first time this happens.

  • First commit to issue fork.
  • Pipeline finished with Failed
    3 days ago
    Total: 343s
    #519057
  • 🇧🇬Bulgaria pfrenssen Sofia

    Added tests and ported the fix also to the Stable widget. There is a failure but this is also affecting the main dev branch and unrelated to this issue.

  • Pipeline finished with Failed
    3 days ago
    Total: 689s
    #519070
  • 🇧🇬Bulgaria pfrenssen Sofia

    Here is a Drush script I used to fix the data corruption that occurs if paragraphs have been wrongly translated due to this bug. This is specific to my project in that it only considers that nodes will have paragraphs. Other projects might also have paragraphs on other entity types. It updates the database tables directly since it is not possible to update default translations through the entity API.

    This is completely untested and just intended as a starting point. Do not run this in production ;)

    
    use Drupal\node\Entity\Node;
    
    $query = <<<MYSQL
      SELECT pi.id, pifd.langcode, pifd.default_langcode, pifd.parent_id, pifd.parent_type
      FROM paragraphs_item pi
      LEFT JOIN paragraphs_item_field_data pifd on pi.id = pifd.id
      WHERE pifd.parent_type IN ('node', 'paragraph')
    MYSQL;
    
    $connection = \Drupal::database();
    $paragraphsData = $connection->query($query)->fetchAll();
    // The data set is huge, create a lookup table for quick access.
    $paragraphsLookup = [];
    foreach ($paragraphsData as $i => $paragraphData) {
      $paragraphsLookup[$paragraphData->id] = $i;
    }
    
    $query = <<<MYSQL
      SELECT nid, langcode FROM node_field_data WHERE default_langcode = 1
    MYSQL;
    $nodeLangcodes = $connection->query($query)->fetchAllKeyed();
    
    $foundInvalidParagraphs = [];
    
    foreach ($paragraphsData as $paragraphData) {
      // Skip paragraphs that are not the default translation because they are not
      // affected.
      if (!$paragraphData->default_langcode) {
        continue;
      }
      $hostId = getHostId($paragraphData, $paragraphsData, $paragraphsLookup, $nodeLangcodes);
      if (!$hostId) {
        // Parent paragraph no longer exists, skip.
        continue;
      }
      $hostLangcode = $nodeLangcodes[$hostId] ?? NULL;
      if (!$hostLangcode) {
        // Node no longer exists, skip.
        continue;
      }
      // If the host language is not the same as the paragraph language, we have
      // an invalid translation.
      if ($paragraphData->langcode !== $hostLangcode) {
        $foundInvalidParagraphs[$hostId][] = $paragraphData->id;
      }
    }
    
    foreach ($foundInvalidParagraphs as $hostId => $paragraphIds) {
      $node = Node::load($hostId)->getUntranslated();
      $hostLangcode = $node->language()->getId();
    
      echo "\nProcessing node {$node->id()} - $hostLangcode ({$node->getTitle()})\n";
      foreach ($paragraphIds as $paragraphId) {
        echo "  - Setting default translation for paragraph $paragraphId to $hostLangcode\n";
        $queries = [
          <<<MYSQL
            UPDATE paragraphs_item_revision_field_data
            SET default_langcode = CASE WHEN langcode = :host_langcode THEN 1 ELSE 0 END
            WHERE id = :paragraph_id
          MYSQL,
          <<<MYSQL
            UPDATE paragraphs_item_field_data
            SET default_langcode = CASE WHEN langcode = :host_langcode THEN 1 ELSE 0 END
            WHERE id = :paragraph_id
          MYSQL,
        ];
        foreach ($queries as $query) {
          $connection->query($query, [
            ':host_langcode' => $hostLangcode,
            ':paragraph_id' => $paragraphId,
          ]);
        }
      }
    }
    
    function getHostId($paragraphData, &$paragraphsData, &$paragraphsLookup, &$nodeLangcodes): ?int {
      if ($paragraphData->parent_type === 'node') {
        return (int) $paragraphData->parent_id;
      }
      $parentIndex = $paragraphsLookup[$paragraphData->parent_id] ?? NULL;
      if (!$parentIndex) {
        // Parent paragraph no longer exists, return NULL.
        return NULL;
      }
      return getHostId($paragraphsData[$parentIndex], $paragraphsData, $paragraphsLookup, $nodeLangcodes);
    }
    
Production build 0.71.5 2024