Problem/Motivation

follow up #3041659: Remove the layout tab from translations because Layout Builder does not support translations yet

Part of #3044386: [META] Make Layout Builder layouts translatable

Right now you are not able to translate layouts at all.

The most common use case is probably translating the block labels and inline blocks but having the actual layouts not change from language to language. For different layout per language: see #3040487: Allow Independent Layout Override Translations

Proposed resolution

  1. Provide access to the Layout table on translations
  2. On Translations the Layout builder would only allow translating labels and inline blocks
  3. Translated settings for block would be stored a new field that would be translatable(the current Layout field would still be untranslatable)
  4. Most blocks could only have labels as translated settings
  5. Inline blocks could additionally have a block revision ID stored.

Remaining tasks

User interface changes

API changes

Data model changes

CommentFileSizeAuthor
#363 2946333-interdiff-inlineblock-translation.patch3.1 KBezeedub
#362 2946333-interdiff-bc-fallback.patch999 bytesezeedub
#362 2946333-interdiff-bc-fallback.patch999 bytesezeedub
#361 2946333-11.3.2.patch131.41 KBl_vandamme
#356 2946333-mr5956-1122.patch128.21 KBwranvaud
#354 interdiff_2946333-mr10504-lb-translations-content-language.txt3.72 KBwranvaud
#354 2946333-lb-override-translations-content-language.patch128.61 KBwranvaud
#344 2946333-mr-8480.patch161.76 KBlangelhc
#332 2946333-nr-bot.txt17.44 KBneeds-review-queue-bot
#328 test-block.png29.26 KBwebfaqtory
#327 2946333-327_Revised_patch_319.patch129.38 KBadwivedi008
#319 2946333-319.patch130.87 KBjordik
#310 interdiff-309_310.txt1.25 KBgauravvvv
#310 2946333-310.patch2.44 KBgauravvvv
#309 3101231-D10-allow-synced-layout-override-translations.patch2.22 KBwebfaqtory
#307 interdiff_306_307.txt1.41 KBnginex
#307 2946333-d10-307.patch125.39 KBnginex
#306 interdiff_305_306.txt15.58 KBnginex
#306 2946333-d10-306.patch125.72 KBnginex
#305 2946333-d10-305.patch140.45 KBnginex
#302 2946333-302.patch127.36 KBheddn
#302 diff_301-302.txt7.18 KBheddn
#301 interdiff_299-301.txt1.14 KBsahil.goyal
#301 2946333-301.patch129.06 KBsahil.goyal
#299 reroll_diff-292-299.txt8.11 KBravi.shankar
#299 2946333-299.patch129.04 KBravi.shankar
#296 2946333-9.4.x-296.patch143.73 KBnginex
#292 core-9.5.x-allow-synced-layout-drupal-2946333-292.patch129.03 KBhswong3i
#288 2946333-288.patch129 KBravi.shankar
#283 2946333-283.patch129.13 KBheddn
#280 interdiff_279-280.txt2.48 KBheddn
#280 2946333-280.patch129.13 KBheddn
#279 2946333-279.patch129.19 KBheddn
#279 interdiff_277-279.txt919 bytesheddn
#277 interdiff_271-277.txt928 bytesranjith_kumar_k_u
#277 2946333-277.patch129.13 KBranjith_kumar_k_u
#271 2946333-9.3.x-271.patch129.25 KBjordik
#269 2946333-9.3.x-269.patch129.26 KBp-neyens
#268 2946333-9.3.x-268.patch129.2 KBp-neyens
#266 reroll_diff_258-266.txt3.35 KBj-lee
#266 2946333-9.3.x-266.patch129.19 KBj-lee
#265 2946333-9.3.x-265.patch66.85 KBj-lee
#261 diff-CS-fix.txt2.2 KBj-lee
#261 diff-deprecations.txt3.93 KBj-lee
#260 interdiff_224-260.txt13.94 KBj-lee
#260 2946333-9.2.x-260.patch129.17 KBj-lee
#259 interdiff_224-258.txt15.51 KBj-lee
#259 2946333-9.3.x-258.patch129.2 KBj-lee
#256 interdiff_224-255-9.2.txt9.86 KBj-lee
#256 2946333-9.2.x-255.patch129.12 KBj-lee
#253 interdiff_224-252.txt1.31 KBj-lee
#252 interdiff_224-252.txt9.71 KBj-lee
#252 2946333-252.patch128.94 KBj-lee
#251 interdiff_224-251.txt11.27 KBj-lee
#251 2946333-251.patch128.97 KBj-lee
#249 interdiff_238-249.txt1.31 KBj-lee
#249 2946333-249.patch142.17 KBj-lee
#248 interdiff_238-248.txt1.33 KBj-lee
#248 2946333-248.patch142.53 KBj-lee
#245 2946333-9.1.x-245.patch128.95 KBnginex
#243 2946333-9.1.x-243.patch128.8 KBnginex
#241 2946333-9.1.x-241.patch106.41 KBhswong3i
#240 interdiff_183-240.txt2.53 KBvflirt
#240 2946333-240.patch127.88 KBvflirt
#238 2946333-238.patch129.47 KBmauhg
#237 2946333-237.patch128.93 KBdinarcon
#235 2946333-235.patch129.1 KBcgoffin
#234 2946333-234.patch128.96 KBcgoffin
#233 2946333-233.patch129.1 KBcgoffin
#232 2946333-232.patch98.85 KBcgoffin
#231 2946333-231.patch68.46 KBcgoffin
#230 2946333-230.patch68.39 KBcgoffin
#227 2946333-227.patch129 KBjhedstrom
#224 interdiff_223-224.txt692 bytesnwom
#224 2946333-224.patch128.93 KBnwom
#223 2946333-223.patch128.93 KBheddn
#223 interdiff_221-223.txt927 bytesheddn
#221 interdiff_218-221.txt2.64 KBheddn
#221 2946333-221.patch128.91 KBheddn
#218 2946333-218.patch128.91 KBheddn
#218 interdiff_215-218.txt2.88 KBheddn
#215 2946333-215.patch128.51 KBravi.shankar
#202 2946333-202.patch128.22 KBmaacl
#202 interdiff_199_202.txt1.27 KBmaacl
#199 interdiff_198_199.txt900 bytesmaacl
#199 2946333-199.patch129.13 KBmaacl
#198 2946333-198.patch128.62 KBmaacl
#198 interdiff_197_198.txt2.01 KBmaacl
#197 interdiff_195_197.txt6.26 KBmaacl
#197 2946333-197.patch127.96 KBmaacl
#195 interdiff_192_195.txt12.19 KBmaacl
#195 reroll_diff_183_195.txt48.25 KBmaacl
#195 2946333-195.patch127.86 KBmaacl
#195 reroll_diff_183_192.txt100.08 KBmaacl
#192 2946333-192.patch124.53 KBsanjayk
#191 2946333-191.patch124.17 KBsanjayk
#183 2946333-183-8.8.x.patch125.19 KBaleevas
#183 2946333-183.patch125.19 KBaleevas
#180 2946333-180.patch124.97 KBoleksiy
#180 2946333-180-8.8.x.patch124.97 KBoleksiy
#2 2946333-layout_translation-2-PASS.patch11.23 KBtim.plunkett
#2 2946333-layout_translation-2-FAIL.patch5.77 KBtim.plunkett
#5 2946333-layout_translation-5.patch16.38 KBtim.plunkett
#5 2946333-layout_translation-5-interdiff.txt6.21 KBtim.plunkett
#10 2946333-fields-language-logic-than-should-be-removed.patch1.45 KBswentel
#14 2946333-field-block-translation-do-not-test.patch3.16 KBswentel
#24 2946333-layout_translation-24.patch16.36 KBartem_antonov
#25 2946333-layout_translation-25.patch16.33 KBjohnwebdev
#30 2946333-layout_translation-30.patch16.52 KBjohnwebdev
#30 interdiff_25-30.txt1023 bytesjohnwebdev
#35 2946333-layout_translation-35.patch20.4 KBjohnwebdev
#35 interdiff_30-35.txt3.67 KBjohnwebdev
#37 2946333-layout_translation-37.patch23.23 KBjohnwebdev
#37 interdiff_35-37.txt2.42 KBjohnwebdev
#42 2946333-layout_translation-42.patch28.98 KBjohnwebdev
#42 interdiff_37-42.txt8.66 KBjohnwebdev
#44 2946333-layout_translation-44--REVIEW.patch26.84 KBjohnwebdev
#44 2946333-layout_translation-44--PATCHED.patch36.39 KBjohnwebdev
#48 2946333-layout_translation-48.patch26.9 KBplach
#53 2946333-53.patch26.4 KBtedbow
#55 interdiff-53-55.txt7.16 KBtedbow
#55 2946333-55.patch30.94 KBtedbow
#58 2946333-58_plus_2942907-35.patch75.77 KBtedbow
#58 interdiff-55-58.txt15.32 KBtedbow
#58 2946333-58-do-not-test.patch32.84 KBtedbow
#61 interdiff-58-60.txt6.35 KBtedbow
#61 2946333-60.patch31.87 KBtedbow
#61 2946333-60_plus_2942907-35.patch74.8 KBtedbow
#62 interdiff-61-62.txt9.47 KBtedbow
#62 2946333-62-do-not-test.patch28.74 KBtedbow
#62 2946333-62_plus_2942907-35.patch71.67 KBtedbow
#67 2946333-67-do-not-test.patch28.1 KBtedbow
#67 2946333-67_plus_2942907-40.patch86.94 KBtedbow
#67 interdiff-62-67.txt10.8 KBtedbow
#71 interdiff-67-70_plus-3026434-22.txt17.29 KBtedbow
#71 2946333-70_plus-2942907-43_3026434-22.patch109.35 KBtedbow
#75 interdiff-71-75.txt4.86 KBtedbow
#75 2946333-75-do-not-test.patch33.86 KBtedbow
#75 2946333-75-plus_3026434-43_2942907-49.patch136.53 KBtedbow
#82 2946333-82-do-not-test.patch28.08 KBbendeguz.csirmaz
#83 2946333-82-plus_3026434-66_2942907-115.patch125.56 KBbendeguz.csirmaz
#85 2946333-85-do-not-test.patch31.94 KBtedbow
#85 2946333-85-plus_2942907_149.patch103.41 KBtedbow
#93 interdiff-85-93.txt10.56 KBtedbow
#93 2946333-93.patch39.46 KBtedbow
#96 interdiff-93-96.txt17.37 KBtedbow
#96 2946333-96.patch38.07 KBtedbow
#98 interdiff-96-98.txt2.14 KBtedbow
#98 2946333-98.patch38.72 KBtedbow
#104 interdiff-98-104.txt56.94 KBtedbow
#104 2946333-104-simple_translation.patch45 KBtedbow
#109 interdiff-104-109.txt46.27 KBtedbow
#109 2946333-109.patch57.15 KBtedbow
#112 interdiff-109-112.txt4.62 KBtedbow
#112 2946333-112.patch57.23 KBtedbow
#114 interdiff-112-114.txt6.99 KBtedbow
#114 2946333-114.patch63.77 KBtedbow
#115 interdiff-114-115.txt13.5 KBtedbow
#115 2946333-115.patch68.02 KBtedbow
#118 interdiff-115-118-no-whitespace.txt4.5 KBtedbow
#118 interdiff-115-118.txt6.52 KBtedbow
#118 2946333-118.patch68.41 KBtedbow
#120 interdiff-118-120.txt12.48 KBtedbow
#120 2946333-120.patch75.14 KBtedbow
#123 interdiff-120-123.txt33.03 KBtedbow
#123 2946333-123.patch93.39 KBtedbow
#125 interdiff-123-125.patch37.4 KBtedbow
#125 2946333-125.patch108.25 KBtedbow
#126 interdiff-123-125.txt37.45 KBtedbow
#136 2946333-136-reroll.patch104.87 KBtedbow
#138 interdiff-136-138.txt12.66 KBtedbow
#138 2946333-138.patch105.74 KBtedbow
#140 interdiff-138-140.txt4.18 KBtedbow
#140 2946333-140.patch104.52 KBtedbow
#142 interdiff-140-142.txt5.31 KBtedbow
#142 2946333-142.patch109.02 KBtedbow
#146 2946333-146.patch108.57 KBtedbow
#149 interdiff-146-149.txt34.69 KBtedbow
#149 2946333-149.patch110.83 KBtedbow
#151 interdiff-149-150.txt9.96 KBtedbow
#151 2946333-150.patch120.79 KBtedbow
#152 interdiff-151-152.txt13.69 KBtedbow
#152 2946333-152.patch120.74 KBtedbow
#153 interdiff-152-153.patch8.38 KBtedbow
#153 2946333-153.patch119.18 KBtedbow
#154 2946333-154.patch117.94 KBtedbow
#154 interdiff-153-154.txt9.8 KBtedbow
#155 interdiff-154-155.txt2.87 KBtedbow
#155 2946333-155.patch118.37 KBtedbow
#157 interdiff-155-157.txt25.45 KBtedbow
#157 2946333-157.patch120.6 KBtedbow
#160 interdiff-157-160.txt9.87 KBtedbow
#160 2946333-160.patch128.25 KBtedbow
#168 2946333-168.patch125.54 KByogeshmpawar
#168 interdiff-2946333-164-168.txt760 bytesyogeshmpawar
#171 2946333-171.patch125.59 KBgodotislate
#171 interdiff-2946333-168-171.txt685 bytesgodotislate
#178 2946333-178-8.8.x.patch125.12 KBoleksiy
#178 2946333-178.patch125.12 KBoleksiy
#178 reroll_diff_171-178.txt11.8 KBoleksiy

Issue fork drupal-2946333

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

Chi created an issue. See original summary.

tim.plunkett’s picture

Title: Field block should translate field values » Layout Builder does not respect translations
Version: 8.5.x-dev » 8.6.x-dev
Status: Active » Needs review
Issue tags: +Blocks-Layouts, +Needs issue summary update
StatusFileSize
new11.23 KB
new5.77 KB

FieldBlock is actually fine here, it's that Layout Builder is passing it the wrong entity.

This is somewhat related to #2942907: Entity system does not provide an API for retrieving an entity variant that is safe for editing

The last submitted patch, 2: 2946333-layout_translation-2-PASS.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 2: 2946333-layout_translation-2-FAIL.patch, failed testing. View results

tim.plunkett’s picture

Status: Needs work » Needs review
StatusFileSize
new16.38 KB
new6.21 KB

Thanks d.o for installing in a subdir to catch my naïve implementation of getting the translated entity URL.
Also had to fix/expand the OverridesSectionStorageTest.

samuel.mortenson’s picture

  1. +++ b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php
    @@ -172,8 +174,18 @@ public function buildMultiple(array $entities) {
    +    if ($this->isOverridable() && $entity->hasField('layout_builder__layout')) {
    +      if (!$entity->get('layout_builder__layout')->isEmpty()) {
    +        return $entity->get('layout_builder__layout')->getSections();
    +      }
    +
    +      // If this is a translation consult the default translation.
    +      if ($entity instanceof TranslatableInterface && !$entity->isDefaultTranslation()) {
    +        $default_entity = $entity->getTranslation(LanguageInterface::LANGCODE_DEFAULT);
    +        if ($default_entity instanceof FieldableEntityInterface && $default_entity->hasField('layout_builder__layout') && !$default_entity->get('layout_builder__layout')->isEmpty()) {
    +          return $default_entity->get('layout_builder__layout')->getSections();
    +        }
    +      }
    

    Just following this logic - if I create a translation of an overriden entity, then remove all sections on that translation and save, the default translation's sections will show up. I thought that if a translatable field didn't have a translated value, accessing it would get the default value. This just seems a bit manual to me, but I could be wrong.

  2. +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
    @@ -125,8 +141,14 @@ public function extractIdFromRoute($value, $definition, $name, array $defaults)
    +      list($entity_type_id, $entity_id) = explode('.', $id);
    ...
    +      if ($entity instanceof EntityInterface && $entity instanceof TranslatableInterface) {
    +        $translated_entity = $this->entityRepository->getTranslationFromContext($entity);
    +        if ($translated_entity instanceof FieldableEntityInterface && $translated_entity->hasField('layout_builder__layout')) {
    +          return $translated_entity->get('layout_builder__layout');
    +        }
    +      }
    

    In getStorageId, IDs of translatable entities should now contain their langcode. Why does this method not respect that and check for a langcode when exploding the ID?

tim.plunkett’s picture

Issue tags: +sprint

Tagging as this is currently on our shortlist.

swentel’s picture

Tested this patch on site we're working on and works fine. This is what we saw happening.
The site has 2 languages, where Dutch is the default.
It might be important to note that there were no fields rendered on the page, just views blocks and custom content (text) blocks.

Before the patch

The layout of the dutch node was overridden and had some custom blocks on them.
When we went to the english version, there was just a blank page.
When we edited and added a block, the Dutch version also was affected.

After the patch

When going to the english version, we saw the blocks that were configured on the Dutch version.
When clicking on layout, all the sections were empty and when adding a block, saving and viewing it in the frontend, we could see that one block. The Dutch version was also still rendered as configured before.

So the patch works fine, and I'm totally fine with the behavior at the moment which is:

  • Show the blocks from the default translation on the canonical page
  • Show nothing when on the layout edit page

So +1 from me.

swentel’s picture

There's still one problem with translations and it seems to be a layout builder's problem in combination with entity references.

Setup:

  • two languages
  • translatable taxonomy term
  • on the content type, create entity reference field with this taxonomy as target
  • content type itself is translatable, except for the term - so they sync
  • configure the default layout for this node, put the term field on it, use label for display (but I guess it's a problem for all formatters)
  • now create a term and translate it to both languages
  • create a node in language A, select that term
  • translate the node to language B, the term should be selected still and save it
  • The term on the display will be rendered in the original language. This doesn't happen when not using the layout builder for layout.

Will dig a bit to see if I can quickly find this problem.

swentel’s picture

So the problem is in layout_builder/EntityViewDisplay::buildMultiple().

It contains logic to determine the language the field should be rendered in, but with the current patch, the right entity is already known.
It's then up to the formatters to determine what needs to happen which can be tricky business, especially in case of entity references. However, EntityReferenceFormatterBase::getEntitiesToView() has logic to determine further what needs to happen with the referenced entity, so in the end, the actual fix, is deleting code in layout builder :)

I've attached the diff to see what needs to be removed. Translatable entity reference which are synced are fine now. Less complex fields, like say body, which are marked as translatable on the content type still work fine as well.

Could use confirmation from plach and/or berdir of course :)

- edit - ignore the patch, completely wrong hacking, my god

swentel’s picture

Crap, I did something completely wrong here, hacking in the wrong class, I guess it's too warm :( Ignore my comment in #10

However, the problem is still there, it's in the wrong language.

swentel’s picture

Other than that, while debugging, I noticed that (some) fields are being built twice. However, given my brainfart hacking in #10
I will re-confirm that before I open an issue for that (not sure if it's related with this patch or not)

swentel’s picture

Ok, so, the problem with entity reference and language is related with #2955392: EntityViewBuilder::viewField() does not respect entity current language when used with an entity reference field. The patch in that issue solves it here, going to figure out if I can make something out of it.

Probably important: I'm not even using the current patch at all at the moment, all other fields, marked as translatable on the content type are rendered fine, but it's in the case of synced field on the content type with a translatable reference item where it goes wrong at the moment.
(using default layout, not tested layout per entity, iirc, that's what this patch fixes)

swentel’s picture

Ok, attached diff which respects fields and translations now.
There's much code duplication as the singleFieldDisplay method is protected in the entityViewBuilder.
Just leaving here, I don't necessarily think this should be included, unless we add it temporarily and remove it when the core issue is fixed.

swentel’s picture

swentel’s picture

So in conclusion:

  • The patch in #5 fixes the problem when you allow layout overrides per node and have translations
  • FieldBlock has a problem with translated fields. This is a totally separate problem - and in way, my gut feeling tells me that is the intention of the original bug report to begin with and you were fixing the wrong problem :)

Next steps:

(glad I figured this out finally)

tim.plunkett’s picture

Priority: Normal » Major
Status: Needs review » Needs work
Issue tags: -sprint

Bumping to major, but removing from our tag since it's not actively being worked on.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

johnwebdev’s picture

Title: Layout Builder does not respect translations » Layout overrides cannot be translated
Issue summary: View changes
Issue tags: -Needs issue summary update
johnwebdev’s picture

Issue summary: View changes
johnwebdev’s picture

@swentel Great work! I've updated the issue summary to be scoped to focus on:

The patch in #5 fixes the problem when you allow layout overrides per node and have translations

Feel free to open a follow-up issue for the FieldBlock issue.

tim.plunkett’s picture

ariane’s picture

In drupal core 8.6.1 I am unable to apply patch in #5

Could not apply patch! Skipping. The error was: Cannot apply patch https://www.drupal.org/files/issues/2946333-layout_translation-5.patch

artem_antonov’s picture

StatusFileSize
new16.36 KB

@ariane i just fixed a patch from #5 for 8.6.1 version in order to be applied with composer

johnwebdev’s picture

StatusFileSize
new16.33 KB

Reroll of #5

johnwebdev’s picture

Status: Needs work » Needs review
andypost’s picture

IS needs point how that gonna be solved

The last submitted patch, 24: 2946333-layout_translation-24.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 25: 2946333-layout_translation-25.patch, failed testing. View results

johnwebdev’s picture

StatusFileSize
new16.52 KB
new1023 bytes

Back to green!

johnwebdev’s picture

Status: Needs work » Needs review
johnwebdev’s picture

Status: Needs review » Needs work

The last submitted patch, 30: 2946333-layout_translation-30.patch, failed testing. View results

tedbow’s picture

Issue tags: +Needs tests

I testing this patch with inline blocks and I ran into a problem

To see the error you have to have the changes in #2968110: Layout Builder's ConfigureBlockFormBase forms do not display validation errors on submit
You will still get the error without it just won't be displayed.

  1. Enabled layout and overrides for articles
  2. enabled content translation
  3. added spanish. kept english defualt
  4. Enabled translation on articles kept all defaults
  5. created a node.
  6. save an override with a inline block. Label = "Block en"
  7. created a translation of the node
  8. goto 'es/node/1/layout' to change translations layout.
  9. edit inline block label = "Block es"
  10. save layout.
  11. view node/1 = block label ="Block en"
  12. view es/node/1 = block label = "Block es"
  13. So far so good 😊
  14. goto node/1/layout
  15. configure inline block.
  16. save the block.
  17. Error

    The content has either been modified by another user, or you have already submitted modifications. As a result, your changes cannot be saved.

    If you don't have #2968110: Layout Builder's ConfigureBlockFormBase forms do not display validation errors on submit the form just won't submit with no error.

This is because the Spanish version of the layout saves the same block_content entity. So it creates a newer revision.
When the english layout form tries save the previous entity it gets this error because there is a newer revision.

Possible solutions

If block_content entities are not translatable

When creating the translate layout field override duplicate the block so it is new entity.
This what we do if you are creating an override for the first time.

OR we could only duplicate the block the first time user tries to make a change.

But either way the spanish translation would never see newer changes made to the inline block in the english layout translation because we reference revisions.

If block_content entities are translatable

In this case instead of just creating a duplicate we could actually create a translation of the block.

Non-revisionable entities with Layouts

I haven't tested this but looking at \Drupal\layout_builder\InlineBlockEntityOperations::handlePreSave()
if entity with the layout is not revisionable if we don't duplicate that block we would always be saving the revision of the inline because we would not be creating a new revision.

So the changing the inline block in either layout would effect both layouts. So this favors just duplicating the inline blocks when making a translation.

Either way we need tests for the inline block functionality so tagging.

johnwebdev’s picture

Status: Needs work » Needs review
StatusFileSize
new20.4 KB
new3.67 KB

Here is a updated patch with a failing test based on comment #34.

Status: Needs review » Needs work

The last submitted patch, 35: 2946333-layout_translation-35.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

johnwebdev’s picture

Status: Needs work » Needs review
StatusFileSize
new23.23 KB
new2.42 KB

Let's see what happens now.

nimoatwoodway’s picture

Works with 8.6.2 too! Many thanks! Saved my day!

johnwebdev’s picture

Status: Needs review » Needs work

Setting back to NW, still working on this

tim.plunkett’s picture

johnwebdev’s picture

Issue summary: View changes
johnwebdev’s picture

Status: Needs work » Needs review
StatusFileSize
new28.98 KB
new8.66 KB

This patch includes patch from #3013197: Cloning an implementation of SectionListInterface does not deep clone the Section or SectionComponent objects. Added tests for translating a node, and then overriding it from default.

Also added a test for deleting a translation which currently fails. So i'm expecting this to go back to NW soon :)

Added #3013340: Add langcode column to inline_block_usage which most likely blocks this issue.

Status: Needs review » Needs work

The last submitted patch, 42: 2946333-layout_translation-42.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

johnwebdev’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
StatusFileSize
new26.84 KB
new36.39 KB

Ok, here are one patch with patches from related issues to make it pass, and a review patch. Now looking for reviews!

The last submitted patch, 44: 2946333-layout_translation-44--REVIEW.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

johnwebdev’s picture

Issue summary: View changes
tedbow’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/layout_builder/src/InlineBlockEntityOperations.php
    @@ -148,9 +150,21 @@ public function handlePreSave(EntityInterface $entity) {
    +    if ($entity instanceof TranslatableInterface && $entity->isTranslatable()) {
    +      if (!$entity->isNew() && $entity->isNewTranslation() && !$entity->isDefaultTranslation()) {
    +        $is_new_translation = TRUE;
    +        if ($this->isEntityUsingFieldOverride($entity) && !empty($sections)) {
    +          $duplicate_blocks = TRUE;
    +        }
    +      }
    +    }
    

    Shouldn't all this logic be moved within the if statement where we check if $sections?

    Also we need a comment to why we need to duplicate blocks on translations.

  2. Any thoughts #34 section If block_content entities are translatable?

    It seems weird that inline blocks would the default language when we know they are part of the layout translation. But also when we first duplicate them they obviously aren't the new language yet.

    What if when they are edited if they are translatable and they aren't language of the current translation when set them to be?

plach’s picture

StatusFileSize
new26.9 KB

Here's a plain reroll of #44 now that the blocker is in. #47 was not addressed.

julenmelgar’s picture

Hi, in version 8.6.4 the patch stops working.

tedbow’s picture

@julenmelgar thanks for reporting but patches are not expected to apply/work except against the git branch for Version on the issue. In this case that is 8.7.x.

tim.plunkett’s picture

Was going to tag this as `Needs reroll` but it applies cleanly. But it no longer passes tests, so still NW

johnwebdev’s picture

[#48] misses patch from #3013340: Add langcode column to inline_block_usage, but i'll let the issue remain in NW for now. will look in to this soon, but if someone is interested in continuing the patch feel free to do so

tedbow’s picture

Status: Needs work » Needs review
StatusFileSize
new26.4 KB

just a reroll

Status: Needs review » Needs work

The last submitted patch, 53: 2946333-53.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tedbow’s picture

StatusFileSize
new7.16 KB
new30.94 KB

test fixes

tedbow’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 55: 2946333-55.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tedbow’s picture

Status: Needs work » Needs review
StatusFileSize
new75.77 KB
new15.32 KB
new32.84 KB

Ok this is a first pass to produce a patch using the patch @plach produced in #2942907-35: Entity system does not provide an API for retrieving an entity variant that is safe for editing(say that 3 times fast 😜)

\Drupal\Tests\layout_builder\FunctionalJavascript\InlineBlockTranslationTest will still fail. I started to address that but the logic for inline blocks still has the assumption of how overrides store their sections which needs to refactored. I am going to create an issue for that.

The last submitted patch, 58: 2946333-58_plus_2942907-35.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

johnwebdev’s picture

I know that you're addressing different issues currently, but I still want to adress your question in #47

Any thoughts #34 section If block_content entities are translatable?
It seems weird that inline blocks would the default language when we know they are part of the layout translation. But also when we first duplicate them they obviously aren't the new language yet.

...

I actually lifted this up previously in the proposed serialisation approach and the only benefit I can see is that it would allow us to have the layout not being translatable, but being able to translate the inline blocks. However, there is no UI for that (since we hide the inline blocks from the other UIs.). Maybe there could be a UI if there is a need for this.

What if when they are edited if they are translatable and they aren't language of the current translation when set them to be?

We need to prevent this, and if we were to use translations, we'd need to make sure they don't set the language than the current language to a different language anyway.
There are obviously many more scenarios we need to take in to consideration if we were to go for this approach. What about non-translatable fields on the block?

Are there other benefits I'm overlooking?

tedbow’s picture

+++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
@@ -349,4 +381,47 @@ public function isApplicable(RefinableCacheableDependencyInterface $cacheability
+    if ($langcode) {
+      $contexts[] = new Context(new ContextDefinition('language', 'Interface text'), $langcode);
+      $contexts[] = new Context(new ContextDefinition('language', 'Content'), $langcode);
+    }

@plach point out that I could use \Drupal\Core\Plugin\Context\ContextRepositoryInterface::getAvailableContexts() here. So that removes the need for the language manager and finding the langcode all together.

tedbow’s picture

Ok. this patch focuses on only getting \Drupal\Tests\layout_builder\Functional\LayoutBuilderTranslationTest to pass. I have taken out logic that may have just been put into to deal with inline blocks logic. Those test will fail.

It seems the only non-tests changes are to \Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage which makes sense. This may be easier to review while work is done on #3026434: Ensure that Layout Builder Inline Blocks doesn't assume section storage internals

The last submitted patch, 61: 2946333-60_plus_2942907-35.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Status: Needs review » Needs work

The last submitted patch, 62: 2946333-62_plus_2942907-35.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tedbow’s picture

+++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
@@ -110,7 +139,11 @@ protected function getEntity() {
-    return $entity->getEntityTypeId() . '.' . $entity->id();
+    $id = $entity->getEntityTypeId() . '.' . $entity->id();
+    if ($entity instanceof TranslatableInterface) {
+      $id .= '.' . $entity->language()->getId();
+    }

ok since we are getting the language from the context not the only reason we need it added to the storage id is so the tempstore will be vary by language.

There is not reason tempstore id has to exactly match the storage id but I don't think we can get the language inside \Drupal\layout_builder\LayoutTempstoreRepository::set() because we only have a SectionStorageInterface instance.

tim.plunkett’s picture

tedbow’s picture

Status: Needs work » Needs review
StatusFileSize
new28.1 KB
new86.94 KB
new10.8 KB

Ok here is patch with the changes to the API here: #2942907-40: Entity system does not provide an API for retrieving an entity variant that is safe for editing

I removed the test methods from \Drupal\Tests\layout_builder\Kernel\OverridesSectionStorageTest: testGetSectionListFromIdTranslatableNoTranslation and testGetSectionListFromIdTranslatable

because \Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage::getSectionListFromId() has no logic for translation anymore it just calls \Drupal\Core\Entity\EntityRepositoryInterface::getActive()

We do have to test right now if it can handle when the langcode is in the storage id but that is it. So expanded the dataProvider for testGetSectionListFromId() to cover this. This may change with #3026698: Allow section storage to provide a more granular ID for tempstore if the storage id is always the same but the tempstore id is different.

The new API allows us to remove the thin helper method I had for \Drupal\Core\Entity\EntityRepositoryInterface::getActive() to load the entity and get the contexts, since this is now handled by \Drupal\Core\Entity\EntityRepositoryInterface::getActive() 🎉

Status: Needs review » Needs work

The last submitted patch, 67: 2946333-67_plus_2942907-40.patch, failed testing. View results

plach’s picture

Quick code review:

+++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
@@ -84,7 +94,9 @@ public static function create(ContainerInterface $container, array $configuratio
+      $container->get('context.repository')

This can go now :)

tedbow’s picture

Status: Needs work » Needs review
Related issues: +#3013340: Add langcode column to inline_block_usage

Fixed #69

This is mega patch with #2942907: Entity system does not provide an API for retrieving an entity variant that is safe for editing & #3026434: Ensure that Layout Builder Inline Blocks doesn't assume section storage internals

  1. I had #3026698: Allow section storage to provide a more granular ID for tempstore applied but I don't think we need this for patch.
  2. At least \Drupal\Tests\layout_builder\FunctionalJavascript\InlineBlockTranslationTest::testDeletingTranslatedEntityWithInlineBlock() should fail. Because it is expects that the inline block would be deleted when you delete a translation.
    For that to work we would need #3013340: Add langcode column to inline_block_usage. But I think there problems with that approach. Namely before you make a translation you can actually switch the language of the entity with the layout. This would complicate the tracking.

    One option would be to just not delete any inline blocks until the parent entity is deleted. This would you would have inline for translations that have not been delete but you would not be deleting in blocks for previous revisions that had a different language when you delete the translation that was afterwards added in that language. You can read more about the scenario on #3013340: Add langcode column to inline_block_usage

  3. +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
    @@ -92,7 +104,15 @@ public static function create(ContainerInterface $container, array $configuratio
    +    if (count($section_list) === 0 && $entity instanceof TranslatableInterface && !$entity->isDefaultTranslation()) {
    +      // If a translated entity has no sections the untranslated entity's
    +      // sections should be used.
    +      $entity = $entity->getUntranslated();
    +      $section_list = $entity->get(static::FIELD_NAME);
    +    }
    +    return $section_list;
    

    My idea here is that if there no sections on the translation we need to get the sections from untranslated entity.

    This is true but if we actually return back the $section_list itself then this will be used to update the sections when configuring the translated override. This results in the translated override actually being stored in the default language key.

    So the solution here was just to copy the sections into the translated section list if empty. This will work for render and for configuring the entity.

tedbow’s picture

Whoops here are the patches

Status: Needs review » Needs work

The last submitted patch, 71: 2946333-70_plus-2942907-43_3026434-22.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tim.plunkett’s picture

+++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
@@ -110,7 +130,11 @@ protected function getEntity() {
   public function getStorageId() {
     $entity = $this->getEntity();
-    return $entity->getEntityTypeId() . '.' . $entity->id();
+    $id = $entity->getEntityTypeId() . '.' . $entity->id();
+    if ($entity instanceof TranslatableInterface) {
+      $id .= '.' . $entity->language()->getId();
+    }
+    return $id;
   }

This should be done in #3026698: Allow section storage to provide a more granular ID for tempstore

ismail cherri’s picture

I tested the patch in #71 and it fixes the issue in Drupal 8.7.x-dev

tedbow’s picture

Status: Needs work » Needs review
StatusFileSize
new4.86 KB
new33.86 KB
new136.53 KB

Status: Needs review » Needs work

The last submitted patch, 75: 2946333-75-plus_3026434-43_2942907-49.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

nikhileshpaul’s picture

Just to be clear, does this issue affect only versions 8.6.x+ or not. I was able to update the translated content in my Drupal installation 8.5.4, but not in 8.6.1

tedbow’s picture

Status: Needs work » Postponed
  1. re #77 not sure of your question. You can still translate content. The problem here is for having a different layout override per language.
  2. +++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTranslationTest.php
    @@ -0,0 +1,250 @@
    +    $this->drupalGet($translated_layout_url);
    ...
    +    $assert_session->linkExists('Save Layout');
    ...
    +    $assert_session->addressEquals($translated_entity_url);
    +    $assert_session->pageTextNotContains('The untranslated field value');
    +    $assert_session->pageTextContains('The translated field value');
    +    $assert_session->pageTextNotContains('Powered by Drupal');
    

    #2942907: Entity system does not provide an API for retrieving an entity variant that is safe for editing needs a reroll so not providing a patch now but I found a problem here.

    If we add after this the check

    // Ensure the untranslated entity still has the added block.
    $this->drupalGet($entity_url);
    $assert_session->pageTextNotContains('The translated field value');
    $assert_session->pageTextContains('The untranslated field value');
    $assert_session->pageTextContains('Powered by Drupal');
    

    The last assert here will fail. That is because the issue detail in this new issue #3033686: Saving Layout override will revert other field values to their values when the Layout was started.

    Basically what happens in the test is:

    1. translated_layout_url is hit before we have a saved override for the untranslated entity.
    2. This version of the entity is saved in the tempstore for the translated layout
    3. The untranslated layout is saved.
    4. The translated layout is saved contain the empty sections from tempstore for the untranslated layout.

    This wipes a layout but points a general problem that we save all the previous field values in the tempstore so this would happen with any field value which why I created the other issue to address this first.

tedbow’s picture

tedbow’s picture

Assigned: Unassigned » tedbow
tim.plunkett’s picture

Status: Postponed » Needs work

The blocker is back in!

bendeguz.csirmaz’s picture

StatusFileSize
new28.08 KB

Here's a reroll of "2946333-75-do-not-test.patch".
I had to remove the OverridesSectionStorageTest modifications, because that class is completely different now.

johnwebdev’s picture

Status: Needs work » Needs review
tedbow’s picture

StatusFileSize
new31.94 KB
new103.41 KB

@bendeguz.csirmaz thanks rerolling. Sorry this issue is a complicated mess right now.

reroll of https://www.drupal.org/files/issues/2019-02-11/2946333-75-do-not-test.patch

we don't actually need #3026434: Ensure that Layout Builder Inline Blocks doesn't assume section storage internals right now.
So just including #2942907-149: Entity system does not provide an API for retrieving an entity variant that is safe for editing

I am not sure the current approach is going to work.

I talked this over with @tim.plunkett and it might be better to have a new section storage
TranslationOverridesSectionStorage extends OverridesSectionStorage
Then we can give it a weight so it has priority over OverridesSectionStorage

tim.plunkett’s picture

I'm not even sure you want to extend it. Possibly, but I'd suggest trying without that first

Status: Needs review » Needs work

The last submitted patch, 85: 2946333-85-plus_2942907_149.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tedbow’s picture

Issue summary: View changes

updated Proposed resolution with detail description

johnwebdev’s picture

Sorry, I'm not up-to-date per se (but I read the updated issue), but why would we need a TranslationOverridesSectionStorage?

johnwebdev’s picture

@tedbow Please adress my concerns in #60 about translating the inline blocks

tedbow’s picture

Issue summary: View changes

I talked with both @johndevman and @plach today about this issue and specifically how inline block translation will work.

  1. One thing that @johndevman pointed out that I had not considered was that translatable block bundles can have some fields that are not translatable. This could lead to editing some fields on an inline block and having that effect multiple translations.

    For this reason I think we should actually be duplicating the blocks when we are creating a translation of the entity with layout rather than creating translations of the blocks. The other reason this will be better is because once you translate an override it is has no connection to the untranslated override. You can add new blocks and reorder blocks. So there will not be a way logically through the UI to connect a block to its translation on a translated override. They could be in totally different parts of the layout or not exist at all.

  2. Another consideration I didn't take into account whether the layout_builder__layout field itself is translatable. It may not be translatable even when the bundle is translatable.

    I talked with @plach about this and he suggested that if the layout_builder__layout field itself is NOT translatable then we should offer the ability to translate the individual inline blocks.

    While I agreed with this when we chatted I have since had second thoughts

    There are 2 big problems with this:
    J

    1. Just because the layout_builder__layout field is not translatable doesn't mean that the field won't later be turned on for translations

      If we have different behavior for when layout_builder__layout field is not translatable vs when it is translatable then we have to some sort of migration for when the field becomes translatable. Would we do that for all entities of the bundle or just as the new translated override layout gets created for an entity that didn't have 1? If don't do migration should we keep offering the ability translate the individual inline blocks if you haven't create an translated override yet?

      I think there are just 2 many possibilities and the migration could be very tricky and involve thousands of entities.

    2. currently inline blocks have not path for editing so what would the UI for providing translations look like. Would it be in context of the larger layout which is not alterable per language?
  3. So my suggestion would be
    If layout_builder__layout field is not translatable don't allow any translating of inline blocks

    If layout_builder__layout field is translatable duplicate the inline blocks when creating a layout translation

tedbow’s picture

Status: Needs work » Needs review

Here is an updated

  1. +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
    @@ -103,7 +113,22 @@ public static function create(ContainerInterface $container, array $configuratio
    +    if (count($section_list) === 0 && $entity instanceof TranslatableInterface && !$entity->isDefaultTranslation()) {
    +      // If a translated entity has no sections the untranslated entity's
    +      // sections should be used.
    +      // We have to actually set the sections on the translated entity and
    +      // return that sections list. If we return $untranslated_section_list
    +      // then the untranslated field value will be updated.
    +      /** @var \Drupal\layout_builder\Field\LayoutSectionItemList $untranslated_section_list */
    +      $untranslated_section_list = $entity->getUntranslated()->get(static::FIELD_NAME);
    +      foreach ($untranslated_section_list->getSections() as $untranslated_section) {
    +        $section_list->appendSection($untranslated_section);
    +      }
    +    }
    +    return $section_list;
    

    this logic I just don't think is going to work.

    I have changed the patch so that there will be separate section override section storage per entity translation.

  2. I have not update any of the logic for the inline blocks yet.
  3. I will provide self-review/explanation comment in the next couple hours
tedbow’s picture

StatusFileSize
new10.56 KB
new39.46 KB

whoops here is the patch

Also #2942907: Entity system does not provide an API for retrieving an entity variant that is safe for editing was committed!!!!!!! So no combo patch!!

tim.plunkett’s picture

Is this 1:1 built on top of #2942675: Layout builder should use the active variant of an entity to avoid orphaned revisions or just similar?

I think the proposed resolution is good.
Just some concerns about how self-contained this logic should or could be.

  1. +++ b/core/modules/layout_builder/layout_builder.module
    @@ -326,3 +326,20 @@ function layout_builder_system_breadcrumb_alter(Breadcrumb &$breadcrumb, RouteMa
    +    $translatable_fields = $translation->getTranslatableFields();
    +    if (array_key_exists(OverridesSectionStorage::FIELD_NAME, $translatable_fields)) {
    
    +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
    @@ -398,4 +397,58 @@ public function isOverridden() {
    +      $translatable_fields = $entity->getTranslatableFields(FALSE);
    +      return array_key_exists(static::FIELD_NAME, $translatable_fields) && $entity->isTranslatable();
    

    This seems like it should have a method somewhere. Could be a follow-up of course, might even be an "upstream" addition.

  2. +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php
    @@ -141,12 +142,20 @@ protected function prepareLayout(SectionStorageInterface $section_storage) {
    -    elseif ($section_storage instanceof OverridesSectionStorageInterface && !$section_storage->isOverridden()) {
    ...
    +      if ($section_storage->isTranslatable() && !$section_storage->isOverridden() && !$section_storage->isDefaultTranslation()) {
    ...
    +      elseif (!$section_storage->isOverridden()) {
    

    Looks like you could keep the !$section_storage->isOverridden() in the original conditional?

  3. +++ b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php
    @@ -288,6 +289,11 @@ protected function buildSections(FieldableEntityInterface $entity) {
         $storage = $this->sectionStorageManager()->findByContext($contexts, $cacheability);
    +    if ($storage instanceof OverridesSectionStorageInterface) {
    +      if ($storage->isTranslatable() && !$storage->isDefaultTranslation() && !$storage->isOverridden()) {
    +        $storage = $storage->getDefaultTranslationSectionStorage();
    +      }
    +    }
    

    I have no idea how possible this might be, but could this be contained within findByContext() somehow, somewhere within that call stack?

  4. +++ b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php
    --- a/core/modules/layout_builder/src/OverridesSectionStorageInterface.php
    +++ b/core/modules/layout_builder/src/OverridesSectionStorageInterface.php
    
    +++ b/core/modules/layout_builder/src/OverridesSectionStorageInterface.php
    @@ -31,4 +31,28 @@ public function getDefaultSectionStorage();
    +  public function isTranslatable();
    ...
    +  public function isDefaultTranslation();
    ...
    +  public function getDefaultTranslationSectionStorage();
    

    While our current implementation does mean that overrides == content entities, I think these 3 might deserve their own interface.
    Layout Library, for example, is a config entity that implements OverridesSectionStorageInterface.
    Plus, adding a new interface is more BC friendly.

  5. +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
    @@ -398,4 +397,58 @@ public function isOverridden() {
    +          'view_mode' => new Context(ContextDefinition::create('string'), 'full'),
    

    Whenever hardcoding full, please include the @todo we have elsewhere.
    It's something like // @todo Expand to work for all view modes in https://www.drupal.org/node/2907413.
    Also see the code in OverridesSectionStorage::deriveContextsFromRoute() that ensures the view mode is the 100% correct one

Status: Needs review » Needs work

The last submitted patch, 93: 2946333-93.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tedbow’s picture

Status: Needs work » Needs review
StatusFileSize
new17.37 KB
new38.07 KB
  1. I am not sure I am actually doing this right. If this is checking for the field being able to translatable or if it is currently translatable. I would like confirm before trying to generalize this.
  2. fixed and simplified
  3. I looked at a couple ways to do this. It is possible in isApplicable() but setting the entity to the untranslated if the default is not override. But feels wrong to set something there is is*() method.

    I look at doing it in setContext() but this is also called in load() which has other side-effects

    I could put the logic directly in but doesn't seem much better.

  4. Created new interface
  5. fixed

also did this with tests

Non-translatable layout field
if the "Layout" field for a bundle is set to NOT be translatable then the Layout tab should not be shown for translations.

Status: Needs review » Needs work

The last submitted patch, 96: 2946333-96.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tedbow’s picture

Status: Needs work » Needs review
StatusFileSize
new2.14 KB
new38.72 KB
+++ b/core/modules/layout_builder/src/InlineBlockEntityOperations.php
@@ -264,4 +268,20 @@ protected function saveInlineBlockComponent(EntityInterface $entity, SectionComp
+  private function isNewEntityTranslation(EntityInterface $entity) {
+    if ($entity instanceof TranslatableInterface && $entity->isTranslatable() && $entity->isNewTranslation()) {
+      return TRUE;
+    }
+    return FALSE;
+  }

Actually we don't need to be testing here if the entity itself is a new translation. It never will be using the layout builder as the entity has to already created.

We need to check if the layout translation is new. So for this translation were the section empty before.

I have fix for this. I thought I could use $entity->original but this does not work.

I had to do.
$unchanged_translation = $this->entityTypeManager->getStorage($entity->getEntityTypeId())->loadUnchanged($entity->id())->getTranslation($entity->language()->getId());

So I am loading the unchanged and then getting the translation we are currently working with.

This gets the test methods in InlineBlockTranslationTest to pass except for testDeletingTranslatedEntityWithInlineBlock() which I don't expect to work yet.

Status: Needs review » Needs work

The last submitted patch, 98: 2946333-98.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

grahl’s picture

Hi there

I was evaluating Layout Builder as a Paragraphs alternative and found this issue when looking around for inline block translations. I'm concerned about the feedback in #91, specifically:

For this reason I think we should actually be duplicating the blocks when we are creating a translation of the entity with layout rather than creating translations of the blocks. The other reason this will be better is because once you translate an override it is has no connection to the untranslated override. You can add new blocks and reorder blocks.

That approach is impractical for the vast majority of editors I've worked with. It would mean that after initial page creation, that adding a block to n languages would mean creating n blocks. Reordering a page would have to be done in each language separately. Furthermore, even if you use media entities, changing a reference to another media entity could mean editing n blocks again, even if the field itself is untranslated.

The proposed solution in #91 is a consistent solution to asynchronous translations but I believe the synced translations are the much more common scenario, and if I read the issue correctly, synced translations are not planned to be supported, right?

The solution which would make most sense to me would be to make layout overrides untranslatable (that can be set already but makes "Layout" disappear) and add a translation edit mask for inline blocks (in lieu of "customize" on the block).

Is there maybe a chance that this approach could be revisited or amended for this use-case?

tedbow’s picture

Issue summary: View changes

@grahl you concerns are very similar to others I have heard from @Gábor Hojtsy, @plach, @lauriii and others.

I think agree need to switch to supporting synced translations.

Right now in 8.7.x we have minimal support for un-synced translations but this was unintended in consequence of #3004536: Move the Layout Builder UI into an entity form for better integration with other content authoring modules and core features.

I was assuming we had to support this because to take away this functionality would be BC break. But since #3004536 was not backported to 8.6.x this functionality has actually never been in core release! so are free to not support this anymore.

I am working on a more detailed plan. But right now the idea is to have the actually layout not be translatable but have 2 things be translatable

  1. The inline blocks(if the block types are translatable)
  2. The block labels which are entered in the Layout builder.
grahl’s picture

@tedbow Thanks so much for the quick response, the approach sounds great!

Will keep an eye on this issue to see if I can assist with that feature in some way.

tedbow’s picture

Title: Layout overrides cannot be translated » Allow translating labels and inline blocks for Layout overrides
Status: Needs work » Needs review
StatusFileSize
new56.94 KB
new45 KB

Got a help from @tim.plunkett about this actually might be implemented.
Ok here is a start to #102. The interdiff is bigger than the patch so I am not sure how useful that will be.

  1. It changes to store labels in configuration of each plugin. We could also store on the component level I guess.
  2. On the layout translation override tab it only gives the ability to change labels, not inline blocks yet
  3. All other layout changes are not allowed for translations(because it actually stores in the untranslated)
  4. If there not a default translation override yet it you can't access the translation layout tab.

It doesn't handle inline blocks yet.

We also will need to be able translate the default(per bundle) layouts. Probably much of the same logic can be reused(provided we have same behavior of only allowing translating of block labels and inline blocks)

Because this method does not use the actual layout field translate value for storing translated sections(sections themselves aren't different) it leaves the possibility use field per language values later for actually creating totally independent layouts per language(truly different field values)

Status: Needs review » Needs work

The last submitted patch, 104: 2946333-104-simple_translation.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

johnwebdev’s picture

Ended up being a small review for now, will make a more in-depth review as the patch is in a more clear state. Some questions and thoughts though:

  1. +++ b/core/modules/layout_builder/src/Access/LayoutBuilderTranslationAccessCheck.php
    @@ -0,0 +1,37 @@
    +    $access = AccessResult::allowedIf(!($section_storage instanceof TranslatableSectionStorageInterface && !$section_storage->isDefaultTranslation()));
    

    Using 'not' operators make this really hard to interpret by looking at it.

  2. +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php
    @@ -105,6 +108,7 @@ public function preRender($element) {
    +    $is_translation = $section_storage instanceof TranslatableSectionStorageInterface && !$section_storage->isDefaultTranslation();
    
    @@ -114,17 +118,27 @@ protected function layout(SectionStorageInterface $section_storage) {
    +      if (!$is_translation) {
    ...
    +    if (!$is_translation) {
    

    Could we move the "$is_translation" - part to a method instead. This would make it easier to override for contrib. We don't really need to say "isTranslation", either it could be more generic, which would make sense for a contrib. like "Section lock".

  3. +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php
    @@ -231,7 +253,8 @@ protected function buildAdministrativeSection(SectionStorageInterface $section_s
    +    $is_translation = $section_storage instanceof TranslatableSectionStorageInterface && !$section_storage->isDefaultTranslation();
    

    It could be a "Read-only" state if we were to make this more generic.

  4. +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php
    @@ -240,46 +263,56 @@ protected function buildAdministrativeSection(SectionStorageInterface $section_s
    +          if (!$is_translation) {
    ...
    +          if (!$is_translation || $needs_translation_link) {
    ...
    +      if (!$is_translation) {
    

    It would be cleaner code to have separate methods for a read-only and default mode.

  5. +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php
    @@ -294,50 +327,81 @@ protected function buildAdministrativeSection(SectionStorageInterface $section_s
    +  protected function defaultComponentHasTranslatableSettings(SectionStorageInterface $section_storage, SectionComponent $component) {
    

    How would this work if the block plugin provides its own layout_builder_translation form and ignores labels?

  6. +++ b/core/modules/layout_builder/src/EventSubscriber/ComponentPluginLabelTranslate.php
    @@ -0,0 +1,46 @@
    +    if (!$plugin instanceof ConfigurableInterface && !isset($contexts['@language.current_language_context:language_interface'])) {
    ...
    +    $language = $contexts['@language.current_language_context:language_interface']->getContextValue();
    

    Do we really use the language interface here? Not the content language? Might work differently in contexts though, I don't know for sure.

  7. +++ b/core/modules/layout_builder/src/Form/BlockPluginTranslationForm.php
    @@ -0,0 +1,84 @@
    +class BlockPluginTranslationForm extends PluginFormBase implements ContainerInjectionInterface {
    

    Some obvious things here with code standards, but I suppose you haven't gotten to that yet.

  8. +++ b/core/modules/layout_builder/src/Form/BlockPluginTranslationForm.php
    @@ -0,0 +1,84 @@
    +     $form['translated_label'] = [
    +       '#title' => $this->t('Translated label'),
    

    Maybe just Label? If you add the translate form block, it should be kinda implicit anyway.

plach’s picture

I just had a first code review, I realize this is a wip so feel free to disregard all the nitpicks :)

  1. +++ b/core/modules/layout_builder/layout_builder.routing.yml
    @@ -101,6 +108,21 @@ layout_builder.update_block:
    +layout_builder.translate_block:
    +  path: '/layout_builder/translate/block/{section_storage_type}/{section_storage}/{delta}/{region}/{uuid}'
    +  defaults:
    +    _form: '\Drupal\layout_builder\Form\TranslateBlockForm'
    +    _title: 'Translate block'
       requirements:
         _permission: 'configure any layout'
         _layout_builder_access: 'view'
    

    No translation requirement here?

  2. +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php
    @@ -138,6 +152,14 @@ protected function prepareLayout(SectionStorageInterface $section_storage) {
    +        //   save since the last time this page was opened.
    

    Typo, also I'm not sure what this comment is trying to tell me, but maybe that's ok :)

  3. +++ b/core/modules/layout_builder/src/Form/BlockPluginTranslationForm.php
    @@ -0,0 +1,84 @@
    +use Drupal\Core\Render\Element;
    

    Unused

  4. +++ b/core/modules/layout_builder/src/Form/BlockPluginTranslationForm.php
    @@ -0,0 +1,84 @@
    +  protected $current_language;
    ...
    +   * BlockPluginTranslationForm constructor.
    +   *
    +   * @param $current_language
    

    Missing PHP docs

  5. +++ b/core/modules/layout_builder/src/Form/BlockPluginTranslationForm.php
    @@ -0,0 +1,84 @@
    +  public function __construct($current_language) {
    

    Missing type hint

  6. +++ b/core/modules/layout_builder/src/Form/BlockPluginTranslationForm.php
    @@ -0,0 +1,84 @@
    +    $this->current_language = $current_language;
    

    We normally use langcode to denote language codes, this should be current_langcode.

  7. +++ b/core/modules/layout_builder/src/Form/BlockPluginTranslationForm.php
    @@ -0,0 +1,84 @@
    +      $configuration['layout_builder_translations'][$this->current_language]['label']  = $form_state->getValue('translated_label');
    

    Extra whitespace before =

  8. +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
    @@ -385,4 +387,44 @@ public function isOverridden() {
    +    // @todo If not translatable should we always consider this the default
    +    //   translation?
    

    Seems fair

  9. +++ b/core/modules/layout_builder/src/TranslatableSectionStorageInterface.php
    @@ -0,0 +1,39 @@
    + * @internal
    + *   Layout Builder is currently experimental and should only be leveraged by
    + *   experimental modules and development releases of contributed modules.
    + *   See https://www.drupal.org/core/experimental for more information.
    

    Is this really the case with LB targeting stable?

  10. +++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTranslationTest.php
    @@ -0,0 +1,142 @@
    +    // The translate layout is not available.
    

    I'm not sure what this comment is trying to tell me either :)

  11. +++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTranslationTest.php
    @@ -0,0 +1,142 @@
    +    $page = $this->getSession()->getPage();
    ...
    +    $layout_url = $entity_url . '/layout';
    

    Not used

  12. +++ b/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php
    @@ -127,6 +127,7 @@ public function providerTestExtractIdFromRoute() {
    +    $this->entityRepository->getTranslationFromContext(Argument::cetera())->shouldNotBeCalled();
    

    Why?

plach’s picture

Do we really use the language interface here? Not the content language? Might work differently in contexts though, I don't know for sure.

Good point, we should probably use content language here. However with the Context API it's a bit tricky, see EntityRepository::getContentLanguageFromContexts().

tedbow’s picture

Status: Needs work » Needs review
StatusFileSize
new46.27 KB
new57.15 KB

re test fails in #104

  1. LayoutBuilderMultilingualTest failed because now you cannot add new blocks in the layout translation. Changed this use a node in different language(not a translation). This still will fail if the fix if #3019333: If you translate the literal "inline blocks" to another language in the layout builder, it stops working. is commented out.
  2. Same issue with \Drupal\Tests\layout_builder\Functional\LayoutSectionTest::testMultilingualLayoutSectionFormatter change a node in another language.

New Tests

Added basic tests for translating labels. Wanted to do this before address the reviews so I don't mess up existing behavior.

re #106

@johndevman thanks for the review.

  1. Ok made this simpler $this->isDefaultTranslation() || $this->isOverridden()
  2. I change the variable name to $sections_editabl. This allow removing most of the if statements and just using '#access' => $sections_editable.
    Since this is local var this won't really help contrib except if that wanted to undo the access restriction which is dangerous but anytime you undo access it is dangerous.
  3. changing to $sections_editable to consistent.
  4. I made misread which sections of code you were commenting on(can't wait for gitlab!) but I think using the '#access' property makes this cleaner now. Also cleaned up contextual links logic.
  5. Good point but if I block provides its own layout_builder_translation form then they should still not ignore labels. But they maybe other things to translate like in the case of inline blocks.

    So I will tackle this when I do the inline blocks logic. Because even if no label is displayed in the case of translatable inline blocks we still need to be able to translate the block.
    adding todo for now.

  6. good catch!

    Actually we have the entity that we are going to display so I think . we can just use the langcode from the entity itself. Because isn't the context on the request scope? We could be rendering an entity in a different language than the main page is, correct? So we want the langcod of the actual entity being rendered by the laytout builder. which might be a entity rendered in a view.

  7. Yes it was late. fixed
  8. fixed

re #107

@plach thanks for review

  1. Yes, sorry this was confusing. The current was only give access if it is NOT as translation. So I changed this requirement to be on all routes but with a value of either 'untranslated'(don't want to use 'default) because 'defaults' has other meaning in Layout Builder) or 'translated'. Update the access check class to handle this.
  2. fixed typo. This todo has to be handled in this issue.

    Basically right now if you open up the translated layout and at the same time you(or someone else) opens up the untranslated layout and saves untranslate first with changes, I think saving translated will undo changes. Because of the tempstore. Need to confirm and prevent this.

  3. fixed
  4. fixed
  5. fixed
  6. fixed
  7. fixed
  8. thanks for confirming. removing todo
  9. I think this is boilerplate text we put on all class that will have @internal removed. and when we mark it stable we remove all at once. That will be in the final "Mark Layout builder as stable" issue.

    Basically it is true until it is not 😜

  10. fixed
  11. updated tests use these.
  12. yes don't need that explicitly. removing
tedbow’s picture

  1. +++ b/core/modules/layout_builder/src/Access/LayoutBuilderTranslationAccessCheck.php
    @@ -0,0 +1,47 @@
    +    if ($translation_type === 'untranslated') {
    +      $access = AccessResult::allowedIf(!$section_storage instanceof TranslatableSectionStorageInterface || $section_storage->isDefaultTranslation());
    +    }
    +    elseif ($translation_type === 'translated') {
    +      $access = AccessResult::allowedIf($section_storage instanceof TranslatableSectionStorageInterface && !$section_storage->isDefaultTranslation());
    +    }
    

    Need an else here with an exception for unexpected value.

  2. +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/TranslationTest.php
    @@ -0,0 +1,140 @@
    +    drupal_static_reset();
    +    \Drupal::entityTypeManager()->clearCachedDefinitions();
    +    \Drupal::service('router.builder')->rebuild();
    +    \Drupal::service('entity.definition_update_manager')->applyUpdates();
    +
    +    $this->rebuildContainer();
    

    All of this seems be unneeded. Tests pass without it.
    Removing

Status: Needs review » Needs work

The last submitted patch, 109: 2946333-109.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tedbow’s picture

Status: Needs work » Needs review
StatusFileSize
new4.62 KB
new57.23 KB

fix #110 and code sniffer problems.

There is problem with field blocks and label translations. Pretty sure this because they are ContextAwarePluginInterface and other blocks aren't.

Need to fix and use them in tests.

johnwebdev’s picture

Re #109

2. We could use a "isEditable", method or something similar on the section storage as well. But I like this change I think, it's less duplicated code now :)
5. Well, it depends, the labels might not be used. For instance, in a project I build, we've removed them.
6. I agree, that looks good.

tedbow’s picture

StatusFileSize
new6.99 KB
new63.77 KB

fixed the field blocks issue. It won't crash layout builder now but still the translated labels won't show up because of #3039185: Allow field blocks to display the configuration label when set in Layout Builder.

So added test coverage which I have commented out with todos.

tedbow’s picture

StatusFileSize
new13.5 KB
new68.02 KB
  1. +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php
    @@ -340,4 +375,33 @@ protected function buildAdministrativeSection(SectionStorageInterface $section_s
    +      foreach ($section_storage->getDefaultTranslationSections() as $default_translation_section) {
    

    We don't actually need to check the defualt translation sections anymore because we are not using separate sections per translation. Removed this and removed it from the interface.

  2. Adding support for translating inline blocks.
    1. Added LayoutBuilderTranslatablePluginInterface with hasTranslatableConfiguration. This allows plugins to determine for themselves if they aren currently translatable. For inline blocks if the block type is translatable they are too.
    2. added InlineBlockTranslationForm extends BlockPluginTranslationForm This will simply create a translation if it doesn't exist or load the existing translation.
    3. Set InlineBlockTranslationForm as the layout_builder_translation for inline block plugin.
  3. Changed the existing inline block for to deny access to the langcode field. Users should not be able to choose. Because it should based on the entity that has the layout.
  4. I don't have time to do the test right now for inline block translation but I will do that next.
johnwebdev’s picture

Looked through the interdiff, looks good to me!

+++ b/core/modules/layout_builder/src/Plugin/Block/InlineBlock.php
@@ -153,6 +157,9 @@ public static function processBlockForm(array $element, FormStateInterface $form
+      $element['langcode'] = FALSE;

$element['langcode']['#access'] = FALSE

Doing a manual review now.

johnwebdev’s picture

Status: Needs review » Needs work
+++ b/core/modules/layout_builder/src/Plugin/Block/InlineBlock.php
@@ -280,4 +287,12 @@ public function saveBlockContent($new_revision = FALSE, $duplicate_block = FALSE
+    return $block_content->isTranslatable() ||  (!empty($this->configuration['label_display']) && !empty($this->configuration['label']));

If this condition is true because of the configuration, the inline block form must be hidden.

Tested on 8.7.x

Setup

Installed Language, Content Translation
Installed Layout Builder, Layout Discovery

Added Swedish language

Enabled Layout Builder and Overrides on Basic page

* Layout is not shown under Content language 👏
* All inline blocks was successfully deleted when I removed all nodes after the tests below, in preparation for next patch :)


#1

* Created a node (en)
* Added a 'Basic block' inline block with label and body
* Translated node
* Translate the inline block
* Sets the label and body differently
* The label is translated properly
* The body shows the Swedish translation on the English node. If I update the English inline block again it is shown on the Swedish node.

So, it looks like I didn't enable translation for the block type. When I do that, the translations works as expected.


#2

Now I set the 'Body' field to be untranslated on the Block type.

* Created a node (en)
* Added a 'Basic block' inline block with label and body
* Translated node
* Tries to change the inline block body of the translated node, nothing happens (as in it's not translated, when I update the translation)
* Tries to change the inline block body of the original node, nothing happens. (as in it's not translated, when I update the translation)


#2 Untranslated fields should not be shown on the translate inline block form?

tedbow’s picture

Status: Needs work » Needs review
StatusFileSize
new4.5 KB
new6.52 KB
new68.41 KB

@johndevman thanks for the manual review.

  1. #116 fixed
  2. #117

    So, it looks like I didn't enable translation for the block type. When I do that, the translations works as expected.

    Ok so the ability to translate the block should NOT have been there if the block was not translatable. You should have only be able to translate the label(if shown) as with other blocks.

    +++ b/core/modules/layout_builder/src/Plugin/Block/InlineBlock.php
    @@ -280,4 +287,12 @@ public function saveBlockContent($new_revision = FALSE, $duplicate_block = FALSE
    +  public function hasTranslatableConfiguration() {
    +    $block_content = $this->getEntity();
    +    return $block_content->isTranslatable() ||  (!empty($this->configuration['label_display']) && !empty($this->configuration['label']));
    +  }
    

    I forgot that the translation form was being shown even if the block is not translatable when the label needs to be translated. But in the form logic I didn't account for this.

    fixed so that the block entity form is only show if the block entity is translatable.

  3. #2 Untranslated fields should not be shown on the translate inline block form?

    there is setting on the translation page "Hide non translatable fields on translation forms" that we should probably respect. In less we think inline blocks are such a special case that the non translatable fields should only be shown on the default translation layout.

    That would be inline with other settings in the other blocks. They affect both untranslated and translated layout but we only show them on untranslated.

    Anyways if we did want to respect that setting I am not sure how content_translation is enforcing this now. I looked for the setting untranslatable_fields_hide but I don't see affecting the forms fields.

  4. I added a todo to check \Drupal\content_translation\Controller\ContentTranslationController::add() and edit() to see if we need to implement any of the special logic it does to create the translation form. We might need to copy the logic now and do a follow to refactor that controller so we can just ask content_translation for the form. Of course we may not only all of the behaviors.

    This could handle the untranslatable_fields_hide setting for us. not sure

    Maybe @plach would know more about this?

johnwebdev’s picture

#118.3 I'm not entirely sure on that setting, but I think it's more about solving data losses on pending vs. default revisions when using Content Moderation. Do we intend to support Content Moderation on Inline blocks? Or should we ignore it? If we should use that to hide neutral fields on the translations form I don't know.

Also, just to clarify: Currently you cannot change a language neutral field on the translation form.

tedbow’s picture

StatusFileSize
new12.48 KB
new75.14 KB
  1. Do we intend to support Content Moderation on Inline blocks?

    I would say no. The entities with layouts can use content_moderation.

    I suppose you could still enable content_moderation on the block types. So I don't know if we just don't support any block types that have content_moderation enabled or only support them if you can always save to the published state. And if a user cannot then they can't add/edit inline blocks.

  2. I talked to @plach about and gets very complicated. My next step is change InlineBlockTranslationForm to extend ContentEntityForm. This will make it easier to integrate with content_moderation. We will then have to implement PluginFormInterface. and maybe move some logic in BlockPluginTranslationForm to a trait so it can reused in InlineBlockTranslationForm still enve that we can't extend BlockPluginTranslationForm any more.
  3. This patch has basic tests for inline blocks in translations.

Status: Needs review » Needs work

The last submitted patch, 120: 2946333-120.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tedbow’s picture

talked to @tim.plunkett and @plach

The combo of layout_builder + content_moderation + content_translation is going to make it super difficult to store a layout that will be the same across all languages(when each could have a forward revision) and the translate labels in 1 field.

I am working storing the translated labels(and maybe future other plugin settings) in another field that will be translatable.

This is NOT different layouts per language. Just different settings per component per language. With core hardcoding the restriction for just 'labels' for now.

tedbow’s picture

Status: Needs work » Needs review
StatusFileSize
new33.03 KB
new93.39 KB

Here is the first try at #122

Sorry I don't have to explain more about the approach. I will in 5 or so hours

Status: Needs review » Needs work

The last submitted patch, 123: 2946333-123.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tedbow’s picture

Status: Needs work » Needs review
StatusFileSize
new37.4 KB
new108.25 KB

This adds translations of inline blocks in layout translations with test coverage.

Next working on translations of layout defaults, DefaultsSectionStorage.

tedbow’s picture

StatusFileSize
new37.45 KB

The interdiff was incorrect and used the wrong extension

Status: Needs review » Needs work

The last submitted patch, 125: 2946333-125.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

plach’s picture

Code review for #123:

  1. +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php
    @@ -93,7 +94,9 @@ public function getInfo() {
    +      $language = !empty($element['#lanuauge']) ? $element['#lanuauge'] : NULL;
    

    Typo

  2. +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php
    @@ -93,7 +94,9 @@ public function getInfo() {
    +      $element['layout_builder'] = $this->layout($element['#section_storage'], $language);
    

    $language can't be null according to the interface

  3. +++ b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php
    @@ -212,6 +213,41 @@ protected function addSectionField($entity_type_id, $bundle, $field_name) {
    +      $field = FieldConfig::create([
    +        'field_storage' => $field_storage,
    +        'bundle' => $bundle,
    +        'label' => t('Layout Labels'),
    +      ]);
    +      $field->save();
    

    Should we mark this as internal or are we planning to expose it to HTTP API clients?

  4. +++ b/core/modules/layout_builder/src/EventSubscriber/ComponentPluginTranslate.php
    @@ -0,0 +1,55 @@
    +    $plugin = $event->getPlugin();
    

    We could return early here if the site is not multilingual.

  5. +++ b/core/modules/layout_builder/src/EventSubscriber/ComponentPluginTranslate.php
    @@ -0,0 +1,55 @@
    +    $entity = $contexts['layout_builder.entity']->getContextValue();
    

    Does this return the correct entity translation?

  6. +++ b/core/modules/layout_builder/src/Form/LayoutBuilderEntityViewDisplayForm.php
    @@ -69,6 +70,29 @@ public function form(array $form, FormStateInterface $form_state) {
    +      foreach (\Drupal::languageManager()->getLanguages() as $language) {
    

    Let's inject the language manager :)

  7. +++ b/core/modules/layout_builder/src/Form/OverridesEntityForm.php
    @@ -86,16 +88,32 @@ protected function init(FormStateInterface $form_state) {
    -    // Add the widget for Layout Builder after the alter.
    -    $display->setComponent(OverridesSectionStorage::FIELD_NAME, [
    -      'type' => 'layout_builder_widget',
    -      'weight' => -10,
    -      'settings' => [],
    -    ]);
    +       // Add the widget for Layout Builder after the alter.
    +      $display->setComponent(OverridesSectionStorage::FIELD_NAME, [
    +        'type' => 'layout_builder_widget',
    +        'weight' => -10,
    +        'settings' => [],
    +      ]);
    

    Wrong indentation

  8. +++ b/core/modules/layout_builder/src/Form/OverridesEntityForm.php
    @@ -86,16 +88,32 @@ protected function init(FormStateInterface $form_state) {
    +   * @inheritDoc
    

    Missing { }

  9. +++ b/core/modules/layout_builder/src/Plugin/Field/FieldType/LayoutTranslationItem.php
    @@ -0,0 +1,67 @@
    + * @FieldType(
    + *   id = "layout_translation",
    

    Why do we need a new type? Isn't a map item enough?

  10. +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
    @@ -37,7 +39,8 @@
    + *       "EntityHasField" =
    + *   \Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage::FIELD_NAME,
    

    Unintended change?

  11. +++ b/core/modules/layout_builder/src/TranslatableSectionStorageInterface.php
    @@ -28,4 +28,29 @@ public function isTranslatable();
    +   * Sets the translated component configuration.
    +   *
    +   * @param string $uuid
    +   * @param array $configuration
    ...
    +   * Gets the translated component configuration.
    +   *
    +   * @param string $uuid
    ...
    +   * Get the translated configuration for the layout.
    +   *
    +   * @return array
    

    Missing @param description.


Code review for #125:

+++ b/core/modules/layout_builder/src/Form/InlineBlockTranslationForm.php
@@ -64,6 +74,19 @@ public static function create(ContainerInterface $container) {
+        $entity = $this->entityRepository->getActive('block_content', $entity->id());

@@ -71,6 +94,7 @@ protected function getEntity() {
+      $entity = $this->entityRepository->getActive('block_content', $entity->id());

+++ b/core/modules/layout_builder/src/Plugin/Block/InlineBlock.php
@@ -226,6 +238,7 @@ protected function getEntity() {
+        $entity = $this->entityRepository->getActive('block_content', $entity->id());

Why are we doing this? Revision negotiation should not be performed when the ID of the revision we need to load is known in advance. Additionally we are instantiating the desired translation afterwards, so I don't see the point of these calls.

tedbow’s picture

@plach thanks for the review. Just want to address to 2 points as I am working on the defaults translations.

#123

  1. Yes it should be internal. I am not sure where are other field is marked as internal.
    But we will have to do the same thing we did with our existing field here #3028301: Do not expose to Layout Builder's sections either in defaults or overrides to REST and other external APIs

#125

Revision negotiation should not be performed when the ID of the revision we need to load is known in advance.

We store the revision because we want to render the exact revision of block with the exact revision of entity with the layout.

but if we don't call $this->entityRepository->getActive() and the revision id is not the latest, because a new revision was made when creating a translation of the inline block in the translation layout we will get an error

The content has either been modified by another user, or you have already submitted modifications. As a result, your changes cannot be saved.

This why I expanded the test \Drupal\Tests\layout_builder\FunctionalJavascript\InlineBlockTranslationTest::testInlineBlockContentTranslation()

  1. +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTranslationTest.php
    @@ -0,0 +1,238 @@
    +    // Update the default translation's version of the block.
    +    $this->drupalGet('node/1/layout');
    +    $this->configureInlineBlock('Block en body', 'Block updated en body');
    +    $this->assertSaveLayout();
    

    This would fail if we didn't have the getActive() call in \Drupal\layout_builder\Plugin\Block\InlineBlock::getEntity() because we have just update the translations layouts inline block.

  2. +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/InlineBlockTranslationTest.php
    @@ -0,0 +1,238 @@
    +    // Update the translation block after updating default translation block.
    +    $this->drupalGet('it/node/1/layout');
    +    $this->updateTranslatedBlock('Block newer updated it label', 'Block updated it body', 'Block even newer updated it label', 'Block newer updated it body');
    +    $this->assertSaveLayout();
    

    This would also fail if we didn't have getActive() call in \Drupal\layout_builder\Form\InlineBlockTranslationForm::getEntity()

xjm’s picture

Status: Needs work » Postponed
Issue tags: -Layout Builder stable blocker +Needs followup

Thanks @plach and @tedbow.

@webchick, @Gábor Hojtsy, @effulgentsia, and I discussed this issue today. We should take the approach here that we did for Layout Builder's REST support as well as JSON:API's translation support: Start by disabling translation entirely, so that we don't get into a situation where we might have data integrity problems or lost work due to incomplete translation support. That's the minimum scope that needs to be a stable blocker, because it affects site data and sitebuilding decisions that are difficult to change after the fact. Tagging "Needs followup" to create a new issue for that first step, and postponing on it.

Then, we'll add Layout Builder translation support back once we're ready to ship translation of labels in defaults, labels in overrides, and inline blocks as a single integrated feature with integrated UX. @webchick and @Gábor Hojtsy confirmed that from a product perspective this can be treated as a missing key feature, which does not need to block marking Layout Builder itself stable. (It would be a blocker for enabling Layout Builder in Standard.)

johnwebdev’s picture

Status: Postponed » Needs work
Issue tags: +Needs issue summary update

This is no longer postponed(?) and I also think this deserves a issue summary update, because the goals and approaches of the issue has changed a lot.

tedbow’s picture

tedbow’s picture

Title: Allow translating labels and inline blocks for Layout overrides » Allow synced Layout override Translations: translating labels and inline blocks
Issue summary: View changes
tedbow’s picture

Copying over @berdir's comment from #3044386-7: [META] Make Layout Builder layouts translatable because it deals with overrides so is more relevant here.

As mentioned earlier, this is what we call symmetric and asymmetric translations in paragraphs and we currently only support symetric in the main module, but there's a contrib module and issues/patches for the other variant.

I don't know the layout builder structure in detail, but the advantage of paragraphs/ERR is that the field itself doesn't store any translatable things and is very simple/structured, just two integer references. So we can just make the field untranslatable, handle the logic of displaying/editing the right translation of the paragraphs in the widget and we're done (as long as you ignore workflows, see below). (Obviously even that part is far from simple, because current entity forms are not very good at tracking the current language, it can be changed and so on..)

However, it's more complicated for you, because even when you also just store references (non-reusable content blocks), then those are in a blob, and you can also have block settings that can be translatable labels, like the block title. Given that, I'm not sure that an untranslatable field is even an option for you. It sounds more like image field translations work, where each property can be translated or not, and the untranslatable ones are synced between all translations. Except in your case it's much more complicated, as it would be based on the block config schema to decide recursively what can be merged/changed in a translation and what can't? Such a feature could also be interesting for the block_field module, as well as things like commerce shipping methods, which has a visible shipping rate label in within plugin settings stored in a field (#2934142: Shipping method entity does not support translations).

Where it gets really complicated is workflows and specifically drafts, because each translation is basically its own revision branch *but* each revisions contains all translations that exist at that point in time. Imagine this scenario:

1. You create a published EN node with a non-reusable content block, node revision 1 (N1) and block revision 1 (B1).
2. Now you create a EN draft and also change the block giving you N2 and B2.
3. Now you want to translate it and add a DE draft and also translate the block as N3 and B3.
4. Then you publish the EN draft and maybe you also make a few more changes to that, keeping it published, that gives you N4 + N5 and B4 and B5 (maybe the block has fewer revisions, maybe not)
5. Finally, you also want to to publish the DE draft, which is N3 and B3. But you can't just take those revisions, because they do not contain the more recent EN changes. So what core does at this point is prepare N6 based on the current default revision N5 (so there could even be extra EN drafts now), then copies over all the DE values of translatable fields into that and saves that.

Now, if you don't do anything, if the layout field is translatable, then you go back to the EN content in B3 because the whole thing is copied over 1:1. And if it's not translatable, you don't actually get the DE translation of the block. Either way, you lose. Additionally, entities also need to know which translations changed in a revision, which it for example uses to filter the list of revisions shown based on the current language. And if you think that's not enough fun yet, add a few more translations to the mix.

What we did for paragraphs is we put several things into core that are explained in this change record: https://www.drupal.org/node/2975280. This allows us to...

a) show the widget of our untranslatable field on translations (we disallow all structure changes ourself then, like add/remove/reorder paragraphs). This is not directly an issue for you, but you still need to figure out how the UI should look/work while being on a translation.
b) then we basically lie to core about the field having language-affecting changes (technically it always changes on translations, but we'll deal with that later)
c) finally, we use the revision create hook to repeat the merge process that core just did for the host entity and repeat that (recursively) for all referenced paragraphs.

As far as I understand, layout builder *must* implement something similar, except again, I expect it to be more complicated, as you need to merge whole block configurations, which themself might contain references which need to be merged too.

That's where we are now with paragraphs and it works quite well, but there is at least one major limitation. It is currently not possible to prepare new paragraphs (or reorder/remove) in a draft EN translation and then add translations based on that draft, because when creating a draft for DE, it will be based on the default revision, not the EN draft. layout builder should have the same problem, just replace "add paragraph" with "change layout/place block". I should have tried to find solutions for that for months now, but I'm kinda stuck. See #3004099: Allow to translate paragraphs from pending revisions issue for that and #3007233: Draft translations should be based on the latest revision of the source language, not the published version for the core issue that I opened as a possible option for resolving that.

tedbow’s picture

Status: Needs work » Needs review
Issue tags: -Needs followup
StatusFileSize
new104.87 KB

Here is a re-roll of #125. There was a lot changes since then so I still need to double check but wanted to get the work started again here and see what tests fail.

Still needs work. Translations of labels is working but not for inline blocks.

Removing "Needs follow-up" as now this is a child issue of #3044386: [META] Make Layout Builder layouts translatable which has other related issues

Status: Needs review » Needs work

The last submitted patch, 136: 2946333-136-reroll.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tedbow’s picture

Status: Needs work » Needs review
StatusFileSize
new12.66 KB
new105.74 KB

Cleaning up the last patch

Status: Needs review » Needs work

The last submitted patch, 138: 2946333-138.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tedbow’s picture

Status: Needs work » Needs review
StatusFileSize
new4.18 KB
new104.52 KB
  1. +++ b/core/modules/layout_builder/src/Form/BlockPluginTranslationFormTrait.php
    @@ -0,0 +1,38 @@
    +trait BlockPluginTranslationFormTrait {
    

    This trait was no longer used.

  2. +++ b/core/modules/layout_builder/src/Plugin/Block/InlineBlock.php
    @@ -130,6 +150,10 @@ public function defaultConfiguration() {
    +    if (!$this->isNew) {
    +      // Get the active block for editing purposes.
    +      $block = $this->entityRepository->getActive('block_content', $block->id());
    +    }
    

    need to check here for $block->isNew() because $this->isNew actually determines if form is adding a new block. If the block has been added and then you edit the block before saving the layout the block will still be new and there will be no active.

  3. \Drupal\Tests\layout_builder\Kernel\OverridesSectionStorageTest::testAccess still has some failures I am figuring out.
    +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
    @@ -415,7 +426,7 @@ public function access($operation, AccountInterface $account = NULL, $return_as_
    -    return $result->andIf(AccessResult::allowedIf(!($entity instanceof TranslatableInterface && !$entity->isDefaultTranslation())))->addCacheableDependency($entity);
    +    return $result->andIf(AccessResult::allowedIf($this->isDefaultTranslation() || ($entity instanceof TranslatableInterface && $this->isOverridden())))->addCacheableDependency($entity);
    

    It might be related to \Drupal\Core\Entity\EntityBase::getCacheTagsToInvalidate()

    Not factoring in language so since the test already calls access() the result will be cached.

  4. Clean up some test fails because of case changes in links.

Status: Needs review » Needs work

The last submitted patch, 140: 2946333-140.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tedbow’s picture

Status: Needs work » Needs review
StatusFileSize
new5.31 KB
new109.02 KB

Had to look at \Drupal\Tests\content_translation\Kernel\ContentTranslationEntityBundleInfoTest::testFieldSynchronizationWithDisabledBundle() to determine how to get a kernel test to work with translations. The problem before was $entity->isTranslatable() was always returning FALSE which affected \Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage::isTranslatable().

berdir’s picture

+++ b/core/modules/layout_builder/tests/src/Kernel/OverridesSectionStorageTest.php
@@ -54,7 +57,9 @@ protected function setUp() {
     $this->installEntitySchema('entity_test');
+    $this->installEntitySchema('entity_test_mul');
     $this->installEntitySchema('user');
+    $this->installEntitySchema('configurable_language');
 

configurable_language is a config entity, that's not something you need to install.

Status: Needs review » Needs work

The last submitted patch, 142: 2946333-142.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tedbow’s picture

Status: Needs work » Needs review

@Berdir thanks for pointing this out. I copied this from \Drupal\KernelTests\Core\Entity\EntityNonRevisionableTranslatableFieldTest.

I couldn't find an existing issue for this so I created #3052430: Remove calls to installEntitySchema for config entity types in Kernel tests. It seems like this is done many times for config entities.

tedbow’s picture

StatusFileSize
new108.57 KB

#143 fixed.

finally getting back to #128. @plach thanks for the review

  1. Language is no longer needed here
  2. The layout_section was already added and wasn't marked as internal so following same pattern here. Not sure why it wasn't marked as internal. Maybe we should mark both?

    I will ask @tim.plunkett why we didn't use setInternal() on the field type definition.

  3. fixed
  4. Yes, this is used to render the entity so it should be the correct variant.
  5. No longer need the language manager
  6. Indentation seems to already have been fixed.
  7. already fixed
  8. Not sure about the MapItem. If I try use a Map field type in \Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay::addTranslationField
    I run into #2743175: SqlContentEntityStorageSchema::getDedicatedTableSchema assumes that a fields property definitions matches the columns. I thought I was using the Map field incorrectly but I think it is bug. will look into that issue. If we could use map that remove a couple of classes.
  9. Yes, unintended change. fixed.
  10. fixed in previous patch

Looking at the failure in InlineBlockTranslationTest. I added the new permission "create and edit custom blocks" to this test which should get it further but it still fails.

Status: Needs review » Needs work

The last submitted patch, 146: 2946333-146.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

tim.plunkett’s picture

#2871591: Allow ComplexData in TypedData to specify computed properties that should be exposed in normalization and other contexts added setInternal and that landed in core after the LayoutSectionItem had been written.

tedbow’s picture

Status: Needs work » Needs review
StatusFileSize
new34.69 KB
new110.83 KB

This patch

  1. fixes coding standards from #147
  2. Changes from using InlineBlockTranslationForm which is plugin form to using different route with an entityform BlockContentInlineBlockTranslateForm extends BlockContentForm.

    I did this because to work with content_translation will really need to have a form that implements ContentEntityFormInterface. This is because \content_translation_form_alter checks for this. It will trigger \Drupal\content_translation\ContentTranslationHandler::entityFormAlter() which also probably assumes a form structure. So this would make it very difficult to use a plugin form.

    This seems to work well.

tedbow’s picture

Added ModeratedTranslationTest which tests the interaction between layout_builder, content_moderation, and content_translation.

tedbow’s picture

StatusFileSize
new9.96 KB
new120.79 KB

..... and the patch

tedbow’s picture

StatusFileSize
new13.69 KB
new120.74 KB
  1. +++ b/core/modules/layout_builder/src/Access/LayoutBuilderTranslationAccessCheck.php
    @@ -0,0 +1,50 @@
    + * When accessing the layout builder for a translation only translating labels
    + * and inline blocks are supported.
    

    Removing this comment because plugins can determine what can be configured in translations by providing their own 'layout_builder_translation' form.

  2. +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php
    @@ -83,6 +96,7 @@ public static function create(ContainerInterface $container, array $configuratio
    +      '#language' => NULL,
    

    Not used anymore

  3. +++ b/core/modules/layout_builder/src/SectionComponent.php
    @@ -317,4 +318,24 @@ public static function fromArray(array $component) {
    +    if ($plugin instanceof LayoutBuilderTranslatablePluginInterface) {
    +      return $plugin->hasTranslatableConfiguration();
    +    }
    ...
    +    return FALSE;
    

    Thought of replacing this with just checking if configuration has values in translatable(according to schema) but here for example the label value only needs to be translated if it is displayed.

    Also inline blocks only need to be translated if the blocks support translation

    So I think we still need \Drupal\layout_builder\LayoutBuilderTranslatablePluginInterface::hasTranslatableConfiguration()

  4. +++ b/core/modules/layout_builder/src/TranslatableSectionStorageInterface.php
    @@ -0,0 +1,55 @@
    +  public function isTranslatable();
    

    Removing this from the interface for now because it is never called.

  5. +++ b/core/modules/layout_builder/layout_builder.module
    @@ -83,6 +85,10 @@ function layout_builder_entity_type_alter(array &$entity_types) {
    +  if (isset($entity_types['block_content'])) {
    +    $entity_types['block_content']->setFormClass('layout_builder_translate', BlockContentInlineBlockTranslateForm::class);
    +  }
    

    Also should check here if $definition['class'] implements ConfigurableInterface. Then we can remvoe this check in BlockPluginTranslationForm.

  6. '

  7. Added @internal to new classes that needed it
tedbow’s picture

Issue summary: View changes
Issue tags: -Needs issue summary update
StatusFileSize
new8.38 KB
new119.18 KB
  1. +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php
    @@ -135,6 +155,10 @@ protected function layout(SectionStorageInterface $section_storage) {
    +
    +    // @todo Add message if not components have translate links!
    +    //    "There are no settings to translate"
    +
    

    Added this message

  2. +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php
    @@ -148,6 +172,14 @@ protected function prepareLayout(SectionStorageInterface $section_storage) {
    +      if ($section_storage instanceof TranslatableSectionStorageInterface && !$section_storage->isDefaultTranslation()) {
    +        // @todo Copy in any change from the default translation and then
    +        //   reapply any translated labels where the original labels has not
    +        //   changed. This should avoid data loss if the layout has been
    +        //   updated since this layout override has started. This probably also
    +        //   needs to be done on save to avoid overriding the layout if it was
    +        //   saved since the last time this page was opened.
    +      }
    

    I think this predates the translation settings being in a different field so now we can't overwrite the layout settings.

  3. +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php
    @@ -251,33 +284,60 @@ protected function buildAdministrativeSection(SectionStorageInterface $section_s
    +          if ($sections_editable) {
    +            // Add metadata about the current operations available in
    +            // contextual links. This will invalidate the client-side cache of
    +            // links that were cached before the 'move' link was added.
    +            // @see layout_builder.links.contextual.yml
    +            $contextual_link_settings['metadata'] = [
    +              'operations' => 'move:update:remove',
    +            ];
    +            $build[$region][$uuid]['#contextual_links'] = [
    +              'layout_builder_block' => $contextual_link_settings,
    +            ];
    

    refactored this because what is changing is the contextual group so not setting $build[$region][$uuid]['#contextual_links'] in multiple places just settings $contextual_group

  4. +++ b/core/modules/layout_builder/src/TranslatableSectionStorageInterface.php
    --- /dev/null
    +++ b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Block/TestSimpleBlock.php
    
    +++ b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Block/TestSimpleBlock.php
    +++ b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Block/TestSimpleBlock.php
    @@ -0,0 +1,31 @@
    

    Remove the need for this block by using an existing test block from block_test module. I needed 2 blocks and I was using "Powered by" and Search. But search was running into #3045171: Form blocks rendered inside layout builder break save

tedbow’s picture

Assigned: tedbow » Unassigned
StatusFileSize
new117.94 KB
new9.8 KB

Started on #3044993: Allow synced Layout default Translations: translating labels and inline blocks which led to a couple changes here

  1. +++ b/core/modules/layout_builder/layout_builder.routing.yml
    @@ -97,6 +104,37 @@ layout_builder.update_block:
    +layout_builder.translate_block:
    +  path: '/layout_builder/translate/block/{section_storage_type}/{section_storage}/{delta}/{region}/{uuid}'
    

    So the langcode need to be added this path so it could be extracted in \Drupal\layout_builder\Plugin\SectionStorage\DefaultsSectionStorage::deriveContextsFromRoute() in #3044993: Allow synced Layout default Translations: translating labels and inline blocks(not used in this issue

  2. +++ b/core/modules/layout_builder/src/Form/BlockContentInlineBlockTranslateForm.php
    @@ -0,0 +1,151 @@
    +    $translated_configuration = $this->sectionStorage->getTranslatedComponentConfiguration($this->uuid);
    +    $current_langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId();
    

    This was relying on the translation language being the same as the content language. That will not be true for config_translation.

    Added \Drupal\layout_builder\TranslatableSectionStorageInterface::getTranslationLanguage() so the translation can be determined by the storage itself

tedbow’s picture

StatusFileSize
new2.87 KB
new118.37 KB

More changes from #3044993: Allow synced Layout default Translations: translating labels and inline blocks

+++ b/core/modules/layout_builder/src/EventSubscriber/ComponentPluginTranslate.php
@@ -0,0 +1,76 @@
+    $entity = $contexts['layout_builder.entity']->getContextValue();
+    $configuration = $plugin->getConfiguration();
+    $section_storage = $this->getSectionStorageForEntity($entity);
+    if ($section_storage instanceof TranslatableSectionStorageInterface && !$section_storage->isDefaultTranslation()) {

This will not work for defaults.

It is only working here because the entity context is from the storage and the entity itself store the translations in another field. So $section_storage = $this->getSectionStorageForEntity($entity); will return not the same as in tempstore but made from the same entity which stores the translation data.

so here we need to change this to actually use the section storage when $entity->inPreview(). Then we can be sure the translation data will be available regards of how it is stored internally in the storage.

tim.plunkett’s picture

Overall I think the approach is sound. This is tricky stuff!

  1. +++ b/core/modules/layout_builder/layout_builder.routing.yml
    @@ -97,6 +104,37 @@ layout_builder.update_block:
    +    _permission: 'configure any layout'
    +    _layout_builder_access: 'view'
    

    Any particular reason the _permission is still used? That was removed from all other routes when we improved _layout_builder_access

  2. +++ b/core/modules/layout_builder/src/Access/LayoutBuilderTranslationAccessCheck.php
    @@ -0,0 +1,50 @@
    +    if ($access instanceof RefinableCacheableDependencyInterface) {
    

    Nit: not needed, AccessResult is always refinable

  3. +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php
    @@ -44,6 +47,12 @@ class LayoutBuilder extends RenderElement implements ContainerFactoryPluginInter
    +   * The entity type manager.
    +   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
    

    Nit: missing blank line

  4. +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php
    @@ -57,11 +66,14 @@ class LayoutBuilder extends RenderElement implements ContainerFactoryPluginInter
    -  public function __construct(array $configuration, $plugin_id, $plugin_definition, LayoutTempstoreRepositoryInterface $layout_tempstore_repository, MessengerInterface $messenger) {
    +  public function __construct(array $configuration, $plugin_id, $plugin_definition, LayoutTempstoreRepositoryInterface $layout_tempstore_repository, MessengerInterface $messenger, EntityTypeManagerInterface $entity_type_manager) {
    

    In theory we have to treat this as an optional param for BC

  5. +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php
    @@ -135,6 +155,22 @@ protected function layout(SectionStorageInterface $section_storage) {
    +            break 2;
    

    That's not something you see every day!

  6. +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php
    @@ -251,33 +288,63 @@ protected function buildAdministrativeSection(SectionStorageInterface $section_s
    +          if ($is_translation) {
    +            $component = $section->getComponent($uuid);
    +            if ($component->hasTranslatableConfiguration()) {
    

    This gets complicated very quickly. Should it be split out to a new method?

  7. +++ b/core/modules/layout_builder/src/EventSubscriber/ComponentPluginTranslate.php
    @@ -0,0 +1,90 @@
    +   * @var \Drupal\Core\Routing\RouteMatchInterface
    ...
    +   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
    

    Nit: missing one-line doc

  8. +++ b/core/modules/layout_builder/src/EventSubscriber/ComponentPluginTranslate.php
    @@ -0,0 +1,90 @@
    +    $entity = $contexts['layout_builder.entity']->getContextValue();
    

    Need to be careful here, reminds me of #3018782: Remove extraneous context mapping of layout_builder.entity

  9. +++ b/core/modules/layout_builder/src/Form/BlockContentInlineBlockTranslateForm.php
    @@ -0,0 +1,140 @@
    +    $configuration = $this->sectionStorage->getSection($this->delta)->getComponent($this->uuid)->getPlugin()->getConfiguration();
    

    Reminds me of \Drupal\layout_builder\Form\ConfigureBlockFormBase::getCurrentComponent()
    Maybe that should be a trait...

  10. +++ b/core/modules/layout_builder/src/Form/BlockContentInlineBlockTranslateForm.php
    @@ -0,0 +1,140 @@
    +    $form['langcode']['#access'] = FALSE;
    +    $form['revision_log']['#access'] = FALSE;
    +    $form['revision']['#access'] = FALSE;
    

    Why these 3? Are there others that might conditionally appear?

  11. +++ b/core/modules/layout_builder/src/Form/BlockPluginTranslationForm.php
    @@ -0,0 +1,69 @@
    +class BlockPluginTranslationForm extends PluginFormBase implements LayoutBuilderPluginTranslationFormInterface {
    ...
    +  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
    +  }
    

    That seems odd

  12. +++ b/core/modules/layout_builder/src/InlineBlockEntityOperations.php
    @@ -282,4 +309,30 @@ protected function saveInlineBlockComponent(EntityInterface $entity, SectionComp
    +  protected function saveTranslatedInlineBlock($entity, $component_uuid, $translated_component_configuration, $new_revision) {
    

    This method is kinda dense. Some newlines and/or inline docs would be nice

  13. +++ b/core/modules/layout_builder/src/Plugin/Block/InlineBlock.php
    @@ -297,4 +321,12 @@ public function saveBlockContent($new_revision = FALSE, $duplicate_block = FALSE
    +    return $block_content->isTranslatable() ||  (!empty($this->configuration['label_display']) && !empty($this->configuration['label']));
    

    Nit: double space after the ||

  14. +++ b/core/modules/layout_builder/src/Plugin/Field/FieldType/LayoutTranslationItem.php
    @@ -0,0 +1,67 @@
    +  public function __get($name) {
    +    // @todo \Drupal\Core\Field\FieldItemBase::__get() does not return default
    +    //   values for uninstantiated properties. This will forcibly instantiate
    +    //   all properties with the side-effect of a performance hit, resolve
    +    //   properly in https://www.drupal.org/node/2413471.
    +    $this->getProperties();
    +
    +    return parent::__get($name);
    +  }
    

    Do we know we need this or is it just copy/paste

  15. +++ b/core/modules/layout_builder/src/Plugin/Field/FieldType/LayoutTranslationItem.php
    @@ -0,0 +1,67 @@
    +          'size' => 'normal',
    

    Do we want to go with 'big' to preemptively sidestep any issues? See #3030154: layout_builder__layout_section column hitting database limit

  16. +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php
    @@ -438,4 +446,74 @@ public function isOverridden() {
    +  public function getTranslatedComponentConfiguration($uuid) {
    ...
    +      return [];
    +    }
    +    else {
    

    Nit: no else needed after a return

tedbow’s picture

StatusFileSize
new25.45 KB
new120.6 KB
  1. Removed permission
  2. fixed
  3. fixed
  4. fixed and create #3058490: Edit Deprecate unneeded parameter in LayoutBuilder constructor
  5. Not something I write everyday(or every year)!
  6. Refactored out createContextualLinkElement(). As refactor this check $section_storage instanceof TranslatableSectionStorageInterface && !$section_storage->isDefaultTranslation() which I used in this class 2x and multiple other classes into \Drupal\layout_builder\LayoutEntityHelperTrait::isTranslation()
  7. fixed
  8. Added a today to change in that issue
  9. ConfigureBlockFormBase::getCurrentComponent() is public and I don't think we need it public here
  10. Added comments
  11. The actually configuration is saved in \Drupal\layout_builder\Form\TranslateBlockForm::submitForm(). Added a comment
  12. Added comments and new lines
  13. fixed
  14. Turns out we didn't need it. Removed.
  15. That issue was caused by a local problem #3030154-32: layout_builder__layout_section column hitting database limit. I don't see us hitting 64k. Are there other places block configuration is stored in a db column? What size is used?
  16. Fixed
phenaproxima’s picture

Partial review; I'm nearly a third of the way through.

  1. +++ b/core/modules/layout_builder/src/Access/LayoutBuilderTranslationAccessCheck.php
    @@ -0,0 +1,52 @@
    +    $is_translation = static::isTranslation($section_storage);
    

    I discussed this with @tedbow, but I'm not entirely convinced that adding an external isTranslation() helper method is the best approach.

    It seems to me that it should be up to the section storage to determine if it is a
    translation. To wit, maybe it would be preferable to add a new method to SectionStorageInterface, called isTranslation(), which would do the determining? I think that might be less new API surface than introducing a whole new interface, plus an opinionated static method on a trait.

    If I understand the BC policy correctly, this change would fall under "Interfaces within non-experimental, non-test modules not tagged with either @api or @internal", which means we can add such a method with a default implementation in SectionStorageBase.

  2. +++ b/core/modules/layout_builder/src/Access/LayoutBuilderTranslationAccessCheck.php
    @@ -0,0 +1,52 @@
    +      default:
    +        throw new \UnexpectedValueException("Unexpected _layout_builder_translation_access route requirement: $translation_type");
    

    This seems a little harsh. Why not simply deny access and give this reason?

  3. +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php
    @@ -119,11 +143,17 @@ protected function layout(SectionStorageInterface $section_storage) {
    +      if (!$is_translation) {
    +        $output[] = $this->buildAddSectionLink($section_storage, $count);
    +      }
    

    Nitpick, but would it make more sense for buildAddSectionLink() to do the isTranslation() check, and return an empty array if so?

  4. +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php
    @@ -135,6 +165,22 @@ protected function layout(SectionStorageInterface $section_storage) {
    +    if ($is_translation) {
    +      $has_translatable_component = FALSE;
    +      foreach ($section_storage->getSections() as $section) {
    +        foreach ($section->getComponents() as $uuid => $component) {
    +          if ($component->hasTranslatableConfiguration()) {
    +            $has_translatable_component = TRUE;
    +            break 2;
    +          }
    +        }
    +      }
    +      if (!$has_translatable_component) {
    +        $this->messenger()->addStatus($this->t('There are currently no settings that can be translated'));
    +      }
    +    }
    

    This, on the other hand, seems like a good candidate for inclusion as a helper method in a trait. :)

  5. +++ b/core/modules/layout_builder/src/Element/LayoutBuilder.php
    @@ -402,4 +437,75 @@ protected function buildAdministrativeSection(SectionStorageInterface $section_s
    +        if ($plugin instanceof DerivativeInspectionInterface && $plugin->getBaseId() === 'inline_block') {
    +          $configuration = $plugin->getConfiguration();
    +          /** @var \Drupal\block_content\Entity\BlockContent $block */
    +          $block = $this->entityTypeManager->getStorage('block_content')
    +            ->loadRevision($configuration['block_revision_id']);
    +          if ($block->isTranslatable()) {
    +            $contextual_group = 'layout_builder_inline_block_translation';
    +          }
    +        }
    

    Everything inside this if statement is coupled to the plugin implementation, so why not just check if $plugin instanceof InlineBlock?

  6. +++ b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php
    @@ -217,6 +217,41 @@ protected function addSectionField($entity_type_id, $bundle, $field_name) {
    +        'label' => t('Layout Labels'),
    

    I'm not sure about this label...it seems a bit limited considering the other stuff that can be (or may be in the future) considered part of a "layout translation".

  7. +++ b/core/modules/layout_builder/src/EventSubscriber/ComponentPluginTranslate.php
    @@ -0,0 +1,92 @@
    +    if (!$plugin instanceof ConfigurableInterface && !isset($contexts['layout_builder.entity'])) {
    +      return;
    +    }
    

    This seems like it should be a || condition, not &&.

  8. +++ b/core/modules/layout_builder/src/EventSubscriber/ComponentPluginTranslate.php
    @@ -0,0 +1,92 @@
    +    // @todo Change to 'entity' in https://www.drupal.org/node/3018782.
    +    $entity = $contexts['layout_builder.entity']->getContextValue();
    

    Should we also check if $contexts['layout_builder.entity']->hasContextValue()?

  9. +++ b/core/modules/layout_builder/src/EventSubscriber/ComponentPluginTranslate.php
    @@ -0,0 +1,92 @@
    +      if ($translated_plugin_configuration = $section_storage->getTranslatedComponentConfiguration($component->getUuid())) {
    

    So, seeing as how section storage translations will always have the same sections, with the same layouts, containing the same components, I wonder if we shouldn't change the API design somewhat so that it reads more like an entity: $section_storage->getTranslation($langcode)->getComponent($uuid)->getConfiguration(). The storage would return the translated component configuration if it existed, and the original language configuration if it didn't. IMHO, this is better encapsulation because it relieves the calling code of the responsibility of knowing if a translation exists: "just give me whatever you have in this language, if you have anything." The approach of this patch is to treat translation data as an "overlay" over the original language, so to me, it makes sense for the section storage (and calling code) to transparently fall back to the original language when there is no translation data.

    Just my $0.02.

  10. +++ b/core/modules/layout_builder/src/Field/LayoutTranslationItemList.php
    @@ -0,0 +1,21 @@
    +  /**
    +   * Overrides \Drupal\Core\Field\FieldItemListInterface::defaultAccess().
    +   *
    +   * @ingroup layout_builder_access
    +   */
    +  public function defaultAccess($operation = 'view', AccountInterface $account = NULL) {
    

    Can't this just be @inheritdoc?

phenaproxima’s picture

Reviewed one more class...

  1. +++ b/core/modules/layout_builder/src/Form/BlockContentInlineBlockTranslateForm.php
    @@ -0,0 +1,142 @@
    +  public function getEntityFromRouteMatch(RouteMatchInterface $route_match, $entity_type_id) {
    

    I know this is not a particularly helpful complaint, but this method generally seems to be a bit scattered in terms of how many code paths it contains. It's not clear to me when it loads a translation, when it tries to create one, and when there is an error condition. I think we could really use some comments to explain the assumptions and code flow of the method, and possibly break up the code into protected helper methods.

  2. +++ b/core/modules/layout_builder/src/Form/BlockContentInlineBlockTranslateForm.php
    @@ -0,0 +1,142 @@
    +      if (!empty($translated_configuration['block_serialized'])) {
    +        return unserialize($translated_configuration['block_serialized']);
    +      }
    

    Do we need to call getTranslation() on the unserialized block?

  3. +++ b/core/modules/layout_builder/src/Form/BlockContentInlineBlockTranslateForm.php
    @@ -0,0 +1,142 @@
    +        if ($entity->hasTranslation($langcode)) {
    +          return $entity->getTranslation($langcode);
    +        }
    

    If the entity does not have the desired translation, it seems like we might want to throw an exception or something...? I know the code will fall through to the \LogicException at the end of the method, but the message that one carries might not be too useful under the circumstances...

  4. +++ b/core/modules/layout_builder/src/Form/BlockContentInlineBlockTranslateForm.php
    @@ -0,0 +1,142 @@
    +      /** @var \Drupal\block_content\BlockContentInterface $entity */
    +      $entity = $this->entityTypeManager->getStorage('block_content')->loadRevision($configuration['block_revision_id']);
    +      $entity = $this->entityRepository->getActive('block_content', $entity->id());
    +      if ($entity->hasTranslation($langcode)) {
    +        return $entity->getTranslation($langcode);
    +      }
    

    This code appears to be duplicated above for $translated_configuration. Can it be turned into a helper method?

  5. +++ b/core/modules/layout_builder/src/Form/BlockContentInlineBlockTranslateForm.php
    @@ -0,0 +1,142 @@
    +  public function save(array $form, FormStateInterface $form_state) {
    +    $entity = $this->entity;
    +    $translated_configuration = $this->sectionStorage->getTranslatedComponentConfiguration($this->uuid);
    +    $translated_configuration['block_serialized'] = serialize($entity);
    +    $translated_configuration['label'] = $entity->label();
    +    $this->sectionStorage->setTranslatedComponentConfiguration($this->uuid, $translated_configuration);
    +    $this->layoutTempstoreRepository->set($this->sectionStorage);
    +  }
    

    It's interesting that this method always sets block_serialized in the translated configuration. Why is that?

  6. +++ b/core/modules/layout_builder/src/Form/BlockContentInlineBlockTranslateForm.php
    @@ -0,0 +1,142 @@
    +  public function form(array $form, FormStateInterface $form_state) {
    +    $form = parent::form($form, $form_state);
    +    // The language of the translation cannot be changed.
    +    $form['langcode']['#access'] = FALSE;
    +    $form['revision_log']['#access'] = FALSE;
    +    // Creating new revisions is based on the entity with the layout.
    +    $form['revision']['#access'] = FALSE;
    +    return $form;
    +  }
    

    If these fields cannot be changed, we should completely unset them, so they're not part of the form structure at all, rather than set #access to FALSE (which, I think, just prevents them from being rendered).

tedbow’s picture

StatusFileSize
new9.87 KB
new128.25 KB

@phenaproxima thanks for the review!

I have looked it over but haven't had time to respond yet.

For now I want to catch a major problem with the patch. It does not add the new field to bundles that already have the layout builder enabled for overrides. This patch addresses that.

It also updates \Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage::handleTranslationAccess() to not allow access if the Layout field itself is translatable. This whole workflow really depends on the layout not being translatable.

A site could have the field translatable because of custom code or if they are using Layout Builder Asymmetric Translation. That module already has to override handleTranslationAccess() so it should be fine as far as access(they probably will need other updates after this patch is committed)

sam152’s picture

+++ b/core/modules/layout_builder/src/Plugin/Block/InlineBlock.php
@@ -130,6 +147,10 @@ public function defaultConfiguration() {
+    if (!$this->isNew && !$block->isNew()) {
+      // Get the active block for editing purposes.
+      $block = $this->entityRepository->getActive('block_content', $block->id());
+    }

I believe the the test run here proves that this breaks editing blocks where the latest host-revision references a non-latest block revision.

That is, this prevents you from editing the correct block revision unless both the host and block revisions are linear, which isn't the case when you revert a host revision.

tedbow’s picture

StatusFileSize
new125 KB
new28.14 KB

re #161 @Sam152 thanks I will look into this.

@phenaproxima sorry again I haven't addressed your review

I talked to @godotislate about this. He wondered if we could just us Config Translation edit for blocks because he is looking to use translation and he has a number of block plugins he is using with Layout Builder. The plugins have schema defined that is translatable.

Using these blocks outside of Layout Builder config_translation would just create the form based on which parts of the schema were translatable. For layout builder with the current patch a form class would have to be set as the layout_builder_translation form handler in the plugin translation.

We are setting a default 1 here:

+++ b/core/modules/layout_builder/layout_builder.module
@@ -405,3 +412,19 @@ function layout_builder_preprocess_language_content_settings_table(&$variables)
+    // If a block plugin does not define its own 'layout_builder_translation'
+    // form use the one provided by this module.
+    if (!isset($definition['forms']['layout_builder_translation']) && !empty($definition['class']) && in_array(ConfigurableInterface::class, class_implements($definition['class']))) {
+      $definition['forms']['layout_builder_translation'] = BlockPluginTranslationForm::class;
+    }

Of course this would be a pain for everyone that have blocks that just work with Config Translation but for layout builder new form classes would have to be created.

So it would make sense to use config_translation for this.

Of course there are 2 big problems with this

  1. Layout Overrides are content so they rely on content_translation not config_translation for translation. If a site just needs overrides translated then they won't need config_translation installed.
  2. config_translations translate block config entities and layout builder doesn't use these but rather the block plugins directly so the form would not just work.

But I think it would be big win if the translation of block in layout builder and block via the block module were translated in the same manner to the end user and the developer.

So this patch does:

  1. Gets rid of the layout_builder_translation plugin form handler.
  2. For all blocks except inline blocks 1 from class is used for translation.
  3. This allows removing LayoutBuilderPluginTranslationFormInterface, 🎉for less API surface
  4. Inline blocks will still use ContentEntityForm by using a different contextual link( though it appears as the same "Translate Block" link to the user)
  5. If config_translation is installed the translation elements are created via \Drupal\config_translation\FormElement\ElementInterface::getTranslationBuild
  6. If config_translation is not enabled a form is made that is similar but only allows translating top level "label" and "text" data types. This will probably cover a lot of the block plugins
  7. (we could consider moving \Drupal\config_translation\FormElement\ElementInterface::getTranslationBuild and related logic out of config_translation module because this really deals with TypedData translation not just config entities.)
  8. It also gets rid of LayoutBuilderTranslatablePluginInterface 🎉 which had 1 method hasTranslatableConfiguration() which main purpose was to not show the "Translate Block" context link if the label wasn't actively being displayed to the user. config_translation doesn't worry about this and shows "Translate block" contextual link even if the block isn't showing the translated label to the user. This has the benefit of not having to get the translator if the block label is shown later. I think it is good to follow the config_translation pattern here and if it needs to change to change it in both modules.
  9. This also means we can get rid of SectionComponent::hasTranslatableConfiguration()
tedbow’s picture

StatusFileSize
new125.69 KB
new12.92 KB
  1. Simplified \Drupal\layout_builder\Element\LayoutBuilder::createContextualLinkElement() because this will always return a link
  2. Add a test for when config_translation is installed and when it is not to make sure the form is still created.
mathg16’s picture

There is an issue with the latest patch. If one of the field of the inline block is a Paragraph reference, it loses it's reference to it and displays a permission error about not being allowed to view, edit or remove that paragraph, even in the original language.

sam152’s picture

yogeshmpawar’s picture

Assigned: Unassigned » yogeshmpawar
yogeshmpawar’s picture

Assigned: yogeshmpawar » Unassigned
StatusFileSize
new125.54 KB
new760 bytes

Resolved the coding standard issue by removing the unused use statement & added an interdiff as well.

sam152’s picture

Status: Needs review » Needs work

Unfortunately, I think this should be NW until #161 is addressed. My preference would be to resolve all of the issues with reverted revisions before committing this, so the test coverage that proves the need for changes here is already in HEAD. I think that sequencing also works given translations are a new feature and reverts are currently possible and broken from the existing UI.

godotislate’s picture

We noticed that when translating an inline block, if we set the block title/label to be the same as the description for another block_content entity, saving the block will fail the UniqueField constraint on the block_content info field. This is occurring because the BlockContentInlineBlockTranslateForm is saving the block_content entity directly, and runs validation on the entity in form validation.

This validation isn't occurring on the default translation of the inline block, because while the entity is validated via \Drupal\layout_builder\Plugin\Block\InlineBlock::blockValidate(), the violation is suppressed because access to the info field is set to FALSE in \Drupal\layout_builder\Plugin\Block\InlineBlock::processBlockFrom().

It's probably ideal for the block_content entity to always be validated, but I think it'd also be best for the UniqueField constraint on the info field to be ignored for block_content entities that aren't reusable. (See #3054197: Cannot add a custom block with a block description already used by a non-reusable block).

godotislate’s picture

StatusFileSize
new125.59 KB
new685 bytes

The inline block form is always loading the active block_content entity from the entity repository as long as it is not a new inline block or block_content. This is causing an issue in the following situation:
1) Create and save a node of a content type that allows layout builder overrides
2) Edit the layout to create the override, and add an inline block to any section.
3) Add block field data as desired and save the layout.
4) Go back to edit the layout.
5) Click to configure the existing inline block. Change field data to different values and update the block. Do not save the layout.
6) Click to configure the block again and notice that the block fields have stale data.

Patch and interdiff to #168 attached. Basically adds to the conditional to make sure that the serialized block_content isn't available.

godotislate’s picture

I think I discovered an issue with inline blocks not displaying in the correct language when search_api batch indexes rendered nodes, such as when running cron via drush.)

Drupal\layout_builder\Plugin\Block\InlineBlock::build() does not pass a language code to the entity view builder `view()` method, so as a result, the language the inline block's block_content entity is rendered in is the current negotiated content language. For inline blocks in Layout Builder on a non-default language translation of a node, the language can be incorrect, causing the inline block content to be rendered in the incorrect language, so the node is indexed with the inline block's content in the wrong language.

Workaround for now is to add an event subscriber to Drupal\layout_builder\LayoutBuilderEvents::SECTION_COMPONENT_BUILD_RENDER_ARRAY that runs after the component render array is built, and then rebuilds the block content based on the the language of the section storage.

nghai’s picture

hello @tedbow

thanks for the patch! It really wonders.

I have one scenario where if we add the paragraph (entity reference revisions) field to a custom block type. Then the block is rendering without paragraph display when translations is added for the same.

Steps:
- create a custom block type with any paragraph (entity reference revisions) field
- add an inline block of above created custom block in a translatable node
- go to the translatable interface of that node layout and provide the translation of the same block
- result, block displays in frontend after saving the layout but without paragraph rendering fields

On checking, it is due to empty $entity->getAccessDependency(); response in the /core/modules/block_content/src/BlockContentAccessControlHandler.php

looks like some translatable management is required there as well to provide correctly the SetInlineBlockDependency::getInlineBlockDependency

Related issue: https://www.drupal.org/project/drupal/issues/3047022

godotislate’s picture

We've observed some undesirable behavior with content moderation involved:

Steps:

  1. Create and publish a translatable node in one language and add blocks via LB override.
  2. Translate the node and publish
  3. Create a new draft of the node in the original language, and add a new block in LB
  4. Observe that the new block is not available to the second language translation unless the original is published.

This isn't ideal if new versions of both languages should go through moderation at the same time (or at least without one language being published first).

Related issue: #3007233: Draft translations should be based on the latest revision of the source language, not the published version

nghai’s picture

with respect to comment #173 - The missing inline block content with paragraphs

now it is determined that in Drupal\layout_builder\EventSubscriber\SetInlineBlockDependency
the function getInlineBlockDependencychecks if the block's latest revision id is same as the revision id stored within
section component plugin configurations because it is mentioned that \Drupal\Core\Access\AccessibleInterface::access() will only check access on the latest revision

But this scenario is not applicable with Translation implementations of Inline blocks, as different block_revision_id stored for each language inside the node__layout_builder__translation. At the time,

1) below functions take the latest revision id from block_content table which could store the revision id of the added translation as well whereas the function getInlineBlockRevisionIdsInSections only retrieves the revision id from node__layout_builder__layout table (source lang field value)

  protected function isBlockRevisionUsedInEntity(EntityInterface $layout_entity, BlockContentInterface $block_content) {
    $sections_blocks_revision_ids = $this->getInlineBlockRevisionIdsInSections($this->getEntitySections($layout_entity));
    return in_array($block_content->getRevisionId(), $sections_blocks_revision_ids);
  }

2) though we can use getTranslatedComponentConfiguration as well inside the function getInlineBlockRevisionIdsInSections like

   $section_storage = $this->getSectionStorageForEntity($entity);
      foreach ($this->getInlineBlockComponents($sections) as $component) {
        if (static::isTranslation($section_storage)) {
          $translated_component_configuration = $section_storage->getTranslatedComponentConfiguration($component->getUuid());

but it will also retrieve the revision id from current language specific component configuration, not all the revision id associated to that entity / section in all languages. Hence, returns again false on not matching revision ids.

In this situation, some different logic is required to check the access for inlineblocks or we add a function that returns the revisions ids in all languages for an inline block

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

oleksiy’s picture

StatusFileSize
new11.8 KB
new125.12 KB
new125.12 KB

Reroll

oleksiy’s picture

Status: Needs work » Needs review
oleksiy’s picture

StatusFileSize
new124.97 KB
new124.97 KB

The last submitted patch, 180: 2946333-180-8.8.x.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 180: 2946333-180.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

aleevas’s picture

Status: Needs work » Needs review
StatusFileSize
new125.19 KB
new125.19 KB

Added fix for latest patches.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

ashwinsh’s picture

The inline-block form is always loading the active block_content while editing.

Steps to reproduce:

  1. Navigate to LB page with a content inline-block which is having body field
  2. Update the body field of the same inline-block and save the changes
  3. Go to Revert and revert LB page to the previous revision
  4. See the content was reverted in LB to the previous content.
  5. Now try to edit the same inline-block.
  6. It always shows the latest content

Not sure but the issue seems to be in this code:

if (!$this->isNew && !$block->isNew() && empty($this->configuration['block_serialized'])) {
      // Get the active block for editing purposes.
      $block = $this->entityRepository->getActive('block_content', $block->id());
}
tim.plunkett’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests

NW for #185

sanjayk’s picture

Assigned: Unassigned » sanjayk
sanjayk’s picture

Assigned: sanjayk » Unassigned
Status: Needs work » Needs review

@tim.plunkett I have checked #185 comment with and without layout builder. Issue is in revision not in layout builder. Same thing happening without layout builder. You can now move in RTBC.

tedbow’s picture

Status: Needs review » Needs work

@sanjayk thanks for checking #185

This needs to be rerolled against 9.1.x.

I would have read back on this comments here but doing a quick search I don't thin anyone has said it is ready for RTBC. It also still has "Needs tests" tag so at least we have to resolve that.

sanjayk’s picture

Assigned: Unassigned » sanjayk
sanjayk’s picture

StatusFileSize
new124.17 KB

I have rerolled the patch for d9

sanjayk’s picture

Assigned: sanjayk » Unassigned
Status: Needs work » Needs review
StatusFileSize
new124.53 KB

Fixed the issues in patch. Test cases getting failed. If anybody interested work on it.

tim.plunkett’s picture

@sanjayk Please upload an interdiff of your changes

maacl’s picture

The Bug report of #185 comes from #3067646: Inline block loading active block_content revision even if serialized block exists of the "layout_builder_st" Module. There are tests, but the issue may not be resolved yet.

maacl’s picture

StatusFileSize
new100.08 KB
new127.86 KB
new48.25 KB
new12.19 KB

I worked on the reroroll and fixed some oversights I found.

Two questions from my side:

  1. Was the removal of MakeLayoutUntranslatableUpdatePathTestBase.php unintentional in https://git.drupalcode.org/project/drupal/-/commit/e1a041c4932d148e7116e...? I re-added the file and the changes to the file from #183.
  2. Is it correct to remove the @todo from the constructor in core/modules/layout_builder/src/InlineBlockEntityOperations.php? The parameters do not appear to be optional any more.

I tried to add a Interdiff to the rerolls as per https://www.drupal.org/documentation/git/interdiff#reroll.

Status: Needs review » Needs work

The last submitted patch, 195: 2946333-195.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

maacl’s picture

StatusFileSize
new127.96 KB
new6.26 KB

Fixing the obvious fails and coding standard errors. I expect one test still to fail.

maacl’s picture

StatusFileSize
new2.01 KB
new128.62 KB

I re-added the @todo I mentioned in #195.2, figured it is for BC. Please review if the changes are correct now.

Still NW for the failing test.

maacl’s picture

Status: Needs work » Needs review
StatusFileSize
new129.13 KB
new900 bytes

This should fix the failing test.

Status: Needs review » Needs work

The last submitted patch, 199: 2946333-199.patch, failed testing. View results

KuroiRoy’s picture

We're trying out patch #183 and it mostly does what we want. The user is able to set a title for a block through the layout builder and then they could translate the title while translating the node. But somehow, for us, the user profile language influences the ability to translate a block in the layout builder.

Step to reproduce:
1. Create an English block.
2. Translate the block to Dutch.
3. Create an English page.
4. Add the block to the page with the layout builder and give it a title. (The block's content is now translated automatically)
5. Translate the page to Dutch.
6. Now, depending on user language settings, there will be an edit button on the block in layout builder that allows translation of the title.

Now this feels pretty weird and it may depend on the language detection settings of the website. If anyone needs further information please let me know

maacl’s picture

Status: Needs work » Needs review
StatusFileSize
new1.27 KB
new128.22 KB

Found the error I did while rerolling, tests should be green now.

Status: Needs review » Needs work

The last submitted patch, 202: 2946333-202.patch, failed testing. View results

maacl’s picture

Status: Needs work » Needs review

This looks like a random fail, re-test is green.

Jorge Navarro’s picture

Patch #202 worked as expected in Drupal 9.0.3, without layout_builder_at nor layout_builder_st.

xmacinfo’s picture

Coming to this issue from #3044386: [META] Make Layout Builder layouts translatable.

Can we expect a reroll of this patch for Drupal 8.9?

tedbow’s picture

@xmacinfo sorry but I don't think this feature will not be added to 8.9.x because it is too big of a change and there will not be a 8.10 release. New features will be on 9.x.

If you only need this for overrides check out Layout Builder Symmetric Translations I created that module from this patch. It is probably the only way to get this functionality for 8.9.x.

Currently though that module does handle defaults which is covered in this issue #3044993: Allow synced Layout default Translations: translating labels and inline blocks

xmacinfo’s picture

@tedbow: Thanks you very much. I will try and use your module as long as we are stucked in Drupal 8.9.x, hoping it solves my use case.

tinytina’s picture

Patch #202 worked for me on Drupal 9.0.2 (fresh installation with basic/minimal configuration in terms of content_translation and layout_builder)

However, when i try to apply it on a system with existing content (including translations and layouts) there is a problem.
The original content works fine (i can view, edit, layout, translate as expected). When i try viewing the translated version i get this exception:

InvalidArgumentException: Field layout_builder__translation is unknown. in Drupal\Core\Entity\ContentEntityBase->getTranslatedField() (line 587 of core\lib\Drupal\Core\Entity\ContentEntityBase.php).
Drupal\Core\Entity\ContentEntityBase->get('layout_builder__translation') (Line: 460)
Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage->getTranslatedComponentConfiguration('c1c1a098-4e47-49ad-93f6-99fe7b3c2db4') (Line: 86)
Drupal\layout_builder\EventSubscriber\ComponentPluginTranslate->onBuildRender(Object, 'section_component.build.render_array', Object)
call_user_func(Array, Object, 'section_component.build.render_array', Object) (Line: 111)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch('section_component.build.render_array', Object) (Line: 90)
Drupal\layout_builder\SectionComponent->toRenderArray(Array, ) (Line: 86)
Drupal\layout_builder\Section->toRenderArray(Array) (Line: 352)
Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay->buildSections(Object) (Line: 311)
Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay->buildMultiple(Array) (Line: 340)
Drupal\Core\Entity\EntityViewBuilder->buildComponents(Array, Array, Array, 'full') (Line: 24)
Drupal\node\NodeViewBuilder->buildComponents(Array, Array, Array, 'full') (Line: 282)
Drupal\Core\Entity\EntityViewBuilder->buildMultiple(Array) (Line: 239)
Drupal\Core\Entity\EntityViewBuilder->build(Array)
call_user_func_array(Array, Array) (Line: 100)

Seems like a new 'layout_builder__translation'-field is added by this patch which is now missing from existing content.
Does anyone have an idea on how to fix this?

tedbow’s picture

Issue summary: View changes
Status: Needs review » Needs work
Issue tags: +Needs update path tests

@tinytina sorry I don't a solution. I think this should work but we don't have tests for it. Thanks for testing it out though. This points out we need more tests.

p-neyens’s picture

@tinytina You need to run updb to trigger the layout_builder_post_update_add_translation_field.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

lawxen’s picture

Issue tags: +Needs reroll

#202 can't be applied to newest 9.1.x

ravi.shankar’s picture

Assigned: Unassigned » ravi.shankar
ravi.shankar’s picture

Assigned: ravi.shankar » Unassigned
Issue tags: -Needs reroll
StatusFileSize
new128.51 KB

Added reroll of patch #202 on Drupal-9.2.x

lawxen’s picture

@ravi.shankar Thanks for reroll the patch,

The patch in #215 has the same problem of layout_builder_st:
Nontranslatable custom block fields are not synchronized between translations

heddn’s picture

StatusFileSize
new2.88 KB
new128.91 KB

Hopefully this fixes some of the outstanding BC test failures.

heddn’s picture

Status: Needs work » Needs review

The errors in #209 indicate not running db updates. I didn't review the test coverage here, but if that is the only reason we added the needs tests flag, we could remove it. We would still need update path tests though, so :shrug:

Status: Needs review » Needs work

The last submitted patch, 218: 2946333-218.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

heddn’s picture

Status: Needs work » Needs review
StatusFileSize
new128.91 KB
new2.64 KB
heddn’s picture

Status: Needs review » Needs work

LayoutBuilderWidget throw exceptions when it is enabled in the form mode on nodes. This happens when editing a node or adding a translation.

The website encountered an unexpected error. Please try again later.
Error: Call to undefined method Drupal\node\NodeForm::getSectionStorage() in Drupal\layout_builder\Plugin\Field\FieldWidget\LayoutBuilderWidget->getSectionStorage() (line 91 of core/modules/layout_builder/src/Plugin/Field/FieldWidget/LayoutBuilderWidget.php).

Drupal\layout_builder\Plugin\Field\FieldWidget\LayoutBuilderWidget->getSectionStorage(Object) (Line: 36)
Drupal\layout_builder\Plugin\Field\FieldWidget\LayoutBuilderWidget->formElement(Object, 0, Array, Array, Object) (Line: 342)
Drupal\Core\Field\WidgetBase->formSingleElement(Object, 0, Array, Array, Object) (Line: 92)
Drupal\Core\Field\WidgetBase->form(Object, Array, Object) (Line: 178)
Drupal\Core\Entity\Entity\EntityFormDisplay->buildForm(Object, Array, Object) (Line: 121)
Drupal\Core\Entity\ContentEntityForm->form(Array, Object) (Line: 127)
Drupal\node\NodeForm->form(Array, Object) (Line: 106)
Drupal\Core\Entity\EntityForm->buildForm(Array, Object)
call_user_func_array(Array, Array) (Line: 532)
Drupal\Core\Form\FormBuilder->retrieveForm('node_bike_form', Object) (Line: 278)
Drupal\Core\Form\FormBuilder->buildForm(Object, Object) (Line: 48)
Drupal\Core\Entity\EntityFormBuilder->getForm(Object, 'default', Array) (Line: 395)
Drupal\content_translation\Controller\ContentTranslationController->add(Object, Object, Object, 'node')
call_user_func_array(Array, Array) (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 573)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 124)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 158)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 80)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 57)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 52)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 706)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
heddn’s picture

StatusFileSize
new927 bytes
new128.93 KB

Fixes a small error in some cases where section storage cannot be loaded.

nwom’s picture

StatusFileSize
new128.93 KB
new692 bytes

Here is an updated patch that includes the fix for Page Manager (#3119208: More defensive handling of empty $contexts['layout_builder.entity'] and broken blocks)

alexpott’s picture

Status: Needs work » Needs review
nwom’s picture

How would someone transition from Layout Builder Symmetric Translations to the patch within this issue on an existing site without having to delete content, fields, etc?

Thanks in advance! I'm sure this is also important for the future for others using the module.

jhedstrom’s picture

Adding a re-roll for 9.2.x.

When testing this, I'm running into what may be #3054197: Cannot add a custom block with a block description already used by a non-reusable block, wherein a translated block that doesn't update the block description hits this error:

A custom block with block description Block Description already exists

jhedstrom’s picture

Making this change in BlockContentInlineBlockTranslateForm::form()

diff --git a/core/modules/layout_builder/src/Form/BlockContentInlineBlockTranslateForm.php b/core/modules/layout_builder/src/Form/BlockContentInlineBlockTranslateForm.php
index a7b1e59798..3043a7f08a 100644
--- a/core/modules/layout_builder/src/Form/BlockContentInlineBlockTranslateForm.php
+++ b/core/modules/layout_builder/src/Form/BlockContentInlineBlockTranslateForm.php
@@ -136,6 +136,9 @@ public function form(array $form, FormStateInterface $form_state) {
     $form['revision_log']['#access'] = FALSE;
     // Creating new revisions is based on the entity with the layout.
     $form['revision']['#access'] = FALSE;
+    // The block info is hidden on the InlineBlock form too, so hide here to
+    // avoid issues with unique constraints.
+    $form['info']['#access'] = FALSE;
     return $form;
   }

seems to resolve the issue, but there is currently an explicit test for this element on the form as part of this patch, so I didn't update the patch yet as it's unclear if this is the proper fix.

Note that InlineBlock::processBlockForm sets access to false for the info part, but the label is later saved into the block in InlineBlock::blockSubmit.

arun ak’s picture

Even after applying patch from #224, I could not see the 'Translate block' link in the contextual filters of my block in the layout builder.

cgoffin’s picture

StatusFileSize
new68.39 KB

I noticed an error rendering the translated block when another formatter then the default is chosen. This is caused by the config merge of the config from the translated block (to get the translated title, ...) and the config of the original block. I added a patch that changes the merge to a deep merge. I will add this to the patch of #224 because this one is working for me.

cgoffin’s picture

StatusFileSize
new68.46 KB

Here also the deep merge added to #227. This one I haven't tested.

cgoffin’s picture

StatusFileSize
new98.85 KB

Ignore the patches of #230 and #231. Something went wrong generating the patches. Here the updated patch started from #224.

cgoffin’s picture

StatusFileSize
new129.1 KB

And the update for #227.

cgoffin’s picture

StatusFileSize
new128.96 KB

Again something went wrong generating the patch. Here the adjusted version started from #224.

cgoffin’s picture

StatusFileSize
new129.1 KB

And the same change against the #227 patch.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

dinarcon’s picture

StatusFileSize
new128.93 KB

Reroll of #235 against 9.3.x

mauhg’s picture

StatusFileSize
new129.47 KB

Reroll of #227 against 9.2.x

vflirt’s picture

I tested patch #183 on Drupal 8.9.14 and it works with Bartik theme however it does not work with other core themes. The issue is caused by seven and claro preprocess block:

function claro_preprocess_block(&$variables) {
  if (isset($variables['title_suffix']['contextual_links']) && !isset($variables['elements']['#contextual_links']['layout_builder_block'])) {
    unset($variables['title_suffix']['contextual_links']);
    unset($variables['elements']['#contextual_links']);

    $variables['attributes']['class'] = array_diff($variables['attributes']['class'], ['contextual-region']);
  }
}

function seven_preprocess_block(&$variables) {
  if (isset($variables['title_suffix']['contextual_links']) && !isset($variables['elements']['#contextual_links']['layout_builder_block'])) {
    unset($variables['title_suffix']['contextual_links']);
    unset($variables['elements']['#contextual_links']);

    $variables['attributes']['class'] = array_diff($variables['attributes']['class'], ['contextual-region']);
  }
}

as you can see they are removing all contextual links of 'layout_builder_block' key is not present which is the case when translating blocks.

vflirt’s picture

StatusFileSize
new127.88 KB
new2.53 KB

Adding patch and interdiff for the theme issue.

hswong3i’s picture

StatusFileSize
new106.41 KB

Re-roll #232 for 9.1.x.

tinytina’s picture

I was testing some of the patches:

With Drupal 9.1.11 and #241 i get the following error:
Fatal error: Class Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage contains 6 abstract methods and must therefore be declared abstract or implement the remaining methods (Drupal\layout_builder\TranslatableSectionStorageInterface::isDefaultTranslation, Drupal\layout_builder\TranslatableSectionStorageInterface::setTranslatedComponentConfiguration, Drupal\layout_builder\TranslatableSectionStorageInterface::getTranslatedComponentConfiguration, ...) in drupal\core\modules\layout_builder\src\Plugin\SectionStorage\OverridesSectionStorage.php on line 50

With Drupal 9.2.3 and #238 translating inline blocks seems to work.

nginex’s picture

StatusFileSize
new128.8 KB

Re-roll #232 for 9.1.x.

#242 is taken into account.

Status: Needs review » Needs work

The last submitted patch, 243: 2946333-9.1.x-243.patch, failed testing. View results

nginex’s picture

StatusFileSize
new128.95 KB

I made a mistake in tests of re-rolling #232 for 9.1.x.

Here is new updated patch

nginex’s picture

Status: Needs work » Needs review
j-lee’s picture

I'm a little bit confused about the progress here. It seems that the last real progress was at #224 for 9.2 roundabout a year ago.
Then there are a lot of re-rolls and new patches against different versions from 9.1 to 9.3 from different other patches:

#224 -> #232 -> #245 -> ...
#224 -> #234 -> ...
#224 -> #227 -> #235 -> ...

So, what is the current status?
Are the remaining tasks done?
From which patch should the work for 9.3 continue?

j-lee’s picture

StatusFileSize
new142.53 KB
new1.33 KB

After some research, it seems that #238 is a good starting point.
I also fixed the problems with the spellcheck issues.

j-lee’s picture

StatusFileSize
new142.17 KB
new1.31 KB

oops ... fixed patches

j-lee’s picture

Strange ... after applying patch #238, I've got an .php.orig file created by git ...
I think, a new reroll for 9.2+ is needed.

j-lee’s picture

StatusFileSize
new128.97 KB
new11.27 KB

Reroll from #224 for 9.3 with the code styling fixes

j-lee’s picture

StatusFileSize
new128.94 KB
new9.71 KB

And the reroll from #224 for 9.2

j-lee’s picture

StatusFileSize
new1.31 KB

This is the diff with the code-style/spellcheck fixes.

The last submitted patch, 251: 2946333-251.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 252: 2946333-252.patch, failed testing. View results

j-lee’s picture

StatusFileSize
new129.12 KB
new9.86 KB

fix missing change from reroll and fix some deprecations for 9.2

j-lee’s picture

The 3 fails are:

Warning: Your XML configuration validates against a deprecated schema.
Suggestion: Migrate your XML configuration using "--migrate-configuration"!

But how can this be fixed?

berdir’s picture

That's not the reason for the fail, you can ignore that. The reason are the deprecation messages at the end of the output:

 3x: UiHelperTrait::drupalPostForm() is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Use $this->submitForm() instead. See https://www.drupal.org/node/3168858
    3x in InlineBlockTranslationTest::testInlineBlockContentTranslation from Drupal\Tests\layout_builder\FunctionalJavascript 3x: UiHelperTrait::drupalPostForm() is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Use $this->submitForm() instead. See https://www.drupal.org/node/3168858
    3x in InlineBlockTranslationTest::testInlineBlockContentTranslation from Drupal\Tests\layout_builder\FunctionalJavascript
j-lee’s picture

StatusFileSize
new129.2 KB
new15.51 KB

Ah ok, Thank you @Berdir.

This patch should (hopefully) fix the deprecations.

j-lee’s picture

StatusFileSize
new129.17 KB
new13.94 KB

And the path for 9.2

j-lee’s picture

StatusFileSize
new3.93 KB
new2.2 KB

Ok, test are green now.
I have fixed deprecated code at Javascript tests and fixed some code styling issues.
Diff files are attached (9.3).

My manual test with a clean Drupal 9.2 installation and the Umami demo were successful.

How can we go further?

j-lee’s picture

Status: Needs work » Needs review

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

j-lee’s picture

StatusFileSize
new66.85 KB

Reroll from patch #259 for 9.3

j-lee’s picture

StatusFileSize
new129.19 KB
new3.35 KB

Missed a few files at #265. So once again ...

aspilicious’s picture

This patch fatals for me when visiting the layout page on a translation.

It fails in function"_contextual_links_to_id" on line $route_parameters = UrlHelper::buildQuery($args['route_parameters']); because 'route_parameters' seem to be empty.

I added an empty check around it locally.

p-neyens’s picture

StatusFileSize
new129.2 KB

To fix following Error: Call to a member function isTranslatable() on null in Drupal\layout_builder\Element\LayoutBuilder->createContextualLinkElement() i have added a extra check to see if the block exists before calling the isTranslatable method.

p-neyens’s picture

StatusFileSize
new129.26 KB

When we use an inline block with custom translatable config the translated block configuration is not being displayed in the translation interface.
I found the following issue which fix this for the layout_builder_st module.
So i created a new patch with the same fix.

hswong3i’s picture

MR re-roll from https://www.drupal.org/project/drupal/issues/2946333#comment-14445267 via 9.4.x-dev, also solve conflict between 9.3.x-dev.

jordik’s picture

StatusFileSize
new129.25 KB

Patch in #269 worked for me, but after a Drupal core update to 9.3.11 it stopped applying with errors in /core/modules/layout_builder/tests/src/FunctionalJavascript/LayoutBuilderDisableInteractionsTest.php

Here is the re-roll for 9.3.11.

heddn’s picture

Status: Needs review » Needs work

What I found is that on initial load of the layout builder page on a translation (/es/node/123/layout) there isn't any inline blocks in the translation language stored/saved. It uses the default langcode of x-default. This is the case until I actually modify something in layout builder. The implications are that even though I might modify the body field to have new wording in the translation, the FieldBlock block placed in LB still uses x-default and loads the body field for _that_ language. Not the translation as expected.

edit - this is not the case. I was confused with how the site had modeled the data. On that specific page, someone had copy/pasted the content from the body into an inline block and wasn't using the FieldBlock for body at all.

heddn’s picture

Status: Needs work » Needs review

Please ignore #272. Still needs a review.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

tinytina’s picture

I'm using patch #271 with dupal core 9.3.15 currently.

I tested translation of new inline block and changing an existing inline block (label and content) - it's working without errors.

heddn’s picture

I've found a bug in retrieving the active entity for inline blocks. The published node references a block that references a media/image. On view, everything works fine. But when I edit the inline block on the translation, the inline block in the edit form is an older version of the block. Because there is a copy of the block that has revision_translation_affected marked. This is the case even if I delete the translation for the node. The inline blocks for that language are left over as cruft and get resurrected again later when I create a new translation. Or even if I don't create a translation. The old inline blocks are picked instead of the content from the default (original) language.

ranjith_kumar_k_u’s picture

StatusFileSize
new129.13 KB
new928 bytes

I just commented the unused variables.

heddn’s picture

+++ b/core/modules/layout_builder/src/Form/BlockContentInlineBlockTranslateForm.php
@@ -0,0 +1,142 @@
+      $entity = $this->entityRepository->getActive('block_content', $entity->id());

This is the line that seems to be causing my issues. Does anyone remember why we are picking the active/published inline block instead of the one pulled out of section storage configuration?

heddn’s picture

StatusFileSize
new919 bytes
new129.19 KB
heddn’s picture

StatusFileSize
new129.13 KB
new2.48 KB

Ignore #279. I removed the wrong line of code.

The last submitted patch, 279: 2946333-279.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 280: 2946333-280.patch, failed testing. View results

heddn’s picture

Status: Needs work » Needs review
StatusFileSize
new129.13 KB

The last couple patches won't work. The block has to be active or you run into the dreaded error: "The content has either been modified by another user, or you have already submitted modifications. As a result, your changes cannot be saved." So back to #277.

Instead I've written a small post update hook that calls a class via \Drupal::classResolver(). The general idea being that I think we have bad data in the system and we need to make the block revisions that are actually rendering on the site the active ones by cloning them. Now I have to hope (pray) that this problem doesn't resurface again.

use Drupal\block_content\BlockContentInterface;
use Drupal\Component\Plugin\DerivativeInspectionInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\layout_builder\LayoutEntityHelperTrait;
use Drupal\node\NodeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

final class TranslationSync implements ContainerInjectionInterface {

  use LayoutEntityHelperTrait;

  private EntityTypeManagerInterface $entityTypeManager;
  private EntityRepositoryInterface $entityRepository;

  public function __construct(EntityTypeManagerInterface $entity_type_manager, EntityRepositoryInterface $entity_repository) {
    $this->entityTypeManager = $entity_type_manager;
    $this->entityRepository = $entity_repository;
  }

  public static function create(ContainerInterface $container): self {
    return new self(
      $container->get('entity_type.manager'),
      $container->get('entity.repository')
    );
  }

  public function update(NodeInterface $node): void {
    $languages = $node->getTranslationLanguages();
    foreach ($languages as $language) {
      $translation = $node->getTranslation($language->getId());
      $section_storage = $this->getSectionStorageForEntity($translation);
      $sections = $this->getEntitySections($translation);
      foreach ($sections as &$section) {
        foreach ($section->getComponents() as $component) {
          $plugin = $component->getPlugin();
          if ($plugin instanceof DerivativeInspectionInterface && $plugin->getBaseId() === 'inline_block') {
            $configuration = $component->getPlugin()->getConfiguration();
            if (\array_key_exists('block_revision_id', $configuration)) {
              $block = $this->entityTypeManager->getStorage('block_content')->loadRevision($configuration['block_revision_id']);
              if ($block !== NULL && $block->hasTranslation($language->getId())) {
                $block = $block->getTranslation($language->getId());
              }
              $active = $this->entityRepository->getActive('block_content', $block->id());
              \assert($active instanceof BlockContentInterface);
              \assert($block instanceof BlockContentInterface);
              if ($block->getRevisionId() !== $active->getRevisionId()) {
                $duplicate = $block->createDuplicate();
                $duplicate->save();
                $plugin = $component->getPlugin();
                $plugin->setConfigurationValue('block_revision_id', $duplicate->getRevisionId());
              }
            }
          }
        }
      }
      $section_storage->save();
    }
  }

}

edurenye made their first commit to this issue’s fork.

mikemadison’s picture

Status: Needs review » Needs work

I had been using the patch from comment 271 but it doesn't seem to apply after Drupal core 9.4.1 and none of the newer ones appear to apply w/ core 9.4.2. I am going to re-open this.

nginex’s picture

mikemadison’s picture

Status: Needs work » Needs review

confirmed that MR does the trick. setting back, thanks!

ravi.shankar’s picture

StatusFileSize
new129 KB

Added a patch for Drupal 9.4.x.

Status: Needs review » Needs work

The last submitted patch, 288: 2946333-288.patch, failed testing. View results

steinmb’s picture

Status: Needs work » Needs review

Put it back to needs review. Only bugfixes against 9.4.x.

joseph.olstad’s picture

Would be nice to get this in, there's only two test fails.

Meanwhile we've been using this module in place of the core fix https://www.drupal.org/project/layout_builder_st

Perhaps layout builder should be removed from core and put into contrib? This issue was aiming to be resolved in 8.8.0 but we're now a major release and 4 minor releases later. Wondering how many more years until we decide to remove layout builder from core?

hswong3i’s picture

Re-roll #288 for 9.5.x-dev

Status: Needs review » Needs work

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

nielsaers’s picture

What still needs to be done to land this one? Only the 2 failing tests?

nginex’s picture

StatusFileSize
new143.73 KB

MR 1504 seems to working well, but I would like to upload a plain diff version of the MR here, so it's fixed patch that will not be overriden by future commits.

Anybody made their first commit to this issue’s fork.

anybody’s picture

Could someone with the appropriate permissions please change the MR target branch to 10.1.x and create a separate MR against 9.5.x?

I don't seem to have the required permissions, sadly. No idea why.

ravi.shankar’s picture

StatusFileSize
new129.04 KB
new8.11 KB

I've needed a patch for Drupal 9.5.x for one of my project, patch #292 was not applying so adding a reroll of patch #292 on Drupal 9.5.x. and uploading here, someone else also might needed a patch as well.

Keeping the status to needs work as there are still some work is pending.

dravenk’s picture

Issue tags: +Quickfix Need Reroll
sahil.goyal’s picture

StatusFileSize
new129.06 KB
new1.14 KB

updating a quick fix for the #299 CCF.

heddn’s picture

Status: Needs work » Needs review
Issue tags: -Quickfix Need Reroll
StatusFileSize
new7.18 KB
new127.36 KB

Rerolled again.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

smustgrave’s picture

Status: Needs review » Needs work

Leaving the needs upgrade path tests as the current test does not do any assertion before running updates.

CC failure but if it was reroll there was failures in 301

nginex’s picture

StatusFileSize
new140.45 KB

Tried with MR but the fork is outdated and I can't get 11.x version.

Tried to create an interdiff file, also failed.

So this is reroll MR 1504 against 11.x

Also applicable for 10.1.x

nginex’s picture

StatusFileSize
new125.72 KB
new15.58 KB

Fixed issues from PHPStan

nginex’s picture

StatusFileSize
new125.39 KB
new1.41 KB

Sorry, phpcs issues was missed. Here is a new patch.

nginex’s picture

The patch #307 now applies successfully, but need to fix/improve new tests, the full report is available in the job

webfaqtory’s picture

I have posted a patch in Fix multilingual site's layout edit context issue with TypeError: Argument 1 passed to UrlHelper::buildQuery() must be of the type array, null given as I was getting this error when trying to access a translation's layout builder in D10.1.2.

I have fixed the missing $args['route_parameters'] (they are availabe in $contextual_links['layout_builder_block_translation'] or $contextual_links['layout_builder_inline_block_translation']) and everything now works.

Maybe someone can check why this patch is required and fix the missing $args['route_parameters'] when _contextual_links_to_id($contextual_links) is called. I am using the 2946333-d10-307.patch

gauravvvv’s picture

Status: Needs work » Needs review
StatusFileSize
new2.44 KB
new1.25 KB

I have fixed the cc failure, added use statement for ClientException and updated elseif condition, Attached patch and interdiff for same

smustgrave’s picture

Status: Needs review » Needs work

Was previously tagged for tests and upgrade path which still need to happen.

Did not review as there’s more to don

webfaqtory’s picture

We need an update of 2946333-d10-307.patch for D10.2.x. When issuing composer update to update from 10.1.7 to 10.2.0 I'm getting:

Cannot apply patch Drupal Layout Builder. Allow synced Layout override Translations (https://www.drupal.org/files/issues/202
  3-07-25/2946333-d10-307.patch)!
marcellinostroosnijder’s picture

Watch out: #310 is wrong. I think this issue is a fix for https://www.drupal.org/project/drupal/issues/3101231. I'll add the patch there.

heddn’s picture

Rebased the previous MR on a new MR that is based on 11.x. Fixed some obvious cruft that made its way into the patch file. And updated the deprecation strings. But we still have a lot of needs... tags, so this still needs more work.

heddn changed the visibility of the branch 9.4.x to hidden.

heddn changed the visibility of the branch 2946333-allow-synced-layout to hidden.

s_leu made their first commit to this issue’s fork.

jordik’s picture

StatusFileSize
new130.87 KB

The latest MR5956 did not apply on D10.2.3, stopping at core/modules/layout_builder/layout_builder.services.yml.
Here is the re-roll for D10.2. as a patch.

joseph.olstad’s picture

Went through coding standards errors and made corrections. Also merged the head of 11.x into the branch.

joseph.olstad’s picture

Status: Needs work » Needs review
joseph.olstad’s picture

Only one small test error left, have to spin up a vanilla environment with the spark theme enabled as well as this patch going in order to figure it out.

joseph.olstad’s picture

@heddn, this is one of the tests you wrote, one last test fix and it'll be ready to go, can you please have a look?

smustgrave’s picture

Status: Needs review » Needs work
Issue tags: -Needs tests +Needs change record

Left some comments, mostly small stuff.

Leaving needs update path tests though as I didn't see those.

Also such a change seems like will need a change record

webfaqtory’s picture

I applied the patch 2946333-319.patch by JordiK to D10.2.3 but there is no edit icon when hovering over a block in Layout builder. Also if my patch 3101231-D10-allow-synced-layout-override-translations.patch is not applied then back to the error TypeError: Drupal\Component\Utility\UrlHelper::buildQuery(): Argument #1 ($query) must be of type array, null given

I have previously used the 2946333-d10-307.patch wich works fine in D10.1.8. I fixed this patch so it applies to D10.2.3 but got the same results. No edit icon for blocks when in Layout builder for a translated node

Mithun S made their first commit to this issue’s fork.

adwivedi008’s picture

StatusFileSize
new129.38 KB

Revised #319 as it cleanly applies for D-10.2.x but was facing an issue with the pencil icon on the layout translation page.

Fixed the issue.

Please review and suggest if any other change is required

webfaqtory’s picture

StatusFileSize
new29.26 KB

I applied the revised patch #327 and still don't get the pencil icon on translated pages when in Layout.

Test

The above is what I get when editing the layout of a translated node on a clean install of D10.2.4. Only the drag/drop icon shows anywhere in the block.

Also the existing blocks that had been translated using previous patches (the last one was 2946333-d10-307.patch) are no longer translated with the patch from #327. I fixed the 2946333-d10-307.patch to be D10.2.x compatible and all existing translated blocks have their translations but no pencil icon.

webfaqtory’s picture

s_leu’s picture

Fixed the issue with the note appearing contextual links as mentioned in #325 and #328. To those people who still upload patches in this massive issue: Nobody will see what you changed if you don't at least upload an interdiff along with your patch. But even if you do and there's an MR in the issue, please just use the MR to push your changes, that will make collaboration much easier.

s_leu’s picture

Status: Needs work » Needs review
needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new17.44 KB

The Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

webfaqtory’s picture

I'm trying to add the 10.2.x fork to my composer.json (sorry never done this before) I have this under repositories:

 {
            "type": "vcs",
            "url": "https://git.drupalcode.org/issue/drupal-2946333.git"
        }

But I don't know what to put under require for the 2946333_3049332_combined-10.2.x branch and would it replace the drupal/core requirement?

smustgrave’s picture

So you only need 1 MR pointed at 11.x can close the others

eit2103’s picture

Was this pushed to the core? Is there a way to translate titles added with Layout Builder without applying a patch now?

j_drupal’s picture

It looks like the MR for 10.3 is not working properly.
I am using this patch: https://git.drupalcode.org/project/drupal/-/merge_requests/8480.diff which applies fine, but blocks added with layout builder cannot be translated.

eit2103’s picture

What does that patch allow us to translate?

almador’s picture

I'm on Drupal 10.3.1 and using layout_builder_st (Layout Builder Symmetric Translations) with patches for Drupal 10 compatibility, but I've noticed some errors with xdebug and ended with this issue.

Am I right that the patch from this issue should resolve the same thing Layout Builder Symmetric Translations is attempted to fix?

If yes, I'd prefer to remove Layout Builder Symmetric Translations and install the patch, but which patch works for 10.3.x?

joe_carvajal’s picture

Subscribe to this issue, I think that Symmettric translations should be covered by core, leaving the Asymmettric translations in the already stable contrib module.

langelhc’s picture

l_vandamme’s picture

This patch is causing problems when reverting revisions.

This is because of the getActive call that was added to retrieve the latest version of the block when editing in stead of the saved revision. I believe it was added to prevent EntityChangedConstraint errors, but they were fixed in https://www.drupal.org/project/drupal/issues/3053881.

Steps to reproduce this:

  1. Create a page with a text block with title AAAA.
  2. Update the page and change the text block title to BBBB.
  3. Revert back to the first version.
  4. The block title is now AAAA. But when you open the edit form of the block, it shows BBBB (the latest revision)

Code that is causing this behavior (in InlineBlock::blockForm()):

    if (!$this->isNew && !$block->isNew() && empty($this->configuration['block_serialized'])) {
      // Get the active block for editing purposes.
      $block = $this->entityRepository->getActive('block_content', $block->id());
    }

I think there might also be other places that this getActive function is called to prevent the error where it is no longer needed.

I would be willing to update the patch, but have not been active in creating it, so I'm not sure about what I might be breaking when removing this code.

anybody’s picture

@l_vandamme: I think the patch is just a static copy from the MR here. Please adjust it there to proceed with the final fix. Patches can then be downloaded statically from there to use with composer patches for example. Thanks!

layout_builder_st is still not D11 compatible and both maintainers seem inactive and do not repond on PM. Maybe someone should request maintainership until this is fixed?

mysdiir made their first commit to this issue’s fork.

mysdiir’s picture

Added a new 10.3.x backport made with the base of 2946333-11.x-rebase.
MR !10504

almador’s picture

Could someone clarify if using the MR!10504 is enough to replace the Layout Builder Symmetric Translations functionality?

I'm using Drupal 10.4.0 right now and feel exhausted fighting with layout_builder_st problems, currently, it's not allowing me to clear caches from the Drupal admin interface.

hydra’s picture

The MR for @mysdiir worked for my 10.4 setup, thx for that!
I ran into an issue which gave me a hard time to understand whats going on, I'll try my best to describe this:
- Create a new block in a layout
- Save and then switch the language to create a translation for this block
- The block created in the source language now is not shown anymore

What I found out is, that the creation of the translation, created a new revision for all languages. But since blocks in layout_builder are referenced by the block_revision_id, the method isBlockRevisionUsedInEntity in SetInlineBlockDependency no longer returns TRUE when determination of the layout_entity takes place, which will be used to inherit the access to the block. This even get's worse when using a paragraph on that block, which also tries to inherit the access.

I have no idea if the other language versions of the layout should be getting updated or if the isBlockRevisionUsedInEntity method should take the latest affected revision in consideration for it's lookup and of course the block_revision_id from the translation as well (currently it always uses the source for that).

To be sure (because it should be backwards compatible and not changing data) I decided to go for the second approach and created this little helper module, which should be obsolete when this gets fixed, or someone gives me a hint what I am doing wrong :)

l_vandamme’s picture

Rebased the MR from 11.x.

As far as I can tell, it still has issues when editing changed blocks after reverting revisions. I might take a look at that later, for now, it should work as before and apply to 11.x.

smulvih2’s picture

This would be great to get done, so we don't need to rely on layout_builder_st which has been lagging for D11 support.

wranvaud’s picture

I had issues with language negotiation, when using the detection method "Content language" (/admin/config/regional/language/detection) there were problems like: the translation link would actually be modifying the original English version or in a more complex situation the interface language would be set to French and the content language would be set to Japanese, the block would appear in Japanese but the translation from the contextual menu would be done to the French version.
I'm adding a fix for this based on the MR!10504 from #348. It applies to 10.4.x as well.

FYI, these were also useful patches if you use these modules:
Paragraphs:
"3090200 - #45 Paragraph access check incorrect revision": "https://www.drupal.org/files/issues/2022-10-26/3084934-13_combine_309020..."
Layout Builder Modal:
"3133695 - Incompatibility with layout builder": "https://www.drupal.org/files/issues/2022-12-15/3133695-13.patch"

joey91133 made their first commit to this issue’s fork.

wranvaud’s picture

StatusFileSize
new128.21 KB

This patch applies to the latest release to date D11.2.2. See diff here: Compare with previous version
I've also merged 11.x for the latest 11.x-dev branch so far.

wranvaud’s picture

@useernamee this patch is for drupal core not for the layout_builder_st module. You should choose either the patch to core or the module, they should both ideally provide symmetrical translations.

clayfreeman made their first commit to this issue’s fork.

clayfreeman’s picture

It had been a few months since this MR got any updates, so I decided to try to rebase it and solve the test failures (where possible). I was able to resolve all but one failure, and I think it's related to my changes to core/modules/layout_builder/src/Plugin/Block/InlineBlock.php. (Having this code in place breaks some other assertions in seemingly unrelated Layout Builder tests. Is there documentation regarding why this is needed?)

I should also note that the "label_display" element is appearing on the translation form for inline blocks, and I'm not convinced one way or another whether this should be happening. In the case that it should appear, I do believe that it should render as a checkbox instead of a text field (as it currently does). As a workaround for now, I'm forcibly setting it to "1" in tests.

So there are definitely still some testing/usability concerns that need work, but hopefully my efforts can get the ball rolling again on this.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

l_vandamme’s picture

StatusFileSize
new131.41 KB

Quick rebase on main + Added a 11.3.2 patch

ezeedub’s picture

The BC fallback in LayoutBuilder::__construct() (introduced in the 2026-01-23 rebase) has an extra "!": if (!$entity_type_manager === NULL) That parses as (!$entity_type_manager) === NULL, which is always false — !$x yields a bool, never NULL — so the deprecation warning and the fallback to \Drupal::entityTypeManager() never run when the constructor is called without the argument. The pre-rebase version used !$entity_type_manager instanceof EntityTypeManagerInterface; the rebase looks like an unintended collapse.

Caught (by Claude) while upgrading a site to 11.3.8 against this patch. Interdiff attached.

ezeedub’s picture

Another drop in the 2026-01-23 rebase: Plugin/Block/InlineBlock.php is missing the EntityRepositoryInterface injection and the getActive() lookup that were in 2946333-mr5956-1122.patch (2025-06-30).

In that earlier patch, InlineBlock::__construct() took an EntityRepositoryInterface $entity_repository parameter and stored it as $this->entityRepository; create() wired entity.repository in; and blockForm() fetched the active translation before building the form:

$block = $this->getEntity();                                                
if (!$this->isNew && !$block->isNew() && empty($this->configuration['block_serialized'])) {                                                                                    
// Get the active block for editing purposes.                                                              
$block = $this->entityRepository->getActive('block_content', $block->id());                                                                                                  
}

The rebase keeps only the $element['langcode']['#access'] = FALSE; piece in processBlockForm() — the editor-facing consequence is that opening an already-translated inline block renders the source-language content instead of the translation.

Interdiff attached to restore the injection and the getActive() call. Caught while upgrading a site to 11.3.8.