Symmetrically translatable config-defined component trees, STEP 3: adjust sequence keys to be unique to avoid translations breaking when component instances are moved
>>> [!note] Migrated issue
<!-- Drupal.org comment -->
<!-- Migrated from issue #3582464. -->
Reported by: [wim leers](https://www.drupal.org/user/99777)
Related to !1028 !831
>>>
<h3 id="approach">Approach</h3>
<p>For high-level overview, see the <a href="https://www.drupal.org/node/3586291">change record</a>.</p>
<ol>
<li>ADR: !901
</li><li>Implementation MR #1: !962 in <span class="drupalorg-gitlab-issue-link project-issue-status-info project-issue-status-7"><a href="https://www.drupal.org/project/canvas/issues/3586342" title="Status: Closed (fixed)">#3586342: Symmetrically translatable component trees, STEP 1: introduce `ComponentInstanceInputsConfigSchemaGeneratorInterface`</a></span>
</li><li>Implementation MR #2: !898 in <span class="drupalorg-gitlab-issue-link project-issue-status-info project-issue-status-2"><a href="https://www.drupal.org/project/canvas/issues/3582478" title="Status: Fixed">#3582478: Symmetrically translatable config-defined component trees, STEP 2: change config schema type for `inputs` to allow translating via core's Config Translation UI for ContentTemplates & PageRegions</a></span>
</li><li>Implementation MR #3: !831, here
</li></ol>
<h3 id="overview">Overview</h3>
<p><span class="drupalorg-gitlab-issue-link project-issue-status-info project-issue-status-7"><a href="https://www.drupal.org/project/experience_builder/issues/3526127" title="Status: Closed (fixed)">#3526127: Ensure deterministic config export order of config-defined component trees</a></span> ensured we could use <code>orderby: key</code>, and ensured good DX.</p>
<p>In doing that, it also (unwittingly!) avoided the trap of having <em>impossible-to-sanely-translate sequences</em> 👍 — sequences without explicit keys cannot be reliably translated.</p>
<p>(This is a trap I stepped into with CKEditor 5's "Style" plugin: <span class="drupalorg-gitlab-issue-link project-issue-status-info project-issue-status-13"><a href="https://www.drupal.org/project/drupal/issues/3382464" title="Status: Needs work">#3382464: [Style] CKEditor 5 styles config storage is not compatible with config ovverides</a></span> 🥺😬)</p>
<p>But Drupal core's config translation system stores only the key-value pairs that are actually translatable, and targets them by the property path. For example:</p>
<dl>
<dt>English</dt>
<dd>Block config entity:
<pre>uuid: 0b34890a-98ab-46e7-97d9-de3aae124361<br>langcode: en<br>✂✂✂✂✂✂✂✂✂<br>plugin: system_powered_by_block<br>settings:<br> id: system_powered_by_block<br> label: 'Proudly Powered by Drupal'<br> label_display: visible<br> provider: system<br>visibility: { }</pre></dd>
<dt>Dutch translation</dt>
<dd>
<pre>settings:<br> id: system_powered_by_block<br> label: 'Aangedreven door Drupal'</pre></dd>
<dt>Stored as</dt>
<dd><img src="https://www.drupal.org/files/issues/2026-03-31/Screenshot%202026-03-31%20at%205.02.45%E2%80%AFPM.png"></dd>
</dl>
<p>This means that the approach Canvas took still prevents reliable translations because the generated key is based on position, which could change! (If component instances are moved, the keys would change.)</p>
<h3 id="proposed-resolution">Proposed resolution</h3>
<ol>
<li>(Per discussion with @lauriii.) If we must, drop the key altogether in favor of a weight/delta within the sequence item, and key by component instance UUID instead. The result would be that the config system itself wouldn't natively ensure consistent sequence ordering anymore. → This is <em>kind of</em> what this MR implements — but with a twist: we also implement config export/import transforms to retain the DX (intelligibility + tamper-resistance) that <span class="drupalorg-gitlab-issue-link project-issue-status-info project-issue-status-7"><a href="https://www.drupal.org/project/experience_builder/issues/3526127" title="Status: Closed (fixed)">#3526127: Ensure deterministic config export order of config-defined component trees</a></span> introduced 🥳
</li><li><del>(Per discussion with @tedbow.) Try to expand the key to also include the UUID. This would get the best of both worlds, and would require only minimal additional logic when loading translations.</del> → impossible per <a href="https://www.drupal.org/project/canvas/issues/3582464#comment-16537537">#23</a>
</li></ol>
<p><del>⚠️ OUT OF SCOPE/pre-existing problem: auto-update translations when component instances are deleted, to also drop obsolete translations.</del> → this is handled automatically by the config system thanks to us picking the first solution above — an unexpected "yay" for <code>ConfigFactoryOverrideBase::filterOverride()</code>! (<a href="https://git.drupalcode.org/project/canvas/-/merge_requests/831/diffs?commit_id=f3480931bda03c9fea5549ed6d799b2ce427af70">test coverage</a>)</p>
<h3 id="ui-changes">User interface changes</h3>
<p>None.</p>
> Related issue: [Issue #3526127](https://www.drupal.org/node/3526127)
> Related issue: [Issue #3382464](https://www.drupal.org/node/3382464)
> Related issue: [Issue #3571232](https://www.drupal.org/node/3571232)
> Related issue: [Issue #2993984](https://www.drupal.org/node/2993984)
> Related issue: [Issue #3160334](https://www.drupal.org/node/3160334)
> Related issue: [Issue #3590196](https://www.drupal.org/node/3590196)
issue