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
>>> [!note] Migrated issue <!-- Drupal.org comment --> <!-- Migrated from issue #3582478. --> Reported by: [wim leers](https://www.drupal.org/user/99777) Related to !898 !901 !868 >>> <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, here </li><li>Implementation MR #3: !831 in <span class="drupalorg-gitlab-issue-link drupalorg-gitlab-link-wrapper"><a href="https://git.drupalcode.org/project/canvas/-/work_items/3582464" class="drupalorg-gitlab-link">https://git.drupalcode.org/project/canvas/-/work_items/3582464</a></span> </li></ol> <h3 id="overview">Overview</h3> <p>The explicit inputs for a component instance in a config-defined component tree currently looks like this:</p> <pre>diff --git a/config/schema/canvas.schema.yml b/config/schema/canvas.schema.yml<br>index 4c9d7514b..df8c05b86 100644<br>--- a/config/schema/canvas.schema.yml<br>+++ b/config/schema/canvas.schema.yml<br>@@ -360,7 +360,7 @@ canvas.component_tree_node:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; constraints: { }<br>&nbsp;&nbsp;&nbsp;&nbsp; # @see \Drupal\canvas\Plugin\DataType\ComponentInputs<br>&nbsp;&nbsp;&nbsp;&nbsp; inputs:<br>-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; type: ignore<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; type: component_instance_inputs<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; label: 'Input values for each component in the component tree'<br>&nbsp;&nbsp;&nbsp;&nbsp; label:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # This is entirely optional; provides more context for content authors.</pre><p>That has been fine, because the <code>ValidComponentTreeItem</code> and <code>ComponentTreeMeetRequirements</code> validation constraints automatically turn the given config blobs into a <code>ComponentTreeItem</code> (the field type) and then use <em>its</em> validation constraints.</p> <p>This is how Canvas is able to store component trees in both content and config entities with perfect consistency: because the same validation infrastructure is used for both.</p> <h4>Config schema must become more precise to support translations</h4> <p><strong>However, translating config-defined component trees</strong> means using Drupal core's <code>LanguageConfigOverride</code>, <code>config_translation</code> and related infrastructure (see <span class="drupalorg-gitlab-issue-link drupalorg-gitlab-link-wrapper"><a href="https://git.drupalcode.org/project/canvas/-/work_items/3582464" class="drupalorg-gitlab-link">https://git.drupalcode.org/project/canvas/-/work_items/3582464</a></span>). All that infrastructure gets its information from &hellip; config schema.</p> <p>&rArr; config schema must become more precise.</p> <p>&#128161; This <em>should</em> also make the "JSON-blobs-in-config-YAML" disappear, i.e.:</p> <pre>component_tree:<br>&nbsp; - <br>&nbsp;&nbsp;&nbsp; uuid: fd141b6e-c57a-4355-9885-a8b2e60627d6<br>&nbsp;&nbsp;&nbsp; component_id: sdc.canvas_test_sdc.heading<br>&nbsp;&nbsp;&nbsp; component_version: 8c01a2bdb897a810<br>&nbsp;&nbsp;&nbsp; inputs: '{"text":{"sourceType":"entity-field","expression":"\u2139\ufe0e\u241centity:node:page\u241dtitle\u241e\u241fvalue"},"style":"primary","element":"h1"}'</pre><p> would become</p> <pre>component_tree:<br>&nbsp; -<br>&nbsp;&nbsp;&nbsp; uuid: fd141b6e-c57a-4355-9885-a8b2e60627d6<br>&nbsp;&nbsp;&nbsp; component_id: sdc.canvas_test_sdc.heading<br>&nbsp;&nbsp;&nbsp; component_version: 8c01a2bdb897a810<br>&nbsp;&nbsp;&nbsp; inputs:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; text: {"sourceType":"entity-field","expression":"\u2139\ufe0e\u241centity:node:page\u241dtitle\u241e\u241fvalue"}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; style: primary<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; element: h1</pre><p> EDIT: </p> <h3 id="proposed-resolution">Proposed resolution</h3> <ol> <li>&#9989; Ensure that the <code>ComponentInputsMapping extends Mapping</code> config schema type class gets its information from the same infrastructure as <code>ComponentTreeItem</code>'s <code>inputs</code> property: <code>public static ComponentInputs::resolveConfigSchemaMapping()</code> available that is used by both classes. That infrastructure was introduced by <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> and is reused here. </li><li>&#9989; Change from <code>type: ignore</code> to <code>type: mapping</code> with an explicitly specified class <code>ComponentInputsMapping</code> that subclasses core's <code>\Drupal\Core\Config\Schema\Mapping</code> to auto-generate at runtime the correct mapping definition: one that corresponds to the explicit inputs the referenced Component config entity (version) supports.<br> (Prior art for this: <code>\Drupal\canvas\Config\Schema\JsonSchemaObject</code>, for validating code components' definition against a JSON Schema subset.) </li><li>&#9989; Update path: <img src="https://www.drupal.org/files/issues/2026-04-28/Screenshot%202026-04-27%20at%202.44.38%E2%80%AFPM.png"> </li><li>&#9989; Introduce <code> ComponentTreeConfigEntityBase:: componentTreeInstancesInputsMustBeArrays()</code> and use it in <code>::set('component_tree', &hellip;)</code> + <code>::preCreate()</code> to convert the JSON blob assigned to a component instance's <code>inputs</code> (explicit inputs) to an array of key-value pairs (with the keys being the explicit inputs for which values are being specified). </li><li>&#9989; Ensure that a config-defined component instance that populates an explicit input using structured data (<code>EntityFieldPropSource</code>, <code>HostEntityUrlPropSource</code>) correctly detects and prevents duplicating or overriding that structured data in a symmetrical translation. This is achieved using the same dynamic config schema: this MR's <code>ComponentInputsMapping extends Mapping</code> class takes into account the values for the <code>inputs</code> in the default translation: if it's populated by structured data, set <code>translatable: false</code>. <a href="https://git.drupalcode.org/project/canvas/-/merge_requests/868/diffs?commit_id=828828050e559c942aa404645135bfd8c7c331c8">Test coverage</a> was added for this. </li><li>&#9989; Ensure that a config-defined component tree is symmetrically translatable using core's Config Translation UI: <ul> <li>For block component instances, reuse all the existing config translation infrastructure+UX that would be used for <code>block</code> config entities </li><li>For SDC+code component component instances, specify for every input <code>form_element_class: CanvasStaticPropSourceFieldWidget extends FormElementBase</code> to generate a field widget-powered UX, and its submission handling will automatically use the same optimizing ("collapsing") for storage, resulting in correct overrides. &rarr; this means any prop shape or any specific prop can be marked as translatable, and it will automatically work. </li> </ul> <p>&rarr; <a href="https://git.drupalcode.org/project/canvas/-/merge_requests/898#note_747622">Details</a><br> &rarr; Screenshot:<br> <img src="https://www.drupal.org/files/issues/2026-04-30/ai.test_admin_structure_content-template_node.something.full_translate__2_.png"> </p></li> </ol> <h4>Test coverage</h4> <ol> <li>&#9989; Kernel test coverage for configuration translation foundations in <code>\Drupal\Tests\canvas\Kernel\Config\ContentTemplateTest::testTranslationLifeCycleInDepth()</code></li> <li>&#9989; Integration test coverage to prove it yields the expected results when rendering a component tree and visiting as an English and a French site visitor: <code>\Drupal\Tests\canvas\Functional\TranslationTest::testContentTemplateTranslationRendered()</code></li> <li>&#9989; Integration test coverage to prove it is possible to generate valid config translations (<code>LanguageConfigOverride</code>s) using the UI: <code>TranslationTest::testContentTemplateConfigTranslationUi()</code>. </li><li>&#9989; Update path test coverage, to prove that the config-defined component trees are updated correctly, both when &gt;=1 config-defined component instance's <code>inputs</code> still is a JSON blob (typical scenario) and when it already is an array (atypical, but possible when e.g. using recipes, possible due to <code>type: ignore</code> not applying any transformation at saving time). : <code>ConfigComponentInputsMustBeArraysUpdateTest</code>. </li></ol> <h4>Out of scope</h4> <ul> <li>validation of config translations. Core's <code>LanguageConfigOverride</code> does not seem to have any validation at all. Created <span class="drupalorg-gitlab-issue-link project-issue-status-info project-issue-status-1"><a href="https://www.drupal.org/project/canvas/issues/3583854" title="Status: Active">#3583854: [upstream] Validate LanguageConfigOverrides targeting Canvas config entities</a></span> for this. </li> <li>alter hook to opt in more prop shapes to become translatable: <span class="drupalorg-gitlab-issue-link project-issue-status-info project-issue-status-4"><a href="https://www.drupal.org/project/canvas/issues/3584178" title="Status: Postponed">#3584178: [later phase] Add alter hook for marking additional SDC/code component prop shapes AND/OR specific props as translatable</a></span> </li><li>TMGMT support &rarr; <span class="drupalorg-gitlab-issue-link project-issue-status-info project-issue-status-13"><a href="https://www.drupal.org/project/canvas/issues/3582558" title="Status: Needs work">#3582558: Symmetrically translatable config-defined component trees, STEP 4: allow translating component instance inputs via TMGMT for ContentTemplates &amp; PageRegions</a></span> </li></ul> <h3 id="ui-changes">User interface changes</h3> <p>Page Regions and Content Templates become translatable in core's Config Translation UI. <img src="https://www.drupal.org/files/issues/2026-04-30/ai.test_admin_structure_content-template_node.something.full_translate__2_.png"></p> > Related issue: [Issue #3568264](https://www.drupal.org/node/3568264) > Related issue: [Issue #3582478](https://www.drupal.org/node/3582478) > Related issue: [Issue #3520484](https://www.drupal.org/node/3520484) > Related issue: [Issue #3583945](https://www.drupal.org/node/3583945) > Related issue: [Issue #3584143](https://www.drupal.org/node/3584143) > Related issue: [Issue #3584178](https://www.drupal.org/node/3584178) > Related issue: [Issue #3586342](https://www.drupal.org/node/3586342) > Related issue: [Issue #3590196](https://www.drupal.org/node/3590196)
issue