diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml
index 3047561ed69cbdf667f05e0901a7abaf6c1f74db..c7f806a053a189d21cdd7d8e41f10beaca6107ad 100644
--- a/core/config/schema/core.data_types.schema.yml
+++ b/core/config/schema/core.data_types.schema.yml
@@ -207,7 +207,15 @@ config_object:
       requiredKey: false
       type: _core_config_info
     langcode:
+      requiredKey: false
       type: langcode
+  constraints:
+    # The `langcode` key:
+    # - MUST be specified when there are translatable values
+    # - MUST NOT be specified when there are no translatable values.
+    # Translatable values are specified for this config schema type (a subtype of `type: config_object`) if the
+    # `translatable` flag is present and set to `true` for *any* element in that config schema type.
+    LangcodeRequiredIfTranslatableValues: ~
 
 # Mail text with subject and body parts.
 mail:
diff --git a/core/lib/Drupal/Core/Config/Plugin/Validation/Constraint/LangcodeRequiredIfTranslatableValuesConstraint.php b/core/lib/Drupal/Core/Config/Plugin/Validation/Constraint/LangcodeRequiredIfTranslatableValuesConstraint.php
new file mode 100644
index 0000000000000000000000000000000000000000..41205ac57e5f9f4a47e599a734246a23918ae38f
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Plugin/Validation/Constraint/LangcodeRequiredIfTranslatableValuesConstraint.php
@@ -0,0 +1,32 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Config\Plugin\Validation\Constraint;
+
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Core\Validation\Attribute\Constraint;
+use Symfony\Component\Validator\Constraint as SymfonyConstraint;
+
+#[Constraint(
+  id: 'LangcodeRequiredIfTranslatableValues',
+  label: new TranslatableMarkup('Translatable config has langcode', [], ['context' => 'Validation']),
+  type: ['config_object']
+)]
+class LangcodeRequiredIfTranslatableValuesConstraint extends SymfonyConstraint {
+
+  /**
+   * The error message if this config object is missing a `langcode`.
+   *
+   * @var string
+   */
+  public string $missingMessage = "The @name config object must specify a language code, because it contains translatable values.";
+
+  /**
+   * The error message if this config object contains a superfluous `langcode`.
+   *
+   * @var string
+   */
+  public string $superfluousMessage = "The @name config object does not contain any translatable values, so it should not specify a language code.";
+
+}
diff --git a/core/lib/Drupal/Core/Config/Plugin/Validation/Constraint/LangcodeRequiredIfTranslatableValuesConstraintValidator.php b/core/lib/Drupal/Core/Config/Plugin/Validation/Constraint/LangcodeRequiredIfTranslatableValuesConstraintValidator.php
new file mode 100644
index 0000000000000000000000000000000000000000..b6b3dbe9e07d170f0881e1bed0331f048e71d64d
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Plugin/Validation/Constraint/LangcodeRequiredIfTranslatableValuesConstraintValidator.php
@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Config\Plugin\Validation\Constraint;
+
+use Drupal\Core\Config\Schema\Mapping;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\LogicException;
+
+/**
+ * Validates the LangcodeRequiredIfTranslatableValues constraint.
+ */
+final class LangcodeRequiredIfTranslatableValuesConstraintValidator extends ConstraintValidator {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate(mixed $value, Constraint $constraint) {
+    assert($constraint instanceof LangcodeRequiredIfTranslatableValuesConstraint);
+
+    $mapping = $this->context->getObject();
+    assert($mapping instanceof Mapping);
+    if ($mapping !== $this->context->getRoot()) {
+      throw new LogicException('The LangcodeRequiredIfTranslatableValues constraint can only operate on the root object being validated.');
+    }
+
+    assert(in_array('langcode', $mapping->getValidKeys(), TRUE));
+
+    $is_translatable = $mapping->hasTranslatableElements();
+
+    if ($is_translatable && !array_key_exists('langcode', $value)) {
+      $this->context->buildViolation($constraint->missingMessage)
+        ->setParameter('@name', $mapping->getName())
+        ->addViolation();
+      return;
+    }
+    if (!$is_translatable && array_key_exists('langcode', $value)) {
+      // @todo Convert this deprecation to an actual validation error in
+      //   https://www.drupal.org/project/drupal/issues/3440238.
+      // phpcs:ignore
+      @trigger_error(str_replace('@name', $mapping->getName(), $constraint->superfluousMessage), E_USER_DEPRECATED);
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Config/Schema/ArrayElement.php b/core/lib/Drupal/Core/Config/Schema/ArrayElement.php
index 11fc9aca2414d733946e58cd37e50423ed602342..9eb649be771e4df2cb745a050143f246a8c20ab2 100644
--- a/core/lib/Drupal/Core/Config/Schema/ArrayElement.php
+++ b/core/lib/Drupal/Core/Config/Schema/ArrayElement.php
@@ -14,6 +14,25 @@ abstract class ArrayElement extends Element implements \IteratorAggregate, Typed
    */
   protected $elements;
 
+  /**
+   * Determines if there is a translatable value.
+   *
+   * @return bool
+   *   Returns true if a translatable element is found.
+   */
+  public function hasTranslatableElements(): bool {
+    foreach ($this as $element) {
+      // Early return if found.
+      if ($element->getDataDefinition()['translatable'] === TRUE) {
+        return TRUE;
+      }
+      if ($element instanceof ArrayElement && $element->hasTranslatableElements()) {
+        return TRUE;
+      }
+    }
+    return FALSE;
+  }
+
   /**
    * Gets valid configuration data keys.
    *
diff --git a/core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php b/core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php
index 3adb9075ad68013e30dcc373d93737fb26b04b59..b928df3eebe4ef2fe4ba1fda8885e69b5a23644d 100644
--- a/core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php
+++ b/core/lib/Drupal/Core/Config/Schema/SchemaCheckTrait.php
@@ -93,13 +93,6 @@ trait SchemaCheckTrait {
    *   valid.
    */
   public function checkConfigSchema(TypedConfigManagerInterface $typed_config, $config_name, $config_data, bool $validate_constraints = FALSE) {
-    // We'd like to verify that the top-level type is either config_base,
-    // config_entity, or a derivative. The only thing we can really test though
-    // is that the schema supports having langcode in it. So add 'langcode' to
-    // the data if it doesn't already exist.
-    if (!isset($config_data['langcode'])) {
-      $config_data['langcode'] = 'en';
-    }
     $this->configName = $config_name;
     if (!$typed_config->hasConfigSchema($config_name)) {
       return FALSE;
diff --git a/core/modules/config/tests/config_schema_test/config/install/config_schema_test.ignore.yml b/core/modules/config/tests/config_schema_test/config/install/config_schema_test.ignore.yml
index 37e231ac0618f998d7ed4f9365a470123a255a15..a37159915e739213a0be8e17f59732e2e966e67d 100644
--- a/core/modules/config/tests/config_schema_test/config/install/config_schema_test.ignore.yml
+++ b/core/modules/config/tests/config_schema_test/config/install/config_schema_test.ignore.yml
@@ -1,3 +1,4 @@
+langcode: en
 label: 'Label string'
 irrelevant: 123
 indescribable:
diff --git a/core/modules/config/tests/config_schema_test/config/install/config_schema_test.some_schema.some_module.section_one.subsection.yml b/core/modules/config/tests/config_schema_test/config/install/config_schema_test.some_schema.some_module.section_one.subsection.yml
index e6fae1ad0cbcde435182688cec65f635f4a7222d..a1c5f722fcd4853bee303856884aaabd35f9c8fe 100644
--- a/core/modules/config/tests/config_schema_test/config/install/config_schema_test.some_schema.some_module.section_one.subsection.yml
+++ b/core/modules/config/tests/config_schema_test/config/install/config_schema_test.some_schema.some_module.section_one.subsection.yml
@@ -1,2 +1,3 @@
+langcode: en
 test_id: 'Test id'
 test_description: 'Test description'
diff --git a/core/modules/config/tests/config_schema_test/config/install/config_schema_test.some_schema.some_module.section_two.subsection.yml b/core/modules/config/tests/config_schema_test/config/install/config_schema_test.some_schema.some_module.section_two.subsection.yml
index e6fae1ad0cbcde435182688cec65f635f4a7222d..a1c5f722fcd4853bee303856884aaabd35f9c8fe 100644
--- a/core/modules/config/tests/config_schema_test/config/install/config_schema_test.some_schema.some_module.section_two.subsection.yml
+++ b/core/modules/config/tests/config_schema_test/config/install/config_schema_test.some_schema.some_module.section_two.subsection.yml
@@ -1,2 +1,3 @@
+langcode: en
 test_id: 'Test id'
 test_description: 'Test description'
diff --git a/core/modules/config/tests/config_test/config/install/config_test.no_status.default.yml b/core/modules/config/tests/config_test/config/install/config_test.no_status.default.yml
index 3e50e3bbd3de64665a873daf098a59326e0cefba..8892305f39d687990b6ba0b765a12bb5ec61131d 100644
--- a/core/modules/config/tests/config_test/config/install/config_test.no_status.default.yml
+++ b/core/modules/config/tests/config_test/config/install/config_test.no_status.default.yml
@@ -1,2 +1,3 @@
+langcode: en
 id: default
 label: Default
diff --git a/core/modules/config/tests/config_test/config/install/config_test.validation.yml b/core/modules/config/tests/config_test/config/install/config_test.validation.yml
index 5d6d8686526e356169c91d9caa1f344256904e53..01d07b3793fa958ff0a204a956925fed40d09003 100644
--- a/core/modules/config/tests/config_test/config/install/config_test.validation.yml
+++ b/core/modules/config/tests/config_test/config/install/config_test.validation.yml
@@ -6,5 +6,4 @@ giraffe:
   hum1: hum1
   hum2: hum2
 uuid: '7C30C50E-641A-4E34-A7F1-46BCFB9BE5A3'
-langcode: en
 string__not_blank: 'this is a label'
diff --git a/core/modules/config/tests/config_test/config_test.module b/core/modules/config/tests/config_test/config_test.module
index af5d99cbd28fc34f755551351fc76fad102e9961..b9fac8f793bc13cb758d4bceb812daa915f4b3c9 100644
--- a/core/modules/config/tests/config_test/config_test.module
+++ b/core/modules/config/tests/config_test/config_test.module
@@ -38,7 +38,7 @@ function config_test_entity_type_alter(array &$entity_types) {
   $config_test_no_status->set('id', 'config_test_no_status');
   $config_test_no_status->set('entity_keys', $keys);
   $config_test_no_status->set('config_prefix', 'no_status');
-  $config_test_no_status->set('mergedConfigExport', ['id' => 'id', 'label' => 'label', 'uuid' => 'uuid']);
+  $config_test_no_status->set('mergedConfigExport', ['id' => 'id', 'label' => 'label', 'uuid' => 'uuid', 'langcode' => 'langcode']);
   if (\Drupal::service('state')->get('config_test.lookup_keys', FALSE)) {
     $entity_types['config_test']->set('lookup_keys', ['uuid', 'style']);
   }
diff --git a/core/modules/dblog/config/install/dblog.settings.yml b/core/modules/dblog/config/install/dblog.settings.yml
index 138b3ea58be9e07ff274c877bdd91bbd084d083a..88add889ac50e964a7bcfb8ad068d9893c0339fe 100644
--- a/core/modules/dblog/config/install/dblog.settings.yml
+++ b/core/modules/dblog/config/install/dblog.settings.yml
@@ -1,2 +1 @@
-langcode: en
 row_limit: 1000
diff --git a/core/modules/dblog/dblog.post_update.php b/core/modules/dblog/dblog.post_update.php
index cae2098378f9c263caafd508b1c5ac23860df376..9fa7a0c2fe6afc673e6c9cfaff19fab92ada566b 100644
--- a/core/modules/dblog/dblog.post_update.php
+++ b/core/modules/dblog/dblog.post_update.php
@@ -5,18 +5,6 @@
  * Post update functions for the Database Logging module.
  */
 
-/**
- * Ensures the `dblog.settings` config has a langcode.
- */
-function dblog_post_update_add_langcode_to_settings(): void {
-  $config = \Drupal::configFactory()->getEditable('dblog.settings');
-  if ($config->get('langcode')) {
-    return;
-  }
-  $config->set('langcode', \Drupal::languageManager()->getDefaultLanguage()->getId())
-    ->save();
-}
-
 /**
  * Implements hook_removed_post_updates().
  */
diff --git a/core/modules/dblog/tests/src/Functional/UpdatePathTest.php b/core/modules/dblog/tests/src/Functional/UpdatePathTest.php
index e6be5c9992667a876e4ecc3e529b1bab8aaeae09..00bf2b7d6826b4b6a154e7defb26814ba765e574 100644
--- a/core/modules/dblog/tests/src/Functional/UpdatePathTest.php
+++ b/core/modules/dblog/tests/src/Functional/UpdatePathTest.php
@@ -24,18 +24,6 @@ protected function setDatabaseDumpFiles() {
     ];
   }
 
-  /**
-   * Tests that updating adds a langcode to the dblog.settings config.
-   */
-  public function testAddLangcodeToSettings(): void {
-    $this->assertEmpty($this->config('dblog.settings')->get('langcode'));
-    $this->runUpdates();
-    $default_langcode = $this->container->get('language_manager')
-      ->getDefaultLanguage()
-      ->getId();
-    $this->assertSame($default_langcode, $this->config('dblog.settings')->get('langcode'));
-  }
-
   /**
    * Tests that, after update 10101, the 'wid' column can be a 64-bit integer.
    */
diff --git a/core/modules/jsonapi/config/install/jsonapi.settings.yml b/core/modules/jsonapi/config/install/jsonapi.settings.yml
index 6ec8f68a47930758d5ad615eefcc03d36a881dd1..dc0e229cb2def1a95873b63116eeb7cf8d4bf584 100644
--- a/core/modules/jsonapi/config/install/jsonapi.settings.yml
+++ b/core/modules/jsonapi/config/install/jsonapi.settings.yml
@@ -1,4 +1,3 @@
-langcode: en
 read_only: true
 maintenance_header_retry_seconds:
   min: 5
diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php
index 631159a8b3efd1db1918469f739c330a27a28ce4..e9608bff6c529189ed2844e0ec989197088306da 100644
--- a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php
+++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php
@@ -703,6 +703,10 @@ public function testSimpleConfigBasedLayout() {
     // Prepare an object with a pre-existing section.
     $this->container->get('config.factory')->getEditable('layout_builder_test.test_simple_config.existing')
       ->set('sections', [(new Section('layout_twocol'))->toArray()])
+      // `layout_builder_test.test_simple_config.existing.sections.0.layout_settings.label`
+      // contains a translatable label, so a `langcode` is required.
+      // @see \Drupal\Core\Config\Plugin\Validation\Constraint\LangcodeRequiredIfTranslatableValuesConstraint
+      ->set('langcode', 'en')
       ->save();
 
     // The pre-existing section is found.
diff --git a/core/modules/locale/src/LocaleConfigManager.php b/core/modules/locale/src/LocaleConfigManager.php
index 9bde0a41009adcfffaf864b8a05204c56cbc2cd9..374bae750fdb89c24de5362b1fbd070ff28fec10 100644
--- a/core/modules/locale/src/LocaleConfigManager.php
+++ b/core/modules/locale/src/LocaleConfigManager.php
@@ -668,8 +668,12 @@ public function updateDefaultConfigLangcodes() {
         // Should only update if still exists in active configuration. If locale
         // module is enabled later, then some configuration may not exist anymore.
         if (!$config->isNew()) {
+          $typed_config = $this->typedConfigManager->createFromNameAndData($config->getName(), $config->getRawData());
           $langcode = $config->get('langcode');
-          if (empty($langcode) || $langcode == 'en') {
+          // Only set a `langcode` if this config actually contains translatable
+          // data.
+          // @see \Drupal\Core\Config\Plugin\Validation\Constraint\LangcodeRequiredIfTranslatableValuesConstraint
+          if (!empty($this->getTranslatableData($typed_config)) && (empty($langcode) || $langcode == 'en')) {
             $config->set('langcode', $default_langcode)->save();
           }
         }
diff --git a/core/modules/locale/tests/modules/locale_test/config/install/locale_test.no_translation.yml b/core/modules/locale/tests/modules/locale_test/config/install/locale_test.no_translation.yml
index 4583231dc91a000eaae03497f7b6017430a29168..9c26a378496c3862a705eaee38b9f0fdf338ce36 100644
--- a/core/modules/locale/tests/modules/locale_test/config/install/locale_test.no_translation.yml
+++ b/core/modules/locale/tests/modules/locale_test/config/install/locale_test.no_translation.yml
@@ -1 +1,2 @@
+langcode: en
 test: Test
diff --git a/core/modules/locale/tests/modules/locale_test/config/install/locale_test.translation.yml b/core/modules/locale/tests/modules/locale_test/config/install/locale_test.translation.yml
index 03d3b1cf9d5ee155ad73442263e9f7f2ea51ccf9..b54fc975d448dcf672434a4a042b55a96b28c348 100644
--- a/core/modules/locale/tests/modules/locale_test/config/install/locale_test.translation.yml
+++ b/core/modules/locale/tests/modules/locale_test/config/install/locale_test.translation.yml
@@ -1 +1,2 @@
+langcode: en
 test: English test
diff --git a/core/modules/locale/tests/modules/locale_test/config/install/locale_test.translation_multiple.yml b/core/modules/locale/tests/modules/locale_test/config/install/locale_test.translation_multiple.yml
index 1be6abaead9816566e3915b0dac584ad940b2dd8..8b898ac41cd2b0fe5ef8a4796ffa63454026c37e 100644
--- a/core/modules/locale/tests/modules/locale_test/config/install/locale_test.translation_multiple.yml
+++ b/core/modules/locale/tests/modules/locale_test/config/install/locale_test.translation_multiple.yml
@@ -1,3 +1,4 @@
+langcode: en
 test: English test
 test_multiple:
   string: 'A string'
diff --git a/core/modules/locale/tests/modules/locale_test_translate/config/install/locale_test_translate.settings.yml b/core/modules/locale/tests/modules/locale_test_translate/config/install/locale_test_translate.settings.yml
index 544dad7cddcf8d1e8bd43a970a771c2f6aeee871..00e9393beceb8640d83655c5286b65ed4954953f 100644
--- a/core/modules/locale/tests/modules/locale_test_translate/config/install/locale_test_translate.settings.yml
+++ b/core/modules/locale/tests/modules/locale_test_translate/config/install/locale_test_translate.settings.yml
@@ -1,3 +1,4 @@
+langcode: en
 translatable_no_default: ''
 translatable_default_with_translation: 'Locale can translate'
 translatable_default_with_no_translation: 'Locale can not translate'
diff --git a/core/modules/migrate/src/Plugin/migrate/destination/Config.php b/core/modules/migrate/src/Plugin/migrate/destination/Config.php
index ea560e1832224a8a8cce85b5dc2e63328c05a35f..bfa4da591967d3e8c54a76100b6656f2055c562f 100644
--- a/core/modules/migrate/src/Plugin/migrate/destination/Config.php
+++ b/core/modules/migrate/src/Plugin/migrate/destination/Config.php
@@ -4,6 +4,7 @@
 
 use Drupal\Component\Plugin\DependentPluginInterface;
 use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\Entity\DependencyTrait;
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
@@ -85,6 +86,13 @@ class Config extends DestinationBase implements ContainerFactoryPluginInterface,
    */
   protected $language_manager;
 
+  /**
+   * The typed config manager service.
+   *
+   * @var \Drupal\Core\Config\TypedConfigManagerInterface
+   */
+  protected TypedConfigManagerInterface $typedConfigManager;
+
   /**
    * Constructs a Config destination object.
    *
@@ -100,14 +108,21 @@ class Config extends DestinationBase implements ContainerFactoryPluginInterface,
    *   The configuration factory.
    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
    *   The language manager.
+   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager
+   *   The typed config manager.
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, ConfigFactoryInterface $config_factory, LanguageManagerInterface $language_manager) {
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, ConfigFactoryInterface $config_factory, LanguageManagerInterface $language_manager, TypedConfigManagerInterface $typed_config_manager = NULL) {
     parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
     $this->config = $config_factory->getEditable($configuration['config_name']);
     $this->language_manager = $language_manager;
     if ($this->isTranslationDestination()) {
       $this->supportsRollback = TRUE;
     }
+    if ($typed_config_manager === NULL) {
+      @trigger_error('Calling ' . __METHOD__ . '() without the $typed_config_manager argument is deprecated in drupal:10.3.0 and is removed in drupal:11.0.0. See https://www.drupal.org/node/3440502', E_USER_DEPRECATED);
+      $typed_config_manager = \Drupal::service(TypedConfigManagerInterface::class);
+    }
+    $this->typedConfigManager = $typed_config_manager;
   }
 
   /**
@@ -120,7 +135,8 @@ public static function create(ContainerInterface $container, array $configuratio
       $plugin_definition,
       $migration,
       $container->get('config.factory'),
-      $container->get('language_manager')
+      $container->get('language_manager'),
+      $container->get(TypedConfigManagerInterface::class)
     );
   }
 
@@ -137,6 +153,16 @@ public function import(Row $row, array $old_destination_id_values = []) {
         $this->config->set(str_replace(Row::PROPERTY_SEPARATOR, '.', $key), $value);
       }
     }
+
+    $name = $this->config->getName();
+    // Ensure that translatable config has `langcode` specified.
+    // @see \Drupal\Core\Config\Plugin\Validation\Constraint\LangcodeRequiredIfTranslatableValuesConstraint
+    if ($this->typedConfigManager->hasConfigSchema($name)
+      && $this->typedConfigManager->createFromNameAndData($name, $this->config->getRawData())->hasTranslatableElements()
+      && !$this->config->get('langcode')
+    ) {
+      $this->config->set('langcode', $this->language_manager->getDefaultLanguage()->getId());
+    }
     $this->config->save();
     $ids[] = $this->config->getName();
     if ($this->isTranslationDestination()) {
diff --git a/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/CheckRequirementsTest.php b/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/CheckRequirementsTest.php
index b53f0004dfeec511a1ed4132ab4f68d6bac8d17b..ac5c1023c735d9074fd9e7c036383f45cca9c19f 100644
--- a/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/CheckRequirementsTest.php
+++ b/core/modules/migrate/tests/src/Unit/Plugin/migrate/destination/CheckRequirementsTest.php
@@ -5,6 +5,7 @@
 namespace Drupal\Tests\migrate\Unit\Plugin\migrate\destination;
 
 use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Tests\UnitTestCase;
 use Drupal\migrate\Exception\RequirementsException;
@@ -28,7 +29,8 @@ public function testException() {
       [],
       $this->prophesize(MigrationInterface::class)->reveal(),
       $this->prophesize(ConfigFactoryInterface::class)->reveal(),
-      $this->prophesize(LanguageManagerInterface::class)->reveal()
+      $this->prophesize(LanguageManagerInterface::class)->reveal(),
+      $this->prophesize(TypedConfigManagerInterface::class)->reveal(),
     );
     $this->expectException(RequirementsException::class);
     $this->expectExceptionMessage("Destination plugin 'test' did not meet the requirements");
diff --git a/core/modules/migrate/tests/src/Unit/destination/ConfigTest.php b/core/modules/migrate/tests/src/Unit/destination/ConfigTest.php
index e99dafbcce42cf58d8d84238f31fab834dba284d..b4da80b4fd5f1508b20c3f65cb19a226e7536c5f 100644
--- a/core/modules/migrate/tests/src/Unit/destination/ConfigTest.php
+++ b/core/modules/migrate/tests/src/Unit/destination/ConfigTest.php
@@ -4,6 +4,7 @@
 
 namespace Drupal\Tests\migrate\Unit\destination;
 
+use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\migrate\Plugin\MigrationInterface;
 use Drupal\migrate\Plugin\migrate\destination\Config;
 use Drupal\Tests\UnitTestCase;
@@ -35,7 +36,7 @@ public function testImport() {
     }
     $config->expects($this->once())
       ->method('save');
-    $config->expects($this->once())
+    $config->expects($this->atLeastOnce())
       ->method('getName')
       ->willReturn('d8_config');
     $config_factory = $this->createMock('Drupal\Core\Config\ConfigFactoryInterface');
@@ -56,7 +57,7 @@ public function testImport() {
       ->method('getLanguageConfigOverride')
       ->with('fr', 'd8_config')
       ->willReturn($config);
-    $destination = new Config(['config_name' => 'd8_config'], 'd8_config', ['pluginId' => 'd8_config'], $migration, $config_factory, $language_manager);
+    $destination = new Config(['config_name' => 'd8_config'], 'd8_config', ['pluginId' => 'd8_config'], $migration, $config_factory, $language_manager, $this->createMock(TypedConfigManagerInterface::class));
     $destination_id = $destination->import($row);
     $this->assertEquals(['d8_config'], $destination_id);
   }
@@ -106,7 +107,7 @@ public function testLanguageImport() {
       ->method('getLanguageConfigOverride')
       ->with('mi', 'd8_config')
       ->willReturn($config);
-    $destination = new Config(['config_name' => 'd8_config', 'translations' => 'true'], 'd8_config', ['pluginId' => 'd8_config'], $migration, $config_factory, $language_manager);
+    $destination = new Config(['config_name' => 'd8_config', 'translations' => 'true'], 'd8_config', ['pluginId' => 'd8_config'], $migration, $config_factory, $language_manager, $this->createMock(TypedConfigManagerInterface::class));
     $destination_id = $destination->import($row);
     $this->assertEquals(['d8_config', 'mi'], $destination_id);
   }
diff --git a/core/modules/system/system.post_update.php b/core/modules/system/system.post_update.php
index 952f25d9ede3ee81d15d33378e880f21f2ebb9c6..4a6c601b8c06edc760afc449582b68ab006db60b 100644
--- a/core/modules/system/system.post_update.php
+++ b/core/modules/system/system.post_update.php
@@ -5,12 +5,17 @@
  * Post update functions for System.
  */
 
+use Drupal\Core\Config\ConfigManagerInterface;
 use Drupal\Core\Config\Entity\ConfigEntityUpdater;
+use Drupal\Core\Config\Schema\Mapping;
+use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
 use Drupal\Core\Entity\EntityFormModeInterface;
 use Drupal\Core\Entity\EntityViewModeInterface;
 use Drupal\Core\Field\Plugin\Field\FieldFormatter\TimestampFormatter;
 use Drupal\Core\Site\Settings;
+use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 
 /**
  * Implements hook_removed_post_updates().
@@ -233,6 +238,70 @@ function system_post_update_sdc_uninstall() {
   }
 }
 
+/**
+ * Adds a langcode to all simple config which needs it.
+ */
+function system_post_update_add_langcode_to_all_translatable_config(&$sandbox = NULL): TranslatableMarkup {
+  $config_factory = \Drupal::configFactory();
+
+  // If this is the first run, populate the sandbox with the names of all
+  // config objects.
+  if (!isset($sandbox['names'])) {
+    $sandbox['names'] = $config_factory->listAll();
+    $sandbox['max'] = count($sandbox['names']);
+  }
+
+  /** @var \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager */
+  $typed_config_manager = \Drupal::service(TypedConfigManagerInterface::class);
+  /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
+  $config_manager = \Drupal::service(ConfigManagerInterface::class);
+  $default_langcode = \Drupal::languageManager()->getDefaultLanguage()->getId();
+
+  $names = array_splice($sandbox['names'], 0, Settings::get('entity_update_batch_size', 50));
+  foreach ($names as $name) {
+    // We're only dealing with simple config, which won't map to an entity type.
+    // But if this is a simple config object that has no schema, we can't do
+    // anything here and we don't need to, because config must have schema in
+    // order to be translatable.
+    if ($config_manager->getEntityTypeIdByName($name) || !$typed_config_manager->hasConfigSchema($name)) {
+      continue;
+    }
+
+    $config = \Drupal::configFactory()->getEditable($name);
+    $typed_config = $typed_config_manager->createFromNameAndData($name, $config->getRawData());
+    // Simple config is always a mapping.
+    assert($typed_config instanceof Mapping);
+
+    // If this config contains any elements (at any level of nesting) which
+    // are translatable, but the config hasn't got a langcode, assign one. But
+    // if nothing in the config structure is translatable, the config shouldn't
+    // have a langcode at all.
+    if ($typed_config->hasTranslatableElements()) {
+      if ($config->get('langcode')) {
+        continue;
+      }
+      $config->set('langcode', $default_langcode);
+    }
+    else {
+      if (!array_key_exists('langcode', $config->get())) {
+        continue;
+      }
+      $config->clear('langcode');
+    }
+    $config->save();
+  }
+
+  $sandbox['#finished'] = empty($sandbox['max']) || empty($sandbox['names']) ? 1 : ($sandbox['max'] - count($sandbox['names'])) / $sandbox['max'];
+  if ($sandbox['#finished'] === 1) {
+    return new TranslatableMarkup('Finished updating simple config langcodes.');
+  }
+  return new PluralTranslatableMarkup($sandbox['max'] - count($sandbox['names']),
+    'Processed @count items of @total.',
+    'Processed @count items of @total.',
+    ['@total' => $sandbox['max']],
+  );
+}
+
 /**
  * Move development settings from state to raw key-value storage.
  */
diff --git a/core/modules/system/tests/modules/form_test/src/Form/TreeConfigTargetForm.php b/core/modules/system/tests/modules/form_test/src/Form/TreeConfigTargetForm.php
index dbf1c427249b299372255fc6321caca025c0ab67..7fc6bed7892ffd6aa2ae74a528db80fd589770de 100644
--- a/core/modules/system/tests/modules/form_test/src/Form/TreeConfigTargetForm.php
+++ b/core/modules/system/tests/modules/form_test/src/Form/TreeConfigTargetForm.php
@@ -43,6 +43,13 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       '#title' => t('Nemesis'),
       '#config_target' => 'form_test.object:nemesis_vegetable',
     ];
+    // Since form_test.object contains translatable values, it must specify a
+    // language.
+    $form['langcode'] = [
+      '#type' => 'value',
+      '#value' => 'en',
+      '#config_target' => 'form_test.object:langcode',
+    ];
 
     $form['test1'] = [
       '#type' => 'select',
diff --git a/core/modules/system/tests/modules/menu_test/config/install/menu_test.links.action.yml b/core/modules/system/tests/modules/menu_test/config/install/menu_test.links.action.yml
index e0a4853aad803a83b708eef408acdf20ad6298be..f8ba6d651e54b585617db3e8cddd20d2417d1642 100644
--- a/core/modules/system/tests/modules/menu_test/config/install/menu_test.links.action.yml
+++ b/core/modules/system/tests/modules/menu_test/config/install/menu_test.links.action.yml
@@ -1 +1,2 @@
+langcode: en
 title: 'Original title'
diff --git a/core/modules/system/tests/modules/menu_test/config/install/menu_test.menu_item.yml b/core/modules/system/tests/modules/menu_test/config/install/menu_test.menu_item.yml
index ad3ab766d9634636fe5d6178f5501197f5b48b46..3256fb41cbb0b655f319321fa346d7cddf67f1f3 100644
--- a/core/modules/system/tests/modules/menu_test/config/install/menu_test.menu_item.yml
+++ b/core/modules/system/tests/modules/menu_test/config/install/menu_test.menu_item.yml
@@ -1 +1,2 @@
+langcode: en
 title: English
diff --git a/core/modules/system/tests/src/Functional/Form/ConfigTargetTest.php b/core/modules/system/tests/src/Functional/Form/ConfigTargetTest.php
index a4bf46ff398799664dc2d144745c450edeb88986..43771a3f81706ea436c30c844777ab1debd9c5df 100644
--- a/core/modules/system/tests/src/Functional/Form/ConfigTargetTest.php
+++ b/core/modules/system/tests/src/Functional/Form/ConfigTargetTest.php
@@ -97,6 +97,7 @@ public function testNested(): void {
     $assert_session->statusMessageContains('The configuration options have been saved.', 'status');
 
     $this->assertSame([
+      'langcode' => 'en',
       'favorite_fruits' => [
         $most_favorite_fruit,
         $second_favorite_fruit,
@@ -125,6 +126,7 @@ public function testNested(): void {
     $assert_session->statusMessageContains('The configuration options have been saved.', 'status');
 
     $this->assertSame([
+      'langcode' => 'en',
       'favorite_fruits' => [
         $most_favorite_fruit,
         $second_favorite_fruit,
@@ -144,6 +146,7 @@ public function testNested(): void {
     $assert_session->statusMessageContains('The configuration options have been saved.', 'status');
 
     $this->assertSame([
+      'langcode' => 'en',
       'favorite_fruits' => [
         $most_favorite_fruit,
         $second_favorite_fruit,
diff --git a/core/modules/system/tests/src/Functional/Update/SimpleConfigLangcodeTest.php b/core/modules/system/tests/src/Functional/Update/SimpleConfigLangcodeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..2eab795850f05dda2e8c2d2a5485d5bdbad2c98e
--- /dev/null
+++ b/core/modules/system/tests/src/Functional/Update/SimpleConfigLangcodeTest.php
@@ -0,0 +1,60 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\system\Functional\Update;
+
+use Drupal\Core\Database\Connection;
+use Drupal\FunctionalTests\Update\UpdatePathTestBase;
+
+/**
+ * @group system
+ * @group Update
+ * @covers system_post_update_add_langcode_to_all_translatable_config
+ */
+class SimpleConfigLangcodeTest extends UpdatePathTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setDatabaseDumpFiles() {
+    $this->databaseDumpFiles = [
+      __DIR__ . '/../../../fixtures/update/drupal-9.4.0.bare.standard.php.gz',
+    ];
+  }
+
+  /**
+   * Tests that langcodes are added to simple config objects that need them.
+   */
+  public function testLangcodesAddedToSimpleConfig(): void {
+    /** @var \Drupal\Core\Database\Connection $database */
+    $database = $this->container->get(Connection::class);
+
+    // Remove the langcode from `user.mail`, which has translatable values; it
+    // should be restored by the update path. We need to change it in the
+    // database directly, to avoid running afoul of config validation.
+    $data = $this->config('user.mail')->clear('langcode')->getRawData();
+    $database->update('config')
+      ->fields([
+        'data' => serialize($data),
+      ])
+      ->condition('name', 'user.mail')
+      ->execute();
+
+    // Add a langcode to `node.settings`, which has no translatable values; it
+    // should be removed by the update path. We need to change it in the
+    // database directly, to avoid running afoul of config validation.
+    $data = $this->config('node.settings')->set('langcode', 'en')->getRawData();
+    $database->update('config')
+      ->fields([
+        'data' => serialize($data),
+      ])
+      ->condition('name', 'node.settings')
+      ->execute();
+
+    $this->runUpdates();
+    $this->assertSame('en', $this->config('user.mail')->get('langcode'));
+    $this->assertArrayNotHasKey('langcode', $this->config('node.settings')->getRawData());
+  }
+
+}
diff --git a/core/modules/system/tests/src/Kernel/Mail/MailTest.php b/core/modules/system/tests/src/Kernel/Mail/MailTest.php
index 74edf325259f94eb2c8e14498b18b2ebeec9baf4..e688d3fe4b20ac4b9c1f7cfe2cb265fb78caf283 100644
--- a/core/modules/system/tests/src/Kernel/Mail/MailTest.php
+++ b/core/modules/system/tests/src/Kernel/Mail/MailTest.php
@@ -44,6 +44,7 @@ protected function setUp(): void {
     parent::setUp();
     $this->installEntitySchema('user');
     $this->installEntitySchema('file');
+    $this->installConfig(['system']);
 
     // Set required site configuration.
     $this->config('system.site')
diff --git a/core/modules/user/tests/src/Kernel/UserAdminSettingsFormTest.php b/core/modules/user/tests/src/Kernel/UserAdminSettingsFormTest.php
index 5ea3785e7c0fce4cd87991e33d7140fc93effe1e..4bdad8a594d5f74035cfc77412a6dce6cb20cb0b 100644
--- a/core/modules/user/tests/src/Kernel/UserAdminSettingsFormTest.php
+++ b/core/modules/user/tests/src/Kernel/UserAdminSettingsFormTest.php
@@ -24,6 +24,7 @@ class UserAdminSettingsFormTest extends ConfigFormTestBase {
    */
   protected function setUp(): void {
     parent::setUp();
+    $this->installConfig(['user']);
 
     $this->form = AccountSettingsForm::create($this->container);
     $this->values = [
diff --git a/core/modules/user/tests/src/Kernel/UserEntityLabelTest.php b/core/modules/user/tests/src/Kernel/UserEntityLabelTest.php
index 6594baa7ffb66fcbc191c85e8ec34606bae06fa6..52c34ccfdc6ee36f919d4ec95edbb43590a3769c 100644
--- a/core/modules/user/tests/src/Kernel/UserEntityLabelTest.php
+++ b/core/modules/user/tests/src/Kernel/UserEntityLabelTest.php
@@ -31,6 +31,7 @@ class UserEntityLabelTest extends KernelTestBase {
    */
   public function testLabelCallback() {
     $this->installEntitySchema('user');
+    $this->installConfig(['user']);
 
     $account = $this->createUser();
     $anonymous = User::create(['uid' => 0]);
diff --git a/core/tests/Drupal/KernelTests/Config/Schema/MappingTest.php b/core/tests/Drupal/KernelTests/Config/Schema/MappingTest.php
index 70ae50c01e19ff197ddb4c85bf77d66069306f94..ea1151a10e0c5923f59c78ba21a7d2dfeb71931e 100644
--- a/core/tests/Drupal/KernelTests/Config/Schema/MappingTest.php
+++ b/core/tests/Drupal/KernelTests/Config/Schema/MappingTest.php
@@ -216,6 +216,7 @@ public static function providerMappingInterpretation(): \Generator {
       ],
       [
         '_core',
+        'langcode',
         'profile',
       ],
       [],
@@ -237,7 +238,7 @@ public static function providerMappingInterpretation(): \Generator {
         // @see core/modules/config/tests/config_schema_deprecated_test/config/schema/config_schema_deprecated_test.schema.yml
         'complex_structure_deprecated',
       ],
-      ['_core', 'complex_structure_deprecated'],
+      ['_core', 'langcode', 'complex_structure_deprecated'],
       [],
     ];
     yield 'No dynamic type: config_schema_deprecated_test.settings:complex_structure_deprecated' => [
diff --git a/core/tests/Drupal/KernelTests/Config/TypedConfigTest.php b/core/tests/Drupal/KernelTests/Config/TypedConfigTest.php
index 1c12076188d21a1d65b82e976366c94c6f61ef29..c26ec8ca8058a8343acf2e7e952c7dc2bac48ffe 100644
--- a/core/tests/Drupal/KernelTests/Config/TypedConfigTest.php
+++ b/core/tests/Drupal/KernelTests/Config/TypedConfigTest.php
@@ -97,7 +97,7 @@ public function testTypedDataAPI() {
     $typed_config_manager = \Drupal::service('config.typed');
     $typed_config = $typed_config_manager->createFromNameAndData('config_test.validation', \Drupal::configFactory()->get('config_test.validation')->get());
     $this->assertInstanceOf(TypedConfigInterface::class, $typed_config);
-    $this->assertEquals(['_core', 'llama', 'cat', 'giraffe', 'uuid', 'langcode', 'string__not_blank'], array_keys($typed_config->getElements()));
+    $this->assertEquals(['_core', 'llama', 'cat', 'giraffe', 'uuid', 'string__not_blank'], array_keys($typed_config->getElements()));
     $this->assertSame('config_test.validation', $typed_config->getName());
     $this->assertSame('config_test.validation', $typed_config->getPropertyPath());
     $this->assertSame('config_test.validation.llama', $typed_config->get('llama')->getPropertyPath());
diff --git a/core/tests/Drupal/KernelTests/Core/Config/ConfigLanguageOverrideTest.php b/core/tests/Drupal/KernelTests/Core/Config/ConfigLanguageOverrideTest.php
index a60dc557e4c08baaefd77067528078fea068b384..3b49870ef1bec39811482ae622fadfbb1d1435b9 100644
--- a/core/tests/Drupal/KernelTests/Core/Config/ConfigLanguageOverrideTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Config/ConfigLanguageOverrideTest.php
@@ -78,6 +78,9 @@ public function testConfigLanguageOverride() {
     \Drupal::configFactory()->getEditable('config_test.foo')
       ->set('value', ['key' => 'original'])
       ->set('label', 'Original')
+      // `label` is translatable, hence a `langcode` is required.
+      // @see \Drupal\Core\Config\Plugin\Validation\Constraint\LangcodeRequiredIfTranslatableValuesConstraint
+      ->set('langcode', 'en')
       ->save();
     \Drupal::languageManager()
       ->getLanguageConfigOverride('de', 'config_test.foo')
diff --git a/core/tests/Drupal/KernelTests/Core/Config/ConfigModuleOverridesTest.php b/core/tests/Drupal/KernelTests/Core/Config/ConfigModuleOverridesTest.php
index 4fdbbd6a086e8b3cfbc2a12056690c1bc4dc5a8e..12cb8e35a9ef366f3354529e722aea36a2e2e626 100644
--- a/core/tests/Drupal/KernelTests/Core/Config/ConfigModuleOverridesTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Config/ConfigModuleOverridesTest.php
@@ -32,6 +32,9 @@ public function testSimpleModuleOverrides() {
       ->getEditable($name)
       ->set('name', $non_overridden_name)
       ->set('slogan', $non_overridden_slogan)
+      // `name` and `slogan` are translatable, hence a `langcode` is required.
+      // @see \Drupal\Core\Config\Plugin\Validation\Constraint\LangcodeRequiredIfTranslatableValuesConstraint
+      ->set('langcode', 'en')
       ->save();
 
     $this->assertEquals($non_overridden_name, $config_factory->get('system.site')->getOriginal('name', FALSE));
diff --git a/core/tests/Drupal/KernelTests/Core/Config/ConfigOverridesPriorityTest.php b/core/tests/Drupal/KernelTests/Core/Config/ConfigOverridesPriorityTest.php
index 271c313a3135ae5ce3f760c1154ef7a38cc457b5..3e4e5bc0af5481f6856fe16d3b51677ff9de3e71 100644
--- a/core/tests/Drupal/KernelTests/Core/Config/ConfigOverridesPriorityTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Config/ConfigOverridesPriorityTest.php
@@ -53,6 +53,9 @@ public function testOverridePriorities() {
       ->set('slogan', $non_overridden_slogan)
       ->set('mail', $non_overridden_mail)
       ->set('weight_select_max', 50)
+      // `name` and `slogan` are translatable, hence a `langcode` is required.
+      // @see \Drupal\Core\Config\Plugin\Validation\Constraint\LangcodeRequiredIfTranslatableValuesConstraint
+      ->set('langcode', 'en')
       ->save();
 
     // Ensure that no overrides are applying.
diff --git a/core/tests/Drupal/KernelTests/Core/Config/ConfigSchemaTest.php b/core/tests/Drupal/KernelTests/Core/Config/ConfigSchemaTest.php
index dfd08b885cb85d5ce99fb031b104106e07803eef..d3bea040889d132ff87a85ba495dd9897a72a042 100644
--- a/core/tests/Drupal/KernelTests/Core/Config/ConfigSchemaTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Config/ConfigSchemaTest.php
@@ -72,6 +72,7 @@ public function testSchemaMapping() {
     $expected['label'] = 'Schema test data';
     $expected['class'] = Mapping::class;
     $expected['mapping']['langcode']['type'] = 'langcode';
+    $expected['mapping']['langcode']['requiredKey'] = FALSE;
     $expected['mapping']['_core']['type'] = '_core_config_info';
     $expected['mapping']['_core']['requiredKey'] = FALSE;
     $expected['mapping']['test_item'] = ['label' => 'Test item'];
@@ -79,7 +80,10 @@ public function testSchemaMapping() {
     $expected['type'] = 'config_schema_test.some_schema';
     $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
     $expected['unwrap_for_canonical_representation'] = TRUE;
-    $expected['constraints'] = ['ValidKeys' => '<infer>'];
+    $expected['constraints'] = [
+      'ValidKeys' => '<infer>',
+      'LangcodeRequiredIfTranslatableValues' => NULL,
+    ];
     $this->assertEquals($expected, $definition, 'Retrieved the right metadata for configuration with only some schema.');
 
     // Check type detection on elements with undefined types.
@@ -123,6 +127,7 @@ public function testSchemaMapping() {
     $expected['mapping']['langcode'] = [
       'type' => 'langcode',
     ];
+    $expected['mapping']['langcode']['requiredKey'] = FALSE;
     $expected['mapping']['_core']['type'] = '_core_config_info';
     $expected['mapping']['_core']['requiredKey'] = FALSE;
     $expected['type'] = 'system.maintenance';
@@ -131,6 +136,7 @@ public function testSchemaMapping() {
     $expected['constraints'] = [
       'ValidKeys' => '<infer>',
       'FullyValidatable' => NULL,
+      'LangcodeRequiredIfTranslatableValues' => NULL,
     ];
     $this->assertEquals($expected, $definition, 'Retrieved the right metadata for system.maintenance');
 
@@ -143,6 +149,7 @@ public function testSchemaMapping() {
     $expected['mapping']['langcode'] = [
       'type' => 'langcode',
     ];
+    $expected['mapping']['langcode']['requiredKey'] = FALSE;
     $expected['mapping']['_core']['type'] = '_core_config_info';
     $expected['mapping']['_core']['requiredKey'] = FALSE;
     $expected['mapping']['label'] = [
@@ -163,7 +170,10 @@ public function testSchemaMapping() {
     ];
     $expected['type'] = 'config_schema_test.ignore';
     $expected['unwrap_for_canonical_representation'] = TRUE;
-    $expected['constraints'] = ['ValidKeys' => '<infer>'];
+    $expected['constraints'] = [
+      'ValidKeys' => '<infer>',
+      'LangcodeRequiredIfTranslatableValues' => NULL,
+    ];
 
     $this->assertEquals($expected, $definition);
 
@@ -274,6 +284,7 @@ public function testSchemaMapping() {
     $expected['label'] = 'Schema multiple filesystem marker test';
     $expected['class'] = Mapping::class;
     $expected['mapping']['langcode']['type'] = 'langcode';
+    $expected['mapping']['langcode']['requiredKey'] = FALSE;
     $expected['mapping']['_core']['type'] = '_core_config_info';
     $expected['mapping']['_core']['requiredKey'] = FALSE;
     $expected['mapping']['test_id']['type'] = 'string';
@@ -283,7 +294,10 @@ public function testSchemaMapping() {
     $expected['type'] = 'config_schema_test.some_schema.some_module.*.*';
     $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
     $expected['unwrap_for_canonical_representation'] = TRUE;
-    $expected['constraints'] = ['ValidKeys' => '<infer>'];
+    $expected['constraints'] = [
+      'ValidKeys' => '<infer>',
+      'LangcodeRequiredIfTranslatableValues' => NULL,
+    ];
 
     $this->assertEquals($expected, $definition, 'Retrieved the right metadata for config_schema_test.some_schema.some_module.section_one.subsection');
 
@@ -560,6 +574,7 @@ public function testSchemaFallback() {
     $expected['definition_class'] = '\Drupal\Core\TypedData\MapDataDefinition';
     $expected['unwrap_for_canonical_representation'] = TRUE;
     $expected['mapping']['langcode']['type'] = 'langcode';
+    $expected['mapping']['langcode']['requiredKey'] = FALSE;
     $expected['mapping']['_core']['type'] = '_core_config_info';
     $expected['mapping']['_core']['requiredKey'] = FALSE;
     $expected['mapping']['test_id']['type'] = 'string';
@@ -567,7 +582,10 @@ public function testSchemaFallback() {
     $expected['mapping']['test_description']['type'] = 'text';
     $expected['mapping']['test_description']['label'] = 'Description';
     $expected['type'] = 'config_schema_test.wildcard_fallback.*';
-    $expected['constraints'] = ['ValidKeys' => '<infer>'];
+    $expected['constraints'] = [
+      'ValidKeys' => '<infer>',
+      'LangcodeRequiredIfTranslatableValues' => NULL,
+    ];
 
     $this->assertEquals($expected, $definition, 'Retrieved the right metadata for config_schema_test.wildcard_fallback.something');