- 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.
- Merge request !189Issue #2864682: Sub-paragraph fields cannot be translated → (Open) created by pfrenssen
- 🇧🇬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.
- 🇧🇬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); }