diff --git a/typo3/sysext/backend/Classes/Form/Container/InlineControlContainer.php b/typo3/sysext/backend/Classes/Form/Container/InlineControlContainer.php
index b4e40b370200dab88c8c55a4f7eda7fc7d90498c..d684a70efdd372bd82ff501bcf0cea34f02ed71a 100644
--- a/typo3/sysext/backend/Classes/Form/Container/InlineControlContainer.php
+++ b/typo3/sysext/backend/Classes/Form/Container/InlineControlContainer.php
@@ -61,6 +61,15 @@ class InlineControlContainer extends AbstractContainer
      */
     protected $requireJsModules = [];
 
+    /**
+     * @var array Default wizards
+     */
+    protected $defaultFieldWizard = [
+        'localizationStateSelector' => [
+            'renderType' => 'localizationStateSelector',
+        ],
+    ];
+
     /**
      * Container objects give $nodeFactory down to other containers.
      *
@@ -295,6 +304,11 @@ class InlineControlContainer extends AbstractContainer
 
         $html .= '</div>';
 
+        $fieldWizardResult = $this->renderfieldWizard();
+        $fieldWizardHtml = $fieldWizardResult['html'];
+        $resultArray = $this->mergeChildReturnIntoExistingResult($resultArray, $fieldWizardResult, false);
+        $html .= $fieldWizardHtml;
+
         // Add the level links after all child records:
         if ($config['appearance']['levelLinksPosition'] ===  'both' || $config['appearance']['levelLinksPosition'] === 'bottom') {
             $html .= $levelLinks . $localizationLinks;
diff --git a/typo3/sysext/backend/Classes/Form/Element/CheckboxElement.php b/typo3/sysext/backend/Classes/Form/Element/CheckboxElement.php
index 913ef29d74249133fd04950984d27bd732f1bf8a..b697f15984519a7dbd8582a76bc91c7d39443406 100644
--- a/typo3/sysext/backend/Classes/Form/Element/CheckboxElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/CheckboxElement.php
@@ -27,8 +27,14 @@ class CheckboxElement extends AbstractFormElement
      * @var array
      */
     protected $defaultFieldWizard = [
+        'localizationStateSelector' => [
+            'renderType' => 'localizationStateSelector',
+        ],
         'otherLanguageContent' => [
             'renderType' => 'otherLanguageContent',
+            'after' => [
+                'localizationStateSelector'
+            ],
         ],
         'defaultLanguageDifferences' => [
             'renderType' => 'defaultLanguageDifferences',
diff --git a/typo3/sysext/backend/Classes/Form/Element/GroupElement.php b/typo3/sysext/backend/Classes/Form/Element/GroupElement.php
index b5af2d5049f481f4871c6393e0623e608827e693..846396ecfb425af219b2600a025b5b2cec66f91a 100644
--- a/typo3/sysext/backend/Classes/Form/Element/GroupElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/GroupElement.php
@@ -82,9 +82,13 @@ class GroupElement extends AbstractFormElement
             'renderType' => 'fileUpload',
             'after' => [ 'recordsOverview' ],
         ],
+        'localizationStateSelector' => [
+            'renderType' => 'localizationStateSelector',
+            'after' => [ 'fileUpload' ],
+        ],
         'otherLanguageContent' => [
             'renderType' => 'otherLanguageContent',
-            'after' => [ 'fileUpload' ],
+            'after' => [ 'localizationStateSelector' ],
         ],
         'defaultLanguageDifferences' => [
             'renderType' => 'defaultLanguageDifferences',
diff --git a/typo3/sysext/backend/Classes/Form/Element/ImageManipulationElement.php b/typo3/sysext/backend/Classes/Form/Element/ImageManipulationElement.php
index ea07e6e95efc55b873681140e77369a0ae058eb5..bcf866a8fa98c71aa4770dfaffc677c1fcb7789c 100644
--- a/typo3/sysext/backend/Classes/Form/Element/ImageManipulationElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/ImageManipulationElement.php
@@ -34,8 +34,14 @@ class ImageManipulationElement extends AbstractFormElement
      * @var array
      */
     protected $defaultFieldWizard = [
+        'localizationStateSelector' => [
+            'renderType' => 'localizationStateSelector',
+        ],
         'otherLanguageContent' => [
             'renderType' => 'otherLanguageContent',
+            'after' => [
+                'localizationStateSelector'
+            ],
         ],
         'defaultLanguageDifferences' => [
             'renderType' => 'defaultLanguageDifferences',
diff --git a/typo3/sysext/backend/Classes/Form/Element/InputColorPickerElement.php b/typo3/sysext/backend/Classes/Form/Element/InputColorPickerElement.php
index f7c83a850fe25bb78896557b3d3b6c38332a09bd..eb7c8b69ff71d50e31debbc3ad4f49b803dea27a 100644
--- a/typo3/sysext/backend/Classes/Form/Element/InputColorPickerElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/InputColorPickerElement.php
@@ -30,8 +30,14 @@ class InputColorPickerElement extends AbstractFormElement
      * @var array
      */
     protected $defaultFieldWizard = [
+        'localizationStateSelector' => [
+            'renderType' => 'localizationStateSelector',
+        ],
         'otherLanguageContent' => [
             'renderType' => 'otherLanguageContent',
+            'after' => [
+                'localizationStateSelector'
+            ],
         ],
         'defaultLanguageDifferences' => [
             'renderType' => 'defaultLanguageDifferences',
diff --git a/typo3/sysext/backend/Classes/Form/Element/InputDateTimeElement.php b/typo3/sysext/backend/Classes/Form/Element/InputDateTimeElement.php
index 2002f1b7aa4923b618c7a850d3577a9788c669f7..6b291bd76aa551db73782219aba8f7c220745a59 100644
--- a/typo3/sysext/backend/Classes/Form/Element/InputDateTimeElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/InputDateTimeElement.php
@@ -31,8 +31,14 @@ class InputDateTimeElement extends AbstractFormElement
      * @var array
      */
     protected $defaultFieldWizard = [
+        'localizationStateSelector' => [
+            'renderType' => 'localizationStateSelector',
+        ],
         'otherLanguageContent' => [
             'renderType' => 'otherLanguageContent',
+            'after' => [
+                'localizationStateSelector'
+            ],
         ],
         'defaultLanguageDifferences' => [
             'renderType' => 'defaultLanguageDifferences',
diff --git a/typo3/sysext/backend/Classes/Form/Element/InputLinkElement.php b/typo3/sysext/backend/Classes/Form/Element/InputLinkElement.php
index e66bca8b7033ea893f1a558e9e46b975f01c5b99..f67b5b7b8644300842c64984e295ce0b067909bf 100644
--- a/typo3/sysext/backend/Classes/Form/Element/InputLinkElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/InputLinkElement.php
@@ -52,13 +52,19 @@ class InputLinkElement extends AbstractFormElement
      * @var array
      */
     protected $defaultFieldWizard = [
-        OtherLanguageContent::class => [
+        'localizationStateSelector' => [
+            'renderType' => 'localizationStateSelector',
+        ],
+        'otherLanguageContent' => [
             'renderType' => 'otherLanguageContent',
+            'after' => [
+                'localizationStateSelector'
+            ],
         ],
-        DefaultLanguageDifferences::class => [
+        'defaultLanguageDifferences' => [
             'renderType' => 'defaultLanguageDifferences',
             'after' => [
-                OtherLanguageContent::class
+                'otherLanguageContent',
             ],
         ],
     ];
diff --git a/typo3/sysext/backend/Classes/Form/Element/InputTextElement.php b/typo3/sysext/backend/Classes/Form/Element/InputTextElement.php
index c147ad68bcf06740ace16cdfed4f50e16d2534ab..08badc343301db274439dd20591ef1d213f3dbfa 100644
--- a/typo3/sysext/backend/Classes/Form/Element/InputTextElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/InputTextElement.php
@@ -33,8 +33,14 @@ class InputTextElement extends AbstractFormElement
      * @var array
      */
     protected $defaultFieldWizard = [
+        'localizationStateSelector' => [
+            'renderType' => 'localizationStateSelector',
+        ],
         'otherLanguageContent' => [
             'renderType' => 'otherLanguageContent',
+            'after' => [
+                'localizationStateSelector'
+            ],
         ],
         'defaultLanguageDifferences' => [
             'renderType' => 'defaultLanguageDifferences',
diff --git a/typo3/sysext/backend/Classes/Form/Element/RadioElement.php b/typo3/sysext/backend/Classes/Form/Element/RadioElement.php
index 3d75f71172442a13863cb39074e14b2ff128ccb1..2eb27c858b8c38a1c6169a778cd6d4699d0870ac 100644
--- a/typo3/sysext/backend/Classes/Form/Element/RadioElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/RadioElement.php
@@ -25,8 +25,14 @@ class RadioElement extends AbstractFormElement
      * @var array
      */
     protected $defaultFieldWizard = [
+        'localizationStateSelector' => [
+            'renderType' => 'localizationStateSelector',
+        ],
         'otherLanguageContent' => [
             'renderType' => 'otherLanguageContent',
+            'after' => [
+                'localizationStateSelector'
+            ],
         ],
         'defaultLanguageDifferences' => [
             'renderType' => 'defaultLanguageDifferences',
diff --git a/typo3/sysext/backend/Classes/Form/Element/SelectCheckBoxElement.php b/typo3/sysext/backend/Classes/Form/Element/SelectCheckBoxElement.php
index 64838ea667f5ad643a385aa1d5539b90663d95ee..48c0825d0cf6bbca4a30cc734471e84ca9ad9965 100644
--- a/typo3/sysext/backend/Classes/Form/Element/SelectCheckBoxElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/SelectCheckBoxElement.php
@@ -33,8 +33,14 @@ class SelectCheckBoxElement extends AbstractFormElement
      * @var array
      */
     protected $defaultFieldWizard = [
+        'localizationStateSelector' => [
+            'renderType' => 'localizationStateSelector',
+        ],
         'otherLanguageContent' => [
             'renderType' => 'otherLanguageContent',
+            'after' => [
+                'localizationStateSelector'
+            ],
         ],
         'defaultLanguageDifferences' => [
             'renderType' => 'defaultLanguageDifferences',
diff --git a/typo3/sysext/backend/Classes/Form/Element/SelectMultipleSideBySideElement.php b/typo3/sysext/backend/Classes/Form/Element/SelectMultipleSideBySideElement.php
index 0954b77b4af30af5e079a6174f34b3bba268418c..9cfae06b710c3a0bd22f3ae5fe9dd9dd4dfb589b 100644
--- a/typo3/sysext/backend/Classes/Form/Element/SelectMultipleSideBySideElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/SelectMultipleSideBySideElement.php
@@ -55,8 +55,14 @@ class SelectMultipleSideBySideElement extends AbstractFormElement
      * @var array
      */
     protected $defaultFieldWizard = [
+        'localizationStateSelector' => [
+            'renderType' => 'localizationStateSelector',
+        ],
         'otherLanguageContent' => [
             'renderType' => 'otherLanguageContent',
+            'after' => [
+                'localizationStateSelector'
+            ],
         ],
         'defaultLanguageDifferences' => [
             'renderType' => 'defaultLanguageDifferences',
diff --git a/typo3/sysext/backend/Classes/Form/Element/SelectSingleBoxElement.php b/typo3/sysext/backend/Classes/Form/Element/SelectSingleBoxElement.php
index 94a6f69a60a46dded43c4e0d430f981714b83292..005f83dd0f16d37157c9fe3c66c10ea716df4498 100644
--- a/typo3/sysext/backend/Classes/Form/Element/SelectSingleBoxElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/SelectSingleBoxElement.php
@@ -42,8 +42,14 @@ class SelectSingleBoxElement extends AbstractFormElement
      * @var array
      */
     protected $defaultFieldWizard = [
+        'localizationStateSelector' => [
+            'renderType' => 'localizationStateSelector',
+        ],
         'otherLanguageContent' => [
             'renderType' => 'otherLanguageContent',
+            'after' => [
+                'localizationStateSelector'
+            ],
         ],
         'defaultLanguageDifferences' => [
             'renderType' => 'defaultLanguageDifferences',
diff --git a/typo3/sysext/backend/Classes/Form/Element/SelectSingleElement.php b/typo3/sysext/backend/Classes/Form/Element/SelectSingleElement.php
index 6539009db55fbf1ca792b7a3d974727fda24673c..63319c7dc3c257224978b199caa134fa6ee6f1b4 100644
--- a/typo3/sysext/backend/Classes/Form/Element/SelectSingleElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/SelectSingleElement.php
@@ -37,9 +37,15 @@ class SelectSingleElement extends AbstractFormElement
             'renderType' => 'selectIcons',
             'disabled' => true,
         ],
+        'localizationStateSelector' => [
+            'renderType' => 'localizationStateSelector',
+            'after' => [
+                'selectIcons',
+            ],
+        ],
         'otherLanguageContent' => [
             'renderType' => 'otherLanguageContent',
-            'after' => [ 'selectIcons' ],
+            'after' => [ 'localizationStateSelector' ],
         ],
         'defaultLanguageDifferences' => [
             'renderType' => 'defaultLanguageDifferences',
diff --git a/typo3/sysext/backend/Classes/Form/Element/TextElement.php b/typo3/sysext/backend/Classes/Form/Element/TextElement.php
index 44093fe74d1a68bd74c50140c0f01df898a70915..a7d20fcc2741358ca2398c2b801d883acda0a508 100644
--- a/typo3/sysext/backend/Classes/Form/Element/TextElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/TextElement.php
@@ -31,8 +31,14 @@ class TextElement extends AbstractFormElement
      * @var array
      */
     protected $defaultFieldWizard = [
+        'localizationStateSelector' => [
+            'renderType' => 'localizationStateSelector',
+        ],
         'otherLanguageContent' => [
             'renderType' => 'otherLanguageContent',
+            'after' => [
+                'localizationStateSelector'
+            ],
         ],
         'defaultLanguageDifferences' => [
             'renderType' => 'defaultLanguageDifferences',
diff --git a/typo3/sysext/backend/Classes/Form/Element/TextTableElement.php b/typo3/sysext/backend/Classes/Form/Element/TextTableElement.php
index c3a02508fd2328727ff843ef2d9c7f7b7bd963ab..d6d1c290a629de55cb3eef9a17287875377e7035 100644
--- a/typo3/sysext/backend/Classes/Form/Element/TextTableElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/TextTableElement.php
@@ -30,8 +30,14 @@ class TextTableElement extends AbstractFormElement
      * @var array
      */
     protected $defaultFieldWizard = [
+        'localizationStateSelector' => [
+            'renderType' => 'localizationStateSelector',
+        ],
         'otherLanguageContent' => [
             'renderType' => 'otherLanguageContent',
+            'after' => [
+                'localizationStateSelector'
+            ],
         ],
         'defaultLanguageDifferences' => [
             'renderType' => 'defaultLanguageDifferences',
diff --git a/typo3/sysext/backend/Classes/Form/FieldWizard/LocalizationStateSelector.php b/typo3/sysext/backend/Classes/Form/FieldWizard/LocalizationStateSelector.php
new file mode 100644
index 0000000000000000000000000000000000000000..a7da8bd81a42850a26aa9ba345a96d8157b68631
--- /dev/null
+++ b/typo3/sysext/backend/Classes/Form/FieldWizard/LocalizationStateSelector.php
@@ -0,0 +1,141 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\CMS\Backend\Form\FieldWizard;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Backend\Form\AbstractNode;
+use TYPO3\CMS\Core\DataHandling\Localization\State;
+use TYPO3\CMS\Lang\LanguageService;
+
+/**
+ * Allows to define the localization state per field.
+ */
+class LocalizationStateSelector extends AbstractNode
+{
+    /**
+     * Render the radio buttons if enabled
+     *
+     * @return array Result array
+     */
+    public function render(): array
+    {
+        $languageService = $this->getLanguageService();
+        $result = $this->initializeResultArray();
+
+        $fieldName = $this->data['fieldName'];
+        $l10nStateFieldName = '';
+        if (isset($l10nStateFieldName)) {
+            $l10nStateFieldName = 'l10n_state';
+        }
+        if (
+            !$l10nStateFieldName
+            || !isset($this->data['defaultLanguageRow'])
+            || !isset($this->data['processedTca']['columns'][$fieldName]['config']['behaviour']['allowLanguageSynchronization'])
+            || !$this->data['processedTca']['columns'][$fieldName]['config']['behaviour']['allowLanguageSynchronization']
+        ) {
+            return $result;
+        }
+
+        $l10nParentFieldName = $this->data['processedTca']['ctrl']['transOrigPointerField'] ?? null;
+        $l10nSourceFieldName = $this->data['processedTca']['ctrl']['translationSource'] ?? null;
+
+        $sourceLanguageTitle = '';
+        $fieldValueInParentRow = '';
+        $fieldValueInSourceRow = '';
+        if ($l10nParentFieldName && $this->data['databaseRow'][$l10nParentFieldName] > 0) {
+            if ($l10nSourceFieldName && $this->data['databaseRow'][$l10nSourceFieldName] > 0) {
+                $languageField = $this->data['processedTca']['ctrl']['languageField'] ?? null;
+                if ($languageField
+                    && isset($this->data['sourceLanguageRow'][$languageField])
+                    && $this->data['sourceLanguageRow'][$languageField] > 0
+                ) {
+                    $languageUidOfSourceRow = $this->data['sourceLanguageRow'][$languageField];
+                    $sourceLanguageTitle = $this->data['systemLanguageRows'][$languageUidOfSourceRow]['title'] ?? '';
+                    $fieldValueInSourceRow = $this->data['sourceLanguageRow'][$fieldName] ?? null;
+                }
+            }
+            $fieldValueInParentRow = (string)$this->data['defaultLanguageRow'][$fieldName];
+        }
+
+        $localizationState = State::fromJSON(
+            $this->data['tableName'],
+            $this->data['databaseRow'][$l10nStateFieldName] ?? null
+        );
+
+        $fieldElementName = 'data[' . htmlspecialchars($this->data['tableName']) . ']'
+            . '[' . (int)($this->data['databaseRow']['uid']) . ']'
+            . '[' . htmlspecialchars($l10nStateFieldName) . ']'
+            . '[' . htmlspecialchars($this->data['fieldName']) . ']';
+
+        $html = [];
+        $html[] = '<div class="t3js-l10n-state-container">';
+        $html[] =   '<div>';
+        $html[] =       '<strong>';
+        $html[] =           $languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_wizards.xlf:localizationStateSelector.header');
+        $html[] =       '</strong>';
+        $html[] =   '</div>';
+        $html[] =   '<div class="radio radio-inline">';
+        $html[] =       '<label>';
+        $html[] =           '<input';
+        $html[] =               ' type="radio"';
+        $html[] =               ' name="' . htmlspecialchars($fieldElementName) . '"';
+        $html[] =               ' class="t3js-l10n-state-custom"';
+        $html[] =               ' value="custom"';
+        $html[] =               $localizationState->isCustomState($fieldName) ? ' checked="checked"' : '';
+        $html[] =               ' data-original-language-value=""';
+        $html[] =           '>';
+        $html[] =           $languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_wizards.xlf:localizationStateSelector.customValue');
+        $html[] =       '</label>';
+        $html[] =   '</div>';
+        $html[] =   '<div class="radio radio-inline">';
+        $html[] =       '<label>';
+        $html[] =           '<input';
+        $html[] =               ' type="radio"';
+        $html[] =               ' name="' . htmlspecialchars($fieldElementName) . '"';
+        $html[] =               ' value="parent"';
+        $html[] =               $localizationState->isParentState($fieldName) ? ' checked="checked"' : '';
+        $html[] =               ' data-original-language-value="' . htmlspecialchars((string)$fieldValueInParentRow) . '"';
+        $html[] =           '>';
+        $html[] =           $languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_wizards.xlf:localizationStateSelector.defaultLanguageValue');
+        $html[] =       '</label>';
+        $html[] =   '</div>';
+        if ($fieldValueInSourceRow) {
+            $html[] = '<div class="radio radio-inline">';
+            $html[] =   '<label>';
+            $html[] =       '<input';
+            $html[] =           ' type="radio"';
+            $html[] =           ' name="' . htmlspecialchars($fieldElementName) . '"';
+            $html[] =           ' value="source"';
+            $html[] =           $localizationState->isSourceState($fieldName) ? ' checked="checked"' : '';
+            $html[] =           ' data-original-language-value="' . htmlspecialchars((string)$fieldValueInSourceRow) . '"';
+            $html[] =       '>';
+            $html[] =       sprintf($languageService->sL('LLL:EXT:lang/Resources/Private/Language/locallang_wizards.xlf:localizationStateSelector.sourceLanguageValue'), htmlspecialchars($sourceLanguageTitle));
+            $html[] =   '</label>';
+            $html[] = '</div>';
+        }
+        $html[] = '</div>';
+
+        $result['html'] = implode(LF, $html);
+        return $result;
+    }
+
+    /**
+     * @return LanguageService
+     */
+    protected function getLanguageService()
+    {
+        return $GLOBALS['LANG'];
+    }
+}
diff --git a/typo3/sysext/backend/Classes/Form/FormDataCompiler.php b/typo3/sysext/backend/Classes/Form/FormDataCompiler.php
index 4bc0ad04cda92de26404c53931f6e90320232395..4e572fc1c9426638a1cede9dd3ed175571ecdc46 100644
--- a/typo3/sysext/backend/Classes/Form/FormDataCompiler.php
+++ b/typo3/sysext/backend/Classes/Form/FormDataCompiler.php
@@ -187,6 +187,9 @@ class FormDataCompiler
             'pageLanguageOverlayRows' => [],
             // If the handled row is a localized row, this entry hold the default language row array
             'defaultLanguageRow' => null,
+            // If the handled row is a localived row and $TCA[<tableName>]['ctrl']['translationSource'] is configured,
+            // This entry holds the row of the language source record.
+            'sourceLanguageRow' => null,
             // If the handled row is a localized row and a transOrigDiffSourceField is defined, this
             // is the unserialized version of it. The diff source field is basically a shadow version
             // of the default language record at the time when the language overlay record was created.
diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseLanguageRows.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseLanguageRows.php
index 74b4be8e8657726c883167029e6381274cc4708a..34f0ea48c4480ff78dc1287fe87aa2319c7c6319 100644
--- a/typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseLanguageRows.php
+++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseLanguageRows.php
@@ -103,6 +103,22 @@ class DatabaseLanguageRows implements FormDataProviderInterface
                         }
                     }
                 }
+
+                // @todo do that only if l10n_parent > 0 (not in "free mode")?
+                if (!empty($result['processedTca']['ctrl']['translationSource'])
+                    && is_string($result['processedTca']['ctrl']['translationSource'])
+                ) {
+                    $translationSourceFieldName = $result['processedTca']['ctrl']['translationSource'];
+                    if (isset($result['databaseRow'][$translationSourceFieldName])
+                        && $result['databaseRow'][$translationSourceFieldName] > 0
+                    ) {
+                        $uidOfTranslationSource = $result['databaseRow'][$translationSourceFieldName];
+                        $result['sourceLanguageRow'] = $this->getRecordWorkspaceOverlay(
+                            $result['tableName'],
+                            $uidOfTranslationSource
+                        );
+                    }
+                }
             }
         }
 
diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRecordTypeValue.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRecordTypeValue.php
index 90fdb5bde6f83edcb9fe7cc3151453814c0dd419..2e2f4c4f35f7a74bc177fae30815ef0196682337 100644
--- a/typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRecordTypeValue.php
+++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/DatabaseRecordTypeValue.php
@@ -64,7 +64,7 @@ class DatabaseRecordTypeValue implements FormDataProviderInterface
                         1438183881
                     );
                 }
-                $recordTypeValue = $this->getValueFromDefaultLanguageRecordIfConfigured($result, $tcaTypeField);
+                $recordTypeValue = $result['databaseRow'][$tcaTypeField];
             } else {
                 // If type is configured as localField:foreignField, fetch the type value from
                 // a foreign table. localField then point to a group or select field in the own table,
@@ -81,7 +81,7 @@ class DatabaseRecordTypeValue implements FormDataProviderInterface
                     );
                 }
 
-                $foreignUid = $this->getValueFromDefaultLanguageRecordIfConfigured($result, $pointerField);
+                $foreignUid = $result['databaseRow'][$pointerField];
                 // Resolve the foreign record only if there is a uid, otherwise fall back 0
                 if (!empty($foreignUid)) {
                     // Determine table name to fetch record from
@@ -149,29 +149,4 @@ class DatabaseRecordTypeValue implements FormDataProviderInterface
 
         return $row ?: [];
     }
-
-    /**
-     * If a localized row is handled, the field value of the default language record
-     * is used instead if tca is configured as "exclude" with empty localized value.
-     *
-     * @param array $result Main "$result" data array
-     * @param string $field Field name to fetch value for
-     * @return string field value
-     */
-    protected function getValueFromDefaultLanguageRecordIfConfigured($result, $field)
-    {
-        $value = $result['databaseRow'][$field];
-        if (
-            // is a localized record
-            !empty($result['processedTca']['ctrl']['languageField'])
-            && $result['databaseRow'][$result['processedTca']['ctrl']['languageField']] > 0
-            // l10n_mode for field is configured
-            && !empty($result['processedTca']['columns'][$field]['l10n_mode'])
-            // is exclude -> fall back to value of default record
-            && $result['processedTca']['columns'][$field]['l10n_mode'] === 'exclude'
-        ) {
-            $value = $result['defaultLanguageRow'][$field];
-        }
-        return $value;
-    }
 }
diff --git a/typo3/sysext/backend/Classes/Form/NodeFactory.php b/typo3/sysext/backend/Classes/Form/NodeFactory.php
index 422fcd537fc43ed18c3d312b1939dacbbad5a127..ef6b2661a812a53ce8cd33120353699ea0a38154 100644
--- a/typo3/sysext/backend/Classes/Form/NodeFactory.php
+++ b/typo3/sysext/backend/Classes/Form/NodeFactory.php
@@ -101,6 +101,7 @@ class NodeFactory
         'fileThumbnails' => FieldWizard\FileThumbnails::class,
         'fileTypeList' => FieldWizard\FileTypeList::class,
         'fileUpload' => FieldWizard\FileUpload::class,
+        'localizationStateSelector' => FieldWizard\LocalizationStateSelector::class,
         'otherLanguageContent' => FieldWizard\OtherLanguageContent::class,
         'recordsOverview' => FieldWizard\RecordsOverview::class,
         'selectIcons' => FieldWizard\SelectIcons::class,
diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine.js b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine.js
index 51b1f92e18402b75b4b51a90efe4c94c2f0362e9..ccc69525836672526a10de168fda7f40f1dcf775 100644
--- a/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine.js
+++ b/typo3/sysext/backend/Resources/Public/JavaScript/FormEngine.js
@@ -701,6 +701,34 @@ define(['jquery',
 		}).on('change', '.t3js-form-field-eval-null-placeholder-checkbox input[type="checkbox"]', function(e) {
 			$(this).closest('.t3js-formengine-field-item').find('.t3js-formengine-placeholder-placeholder').toggle();
 			$(this).closest('.t3js-formengine-field-item').find('.t3js-formengine-placeholder-formfield').toggle();
+		}).on('change', '.t3js-l10n-state-container input[type=radio]', function(event) {
+			// Change handler for "l10n_state" field changes
+			var $me = $(this);
+			var $input = $me.closest('.t3js-formengine-field-item').find('[data-formengine-input-name]');
+
+			if ($input.length > 0) {
+				var lastState = $input.data('last-l10n-state') || false,
+					currentState = $(this).val();
+
+				if (lastState && currentState === lastState) {
+					return;
+				}
+
+				if (currentState === 'custom') {
+					if (lastState) {
+						$(this).attr('data-original-language-value', $input.val());
+					}
+					$input.attr('disabled', false);
+				} else {
+					if (lastState === 'custom') {
+						$(this).closest('.t3js-l10n-state-container').find('.t3js-l10n-state-custom').attr('data-original-language-value', $input.val());
+					}
+					$input.attr('disabled', 'disabled');
+				}
+
+				$input.val($(this).attr('data-original-language-value')).trigger('change');
+				$input.data('last-l10n-state', $(this).val());
+			}
 		});
 	};
 
@@ -956,12 +984,26 @@ define(['jquery',
 		FormEngine.initializeNullNoPlaceholderCheckboxes();
 		FormEngine.initializeNullWithPlaceholderCheckboxes();
 		FormEngine.initializeInputLinkToggle();
+		FormEngine.initializeLocalizationStateSelector();
+	};
+
+	/**
+	 * Disable the input field on load if localization state selector is set to "parent" or "source"
+	 */
+	FormEngine.initializeLocalizationStateSelector = function() {
+		$('.t3js-l10n-state-container').each(function() {
+			var $input = $(this).closest('.t3js-formengine-field-item').find('[data-formengine-input-name]');
+			var currentState = $(this).find('input[type="radio"]:checked').val();
+			if (currentState === 'parent' || currentState === 'source') {
+				$input.attr('disabled', 'disabled');
+			}
+		});
 	};
 
 	/**
 	 * Toggle for input link explanation
 	 */
-	FormEngine.initializeInputLinkToggle = function () {
+	FormEngine.initializeInputLinkToggle = function() {
 		$(document).on('click', '.t3js-form-field-inputlink-explanation-toggle', function(e) {
 			e.preventDefault();
 
diff --git a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseLanguageRowsTest.php b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseLanguageRowsTest.php
index df93e6a552d63f62267f912c1e2c70d497673447..2bc0d4d6d6d3ad0aacc0a2b32b41ba2c46c9c93f 100644
--- a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseLanguageRowsTest.php
+++ b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseLanguageRowsTest.php
@@ -367,4 +367,77 @@ class DatabaseLanguageRowsTest extends \TYPO3\Components\TestingFramework\Core\U
 
         $this->assertEquals($expected, $this->subject->addData($input));
     }
+
+    /**
+     * @test
+     */
+    public function addDataSetsSourceLanguageRow()
+    {
+        $input = [
+            'tableName' => 'tt_content',
+            'databaseRow' => [
+                'uid' => 42,
+                'text' => 'localized text',
+                'sys_language_uid' => 3,
+                'l10n_parent' => 23,
+                'l10n_source' => 24,
+            ],
+            'processedTca' => [
+                'ctrl' => [
+                    'languageField' => 'sys_language_uid',
+                    'transOrigPointerField' => 'l10n_parent',
+                    'translationSource' => 'l10n_source',
+                ],
+            ],
+            'systemLanguageRows' => [
+                0 => [
+                    'uid' => 0,
+                    'title' => 'Default Language',
+                    'iso' => 'DEV',
+                ],
+                2 => [
+                    'uid' => 2,
+                    'title' => 'dansk',
+                    'iso' => 'dk,'
+                ],
+                3 => [
+                    'uid' => 3,
+                    'title' => 'french',
+                    'iso' => 'fr',
+                ],
+            ],
+            'defaultLanguageRow' => null,
+            'sourceLanguageRow' => null,
+            'additionalLanguageRows' => [],
+        ];
+
+        // For BackendUtility::getRecord()
+        $GLOBALS['TCA']['tt_content'] = ['foo'];
+        $sourceLanguageRow = [
+            'uid' => 24,
+            'pid' => 32,
+            'text' => 'localized text in dank',
+            'sys_language_uid' => 2,
+        ];
+        $defaultLanguageRow = [
+            'uid' => 23,
+            'pid' => 32,
+            'text' => 'default language text',
+            'sys_language_uid' => 0,
+        ];
+        $this->subject->expects($this->at(0))
+            ->method('getRecordWorkspaceOverlay')
+            ->with('tt_content', 23)
+            ->willReturn($defaultLanguageRow);
+        $this->subject->expects($this->at(1))
+            ->method('getRecordWorkspaceOverlay')
+            ->with('tt_content', 24)
+            ->willReturn($sourceLanguageRow);
+
+        $expected = $input;
+        $expected['defaultLanguageRow'] = $defaultLanguageRow;
+        $expected['sourceLanguageRow'] = $sourceLanguageRow;
+
+        $this->assertEquals($expected, $this->subject->addData($input));
+    }
 }
diff --git a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRecordTypeValueTest.php b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRecordTypeValueTest.php
index 72b53a6e5a0f32086628b2068326468b27d2e7eb..772a72de7ea1fca5526c01fccbe2a2ca72bce222 100644
--- a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRecordTypeValueTest.php
+++ b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRecordTypeValueTest.php
@@ -259,42 +259,6 @@ class DatabaseRecordTypeValueTest extends \TYPO3\Components\TestingFramework\Cor
         $this->subject->addData($input);
     }
 
-    /**
-     * @test
-     */
-    public function addDataSetsRecordTypeValueToValueOfDefaultLanguageRecordIfConfiguredAsExclude()
-    {
-        $input = [
-            'recordTypeValue' => '',
-            'processedTca' => [
-                'ctrl' => [
-                    'languageField' => 'sys_language_uid',
-                    'type' => 'aField',
-                ],
-                'columns' => [
-                    'aField' => [
-                        'l10n_mode' => 'exclude',
-                    ],
-                ],
-                'types' => [
-                    '3' => 'foo',
-                ],
-            ],
-            'databaseRow' => [
-                'sys_language_uid' => 2,
-                'aField' => 4,
-            ],
-            'defaultLanguageRow' => [
-                'aField' => 3,
-            ],
-        ];
-
-        $expected = $input;
-        $expected['recordTypeValue'] = '3';
-
-        $this->assertSame($expected, $this->subject->addData($input));
-    }
-
     /**
      * @test
      */
diff --git a/typo3/sysext/core/Classes/DataHandling/DataHandler.php b/typo3/sysext/core/Classes/DataHandling/DataHandler.php
index 13eefbfd2191d1e795ed74ddd6c6390cb4d49a5f..708d3bfb0fade10642d90cc44350c0a0a2380244 100644
--- a/typo3/sysext/core/Classes/DataHandling/DataHandler.php
+++ b/typo3/sysext/core/Classes/DataHandling/DataHandler.php
@@ -31,6 +31,7 @@ use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\QueryRestrictionContainerInterface;
 use TYPO3\CMS\Core\Database\ReferenceIndex;
 use TYPO3\CMS\Core\Database\RelationHandler;
+use TYPO3\CMS\Core\DataHandling\Localization\DataMapProcessor;
 use TYPO3\CMS\Core\Html\RteHtmlParser;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Messaging\FlashMessageService;
@@ -977,6 +978,8 @@ class DataHandler
                 $hookObjectsArr[] = $hookObject;
             }
         }
+        // Pre-process data-map and synchronize localization states
+        $this->datamap = DataMapProcessor::instance($this->datamap, $this->BE_USER)->process();
         // Organize tables so that the pages-table is always processed first. This is required if you want to make sure that content pointing to a new page will be created.
         $orderOfTables = [];
         // Set pages first.
@@ -1500,6 +1503,9 @@ class DataHandler
                 case 't3ver_tstamp':
                     // t3ver_label is not here because it CAN be edited as a regular field!
                     break;
+                case 'l10n_state':
+                    $fieldArray[$field] = $fieldValue;
+                    break;
                 default:
                     if (isset($GLOBALS['TCA'][$table]['columns'][$field])) {
                         // Evaluating the value
diff --git a/typo3/sysext/core/Classes/DataHandling/DatabaseSchemaService.php b/typo3/sysext/core/Classes/DataHandling/DatabaseSchemaService.php
new file mode 100644
index 0000000000000000000000000000000000000000..f4886d3791db02e2166b67068a2cc940355c4c05
--- /dev/null
+++ b/typo3/sysext/core/Classes/DataHandling/DatabaseSchemaService.php
@@ -0,0 +1,59 @@
+<?php
+namespace TYPO3\CMS\Core\DataHandling;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * This service provides the sql schema database records.
+ */
+class DatabaseSchemaService
+{
+    const TABLE_TEMPLATE = 'CREATE TABLE %s (' . LF . '%s' . LF . ');';
+    const FIELD_L10N_STATE_TEMPLATE = '  l10n_state text';
+
+    /**
+     * Add l10n_state field to tables that provide localization
+     *
+     * @return string Localization fields database schema
+     */
+    public function getLocalizationRequiredDatabaseSchema(array $sqlString)
+    {
+        $tableSchemas = [];
+
+        foreach ($GLOBALS['TCA'] as $tableName => $tableDefinition) {
+            if (
+                empty($tableDefinition['columns'])
+                || empty($tableDefinition['ctrl']['languageField'])
+                || empty($tableDefinition['ctrl']['transOrigPointerField'])
+            ) {
+                continue;
+            }
+
+            $fieldSchemas = [];
+            $fieldSchemas[] = static::FIELD_L10N_STATE_TEMPLATE;
+
+            $tableSchemas[] = sprintf(
+                static::TABLE_TEMPLATE,
+                $tableName,
+                implode(',' . LF, $fieldSchemas)
+            );
+        }
+
+        if (!empty($tableSchemas)) {
+            $sqlString[] = implode(LF, $tableSchemas);
+        }
+
+        return array('sqlString' => $sqlString);
+    }
+}
diff --git a/typo3/sysext/core/Classes/DataHandling/Localization/DataMapItem.php b/typo3/sysext/core/Classes/DataHandling/Localization/DataMapItem.php
new file mode 100644
index 0000000000000000000000000000000000000000..6acc9a6a37cdc61530abdb1e321b26b72cd97ff4
--- /dev/null
+++ b/typo3/sysext/core/Classes/DataHandling/Localization/DataMapItem.php
@@ -0,0 +1,430 @@
+<?php
+namespace TYPO3\CMS\Core\DataHandling\Localization;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
+
+/**
+ * Entity for data-map item.
+ */
+class DataMapItem
+{
+    const TYPE_PARENT = 'parent';
+    const TYPE_DIRECT_CHILD = 'directChild';
+    const TYPE_GRAND_CHILD = 'grandChild';
+
+    const SCOPE_PARENT = State::STATE_PARENT;
+    const SCOPE_SOURCE = State::STATE_SOURCE;
+    const SCOPE_EXCLUDE = 'exclude';
+
+    /**
+     * @var string
+     */
+    protected $tableName;
+
+    /**
+     * @var string|int
+     */
+    protected $id;
+
+    /**
+     * @var array
+     */
+    protected $suggestedValues;
+
+    /**
+     * @var array
+     */
+    protected $persistedValues;
+
+    /**
+     * @var array
+     */
+    protected $configurationFieldNames;
+
+    /**
+     * @var bool
+     */
+    protected $new;
+
+    /**
+     * @var string
+     */
+    protected $type;
+
+    /**
+     * @var State
+     */
+    protected $state;
+
+    /**
+     * @var string|int
+     */
+    protected $language;
+
+    /**
+     * @var string|int
+     */
+    protected $parent;
+
+    /**
+     * @var string|int
+     */
+    protected $source;
+
+    /**
+     * @var DataMapItem[][]
+     */
+    protected $dependencies = [];
+
+    /**
+     * Builds a data-map item. In addition to the constructor, the values
+     * for language, parent and source record pointers are assigned as well.
+     *
+     * @param string $tableName
+     * @param string|int $id
+     * @param array $suggestedValues
+     * @param array $persistedValues
+     * @param array $configurationFieldNames
+     * @return object|DataMapItem
+     */
+    public static function build(
+        string $tableName,
+        $id,
+        array $suggestedValues,
+        array $persistedValues,
+        array $configurationFieldNames
+    ) {
+        $item = GeneralUtility::makeInstance(
+            static::class,
+            $tableName,
+            $id,
+            $suggestedValues,
+            $persistedValues,
+            $configurationFieldNames
+        );
+
+        $item->language = (int)($suggestedValues[$item->getLanguageFieldName()] ?? $persistedValues[$item->getLanguageFieldName()]);
+        $item->setParent($suggestedValues[$item->getParentFieldName()] ?? $persistedValues[$item->getParentFieldName()]);
+        if ($item->getSourceFieldName() !== null) {
+            $item->setSource($suggestedValues[$item->getSourceFieldName()] ?? $persistedValues[$item->getSourceFieldName()]);
+        }
+
+        return $item;
+    }
+
+    /**
+     * @param string $tableName
+     * @param string|int $id
+     * @param array $suggestedValues
+     * @param array $persistedValues
+     * @param array $configurationFieldNames
+     */
+    public function __construct(
+        string $tableName,
+        $id,
+        array $suggestedValues,
+        array $persistedValues,
+        array $configurationFieldNames
+    ) {
+        $this->tableName = $tableName;
+        $this->id = $id;
+
+        $this->suggestedValues = $suggestedValues;
+        $this->persistedValues = $persistedValues;
+        $this->configurationFieldNames = $configurationFieldNames;
+
+        $this->new = !MathUtility::canBeInterpretedAsInteger($id);
+    }
+
+    /**
+     * Gets the current table name of this data-map item.
+     *
+     * @return string
+     */
+    public function getTableName(): string
+    {
+        return $this->tableName;
+    }
+
+    /**
+     * Gets the table name used to resolve the language parent record.
+     *
+     * @return string
+     */
+    public function getFromTableName(): string
+    {
+        if ($this->tableName === 'pages_language_overlay') {
+            return 'pages';
+        }
+        return $this->tableName;
+    }
+
+    /**
+     * Gets the table name used to resolve any kind of translations.
+     *
+     * @return string
+     */
+    public function getForTableName(): string
+    {
+        if ($this->tableName === 'pages') {
+            return 'pages_language_overlay';
+        }
+        return $this->tableName;
+    }
+
+    /**
+     * Gets the id of this data-map item.
+     *
+     * @return mixed
+     */
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    /**
+     * Gets the suggested values that were initially
+     * submitted as the whole data-map to the DataHandler.
+     *
+     * @return array
+     */
+    public function getSuggestedValues(): array
+    {
+        return $this->suggestedValues;
+    }
+
+    /**
+     * Gets the persisted values that represent the persisted state
+     * of the record this data-map item is a surrogate for - does only
+     * contain relevant field values.
+     *
+     * @return array
+     */
+    public function getPersistedValues(): array
+    {
+        return $this->persistedValues;
+    }
+
+    /**
+     * @return array
+     */
+    public function getConfigurationFieldNames(): array
+    {
+        return $this->configurationFieldNames;
+    }
+
+    /**
+     * @return string
+     */
+    public function getLanguageFieldName(): string
+    {
+        return $this->configurationFieldNames['language'];
+    }
+
+    /**
+     * @return string
+     */
+    public function getParentFieldName(): string
+    {
+        return $this->configurationFieldNames['parent'];
+    }
+
+    /**
+     * @return null|string
+     */
+    public function getSourceFieldName()
+    {
+        return $this->configurationFieldNames['source'];
+    }
+
+    /**
+     * @return bool
+     */
+    public function isNew(): bool
+    {
+        return $this->new;
+    }
+
+    /**
+     * @return string
+     */
+    public function getType(): string
+    {
+        if ($this->type === null) {
+            // implicit: default language, it's a parent
+            if ($this->language === 0) {
+                $this->type = static::TYPE_PARENT;
+            // implicit: having source value different to parent value, it's a 2nd or higher level translation
+            } elseif (
+                $this->source !== null
+                && $this->source !== $this->parent
+            ) {
+                $this->type = static::TYPE_GRAND_CHILD;
+            // implicit: otherwise, it's a 1st level translation
+            } else {
+                $this->type = static::TYPE_DIRECT_CHILD;
+            }
+        }
+        return $this->type;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isParentType(): bool
+    {
+        return $this->getType() === static::TYPE_PARENT;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isDirectChildType(): bool
+    {
+        return $this->getType() === static::TYPE_DIRECT_CHILD;
+    }
+
+    /**
+     * @return bool
+     */
+    public function isGrandChildType(): bool
+    {
+        return $this->getType() === static::TYPE_GRAND_CHILD;
+    }
+
+    /**
+     * @return State
+     */
+    public function getState(): State
+    {
+        if ($this->state === null && !$this->isParentType()) {
+            $this->state = State::fromJSON(
+                $this->tableName,
+                $this->persistedValues['l10n_state'] ?? null
+            );
+            $this->state->update(
+                $this->suggestedValues['l10n_state'] ?? []
+            );
+        }
+        return $this->state;
+    }
+
+    /**
+     * @return string|int
+     */
+    public function getLanguage()
+    {
+        return $this->language;
+    }
+
+    /**
+     * @param string|int $language
+     */
+    public function setLanguage($language)
+    {
+        $this->language = $language;
+    }
+
+    /**
+     * @return string|int
+     */
+    public function getParent()
+    {
+        return $this->parent;
+    }
+
+    /**
+     * @param string|int $parent
+     */
+    public function setParent($parent)
+    {
+        $this->parent = $parent;
+    }
+
+    /**
+     * @return string|int
+     */
+    public function getSource()
+    {
+        return $this->source;
+    }
+
+    /**
+     * @param string|int $source
+     */
+    public function setSource($source)
+    {
+        $this->source = $source;
+    }
+
+    /**
+     * @param string $scope
+     * @return int|string
+     */
+    public function getIdForScope($scope)
+    {
+        if (
+            $scope === static::SCOPE_PARENT
+            || $scope === static::SCOPE_EXCLUDE
+        ) {
+            return $this->getParent();
+        }
+        if ($scope === static::SCOPE_SOURCE) {
+            return $this->getSource();
+        }
+        throw new \RuntimeException('Invalid scope', 1486325248);
+    }
+
+    /**
+     * @return DataMapItem[][]
+     */
+    public function getDependencies(): array
+    {
+        return $this->dependencies;
+    }
+
+    /**
+     * @param DataMapItem[][] $dependencies
+     */
+    public function setDependencies(array $dependencies)
+    {
+        $this->dependencies = $dependencies;
+    }
+
+    /**
+     * @param string $scope
+     * @return DataMapItem[]
+     */
+    public function findDependencies(string $scope)
+    {
+        return ($this->dependencies[$scope] ?? []);
+    }
+
+    /**
+     * @return string[]
+     */
+    public function getApplicableScopes()
+    {
+        $scopes = [];
+        if (!empty($this->getSourceFieldName())) {
+            $scopes[] = static::SCOPE_SOURCE;
+        }
+        $scopes[] = static::SCOPE_PARENT;
+        $scopes[] = static::SCOPE_EXCLUDE;
+        return $scopes;
+    }
+}
\ No newline at end of file
diff --git a/typo3/sysext/core/Classes/DataHandling/Localization/DataMapProcessor.php b/typo3/sysext/core/Classes/DataHandling/Localization/DataMapProcessor.php
new file mode 100644
index 0000000000000000000000000000000000000000..d86f73bd7c773c4e09553d8856e6b1727e3adbf8
--- /dev/null
+++ b/typo3/sysext/core/Classes/DataHandling/Localization/DataMapProcessor.php
@@ -0,0 +1,894 @@
+<?php
+namespace TYPO3\CMS\Core\DataHandling\Localization;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Database\Connection;
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
+use TYPO3\CMS\Core\Database\RelationHandler;
+use TYPO3\CMS\Core\DataHandling\DataHandler;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
+use TYPO3\CMS\Core\Utility\StringUtility;
+
+/**
+ * This processor analyses the provided data-map before actually being process
+ * in the calling DataHandler instance. Field names that are configured to have
+ * "allowLanguageSynchronization" enabled are either synchronized from there
+ * relative parent records (could be a default language record, or a l10n_source
+ * record) or to their dependent records (in case a default language record or
+ * nested records pointing upwards with l10n_source).
+ *
+ * Except inline relational record editing, all modifications are applied to
+ * the data-map directly, which ensures proper history entries as a side-effect.
+ * For inline relational record editing, this processor either triggers the copy
+ * or localize actions by instantiation a new local DataHandler instance.
+ */
+class DataMapProcessor
+{
+    /**
+     * @var array
+     */
+    protected $dataMap = [];
+
+    /**
+     * @var BackendUserAuthentication
+     */
+    protected $backendUser;
+
+    /**
+     * @var DataMapItem[]
+     */
+    protected $items = [];
+
+    /**
+     * Class generator
+     *
+     * @param array $dataMap The submitted data-map to be worked on
+     * @param BackendUserAuthentication $backendUser Forwared backend-user scope
+     * @return DataMapProcessor
+     */
+    public static function instance(array $dataMap, BackendUserAuthentication $backendUser)
+    {
+        return GeneralUtility::makeInstance(
+            static::class,
+            $dataMap,
+            $backendUser
+        );
+    }
+
+    /**
+     * @param array $dataMap The submitted data-map to be worked on
+     * @param BackendUserAuthentication $backendUser Forwared backend-user scope
+     */
+    public function __construct(array $dataMap, BackendUserAuthentication $backendUser)
+    {
+        $this->dataMap = $dataMap;
+        $this->backendUser = $backendUser;
+    }
+
+    /**
+     * Processes the submitted data-map and returns the sanitized and enriched
+     * version depending on accordant localization states and dependencies.
+     *
+     * @return array
+     */
+    public function process()
+    {
+        foreach ($this->dataMap as $tableName => $idValues) {
+            $this->collectItems($tableName, $idValues);
+        }
+        $this->sanitize();
+        $this->enrich();
+        return $this->dataMap;
+    }
+
+    /**
+     * Create data map items of all affected rows
+     *
+     * @param string $tableName
+     * @param array $idValues
+     */
+    protected function collectItems(string $tableName, array $idValues)
+    {
+        if (!$this->isApplicable($tableName)) {
+            return;
+        }
+
+        $fieldNames = [
+            'uid' => 'uid',
+            'l10n_state' => 'l10n_state',
+            'language' => $GLOBALS['TCA'][$tableName]['ctrl']['languageField'],
+            'parent' => $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'],
+        ];
+        if (!empty($GLOBALS['TCA'][$tableName]['ctrl']['translationSource'])) {
+            $fieldNames['source'] = $GLOBALS['TCA'][$tableName]['ctrl']['translationSource'];
+        }
+
+        $translationValues = $this->fetchTranslationValues(
+            $tableName,
+            $fieldNames,
+            $this->filterNumericIds(array_keys($idValues))
+        );
+
+        $dependencies = $this->fetchDependencies(
+            $tableName,
+            $this->filterNumericIds(array_keys($idValues))
+        );
+
+        foreach ($idValues as $id => $values)
+        {
+            $recordValues = $translationValues[$id] ?? [];
+//            $values['l10n_state'] = json_decode($values['l10n_state'], true) ?? [];
+            $item = DataMapItem::build(
+                $tableName,
+                $id,
+                $values,
+                $recordValues,
+                $fieldNames
+            );
+
+            // must be any kind of localization and in connected mode
+            if ($item->getLanguage() > 0 && empty($item->getParent())) {
+                unset($item);
+                continue;
+            }
+            // add dependencies
+            if (!empty($dependencies[$id])) {
+                $item->setDependencies($dependencies[$id]);
+            }
+            $this->items[$tableName . ':' . $id] = $item;
+        }
+    }
+
+    /**
+     * Sanitizes the submitted data-map and removes fields which are not
+     * defined as custom and thus rely on either parent or source values.
+     */
+    protected function sanitize()
+    {
+        foreach (['grandChild', 'directChild'] as $type) {
+            foreach ($this->filterItemsByType($type) as $item) {
+                $this->sanitizeTranslationItem($item);
+            }
+        }
+    }
+
+    /**
+     * Handle synchronization of an item list
+     */
+    protected function enrich()
+    {
+        foreach (['grandChild', 'directChild'] as $type) {
+            foreach ($this->filterItemsByType($type) as $item) {
+                foreach ($item->getApplicableScopes() as $scope) {
+                    $fromId = $item->getIdForScope($scope);
+                    $fieldNames = $this->getFieldNamesForItemScope($item, $scope, !$item->isNew());
+                    $this->synchronizeTranslationItem($item, $fieldNames, $fromId);
+                }
+                $this->populateTranslationItem($item);
+                $this->finishTranslationItem($item);
+            }
+        }
+        foreach ($this->filterItemsByType('parent') as $item) {
+            $this->populateTranslationItem($item);
+        }
+    }
+
+    /**
+     * Sanitizes the submitted data-map for a particular item and removes
+     * fields which are not defined as custom and thus rely on either parent
+     * or source values.
+     *
+     * @param DataMapItem $item
+     */
+    protected function sanitizeTranslationItem(DataMapItem $item)
+    {
+        $fieldNames = array_merge(
+            $this->getFieldNamesForItemScope($item, DataMapItem::SCOPE_PARENT, !$item->isNew()),
+            $this->getFieldNamesForItemScope($item, DataMapItem::SCOPE_SOURCE, !$item->isNew())
+        );
+        // remove fields, that are submitted in data-map, but not defined as custom
+        $this->dataMap[$item->getTableName()][$item->getId()] = array_diff_key(
+            $this->dataMap[$item->getTableName()][$item->getId()],
+            array_combine($fieldNames, $fieldNames)
+        );
+    }
+
+    /**
+     * Synchronize a single item
+     *
+     * @param DataMapItem $item
+     * @param array $fieldNames
+     * @param int $fromId
+     */
+    protected function synchronizeTranslationItem(DataMapItem $item, array $fieldNames, int $fromId)
+    {
+        if (empty($fieldNames)) {
+            return;
+        }
+        $fieldNameList = 'uid,' . implode(',', $fieldNames);
+        $fromRecord = BackendUtility::getRecordWSOL(
+            $item->getFromTableName(),
+            $fromId,
+            $fieldNameList
+        );
+        $forRecord = [];
+        if (!$item->isNew()) {
+            $forRecord = BackendUtility::getRecordWSOL(
+                $item->getTableName(),
+                $item->getId(),
+                $fieldNameList
+            );
+        }
+        foreach ($fieldNames as $fieldName) {
+            $this->synchronizeFieldValues(
+                $item,
+                $fieldName,
+                $fromRecord,
+                $forRecord
+            );
+        }
+    }
+
+    /**
+     * Populates values downwards, either from a parent language item or
+     * a source language item to an accordant dependent translation item.
+     *
+     * @param DataMapItem $item
+     */
+    protected function populateTranslationItem(DataMapItem $item)
+    {
+        if ($item->isNew()) {
+            return;
+        }
+
+        foreach ([State::STATE_PARENT, State::STATE_SOURCE] as $scope) {
+            foreach ($item->findDependencies($scope) as $dependentItem) {
+                // use suggested item, if it was submitted in data-map
+                $suggestedDependentItem = $this->findItem(
+                    $dependentItem->getTableName(),
+                    $dependentItem->getId()
+                );
+                if ($suggestedDependentItem !== null) {
+                    $dependentItem = $suggestedDependentItem;
+                }
+                $fieldNames = $this->getFieldNamesForItemScope(
+                    $dependentItem,
+                    $scope,
+                    false
+                );
+                $this->synchronizeTranslationItem(
+                    $dependentItem,
+                    $fieldNames,
+                    $item->getId()
+                );
+            }
+        }
+    }
+
+    /**
+     * Finishes a translation item by updating states to be persisted.
+     *
+     * @param DataMapItem $item
+     */
+    protected function finishTranslationItem(DataMapItem $item)
+    {
+        if (
+            $item->isParentType()
+            || !State::isApplicable($item->getTableName())
+        ) {
+            return;
+        }
+
+        $this->dataMap[$item->getTableName()][$item->getId()]['l10n_state'] = $item->getState()->export();
+    }
+
+    /**
+     * Synchronize simple values like text and similar
+     *
+     * @param DataMapItem $item
+     * @param string $fieldName
+     * @param array $fromRecord
+     * @param array $forRecord
+     */
+    protected function synchronizeFieldValues(DataMapItem $item, string $fieldName, array $fromRecord, array $forRecord)
+    {
+        // skip if this field has been processed already, assumed that proper sanitation happened
+        if (!empty($this->dataMap[$item->getTableName()][$item->getId()][$fieldName])) {
+            return;
+        }
+
+        $fromId = $fromRecord['uid'];
+        $fromValue = $this->dataMap[$item->getFromTableName()][$fromId][$fieldName] ?? $fromRecord[$fieldName];
+
+        // plain values
+        if (!$this->isRelationField($item->getFromTableName(), $fieldName)) {
+            $this->dataMap[$item->getTableName()][$item->getId()][$fieldName] = $fromValue;
+        // direct relational values
+        } elseif (!$this->isInlineRelationField($item->getFromTableName(), $fieldName)) {
+            $this->synchronizeDirectRelations($item, $fieldName, $fromRecord);
+        // inline relational values
+        } else {
+            $this->synchronizeInlineRelations($item, $fieldName, $fromRecord, $forRecord);
+        }
+    }
+
+    /**
+     * Synchronize select and group field localizations
+     *
+     * @param DataMapItem $item
+     * @param string $fieldName
+     * @param array $fromRecord
+     */
+    protected function synchronizeDirectRelations(DataMapItem $item, string $fieldName, array $fromRecord)
+    {
+        $fromId = $fromRecord['uid'];
+        $fromValue = $this->dataMap[$item->getFromTableName()][$fromId][$fieldName] ?? $fromRecord[$fieldName];
+        $configuration = $GLOBALS['TCA'][$item->getFromTableName()]['columns'][$fieldName];
+
+        // non-MM relations are stored as comma separated values, just use them
+        // if values are available in data-map already, just use them as well
+        if (
+            empty($configuration['config']['MM'])
+            || isset($this->dataMap[$item->getFromTableName()][$fromId][$fieldName])
+            || ($configuration['config']['special'] ?? null) === 'languages'
+        ) {
+            $this->dataMap[$item->getTableName()][$item->getId()][$fieldName] = $fromValue;
+            return;
+        }
+
+        // fetch MM relations from storage
+        $type = $configuration['config']['type'];
+        $manyToManyTable = $configuration['config']['MM'];
+        if ($type === 'group' && $configuration['config']['internal_type'] === 'db') {
+            $tableNames = trim($configuration['config']['allowed'] ?? '');
+        } elseif ($configuration['config']['type'] === 'select') {
+            $tableNames = ($configuration['foreign_table'] ?? '');
+        } else {
+            return;
+        }
+
+        $relationHandler = $this->createRelationHandler();
+        $relationHandler->start(
+            '',
+            $tableNames,
+            $manyToManyTable,
+            $fromId,
+            $item->getFromTableName(),
+            $configuration['config']
+        );
+
+        // provide list of relations, optionally prepended with table name
+        // e.g. "13,19,23" or "tt_content_27,tx_extension_items_28"
+        $this->dataMap[$item->getTableName()][$item->getId()][$fieldName] = implode(
+            ',',
+            $relationHandler->getValueArray()
+        );
+    }
+
+    /**
+     * Handle synchonization of inline relations
+     *
+     * @param DataMapItem $item
+     * @param string $fieldName
+     * @param array $fromRecord
+     * @param array $forRecord
+     */
+    protected function synchronizeInlineRelations(DataMapItem $item, string $fieldName, array $fromRecord, array $forRecord)
+    {
+        $fromId = $fromRecord['uid'];
+        $configuration = $GLOBALS['TCA'][$item->getFromTableName()]['columns'][$fieldName];
+        $foreignTableName = $configuration['config']['foreign_table'];
+        $manyToManyTable = ($configuration['config']['MM'] ?? '');
+
+        $languageFieldName = ($GLOBALS['TCA'][$foreignTableName]['ctrl']['languageField'] ?? null);
+        $parentFieldName = ($GLOBALS['TCA'][$foreignTableName]['ctrl']['transOrigPointerField'] ?? null);
+        $sourceFieldName = ($GLOBALS['TCA'][$foreignTableName]['ctrl']['translationSource'] ?? null);
+
+        // determine suggested elements of either translation parent or source record
+        // from data-map, in case the accordant language parent/source record was modified
+        if (isset($this->dataMap[$item->getFromTableName()][$fromId][$fieldName])) {
+            $suggestedAncestorIds = GeneralUtility::trimExplode(
+                ',',
+                $this->dataMap[$item->getFromTableName()][$fromId][$fieldName],
+                true
+            );
+        // determine suggested elements of either translation parent or source record from storage
+        } else {
+            $relationHandler = $this->createRelationHandler();
+            $relationHandler->start(
+                $fromRecord[$fieldName],
+                $foreignTableName,
+                $manyToManyTable,
+                $fromId,
+                $item->getFromTableName(),
+                $configuration['config']
+            );
+            $suggestedAncestorIds = $this->mapRelationItemId($relationHandler->itemArray);
+        }
+        // determine persisted elements for the current data-map item
+        $relationHandler = $this->createRelationHandler();
+        $relationHandler->start(
+            $forRecord[$fieldName] ?? '',
+            $foreignTableName,
+            $manyToManyTable,
+            $item->getId(),
+            $item->getTableName(),
+            $configuration['config']
+        );
+        $persistedIds = $this->mapRelationItemId($relationHandler->itemArray);
+        // The dependent ID map points from language parent/source record to
+        // localization, thus keys: parents/sources & values: localizations
+        $dependentIdMap = $this->fetchDependentIdMap($foreignTableName, $suggestedAncestorIds);
+        // filter incomplete structures - this is a drawback of DataHandler's remap stack, since
+        // just created IRRE translations still belong to the language parent - filter them out
+        $suggestedAncestorIds = array_diff($suggestedAncestorIds, array_values($dependentIdMap));
+        // compile element differences to be resolved
+        // remove elements that are persisted at the language translation, but not required anymore
+        $removeIds = array_diff($persistedIds, array_values($dependentIdMap));
+        // remove elements that are persisted at the language parent/source, but not required anymore
+        $removeAncestorIds = array_diff(array_keys($dependentIdMap), $suggestedAncestorIds);
+        // missing elements that are persisted at the language parent/source, but not translated yet
+        $missingAncestorIds = array_diff($suggestedAncestorIds, array_keys($dependentIdMap));
+        // persisted elements that should be copied or localized
+        $createAncestorIds = $this->filterNumericIds($missingAncestorIds, true);
+        // non-persisted elements that should be duplicated in data-map directly
+        $populateAncestorIds = $this->filterNumericIds($missingAncestorIds, false);
+        // this desired state map defines the final result of child elements of the translation
+        $desiredLocalizationIdMap = array_combine($suggestedAncestorIds, $suggestedAncestorIds);
+        // update existing translations in the desired state map
+        foreach ($dependentIdMap as $ancestorId => $translationId) {
+            if (isset($desiredLocalizationIdMap[$ancestorId])) {
+                $desiredLocalizationIdMap[$ancestorId] = $translationId;
+            }
+        }
+        // nothing to synchronize, but element order could have been changed
+        if (empty($removeAncestorIds) && empty($missingAncestorIds)) {
+            $this->dataMap[$item->getTableName()][$item->getId()][$fieldName] = implode(
+                ',',
+                array_values($desiredLocalizationIdMap)
+            );
+            return;
+        }
+
+        $localCommandMap = [];
+        foreach ($removeIds as $removeId) {
+            $localCommandMap[$foreignTableName][$removeId]['delete'] = true;
+        }
+        foreach ($removeAncestorIds as $removeAncestorId) {
+            $removeId = $dependentIdMap[$removeAncestorId];
+            $localCommandMap[$foreignTableName][$removeId]['delete'] = true;
+        }
+        foreach ($createAncestorIds as $createAncestorId) {
+            // if child table is not aware of localization, just copy
+            if (empty($languageFieldName) || empty($parentFieldName)) {
+                $localCommandMap[$foreignTableName][$createAncestorId]['copy'] = true;
+            // otherwise, trigger the localization process
+            } else {
+                $localCommandMap[$foreignTableName][$createAncestorId]['localize'] = $item->getLanguage();
+            }
+        }
+        // execute copy, localize and delete actions on persisted child records
+        if (!empty($localCommandMap)) {
+            $localDataHandler = GeneralUtility::makeInstance(DataHandler::class);
+            $localDataHandler->start([], $localCommandMap, $this->backendUser);
+            $localDataHandler->process_cmdmap();
+            // update copied or localized ids
+            foreach ($createAncestorIds as $createAncestorId) {
+                if (empty($localDataHandler->copyMappingArray[$foreignTableName][$createAncestorId])) {
+                    throw new \RuntimeException('Child record was not processed', 1486233164);
+                }
+                $newLocalizationId = $localDataHandler->copyMappingArray[$foreignTableName][$createAncestorId];
+                $newLocalizationId = $localDataHandler->getAutoVersionId($foreignTableName, $newLocalizationId) ?? $newLocalizationId;
+                $desiredLocalizationIdMap[$createAncestorId] = $newLocalizationId;
+            }
+        }
+        // populate new child records in data-map
+        if (!empty($populateAncestorIds)) {
+            foreach ($populateAncestorIds as $populateId) {
+                $newLocalizationId = StringUtility::getUniqueId('NEW');
+                $desiredLocalizationIdMap[$populateId] = $newLocalizationId;
+                // @todo l10n_mode=prefixLangTitle is not applied to this "in-memory translation"
+                $this->dataMap[$foreignTableName][$newLocalizationId] = $this->dataMap[$foreignTableName][$populateId];
+                $this->dataMap[$foreignTableName][$newLocalizationId][$languageFieldName] = $item->getLanguage();
+                // @todo Only $populatedIs used in TCA type 'select' is resolved in DataHandler's remapStack
+                $this->dataMap[$foreignTableName][$newLocalizationId][$parentFieldName] = $populateId;
+                if ($sourceFieldName !== null) {
+                    // @todo Not sure, whether $populateId is resolved in DataHandler's remapStack
+                    $this->dataMap[$foreignTableName][$newLocalizationId][$sourceFieldName] = $populateId;
+                }
+            }
+        }
+        // update inline parent field references - required to update pointer fields
+        $this->dataMap[$item->getTableName()][$item->getId()][$fieldName] = implode(
+            ',',
+            array_values($desiredLocalizationIdMap)
+        );
+    }
+
+    /**
+     * Fetches translation related field values for the items submitted in
+     * the data-map. That's why further adjustment for the tables pages vs.
+     * pages_language_overlay is not required.
+     *
+     * @param string $tableName
+     * @param array $fieldNames
+     * @param array $ids
+     * @return array
+     */
+    protected function fetchTranslationValues(string $tableName, array $fieldNames, array $ids)
+    {
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable($tableName);
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
+        $statement = $queryBuilder
+            ->select(...array_values($fieldNames))
+            ->from($tableName)
+            ->where(
+                $queryBuilder->expr()->in(
+                    'uid',
+                    $queryBuilder->createNamedParameter($ids, Connection::PARAM_INT_ARRAY)
+                )
+            )
+            ->execute();
+
+        $translationValues = [];
+        foreach ($statement as $record) {
+            $translationValues[$record['uid']] = $record;
+        }
+        return $translationValues;
+    }
+
+    /**
+     * Create arary of dependent records
+     *
+     * @param string $tableName
+     * @param array $ids
+     * @return DataMapItem[][]
+     */
+    protected function fetchDependencies(string $tableName, array $ids)
+    {
+        if ($tableName === 'pages') {
+            $tableName = 'pages_language_overlay';
+        }
+
+        $fieldNames = [
+            'uid' => 'uid',
+            'l10n_state' => 'l10n_state',
+            'language' => $GLOBALS['TCA'][$tableName]['ctrl']['languageField'],
+            'parent' => $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'],
+        ];
+        if (!empty($GLOBALS['TCA'][$tableName]['ctrl']['translationSource'])) {
+            $fieldNames['source'] = $GLOBALS['TCA'][$tableName]['ctrl']['translationSource'];
+        }
+
+        $dependentElements = $this->fetchDependentElements($tableName, $ids, $fieldNames);
+
+        $dependencyMap = [];
+        foreach ($dependentElements as $dependentElement) {
+            $dependentItem = DataMapItem::build(
+                $tableName,
+                $dependentElement['uid'],
+                [],
+                $dependentElement,
+                $fieldNames
+            );
+
+            if ($dependentItem->isDirectChildType()) {
+                $dependencyMap[$dependentItem->getParent()][State::STATE_PARENT][] = $dependentItem;
+            }
+            if ($dependentItem->isGrandChildType()) {
+                $dependencyMap[$dependentItem->getSource()][State::STATE_SOURCE][] = $dependentItem;
+            }
+        }
+        return $dependencyMap;
+    }
+
+    /**
+     * Fetch dependent records that depend on given record id's in their parent or source field and
+     * create an id map as further lookup array
+     *
+     * @param string $tableName
+     * @param array $ids
+     * @return array
+     */
+    protected function fetchDependentIdMap(string $tableName, array $ids)
+    {
+        if ($tableName === 'pages') {
+            $tableName = 'pages_language_overlay';
+        }
+
+        $fieldNames = [
+            'uid' => 'uid',
+            'l10n_state' => 'l10n_state',
+            'language' => $GLOBALS['TCA'][$tableName]['ctrl']['languageField'],
+            'parent' => $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'],
+        ];
+        if (!empty($GLOBALS['TCA'][$tableName]['ctrl']['translationSource'])) {
+            $fieldNames['source'] = $GLOBALS['TCA'][$tableName]['ctrl']['translationSource'];
+        }
+
+        $dependentElements = $this->fetchDependentElements($tableName, $ids, $fieldNames);
+
+        $dependentIdMap = [];
+        foreach ($dependentElements as $dependentElement) {
+            // implicit: having source value different to parent value, use source pointer
+            if (
+                !empty($fieldNames['source'])
+                && $dependentElement[$fieldNames['source']] !== $dependentElement[$fieldNames['parent']]
+            ) {
+                $dependentIdMap[$dependentElement[$fieldNames['source']]] = $dependentElement['uid'];
+            // implicit: otherwise, use parent pointer
+            } else {
+                $dependentIdMap[$dependentElement[$fieldNames['parent']]] = $dependentElement['uid'];
+            }
+        }
+        return $dependentIdMap;
+    }
+
+
+    /**
+     * Fetch all elements that depend on given record id's in their parent or source field
+     *
+     * @param string $tableName
+     * @param array $ids
+     * @param array|null $fieldNames
+     * @return array
+     */
+    protected function fetchDependentElements(string $tableName, array $ids, array $fieldNames)
+    {
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable($tableName);
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
+
+        $predicates = [
+            $queryBuilder->expr()->in(
+                $fieldNames['parent'],
+                $queryBuilder->createNamedParameter($ids, Connection::PARAM_INT_ARRAY)
+            )
+        ];
+
+        if (!empty($fieldNames['source'])) {
+            $predicates = [
+                $queryBuilder->expr()->in(
+                    $fieldNames['source'],
+                    $queryBuilder->createNamedParameter($ids, Connection::PARAM_INT_ARRAY)
+                )
+            ];
+        }
+
+        $statement = $queryBuilder
+            ->select(...array_values($fieldNames))
+            ->from($tableName)
+            ->andWhere(
+                // must be any kind of localization
+                $queryBuilder->expr()->gt(
+                    $fieldNames['language'],
+                    $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+                ),
+                // must be in connected mode
+                $queryBuilder->expr()->gt(
+                    $fieldNames['parent'],
+                    $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+                ),
+                // any parent or source pointers
+                $queryBuilder->expr()->orX(...$predicates)
+            )
+            ->execute();
+
+        $dependentElements = [];
+        foreach ($statement as $record) {
+            $dependentElements[] = $record;
+        }
+        return $dependentElements;
+    }
+
+    /**
+     * Return array of data map items that are of given type
+     *
+     * @param string $type
+     * @return DataMapItem[]
+     */
+    protected function filterItemsByType(string $type)
+    {
+        return array_filter(
+            $this->items,
+            function(DataMapItem $item) use ($type) {
+                return $item->getType() === $type;
+            }
+        );
+    }
+
+    /**
+     * Return only id's that are integer - so no NEW...
+     *
+     * @param array $ids
+     * @param bool $numeric
+     * @return array
+     */
+    protected function filterNumericIds(array $ids, bool $numeric = true)
+    {
+        return array_filter(
+            $ids,
+            function($id) use ($numeric) {
+                return MathUtility::canBeInterpretedAsInteger($id) === $numeric;
+            }
+        );
+    }
+
+    /**
+     * Flatten array
+     *
+     * @param array $relationItems
+     * @return string[]
+     */
+    protected function mapRelationItemId(array $relationItems)
+    {
+        return array_map(
+            function(array $relationItem) {
+                return (string)$relationItem['id'];
+            },
+            $relationItems
+        );
+    }
+
+    /**
+     * See if an items is in item list and return it
+     *
+     * @param string $tableName
+     * @param string|int $id
+     * @return null|DataMapItem
+     */
+    protected function findItem(string $tableName, $id)
+    {
+        return $this->items[$tableName . ':' . $id] ?? null;
+    }
+
+    /**
+     * Field names we have to deal with
+     *
+     * @param DataMapItem $item
+     * @param string $scope
+     * @param null|bool $modified
+     * @return string[]
+     */
+    protected function getFieldNamesForItemScope(
+        DataMapItem $item,
+        string $scope,
+        bool $modified
+    ) {
+        if (
+            $scope === DataMapItem::SCOPE_PARENT
+            || $scope === DataMapItem::SCOPE_SOURCE
+        ) {
+            if (!State::isApplicable($item->getTableName())) {
+                return [];
+            }
+            return $item->getState()->filterFieldNames($scope, $modified);
+        }
+        if ($scope === DataMapItem::SCOPE_EXCLUDE) {
+            return $this->getLocalizationModeExcludeFieldNames(
+                $item->getTableName()
+            );
+        }
+        return [];
+    }
+
+    /**
+     * Field names of TCA table with columns having l10n_mode=exclude
+     *
+     * @param string $tableName
+     * @return string[]
+     */
+    protected function getLocalizationModeExcludeFieldNames(string $tableName)
+    {
+        $localizationExcludeFieldNames = [];
+        if (empty($GLOBALS['TCA'][$tableName]['columns'])) {
+            return $localizationExcludeFieldNames;
+        }
+
+        foreach ($GLOBALS['TCA'][$tableName]['columns'] as $fieldName => $configuration) {
+            if (($configuration['l10n_mode'] ?? null) === 'exclude') {
+                $localizationExcludeFieldNames[] = $fieldName;
+            }
+        }
+
+        return $localizationExcludeFieldNames;
+    }
+
+    /**
+     * True if we're dealing with a field that has foreign db relations
+     *
+     * @param string $tableName
+     * @param string $fieldName
+     * @return bool True if field is type=group with internalType === db or select with foreign_table
+     */
+    protected function isRelationField(string $tableName, string $fieldName): bool
+    {
+        if (empty($GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config']['type'])) {
+            return false;
+        }
+
+        $configuration = $GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
+
+        return (
+            $configuration['type'] === 'group'
+                && ($configuration['internal_type'] ?? null) === 'db'
+                && !empty($configuration['allowed'])
+            || $configuration['type'] === 'select'
+                && (
+                    !empty($configuration['foreign_table'])
+                        && !empty($GLOBALS['TCA'][$configuration['foreign_table']])
+                    || ($configuration['special'] ?? null) === 'languages'
+                )
+            || $this->isInlineRelationField($tableName, $fieldName)
+        );
+    }
+
+    /**
+     * True if we're dealing with an inline field
+     *
+     * @param string $tableName
+     * @param string $fieldName
+     * @return bool TRUE if field is of type inline with foreign_table set
+     */
+    protected function isInlineRelationField(string $tableName, string $fieldName): bool
+    {
+        if (empty($GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config']['type'])) {
+            return false;
+        }
+
+        $configuration = $GLOBALS['TCA'][$tableName]['columns'][$fieldName]['config'];
+
+        return (
+            $configuration['type'] === 'inline'
+            && !empty($configuration['foreign_table'])
+            && !empty($GLOBALS['TCA'][$configuration['foreign_table']])
+        );
+    }
+
+    /**
+     * Determines whether the table can be localized and either has fields
+     * with allowLanguageSynchronization enabled or l10n_mode set to exclude.
+     *
+     * @param string $tableName
+     * @return bool
+     */
+    protected function isApplicable(string $tableName): bool
+    {
+        return (
+            State::isApplicable($tableName)
+            || BackendUtility::isTableLocalizable($tableName)
+                && count($this->getLocalizationModeExcludeFieldNames($tableName)) > 0
+        );
+    }
+
+    /**
+     * @return RelationHandler
+     */
+    protected function createRelationHandler()
+    {
+        $relationHandler = GeneralUtility::makeInstance(RelationHandler::class);
+        $relationHandler->setWorkspaceId($this->backendUser->workspace);
+        return $relationHandler;
+    }
+}
\ No newline at end of file
diff --git a/typo3/sysext/core/Classes/DataHandling/Localization/State.php b/typo3/sysext/core/Classes/DataHandling/Localization/State.php
new file mode 100644
index 0000000000000000000000000000000000000000..93ed768676f5ccb20fa8bd374e79f9c7648acbdd
--- /dev/null
+++ b/typo3/sysext/core/Classes/DataHandling/Localization/State.php
@@ -0,0 +1,296 @@
+<?php
+namespace TYPO3\CMS\Core\DataHandling\Localization;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Value object for l10n_state field value.
+ */
+class State
+{
+    const STATE_CUSTOM = 'custom';
+    const STATE_PARENT = 'parent';
+    const STATE_SOURCE = 'source';
+
+    /**
+     * @param string $tableName
+     * @return null|State
+     */
+    public static function create(string $tableName)
+    {
+        if (!static::isApplicable($tableName)) {
+            return null;
+        }
+
+        return GeneralUtility::makeInstance(
+            static::class,
+            $tableName
+        );
+    }
+
+    /**
+     * @param string $tableName
+     * @param string|null $json
+     * @return null|State
+     */
+    public static function fromJSON(string $tableName, string $json = null)
+    {
+        if (!static::isApplicable($tableName)) {
+            return null;
+        }
+
+        $states = json_decode($json ?? '', true);
+        return GeneralUtility::makeInstance(
+            static::class,
+            $tableName,
+            $states ?? []
+        );
+    }
+
+    /**
+     * @param string $tableName
+     * @return bool
+     */
+    public static function isApplicable(string $tableName)
+    {
+        return (
+            static::hasColumns($tableName)
+            && static::hasLanguageFieldName($tableName)
+            && static::hasTranslationParentFieldName($tableName)
+            && count(static::getFieldNames($tableName)) > 0
+        );
+    }
+
+    /**
+     * @param string $tableName
+     * @return bool
+     */
+    protected static function hasColumns(string $tableName)
+    {
+        return (
+            !empty($GLOBALS['TCA'][$tableName]['columns'])
+            && is_array($GLOBALS['TCA'][$tableName]['columns'])
+        );
+    }
+
+    /**
+     * @param string $tableName
+     * @return bool
+     */
+    protected static function hasLanguageFieldName(string $tableName)
+    {
+        return !empty($GLOBALS['TCA'][$tableName]['ctrl']['languageField']);
+    }
+
+    /**
+     * @param string $tableName
+     * @return bool
+     */
+    protected static function hasTranslationParentFieldName(string $tableName)
+    {
+        return !empty($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']);
+    }
+
+    /**
+     * @param string $tableName
+     * @return array
+     */
+    protected static function getFieldNames(string $tableName)
+    {
+        return array_keys(
+            array_filter(
+                $GLOBALS['TCA'][$tableName]['columns'],
+                function(array $fieldConfiguration) {
+                    return !empty(
+                        $fieldConfiguration['config']
+                            ['behaviour']['allowLanguageSynchronization']
+                    );
+                }
+            )
+        );
+    }
+
+    /**
+     * @var string
+     */
+    protected $tableName;
+
+    /**
+     * @var array
+     */
+    protected $states;
+
+    /**
+     * @var array
+     */
+    protected $originalStates;
+
+    /**
+     * @param string $tableName
+     * @param array $states
+     */
+    public function __construct(string $tableName, array $states = array())
+    {
+        $this->tableName = $tableName;
+        $this->states = $states;
+        $this->originalStates = $states;
+
+        $this->states = $this->sanitize($states);
+        $this->states = $this->enrich($states);
+    }
+
+    /**
+     * @param array $states
+     */
+    public function update(array $states)
+    {
+        $this->states = array_merge(
+            $this->states,
+            $this->sanitize($states)
+        );
+    }
+
+    /**
+     * @return string|null
+     */
+    public function export()
+    {
+        if (empty($this->states)) {
+            return null;
+        }
+        return json_encode($this->states);
+    }
+
+    /**
+     * @return string[]
+     */
+    public function getModifiedFieldNames()
+    {
+        return array_keys(
+            array_diff_assoc(
+                $this->states,
+                $this->originalStates
+            )
+        );
+    }
+
+    /**
+     * @return bool
+     */
+    public function isModified()
+    {
+        return !empty($this->getModifiedFieldNames());
+    }
+
+    /**
+     * @param string $fieldName
+     * @return bool
+     */
+    public function isUndefined(string $fieldName)
+    {
+        return !isset($this->states[$fieldName]);
+    }
+
+    /**
+     * @param string $fieldName
+     * @return bool
+     */
+    public function isCustomState(string $fieldName)
+    {
+        return ($this->states[$fieldName] ?? null) === static::STATE_CUSTOM;
+    }
+
+    /**
+     * @param string $fieldName
+     * @return bool
+     */
+    public function isParentState(string $fieldName)
+    {
+        return ($this->states[$fieldName] ?? null) === static::STATE_PARENT;
+    }
+
+    /**
+     * @param string $fieldName
+     * @return bool
+     */
+    public function isSourceState(string $fieldName)
+    {
+        return ($this->states[$fieldName] ?? null) === static::STATE_SOURCE;
+    }
+
+    /**
+     * @param string $fieldName
+     * @return null|string
+     */
+    public function getState(string $fieldName)
+    {
+        return ($this->states[$fieldName] ?? null);
+    }
+
+    /**
+     * Filters field names having a desired state.
+     *
+     * @param string $desiredState
+     * @param bool $modified
+     * @return string[]
+     */
+    public function filterFieldNames(string $desiredState, bool $modified = false)
+    {
+        if (!$modified) {
+            $fieldNames = array_keys($this->states);
+        } else {
+            $fieldNames = $this->getModifiedFieldNames();
+        }
+        return array_filter(
+            $fieldNames,
+            function($fieldName) use ($desiredState) {
+                return $this->states[$fieldName] === $desiredState;
+            }
+        );
+    }
+
+    /**
+     * Filter out field names that don't exist in TCA.
+     *
+     * @param array $states
+     * @return array
+     */
+    protected function sanitize(array $states)
+    {
+        $fieldNames = static::getFieldNames($this->tableName);
+        return array_intersect_key(
+            $states,
+            array_combine($fieldNames, $fieldNames)
+        );
+    }
+
+    /**
+     * Add missing states for field names.
+     *
+     * @param array $states
+     * @return array
+     */
+    protected function enrich(array $states)
+    {
+        foreach (static::getFieldNames($this->tableName) as $fieldName) {
+            if (!empty($states[$fieldName])) {
+                continue;
+            }
+            $states[$fieldName] = static::STATE_PARENT;
+        }
+        return $states;
+    }
+}
\ No newline at end of file
diff --git a/typo3/sysext/core/Classes/Migrations/TcaMigration.php b/typo3/sysext/core/Classes/Migrations/TcaMigration.php
index 0a66f0273fc18880853e45e9c2d52becab43f1db..115f7c86fad964d7285232fb8828fa4bc63ad4f5 100644
--- a/typo3/sysext/core/Classes/Migrations/TcaMigration.php
+++ b/typo3/sysext/core/Classes/Migrations/TcaMigration.php
@@ -928,8 +928,13 @@ class TcaMigration
                 }
                 if ($fieldConfig['l10n_mode'] === 'mergeIfNotBlank') {
                     unset($fieldConfig['l10n_mode']);
+                    if (empty($fieldConfig['config']['behaviour']['allowLanguageSynchronization'])) {
+                        $fieldConfig['config']['behaviour']['allowLanguageSynchronization'] = true;
+                    }
                     $this->messages[] = 'The TCA setting \'mergeIfNotBlank\' was removed '
-                        . 'in TCA ' . $table . '[\'columns\'][\'' . $fieldName . '\'][\'l10n_mode\']';
+                        . 'in TCA ' . $table . '[\'columns\'][\'' . $fieldName . '\'][\'l10n_mode\']'
+                        . ' and changed to ' . $table . '[\'columns\'][\'' . $fieldName . '\'][\'behaviour\']'
+                        . '[\'allowLanguageSynchronization\'] = true';
                 }
             }
         }
diff --git a/typo3/sysext/core/Configuration/TCA/sys_category.php b/typo3/sysext/core/Configuration/TCA/sys_category.php
index 721fbbbd4daec1fb6107f8120014925a373832e2..a3a159947bc383328661f816a3be372590ebfda3 100644
--- a/typo3/sysext/core/Configuration/TCA/sys_category.php
+++ b/typo3/sysext/core/Configuration/TCA/sys_category.php
@@ -116,7 +116,10 @@ return [
                 'type' => 'input',
                 'renderType' => 'inputDateTime',
                 'eval' => 'datetime',
-                'default' => 0
+                'default' => 0,
+                'behaviour' => [
+                    'allowLanguageSynchronization' => true,
+                ]
             ]
         ],
         'endtime' => [
@@ -129,6 +132,9 @@ return [
                 'default' => 0,
                 'range' => [
                     'upper' => mktime(0, 0, 0, 1, 1, 2038),
+                ],
+                'behaviour' => [
+                    'allowLanguageSynchronization' => true,
                 ]
             ]
         ],
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-51291-PageRepositoryShouldFieldBeOverlaid.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-51291-PageRepositoryShouldFieldBeOverlaid.rst
new file mode 100644
index 0000000000000000000000000000000000000000..1bcfd1b37a3cb506815b3aaf28d43d8c436f1eb4
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-51291-PageRepositoryShouldFieldBeOverlaid.rst
@@ -0,0 +1,34 @@
+.. include:: ../../Includes.txt
+
+============================================================
+Deprecation: #51291 - PageRepository shouldFieldBeOverlaid()
+============================================================
+
+See :issue:`51291`
+
+Description
+===========
+
+The following method has been deprecated:
+
+* :code:`TYPO3\CMS\Frontend\Page\PageRepository->shouldFieldBeOverlaid()`
+
+
+Impact
+======
+
+Localized record fields are always "overlaid", the method returns true in all cases.
+
+
+Affected Installations
+======================
+
+Instances with extensions calling this method
+
+
+Migration
+=========
+
+The deprecated method returns TRUE in all cases, the call can be omitted.
+
+.. index:: Frontend, PHP-API
\ No newline at end of file
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-51291-SynchronizedFieldValuesInLocalizedRecords.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-51291-SynchronizedFieldValuesInLocalizedRecords.rst
new file mode 100644
index 0000000000000000000000000000000000000000..f938d286a0c4adf56214c696c686990089cf1fac
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-51291-SynchronizedFieldValuesInLocalizedRecords.rst
@@ -0,0 +1,46 @@
+.. include:: ../../Includes.txt
+
+================================================================
+Feature: #51291 - Synchronized field values in localized records
+================================================================
+
+See :issue:`51291`
+
+Description
+===========
+
+The localized record overlay behaviour has been changed to make localization rows standalone.
+
+Previously, if fields in :code:`TCA` columns were set to :code:`l10n_mode` :code:`exclude`
+or :code:`mergeIfNotBlank`, the localized record overlay did not contain values, and those
+values were "pulled up" from the underlying default language records.
+
+This has been changed, the :code:`DataHandler` now copies those values over to the localized
+record and synchronizes them if the default language record is changed.
+
+As a substitution of the :code:`mergeIfNotBlank` feature, the new configuration :code:`allowLanguageSynchronization`
+has been added. Setting this adds a wizard to single fields and an editor can select if a field of a localized record
+should be kept in sync with the default language record, or the localized record it was derived from.
+
+A typical configuration looks like that:
+
+.. code-block:: php
+
+    'columns' => [
+        ...
+        'header' => [
+            'label' => 'My header',
+            'config' => [
+                'type' => 'input',
+                'behaviour' => [
+                    'allowLanguageSynchronization' => true,
+                ],
+            ],
+        ],
+    ],
+
+:code:`TCA` tables that configure the language localization get field :code:`l10n_state` added by the schema analyzer
+which stores an json array with field names and the values :code:`custom`, :code:`parent` or :code:`source` to
+specify if and from which record a single field gets its value.
+
+.. index:: Backend, Database, Frontend, PHP-API, TCA
\ No newline at end of file
diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/Group/AbstractActionTestCase.php b/typo3/sysext/core/Tests/Functional/DataHandling/Group/AbstractActionTestCase.php
index b0950f8fc0e181a9c29debf561a4c9534afa93a1..b091ed9b401865734467798490983fc87a9598f2 100644
--- a/typo3/sysext/core/Tests/Functional/DataHandling/Group/AbstractActionTestCase.php
+++ b/typo3/sysext/core/Tests/Functional/DataHandling/Group/AbstractActionTestCase.php
@@ -217,6 +217,16 @@ abstract class AbstractActionTestCase extends \TYPO3\CMS\Core\Tests\Functional\D
         $this->recordIds['localizedContentId'] = $newTableIds[self::TABLE_Content][self::VALUE_ContentIdLast];
     }
 
+    public function localizeContentOfRelationWithLanguageSynchronization()
+    {
+        $GLOBALS['TCA']['tt_content']['columns']['tx_testdatahandler_group']['config']['behaviour']['allowLanguageSynchronization'] = true;
+        $newTableIds = $this->actionService->localizeRecord(self::TABLE_Content, self::VALUE_ContentIdLast, self::VALUE_LanguageId);
+        $this->recordIds['localizedContentId'] = $newTableIds[self::TABLE_Content][self::VALUE_ContentIdLast];
+        $this->actionService->modifyReferences(
+            self::TABLE_Content, self::VALUE_ContentIdLast, self::FIELD_ContentElement, [self::VALUE_ElementIdFirst, self::VALUE_ElementIdSecond]
+        );
+    }
+
     /**
      * @test
      * @see DataSet/localizeElementOfRelation.csv
diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/Group/Modify/ActionTest.php b/typo3/sysext/core/Tests/Functional/DataHandling/Group/Modify/ActionTest.php
index 658b4074d91fffe083dda993cba2ec86466f51d8..98d197a585e196399a4ee55ae92e3fad578ea21c 100644
--- a/typo3/sysext/core/Tests/Functional/DataHandling/Group/Modify/ActionTest.php
+++ b/typo3/sysext/core/Tests/Functional/DataHandling/Group/Modify/ActionTest.php
@@ -286,6 +286,21 @@ class ActionTest extends \TYPO3\CMS\Core\Tests\Functional\DataHandling\Group\Abs
             ->setTable(self::TABLE_Element)->setField('title')->setValues('Element #2', 'Element #3'));
     }
 
+    /**
+     * @test
+     * @see DataSet/localizeContentOfRelationWSynchronization.csv
+     */
+    public function localizeContentOfRelationWithLanguageSynchronization()
+    {
+        parent::localizeContentOfRelationWithLanguageSynchronization();
+        $this->assertAssertionDataSet('localizeContentOfRelationWSynchronization');
+
+        $responseSections = $this->getFrontendResponse(self::VALUE_PageId, self::VALUE_LanguageId)->getResponseSections();
+        $this->assertThat($responseSections, $this->getRequestSectionStructureHasRecordConstraint()
+            ->setRecordIdentifier(self::TABLE_Content . ':' . self::VALUE_ContentIdLast)->setRecordField(self::FIELD_ContentElement)
+            ->setTable(self::TABLE_Element)->setField('title')->setValues('Element #1', 'Element #2'));
+    }
+
     /**
      * @test
      * @see DataSet/localizeElementOfRelation.csv
diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/Group/Modify/DataSet/localizeContentOfRelationWSynchronization.csv b/typo3/sysext/core/Tests/Functional/DataHandling/Group/Modify/DataSet/localizeContentOfRelationWSynchronization.csv
new file mode 100644
index 0000000000000000000000000000000000000000..f52954fb72a72ab2184bf577246e3067493f9301
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/DataHandling/Group/Modify/DataSet/localizeContentOfRelationWSynchronization.csv
@@ -0,0 +1,16 @@
+"pages",,,,,,,,,,,,,,
+,"uid","pid","sorting","deleted","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title",,,
+,1,0,256,0,0,0,0,0,0,0,"FunctionalTest",,,
+,88,1,256,0,0,0,0,0,0,0,"DataHandlerTest",,,
+,89,88,256,0,0,0,0,0,0,0,"Relations",,,
+,90,88,512,0,0,0,0,0,0,0,"Target",,,
+"tt_content",,,,,,,,,,,,,,
+,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","header","tx_testdatahandler_group"
+,297,89,256,0,0,0,0,0,0,0,0,0,"Regular Element #1","1,2"
+,298,89,512,0,0,0,0,0,0,0,0,0,"Regular Element #2","1,2"
+,299,89,768,0,1,298,298,0,0,0,0,0,"[Translate to Dansk:] Regular Element #2","1,2"
+"tx_testdatahandler_element",,,,,,,,,,,,,,
+,"uid","pid","sorting","deleted","sys_language_uid","l10n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title",
+,1,89,256,0,0,0,0,0,0,0,0,0,"Element #1",
+,2,89,512,0,0,0,0,0,0,0,0,0,"Element #2",
+,3,89,768,0,0,0,0,0,0,0,0,0,"Element #3",
diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/CSV/AbstractActionTestCase.php b/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/CSV/AbstractActionTestCase.php
index 586ee679074e7550dc57b5edbac16bfcae141b94..4d75bc68727f9f058cc5d5ada06418f93f5f12dc 100644
--- a/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/CSV/AbstractActionTestCase.php
+++ b/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/CSV/AbstractActionTestCase.php
@@ -194,6 +194,23 @@ abstract class AbstractActionTestCase extends \TYPO3\CMS\Core\Tests\Functional\D
         $this->recordIds['localizedContentId'] = $newTableIds[self::TABLE_Content][self::VALUE_ContentIdLast];
     }
 
+    public function localizeParentContentWithAllChildrenInSelectModeAndLanguageSynchronization()
+    {
+        $GLOBALS['TCA'][self::TABLE_Content]['columns'][self::FIELD_ContentHotel]['config']['behaviour']['localizationMode'] = 'select';
+        $GLOBALS['TCA'][self::TABLE_Content]['columns'][self::FIELD_ContentHotel]['config']['behaviour']['allowLanguageSynchronization'] = true;
+        $GLOBALS['TCA'][self::TABLE_Content]['columns'][self::FIELD_ContentHotel]['config']['behaviour']['localizeChildrenAtParentLocalization'] = true;
+        $GLOBALS['TCA'][self::TABLE_Hotel]['columns'][self::FIELD_HotelOffer]['config']['behaviour']['localizeChildrenAtParentLocalization'] = true;
+        $newTableIds = $this->actionService->localizeRecord(self::TABLE_Content, self::VALUE_ContentIdLast, self::VALUE_LanguageId);
+        $this->recordIds['localizedContentId'] = $newTableIds[self::TABLE_Content][self::VALUE_ContentIdLast];
+        $this->actionService->modifyRecords(
+            self::VALUE_PageId,
+            [
+                self::TABLE_Content => ['uid' => self::VALUE_ContentIdLast, self::FIELD_ContentHotel => '5,__nextUid'],
+                self::TABLE_Hotel => ['uid' => '__NEW', 'title' => 'Hotel #2'],
+            ]
+        );
+    }
+
     /**
      * @see DataSet/changeParentContentRecordSorting.csv
      */
diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/CSV/Modify/ActionTest.php b/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/CSV/Modify/ActionTest.php
index e1e215b4fa105427df83ee3dfbf292b239671fbe..02255f6ef570f12b11bbd753a320411a3f7b08bb 100644
--- a/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/CSV/Modify/ActionTest.php
+++ b/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/CSV/Modify/ActionTest.php
@@ -227,6 +227,22 @@ class ActionTest extends \TYPO3\CMS\Core\Tests\Functional\DataHandling\IRRE\CSV\
             ->setTable(self::TABLE_Hotel)->setField('title')->setValues('[Translate to Dansk:] Hotel #1'));
     }
 
+    /**
+     * @test
+     * @see DataSet/localizeParentContentWAllChildrenSelectNLanguageSynchronization.csv
+     */
+    public function localizeParentContentWithAllChildrenInSelectModeAndLanguageSynchronization()
+    {
+        parent::localizeParentContentWithAllChildrenInSelectModeAndLanguageSynchronization();
+        $this->assertAssertionDataSet('localizeParentContentWAllChildrenSelectNLanguageSynchronization');
+
+        $responseSections = $this->getFrontendResponse(self::VALUE_PageId, self::VALUE_LanguageId)->getResponseSections();
+        $this->assertThat($responseSections, $this->getRequestSectionStructureHasRecordConstraint()
+            ->setRecordIdentifier(self::TABLE_Content . ':' . self::VALUE_ContentIdLast)->setRecordField(self::FIELD_ContentHotel)
+            // @todo Actually Hotel #2 should be prefixed as well
+            ->setTable(self::TABLE_Hotel)->setField('title')->setValues('[Translate to Dansk:] Hotel #1', 'Hotel #2'));
+    }
+
     /**
      * @test
      * @see DataSet/changeParentContentRecordSorting.csv
diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/CSV/Modify/DataSet/localizeParentContentWAllChildrenSelectNLanguageSynchronization.csv b/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/CSV/Modify/DataSet/localizeParentContentWAllChildrenSelectNLanguageSynchronization.csv
new file mode 100644
index 0000000000000000000000000000000000000000..4c3d1c047cd8c342ed922320bc43813f084a003f
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/CSV/Modify/DataSet/localizeParentContentWAllChildrenSelectNLanguageSynchronization.csv
@@ -0,0 +1,30 @@
+"tt_content",,,,,,,,,,,,,,
+,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","header","tx_irretutorial_1ncsv_hotels"
+,297,89,256,0,0,0,0,0,0,0,0,0,"Regular Element #1","3,4"
+,298,89,512,0,0,0,0,0,0,0,0,0,"Regular Element #2","5,7"
+,299,89,768,0,1,298,298,0,0,0,0,0,"[Translate to Dansk:] Regular Element #2","6,8"
+"tx_irretutorial_1ncsv_hotel",,,,,,,,,,,,,,
+,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title","offers"
+,3,89,256,0,0,0,0,0,0,0,0,0,"Hotel #1","5,6"
+,4,89,128,0,0,0,0,0,0,0,0,0,"Hotel #2",7
+,5,89,64,0,0,0,0,0,0,0,0,0,"Hotel #1",8
+,6,89,96,0,1,5,5,0,0,0,0,0,"[Translate to Dansk:] Hotel #1",9
+,7,89,32,0,0,0,0,0,0,0,0,0,"Hotel #2",""
+,8,89,16,0,1,7,0,0,0,0,0,0,"Hotel #2",""
+"tx_irretutorial_1ncsv_offer",,,,,,,,,,,,,,
+,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title","prices"
+,5,89,256,0,0,0,0,0,0,0,0,0,"Offer #1.1","7,8,9"
+,6,89,128,0,0,0,0,0,0,0,0,0,"Offer #1.2","10,11"
+,7,89,64,0,0,0,0,0,0,0,0,0,"Offer #2.1",12
+,8,89,32,0,0,0,0,0,0,0,0,0,"Offer #1.1",13
+,9,89,48,0,1,8,8,0,0,0,0,0,"[Translate to Dansk:] Offer #1.1",14
+"tx_irretutorial_1ncsv_price",,,,,,,,,,,,,,
+,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title",
+,7,89,256,0,0,0,0,0,0,0,0,0,"Price #1.1.1",
+,8,89,128,0,0,0,0,0,0,0,0,0,"Price #1.1.2",
+,9,89,64,0,0,0,0,0,0,0,0,0,"Price #1.1.3",
+,10,89,32,0,0,0,0,0,0,0,0,0,"Price #1.2.1",
+,11,89,16,0,0,0,0,0,0,0,0,0,"Price #1.2.2",
+,12,89,8,0,0,0,0,0,0,0,0,0,"Price #2.1.1",
+,13,89,4,0,0,0,0,0,0,0,0,0,"Price #1.1.1",
+,14,89,6,0,1,13,13,0,0,0,0,0,"[Translate to Dansk:] Price #1.1.1",
diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/AbstractActionTestCase.php b/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/AbstractActionTestCase.php
index 9ebcea4560e68c9a3fdb4f747300df229d6e695c..81b52e050c947722800f3447fb844a8dfbd33a39 100644
--- a/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/AbstractActionTestCase.php
+++ b/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/AbstractActionTestCase.php
@@ -200,6 +200,26 @@ abstract class AbstractActionTestCase extends \TYPO3\CMS\Core\Tests\Functional\D
         $this->recordIds['localizedContentId'] = $newTableIds[self::TABLE_Content][self::VALUE_ContentIdLast];
     }
 
+    /**
+     * @see DataSet/localizeParentContentWAllChildrenSelect.csv
+     */
+    public function localizeParentContentWithAllChildrenInSelectModeAndLanguageSynchronization()
+    {
+        $GLOBALS['TCA'][self::TABLE_Content]['columns'][self::FIELD_ContentHotel]['config']['behaviour']['localizationMode'] = 'select';
+        $GLOBALS['TCA'][self::TABLE_Content]['columns'][self::FIELD_ContentHotel]['config']['behaviour']['allowLanguageSynchronization'] = true;
+        $GLOBALS['TCA'][self::TABLE_Content]['columns'][self::FIELD_ContentHotel]['config']['behaviour']['localizeChildrenAtParentLocalization'] = true;
+        $GLOBALS['TCA'][self::TABLE_Hotel]['columns'][self::FIELD_HotelOffer]['config']['behaviour']['localizeChildrenAtParentLocalization'] = true;
+        $newTableIds = $this->actionService->localizeRecord(self::TABLE_Content, self::VALUE_ContentIdLast, self::VALUE_LanguageId);
+        $this->recordIds['localizedContentId'] = $newTableIds[self::TABLE_Content][self::VALUE_ContentIdLast];
+        $this->actionService->modifyRecords(
+            self::VALUE_PageId,
+            [
+                self::TABLE_Content => ['uid' => self::VALUE_ContentIdLast, self::FIELD_ContentHotel => '5,__nextUid'],
+                self::TABLE_Hotel => ['uid' => '__NEW', 'title' => 'Hotel #2'],
+            ]
+        );
+    }
+
     /**
      * @see DataSet/changeParentContentRecordSorting.csv
      */
diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/ActionTest.php b/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/ActionTest.php
index 0b199202bd4f5faa142e8c4f899e5f2f9d9e3587..ff28c1a5a1dccc1372ce1caa37f4061b3106fa98 100644
--- a/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/ActionTest.php
+++ b/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/ActionTest.php
@@ -228,6 +228,22 @@ class ActionTest extends \TYPO3\CMS\Core\Tests\Functional\DataHandling\IRRE\Fore
             ->setTable(self::TABLE_Hotel)->setField('title')->setValues('[Translate to Dansk:] Hotel #1'));
     }
 
+    /**
+     * @test
+     * @see DataSet/localizeParentContentWAllChildrenSelectNLanguageSynchronization.csv
+     */
+    public function localizeParentContentWithAllChildrenInSelectModeAndLanguageSynchronization()
+    {
+        parent::localizeParentContentWithAllChildrenInSelectModeAndLanguageSynchronization();
+        $this->assertAssertionDataSet('localizeParentContentWAllChildrenSelectNLanguageSynchronization');
+
+        $responseSections = $this->getFrontendResponse(self::VALUE_PageId, self::VALUE_LanguageId)->getResponseSections('Default', 'Extbase:list()');
+        $this->assertThat($responseSections, $this->getRequestSectionStructureHasRecordConstraint()
+            ->setRecordIdentifier(self::TABLE_Content . ':' . self::VALUE_ContentIdLast)->setRecordField(self::FIELD_ContentHotel)
+            // @todo Actually Hotel #2 should be prefixed as well
+            ->setTable(self::TABLE_Hotel)->setField('title')->setValues('[Translate to Dansk:] Hotel #1', 'Hotel #2'));
+    }
+
     /**
      * @test
      * @see DataSet/changeParentContentRecordSorting.csv
diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/DataSet/localizeParentContentWAllChildrenSelectNLanguageSynchronization.csv b/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/DataSet/localizeParentContentWAllChildrenSelectNLanguageSynchronization.csv
new file mode 100644
index 0000000000000000000000000000000000000000..39054713a0235d5a09b40c8f03a010a3ed0f0540
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/DataSet/localizeParentContentWAllChildrenSelectNLanguageSynchronization.csv
@@ -0,0 +1,30 @@
+"tt_content",,,,,,,,,,,,,,,,,
+,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","header","tx_irretutorial_1nff_hotels",,,
+,297,89,256,0,0,0,0,0,0,0,0,0,"Regular Element #1",2,,,
+,298,89,512,0,0,0,0,0,0,0,0,0,"Regular Element #2",2,,,
+,299,89,768,0,1,298,298,0,0,0,0,0,"[Translate to Dansk:] Regular Element #2",2,,,
+"tx_irretutorial_1nff_hotel",,,,,,,,,,,,,,,,,
+,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title","parentid","parenttable","parentidentifier","offers"
+,3,89,1024,0,0,0,0,0,0,0,0,0,"Hotel #1",297,"tt_content",,2
+,4,89,1536,0,0,0,0,0,0,0,0,0,"Hotel #2",297,"tt_content",,1
+,5,89,1,0,0,0,0,0,0,0,0,0,"Hotel #1",298,"tt_content",,1
+,6,89,1,0,1,5,5,0,0,0,0,0,"[Translate to Dansk:] Hotel #1",299,"tt_content",,1
+,7,89,2,0,0,0,0,0,0,0,0,0,"Hotel #2",298,"tt_content",,0
+,8,89,2,0,1,7,0,0,0,0,0,0,"Hotel #2",299,"tt_content",,0
+"tx_irretutorial_1nff_offer",,,,,,,,,,,,,,,,,
+,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title","parentid","parenttable","parentidentifier","prices"
+,5,89,512,0,0,0,0,0,0,0,0,0,"Offer #1.1",3,"tx_irretutorial_1nff_hotel",,3
+,6,89,1536,0,0,0,0,0,0,0,0,0,"Offer #1.2",3,"tx_irretutorial_1nff_hotel",,2
+,7,89,768,0,0,0,0,0,0,0,0,0,"Offer #2.1",4,"tx_irretutorial_1nff_hotel",,1
+,8,89,1024,0,0,0,0,0,0,0,0,0,"Offer #1.1",5,"tx_irretutorial_1nff_hotel",,1
+,9,89,1,0,1,8,8,0,0,0,0,0,"[Translate to Dansk:] Offer #1.1",6,"tx_irretutorial_1nff_hotel",,1
+"tx_irretutorial_1nff_price",,,,,,,,,,,,,,,,,
+,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title","parentid","parenttable","parentidentifier",
+,7,89,512,0,0,0,0,0,0,0,0,0,"Price #1.1.1",5,"tx_irretutorial_1nff_offer",,
+,8,89,1792,0,0,0,0,0,0,0,0,0,"Price #1.1.2",5,"tx_irretutorial_1nff_offer",,
+,9,89,2304,0,0,0,0,0,0,0,0,0,"Price #1.1.3",5,"tx_irretutorial_1nff_offer",,
+,10,89,768,0,0,0,0,0,0,0,0,0,"Price #1.2.1",6,"tx_irretutorial_1nff_offer",,
+,11,89,2048,0,0,0,0,0,0,0,0,0,"Price #1.2.2",6,"tx_irretutorial_1nff_offer",,
+,12,89,1024,0,0,0,0,0,0,0,0,0,"Price #2.1.1",7,"tx_irretutorial_1nff_offer",,
+,13,89,1280,0,0,0,0,0,0,0,0,0,"Price #1.1.1",8,"tx_irretutorial_1nff_offer",,
+,14,89,1,0,1,13,13,0,0,0,0,0,"[Translate to Dansk:] Price #1.1.1",9,"tx_irretutorial_1nff_offer",,
diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/Regular/AbstractActionTestCase.php b/typo3/sysext/core/Tests/Functional/DataHandling/Regular/AbstractActionTestCase.php
index 51837c519402c42e7cbcbbc845033ea45b29d54e..f487ff37a4d0fa397da6a8b4de28e8418b9dc251 100644
--- a/typo3/sysext/core/Tests/Functional/DataHandling/Regular/AbstractActionTestCase.php
+++ b/typo3/sysext/core/Tests/Functional/DataHandling/Regular/AbstractActionTestCase.php
@@ -137,6 +137,13 @@ abstract class AbstractActionTestCase extends \TYPO3\CMS\Core\Tests\Functional\D
         $this->actionService->localizeRecord(self::TABLE_Content, self::VALUE_ContentIdSecond, self::VALUE_LanguageId);
     }
 
+    public function localizeContentWithLanguageSynchronization()
+    {
+        $GLOBALS['TCA']['tt_content']['columns']['header']['config']['behaviour']['allowLanguageSynchronization'] = true;
+        $this->actionService->localizeRecord(self::TABLE_Content, self::VALUE_ContentIdSecond, self::VALUE_LanguageId);
+        $this->actionService->modifyRecord(self::TABLE_Content, self::VALUE_ContentIdSecond, ['header' => 'Testing #1']);
+    }
+
     /**
      * @test
      * @see DataSet/localizeContentFromNonDefaultLanguage.csv
diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/Regular/Modify/ActionTest.php b/typo3/sysext/core/Tests/Functional/DataHandling/Regular/Modify/ActionTest.php
index 37a38e0c1a07d63b5f0b6d434504e2a069678ccf..25e13f974f8c84cd23d77a06335f7a0675be165e 100644
--- a/typo3/sysext/core/Tests/Functional/DataHandling/Regular/Modify/ActionTest.php
+++ b/typo3/sysext/core/Tests/Functional/DataHandling/Regular/Modify/ActionTest.php
@@ -164,6 +164,20 @@ class ActionTest extends \TYPO3\CMS\Core\Tests\Functional\DataHandling\Regular\A
             ->setTable(self::TABLE_Content)->setField('header')->setValues('[Translate to Dansk:] Regular Element #1', '[Translate to Dansk:] Regular Element #2'));
     }
 
+    /**
+     * @test
+     * @see DataSet/localizeContentWSynchronization.csv
+     */
+    public function localizeContentWithLanguageSynchronization()
+    {
+        parent::localizeContentWithLanguageSynchronization();
+        $this->assertAssertionDataSet('localizeContentWSynchronization');
+
+        $responseSections = $this->getFrontendResponse(self::VALUE_PageId, self::VALUE_LanguageId)->getResponseSections();
+        $this->assertThat($responseSections, $this->getRequestSectionHasRecordConstraint()
+            ->setTable(self::TABLE_Content)->setField('header')->setValues('[Translate to Dansk:] Regular Element #1', 'Testing #1'));
+    }
+
     /**
      * @test
      * @see DataSet/localizeContentFromNonDefaultLanguage.csv
diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/Regular/Modify/DataSet/localizeContentWSynchronization.csv b/typo3/sysext/core/Tests/Functional/DataHandling/Regular/Modify/DataSet/localizeContentWSynchronization.csv
new file mode 100644
index 0000000000000000000000000000000000000000..741cfaa28b39c5d7c72a4e9dd400b3871ea55ace
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/DataHandling/Regular/Modify/DataSet/localizeContentWSynchronization.csv
@@ -0,0 +1,9 @@
+tt_content,,,,,,,,,,,,,,
+,uid,pid,sorting,deleted,sys_language_uid,l18n_parent,l10n_source,t3_origuid,t3ver_wsid,t3ver_state,t3ver_stage,t3ver_oid,t3ver_move_id,header
+,297,89,256,0,0,0,0,0,0,0,0,0,0,Regular Element #1
+,298,89,512,0,0,0,0,0,0,0,0,0,0,Testing #1
+,299,89,768,0,0,0,0,0,0,0,0,0,0,Regular Element #3
+,300,89,1024,0,1,299,299,299,0,0,0,0,0,[Translate to Dansk:] Regular Element #3
+,301,89,384,0,1,297,297,297,0,0,0,0,0,[Translate to Dansk:] Regular Element #1
+,302,89,448,0,2,297,301,301,0,0,0,0,0,[Translate to Deutsch:] [Translate to Dansk:] Regular Element #1
+,303,89,416,0,1,298,298,298,0,0,0,0,0,Testing #1
diff --git a/typo3/sysext/core/Tests/Unit/Migrations/TcaMigrationTest.php b/typo3/sysext/core/Tests/Unit/Migrations/TcaMigrationTest.php
index 306ed0e61f362cd4655b9f40e381acc2229fc1eb..5d58d72cdf16277c423455632267d98182cba459 100644
--- a/typo3/sysext/core/Tests/Unit/Migrations/TcaMigrationTest.php
+++ b/typo3/sysext/core/Tests/Unit/Migrations/TcaMigrationTest.php
@@ -2121,6 +2121,11 @@ class TcaMigrationTest extends \TYPO3\Components\TestingFramework\Core\Unit\Unit
                     'aTable' => [
                         'columns' => [
                             'aColumn' => [
+                                'config' => [
+                                    'behaviour' => [
+                                        'allowLanguageSynchronization' => true,
+                                    ]
+                                ]
                             ],
                         ],
                     ],
diff --git a/typo3/sysext/core/ext_localconf.php b/typo3/sysext/core/ext_localconf.php
index 1f8091c45ba0991ab6dd6dfce8e42224ddaf87c4..22ce307faaef9bf2df51da732de71a69220d8402 100644
--- a/typo3/sysext/core/ext_localconf.php
+++ b/typo3/sysext/core/ext_localconf.php
@@ -69,6 +69,14 @@ $signalSlotDispatcher->connect(
     'processFile'
 );
 
+$signalSlotDispatcher->connect(
+    \TYPO3\CMS\Install\Service\SqlExpectedSchemaService::class,
+    'tablesDefinitionIsBeingBuilt',
+    \TYPO3\CMS\Core\DataHandling\DatabaseSchemaService::class,
+    'getLocalizationRequiredDatabaseSchema'
+);
+
+
 unset($signalSlotDispatcher);
 
 $GLOBALS['TYPO3_CONF_VARS']['FE']['eID_include']['dumpFile'] = \TYPO3\CMS\Core\Controller\FileDumpController::class . '::dumpAction';
diff --git a/typo3/sysext/filemetadata/Configuration/TCA/Overrides/sys_file_metadata.php b/typo3/sysext/filemetadata/Configuration/TCA/Overrides/sys_file_metadata.php
index b3310007cade4b2db0963e3125a449a2fea68cfd..3b15749da90404ae288832fa470759e8ef18e6f8 100644
--- a/typo3/sysext/filemetadata/Configuration/TCA/Overrides/sys_file_metadata.php
+++ b/typo3/sysext/filemetadata/Configuration/TCA/Overrides/sys_file_metadata.php
@@ -279,7 +279,10 @@ $tca = [
             'config' => [
                 'type' => 'input',
                 'size' => 20,
-                'eval' => 'trim'
+                'eval' => 'trim',
+                'behaviour' => [
+                    'allowLanguageSynchronization' => true,
+                ]
             ],
         ],
         'location_region' => [
@@ -289,7 +292,10 @@ $tca = [
             'config' => [
                 'type' => 'input',
                 'size' => 20,
-                'eval' => 'trim'
+                'eval' => 'trim',
+                'behaviour' => [
+                    'allowLanguageSynchronization' => true,
+                ]
             ],
         ],
         'location_city' => [
@@ -299,7 +305,10 @@ $tca = [
             'config' => [
                 'type' => 'input',
                 'size' => 20,
-                'eval' => 'trim'
+                'eval' => 'trim',
+                'behaviour' => [
+                    'allowLanguageSynchronization' => true,
+                ]
             ],
         ],
         'latitude' => [
diff --git a/typo3/sysext/frontend/Classes/Page/PageRepository.php b/typo3/sysext/frontend/Classes/Page/PageRepository.php
index 1ff9ef510731695c90b4ca7ad185ad6b6b62afa1..0098bf758c2fc0ebbcf3c20823c618f94964d9a5 100644
--- a/typo3/sysext/frontend/Classes/Page/PageRepository.php
+++ b/typo3/sysext/frontend/Classes/Page/PageRepository.php
@@ -506,9 +506,7 @@ class PageRepository
                     // Overwrite the original field with the overlay
                     foreach ($overlays[$origPage['uid']] as $fieldName => $fieldValue) {
                         if ($fieldName !== 'uid' && $fieldName !== 'pid') {
-                            if ($this->shouldFieldBeOverlaid('pages_language_overlay', $fieldName, $fieldValue)) {
-                                $pagesOutput[$key][$fieldName] = $fieldValue;
-                            }
+                            $pagesOutput[$key][$fieldName] = $fieldValue;
                         }
                     }
                 }
@@ -594,9 +592,7 @@ class PageRepository
                                 }
                                 foreach ($row as $fN => $fV) {
                                     if ($fN !== 'uid' && $fN !== 'pid' && isset($olrow[$fN])) {
-                                        if ($this->shouldFieldBeOverlaid($table, $fN, $olrow[$fN])) {
-                                            $row[$fN] = $olrow[$fN];
-                                        }
+                                        $row[$fN] = $olrow[$fN];
                                     } elseif ($fN === 'uid') {
                                         $row['_LOCALIZED_UID'] = $olrow['uid'];
                                     }
@@ -1889,10 +1885,7 @@ class PageRepository
         );
         if ($isTableLocalizable && $localizedId !== null) {
             $localizedReferences = $fileRepository->findByRelation($tableName, $fieldName, $localizedId);
-            $localizedReferencesValue = $localizedReferences ?: '';
-            if ($this->shouldFieldBeOverlaid($tableName, $fieldName, $localizedReferencesValue)) {
-                $references = $localizedReferences;
-            }
+            $references = $localizedReferences;
         }
 
         return $references;
@@ -1922,20 +1915,12 @@ class PageRepository
      * @param string $field TCA fieldname
      * @param mixed $value Current value of the field
      * @return bool Returns TRUE if a given record field needs to be overlaid
+     * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
      */
     protected function shouldFieldBeOverlaid($table, $field, $value)
     {
-        $l10n_mode = isset($GLOBALS['TCA'][$table]['columns'][$field]['l10n_mode'])
-            ? $GLOBALS['TCA'][$table]['columns'][$field]['l10n_mode']
-            : '';
-
-        $shouldFieldBeOverlaid = true;
-
-        if ($l10n_mode === 'exclude') {
-            $shouldFieldBeOverlaid = false;
-        }
-
-        return $shouldFieldBeOverlaid;
+        GeneralUtility::logDeprecatedFunction();
+        return true;
     }
 
     /**
diff --git a/typo3/sysext/frontend/Configuration/TCA/tt_content.php b/typo3/sysext/frontend/Configuration/TCA/tt_content.php
index cd31a8520723c1a7c479ab9ca17d9551f727c818..33d74cd7c9b181a75737c74f7f3d57646d340fa4 100644
--- a/typo3/sysext/frontend/Configuration/TCA/tt_content.php
+++ b/typo3/sysext/frontend/Configuration/TCA/tt_content.php
@@ -466,8 +466,8 @@ return [
             'config' => [
                 'type' => 'input',
                 'size' => 50,
-                'max' => 255
-            ]
+                'max' => 255,
+            ],
         ],
         'header_layout' => [
             'exclude' => true,
@@ -971,7 +971,7 @@ return [
             'config' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::getFileFieldTCAConfig('media', [
                 'appearance' => [
                     'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:media.addFileReference'
-                ]
+                ],
             ])
         ],
         'filelink_size' => [
diff --git a/typo3/sysext/frontend/Tests/Unit/Page/PageRepositoryTest.php b/typo3/sysext/frontend/Tests/Unit/Page/PageRepositoryTest.php
index 28770c99562d6162c5c9e3b713b59e5a50067d51..6ff7ad639b3c115e94700840a4f2a7f71fc1d99a 100644
--- a/typo3/sysext/frontend/Tests/Unit/Page/PageRepositoryTest.php
+++ b/typo3/sysext/frontend/Tests/Unit/Page/PageRepositoryTest.php
@@ -126,11 +126,11 @@ class PageRepositoryTest extends \TYPO3\Components\TestingFramework\Core\Unit\Un
     public function getShouldFieldBeOverlaidData()
     {
         return [
-            ['default',               'fake_table', 'foobar', true,  'default is to merge non-empty string'],
-            ['default',               'fake_table', '',       true,  'default is to merge empty string'],
+            ['default',               'fake_table', 'foobar', true,  'default is to overlay non-empty string'],
+            ['default',               'fake_table', '',       true,  'default is to overlay empty string'],
 
-            ['exclude',               'fake_table', '',       false, 'exclude field with empty string'],
-            ['exclude',               'fake_table', 'foobar', false, 'exclude field with non-empty string'],
+            ['exclude',               'fake_table', '',       true, 'exclude field with empty string'],
+            ['exclude',               'fake_table', 'foobar', true, 'exclude field with non-empty string'],
 
             ['prefixLangTitle',       'fake_table', 'foobar', true,  'prefixLangTitle is merged with non-empty string'],
             ['prefixLangTitle',       'fake_table', '',       true,  'prefixLangTitle is merged with empty string'],
diff --git a/typo3/sysext/install/Classes/Updates/DatabaseRowsUpdateWizard.php b/typo3/sysext/install/Classes/Updates/DatabaseRowsUpdateWizard.php
index d922717459e511fd19952ed9895b2f179c039043..eaa9da20ea60bbe6b43b30df77cd65944fb28f5c 100644
--- a/typo3/sysext/install/Classes/Updates/DatabaseRowsUpdateWizard.php
+++ b/typo3/sysext/install/Classes/Updates/DatabaseRowsUpdateWizard.php
@@ -48,7 +48,7 @@ class DatabaseRowsUpdateWizard extends AbstractUpdate
      * @var array Single classes that may update rows
      */
     protected $rowUpdater = [
-        L10nModeUpdater::class,
+//        L10nModeUpdater::class,
     ];
 
     /**
diff --git a/typo3/sysext/install/Classes/Updates/RowUpdater/L10nModeUpdater.php b/typo3/sysext/install/Classes/Updates/RowUpdater/L10nModeUpdater.php
index 7383b587f6e364b37116a571a2c150200d68c421..ee99575cad61857f54d5c31ef12ad1c6ed924fa5 100644
--- a/typo3/sysext/install/Classes/Updates/RowUpdater/L10nModeUpdater.php
+++ b/typo3/sysext/install/Classes/Updates/RowUpdater/L10nModeUpdater.php
@@ -17,7 +17,6 @@ namespace TYPO3\CMS\Install\Updates\RowUpdater;
 
 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
 use TYPO3\CMS\Core\DataHandling\DataHandler;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Install\Service\LoadTcaService;
@@ -25,26 +24,11 @@ use TYPO3\CMS\Install\Service\LoadTcaService;
 /**
  * Migrate values for database records having columns
  * using "l10n_mode" set to "mergeIfNotBlank".
+ *
+ * @todo: This needs a review and finish
  */
 class L10nModeUpdater implements RowUpdaterInterface
 {
-    /**
-     * Field names that previously had a migrated l10n_mode setting in TCA.
-     *
-     * @var array
-     */
-    protected $migratedL10nCoreFieldNames = [
-        'sys_category' => [
-            'starttime' => 'mergeIfNotBlank',
-            'endtime' => 'mergeIfNotBlank',
-        ],
-        'sys_file_metadata' => [
-            'location_country' => 'mergeIfNotBlank',
-            'location_region' => 'mergeIfNotBlank',
-            'location_city' => 'mergeIfNotBlank',
-        ],
-    ];
-
     /**
      * List of tables with information about to migrate fields.
      * Created during hasPotentialUpdateForTable(), used in updateTableRow()
@@ -60,7 +44,8 @@ class L10nModeUpdater implements RowUpdaterInterface
      */
     public function getTitle(): string
     {
-        return 'Migrate values in database records having "l10n_mode" set to "mergeIfNotBlank';
+        return 'Migrate values in database records having "l10n_mode"'
+            . ' either set to "exclude" or "mergeIfNotBlank"';
     }
 
     /**
@@ -102,6 +87,7 @@ class L10nModeUpdater implements RowUpdaterInterface
         $fakeAdminUser = GeneralUtility::makeInstance(BackendUserAuthentication::class);
         $fakeAdminUser->user = ['admin' => 1];
 
+        // disable DataHandler hooks for processing this update
         if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php'])) {
             $dataHandlerHooks = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php'];
             unset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']);
@@ -119,6 +105,7 @@ class L10nModeUpdater implements RowUpdaterInterface
         $sourceRow = $this->getRow($sourceTableName, $source);
 
         $updateValues = [];
+        $l10nState = [];
 
         $row = $this->getRow($tableName, $uid);
         foreach ($row as $fieldName => $fieldValue) {
@@ -126,6 +113,8 @@ class L10nModeUpdater implements RowUpdaterInterface
                 continue;
             }
 
+            $l10nState[$fieldName] = 'custom';
+
             if (
                 // default
                 empty($fieldTypes[$fieldName])
@@ -139,6 +128,7 @@ class L10nModeUpdater implements RowUpdaterInterface
                 )
             ) {
                 $updateValues[$fieldName] = $sourceRow[$fieldName];
+                $l10nState[$fieldName] = 'parent';
             }
             // inline types, but only file references
             if (
@@ -161,12 +151,11 @@ class L10nModeUpdater implements RowUpdaterInterface
                 $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
                 $dataHandler->start([], $commandMap, $fakeAdminUser);
                 $dataHandler->process_cmdmap();
+                $l10nState[$fieldName] = 'parent';
             }
         }
 
-        if (empty($updateValues)) {
-            return $inputRow;
-        }
+        $updateValues['l10n_state'] = json_encode($l10nState);
 
         $queryBuilder = $connectionPool->getQueryBuilderForTable($tableName);
         foreach ($updateValues as $updateFieldName => $updateValue) {
@@ -231,20 +220,16 @@ class L10nModeUpdater implements RowUpdaterInterface
         $fields = [];
         $fieldTypes = [];
         foreach ($tableDefinition['columns'] as $fieldName => $fieldConfiguration) {
-            if (
-                empty($fieldConfiguration['l10n_mode'])
-                && !empty($this->migratedL10nCoreFieldNames[$tableName][$fieldName])
-            ) {
-                $fieldConfiguration['l10n_mode'] = $this->migratedL10nCoreFieldNames[$tableName][$fieldName];
-            }
-
             if (
                 empty($fieldConfiguration['l10n_mode'])
                 || empty($fieldConfiguration['config']['type'])
             ) {
                 continue;
             }
-            if ($fieldConfiguration['l10n_mode'] === 'mergeIfNotBlank') {
+            if (
+                $fieldConfiguration['l10n_mode'] === 'exclude'
+                || $fieldConfiguration['l10n_mode'] === 'mergeIfNotBlank'
+            ) {
                 $fields[$fieldName] = $fieldConfiguration;
             }
         }
@@ -253,35 +238,17 @@ class L10nModeUpdater implements RowUpdaterInterface
             return $payload;
         }
 
-        $parentQueryBuilder = $connectionPool->getQueryBuilderForTable($tableName);
-        $parentQueryBuilder->getRestrictions()->removeAll();
-        $parentQueryBuilder->from($tableName);
+        $queryBuilder = $connectionPool->getQueryBuilderForTable($tableName);
+        $queryBuilder->getRestrictions()->removeAll();
+        $queryBuilder->from($tableName);
 
-        $predicates = [];
         foreach ($fields as $fieldName => $fieldConfiguration) {
-            $predicates[] = $parentQueryBuilder->expr()->comparison(
-                $parentQueryBuilder->expr()->trim($fieldName),
-                ExpressionBuilder::EQ,
-                $parentQueryBuilder->createNamedParameter('', \PDO::PARAM_STR)
-            );
-            $predicates[] = $parentQueryBuilder->expr()->eq(
-                $fieldName,
-                $parentQueryBuilder->createNamedParameter('', \PDO::PARAM_STR)
-            );
-
             if (empty($fieldConfiguration['config']['type'])) {
                 continue;
             }
 
             if ($fieldConfiguration['config']['type'] === 'group') {
                 $fieldTypes[$fieldName] = 'group';
-                $predicates[] = $parentQueryBuilder->expr()->isNull(
-                    $fieldName
-                );
-                $predicates[] = $parentQueryBuilder->expr()->eq(
-                    $fieldName,
-                    $parentQueryBuilder->createNamedParameter('0', \PDO::PARAM_STR)
-                );
             }
             if (
                 $fieldConfiguration['config']['type'] === 'inline'
@@ -291,32 +258,6 @@ class L10nModeUpdater implements RowUpdaterInterface
                 && $fieldConfiguration['config']['foreign_table'] === 'sys_file_reference'
             ) {
                 $fieldTypes[$fieldName] = 'inline/FAL';
-
-                $childQueryBuilder = $connectionPool->getQueryBuilderForTable('sys_file_reference');
-                $childQueryBuilder->getRestrictions()->removeAll();
-                $childExpression = $childQueryBuilder
-                    ->count('uid')
-                    ->from('sys_file_reference')
-                    ->andWhere(
-                        $childQueryBuilder->expr()->eq(
-                            'sys_file_reference.uid_foreign',
-                            $parentQueryBuilder->getConnection()->quoteIdentifier($tableName . '.uid')
-                        ),
-                        $childQueryBuilder->expr()->eq(
-                            'sys_file_reference.tablenames',
-                            $parentQueryBuilder->createNamedParameter($tableName, \PDO::PARAM_STR)
-                        ),
-                        $childQueryBuilder->expr()->eq(
-                            'sys_file_reference.fieldname',
-                            $parentQueryBuilder->createNamedParameter($fieldName, \PDO::PARAM_STR)
-                        )
-                    );
-
-                $predicates[] = $parentQueryBuilder->expr()->comparison(
-                    '(' . $childExpression->getSQL() . ')',
-                    ExpressionBuilder::GT,
-                    $parentQueryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
-                );
             }
         }
 
@@ -330,18 +271,17 @@ class L10nModeUpdater implements RowUpdaterInterface
             );
         }
 
-        $statement = $parentQueryBuilder
+        $statement = $queryBuilder
             ->select(...$selectFieldNames)
             ->andWhere(
-                $parentQueryBuilder->expr()->gt(
+                $queryBuilder->expr()->gt(
                     $tableDefinition['ctrl']['languageField'],
-                    $parentQueryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+                    $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
                 ),
-                $parentQueryBuilder->expr()->gt(
+                $queryBuilder->expr()->gt(
                     $sourceFieldName,
-                    $parentQueryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
-                ),
-                $parentQueryBuilder->expr()->orX(...$predicates)
+                    $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
+                )
             )
             ->execute();
 
diff --git a/typo3/sysext/lang/Resources/Private/Language/locallang_wizards.xlf b/typo3/sysext/lang/Resources/Private/Language/locallang_wizards.xlf
index 02d04ae9af72af3a035c24014f763fb0c3db0c76..d4a6b3eb8ceab2766b108dc6a184689f0a130752 100644
--- a/typo3/sysext/lang/Resources/Private/Language/locallang_wizards.xlf
+++ b/typo3/sysext/lang/Resources/Private/Language/locallang_wizards.xlf
@@ -330,6 +330,18 @@
 			<trans-unit id="imwizard.crop-height">
 				<source>height:</source>
 			</trans-unit>
+			<trans-unit id="localizationStateSelector.header">
+			    <source>Translation behavior</source>
+			</trans-unit>
+			<trans-unit id="localizationStateSelector.customValue">
+			    <source>Custom value</source>
+			</trans-unit>
+			<trans-unit id="localizationStateSelector.defaultLanguageValue">
+			    <source>Value of default language</source>
+			</trans-unit>
+			<trans-unit id="localizationStateSelector.sourceLanguageValue">
+			    <source>Value of %1s language</source>
+			</trans-unit>
 		</body>
 	</file>
 </xliff>