diff --git a/core/lib/Drupal/Core/Form/ConfigFormBase.php b/core/lib/Drupal/Core/Form/ConfigFormBase.php
index 5ed650a..d310dc1 100644
--- a/core/lib/Drupal/Core/Form/ConfigFormBase.php
+++ b/core/lib/Drupal/Core/Form/ConfigFormBase.php
@@ -8,13 +8,14 @@
 namespace Drupal\Core\Form;
 
 use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Form\ConfigFormInterface;
 use Drupal\Core\Form\FormBase;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Base class for implementing system configuration forms.
  */
-abstract class ConfigFormBase extends FormBase {
+abstract class ConfigFormBase extends FormBase implements ConfigFormInterface {
   use ConfigFormBaseTrait;
 
   /**
diff --git a/core/lib/Drupal/Core/Form/ConfigFormBaseTrait.php b/core/lib/Drupal/Core/Form/ConfigFormBaseTrait.php
index 1cbac3c..df275c3 100644
--- a/core/lib/Drupal/Core/Form/ConfigFormBaseTrait.php
+++ b/core/lib/Drupal/Core/Form/ConfigFormBaseTrait.php
@@ -55,7 +55,7 @@ protected function config($name) {
     if (!isset($config_factory) || !($config_factory instanceof ConfigFactoryInterface)) {
       throw new \LogicException('No config factory available for ConfigFormBaseTrait');
     }
-    if (in_array($name, $this->getEditableConfigNames())) {
+    if ($this instanceof ConfigFormInterface && in_array($name, $this->getEditableConfigNames())) {
       // Get a mutable object from the factory.
       $config = $config_factory->getEditable($name);
     }
@@ -65,13 +65,4 @@ protected function config($name) {
     return $config;
   }
 
-  /**
-   * Gets the configuration names that will be editable.
-   *
-   * @return array
-   *   An array of configuration object names that are editable if called in
-   *   conjunction with the trait's config() method.
-   */
-  abstract protected function getEditableConfigNames();
-
 }
diff --git a/core/lib/Drupal/Core/Form/ConfigFormInterface.php b/core/lib/Drupal/Core/Form/ConfigFormInterface.php
new file mode 100644
index 0000000..08335fe
--- /dev/null
+++ b/core/lib/Drupal/Core/Form/ConfigFormInterface.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Form\ConfigFormInterface.
+ */
+
+namespace Drupal\Core\Form;
+
+use Drupal\Core\Form\FormInterface;
+
+interface ConfigFormInterface extends FormInterface {
+
+  /**
+   * Returns an array of configuration that are edited as part of the form.
+   *
+   * @return array
+   *   An array of editable configuration names.
+   */
+  public function getEditableConfigNames();
+
+}
diff --git a/core/lib/Drupal/Core/Form/PluginConfigFormInterface.php b/core/lib/Drupal/Core/Form/PluginConfigFormInterface.php
new file mode 100644
index 0000000..1e9d2cb
--- /dev/null
+++ b/core/lib/Drupal/Core/Form/PluginConfigFormInterface.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Form\PluginConfigFormInterface.
+ */
+
+namespace Drupal\Core\Form;
+
+use Drupal\Core\Form\FormInterface;
+
+interface PluginConfigFormInterface extends FormInterface {
+
+  /**
+   * Returns an array of configuration that are edited as part of the form.
+   *
+   * @return array
+   *   An array of editable configuration names.
+   */
+  public function getEditableConfigNames();
+
+}
diff --git a/core/modules/aggregator/src/Form/SettingsForm.php b/core/modules/aggregator/src/Form/SettingsForm.php
index 69c5ed6..bff3222 100644
--- a/core/modules/aggregator/src/Form/SettingsForm.php
+++ b/core/modules/aggregator/src/Form/SettingsForm.php
@@ -10,6 +10,7 @@
 use Drupal\aggregator\Plugin\AggregatorPluginManager;
 use Drupal\Component\Utility\String;
 use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Form\PluginConfigFormInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\PluginFormInterface;
 use Drupal\Core\StringTranslation\TranslationInterface;
@@ -33,7 +34,7 @@ class SettingsForm extends ConfigFormBase {
    *
    * @var \Drupal\Core\Plugin\PluginFormInterface[]
    */
-  protected $configurableInstances = array();
+  protected $configurableInstances;
 
   /**
    * The aggregator plugin definitions.
@@ -99,8 +100,16 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
-    return ['aggregator.settings'];
+  public function getEditableConfigNames() {
+    $config_names = ['aggregator.settings'];
+
+    foreach ($this->getConfigurableInstances() as $instance) {
+      if ($instance instanceof \Drupal\Core\Form\PluginConfigFormInterface) {
+        $config_names = array_merge($config_names, $instance->getEditableConfigNames());
+      }
+    }
+
+    return array_unique($config_names);
   }
 
   /**
@@ -158,35 +167,8 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       $form['basic_conf'] += $basic_conf;
     }
 
-    // Call buildConfigurationForm() on the active fetcher and parser.
-    foreach (array('fetcher', 'parser') as $type) {
-      $active = $config->get($type);
-      if (array_key_exists($active, $this->definitions[$type])) {
-        $instance = $this->managers[$type]->createInstance($active);
-        if ($instance instanceof PluginFormInterface) {
-          $form = $instance->buildConfigurationForm($form, $form_state);
-          // Store the instance for validate and submit handlers.
-          // Keying by ID would bring conflicts, because two instances of a
-          // different type could have the same ID.
-          $this->configurableInstances[] = $instance;
-        }
-      }
-    }
-
-    // Implementing processor plugins will expect an array at $form['processors'].
-    $form['processors'] = array();
-    // Call buildConfigurationForm() for each active processor.
-    foreach ($this->definitions['processor'] as $id => $definition) {
-      if (in_array($id, $config->get('processors'))) {
-        $instance = $this->managers['processor']->createInstance($id);
-        if ($instance instanceof PluginFormInterface) {
-          $form = $instance->buildConfigurationForm($form, $form_state);
-          // Store the instance for validate and submit handlers.
-          // Keying by ID would bring conflicts, because two instances of a
-          // different type could have the same ID.
-          $this->configurableInstances[] = $instance;
-        }
-      }
+    foreach ($this->getConfigurableInstances() as $instance) {
+      $form = $instance->buildConfigurationForm($form, $form_state);
     }
 
     return parent::buildForm($form, $form_state);
@@ -198,7 +180,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
   public function validateForm(array &$form, FormStateInterface $form_state) {
     parent::validateForm($form, $form_state);
     // Let active plugins validate their settings.
-    foreach ($this->configurableInstances as $instance) {
+    foreach ($this->getConfigurableInstances() as $instance) {
       $instance->validateConfigurationForm($form, $form_state);
     }
   }
@@ -210,7 +192,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
     parent::submitForm($form, $form_state);
     $config = $this->config('aggregator.settings');
     // Let active plugins save their settings.
-    foreach ($this->configurableInstances as $instance) {
+    foreach ($this->getConfigurableInstances() as $instance) {
       $instance->submitConfigurationForm($form, $form_state);
     }
 
@@ -227,4 +209,47 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
     $config->save();
   }
 
+  /**
+   * Collect configurable instances of this form's plugins.
+   */
+  protected function getConfigurableInstances() {
+    if (!isset($this->configurableInstances)) {
+      $this->configurableInstances = array();
+
+      $config = $this->configFactory->get('aggregator.settings');
+
+      // Parse fetchers and parsers and collect their PluginFormInterface
+      // instances.
+      foreach (array('fetcher', 'parser') as $type) {
+        $active = $config->get($type);
+        if (array_key_exists($active, $this->definitions[$type])) {
+          $instance = $this->managers[$type]->createInstance($active);
+          if ($instance instanceof PluginFormInterface) {
+            // Store the instance for validate and submit handlers.
+            // Keying by ID would bring conflicts, because two instances of a
+            // different type could have the same ID.
+            $this->configurableInstances[] = $instance;
+          }
+        }
+      }
+
+      // Implementing processor plugins will expect an array at $form['processors'].
+      $form['processors'] = array();
+      // Call buildConfigurationForm() for each active processor.
+      foreach ($this->definitions['processor'] as $id => $definition) {
+        if (in_array($id, $config->get('processors'))) {
+          $instance = $this->managers['processor']->createInstance($id);
+          if ($instance instanceof PluginFormInterface) {
+            // Store the instance for validate and submit handlers.
+            // Keying by ID would bring conflicts, because two instances of a
+            // different type could have the same ID.
+            $this->configurableInstances[] = $instance;
+          }
+        }
+      }
+    }
+
+    return $this->configurableInstances;
+  }
+
 }
diff --git a/core/modules/aggregator/src/Plugin/AggregatorPluginSettingsBase.php b/core/modules/aggregator/src/Plugin/AggregatorPluginSettingsBase.php
index eb6ad36..5495b25 100644
--- a/core/modules/aggregator/src/Plugin/AggregatorPluginSettingsBase.php
+++ b/core/modules/aggregator/src/Plugin/AggregatorPluginSettingsBase.php
@@ -8,9 +8,9 @@
 namespace Drupal\aggregator\Plugin;
 
 use Drupal\Component\Plugin\ConfigurablePluginInterface;
+use Drupal\Core\Form\PluginConfigFormInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\PluginBase;
-use Drupal\Core\Plugin\PluginFormInterface;
 
 /**
  * Base class for aggregator plugins that implement settings forms.
@@ -24,7 +24,7 @@
  * @see \Drupal\aggregator\Plugin\ParserInterface
  * @see plugin_api
  */
-abstract class AggregatorPluginSettingsBase extends PluginBase implements PluginFormInterface, ConfigurablePluginInterface {
+abstract class AggregatorPluginSettingsBase extends PluginBase implements PluginConfigFormInterface, ConfigurablePluginInterface {
 
   /**
    * {@inheritdoc}
diff --git a/core/modules/aggregator/tests/modules/aggregator_test/src/Plugin/aggregator/processor/TestProcessor.php b/core/modules/aggregator/tests/modules/aggregator_test/src/Plugin/aggregator/processor/TestProcessor.php
index 1a6ee19..5116913 100644
--- a/core/modules/aggregator/tests/modules/aggregator_test/src/Plugin/aggregator/processor/TestProcessor.php
+++ b/core/modules/aggregator/tests/modules/aggregator_test/src/Plugin/aggregator/processor/TestProcessor.php
@@ -69,7 +69,7 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return ['aggregator_test.settings'];
   }
 
diff --git a/core/modules/aggregator/tests/src/Unit/Form/SettingsFormTest.php b/core/modules/aggregator/tests/src/Unit/Form/SettingsFormTest.php
new file mode 100644
index 0000000..2491c66
--- /dev/null
+++ b/core/modules/aggregator/tests/src/Unit/Form/SettingsFormTest.php
@@ -0,0 +1,125 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\aggregator\Tests\Form\SettingsFormTest.
+ */
+
+namespace Drupal\aggregator\Tests\Form;
+
+use Drupal\aggregator\Form\SettingsForm;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests \Drupal\aggregator\Form\SettingsForm.
+ */
+class SettingsFormTest extends UnitTestCase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Aggregator settings form tests',
+      'description' => 'Unit tests for the aggregator settings form',
+      'group' => 'Aggregator',
+    );
+  }
+
+  /**
+   * Tests getConfigNames().
+   */
+  public function testGetConfigNames() {
+    // Test with no aggregator plugins.
+    $configs = array('aggregator.settings' => array());
+    $definitions = array();
+    $settings_form = $this->getSettingsForm($configs, $definitions);
+    $expected = array('aggregator.settings');
+    $this->assertSame($expected, array_values($settings_form->getEditableConfigNames()));
+
+    // Test that aggregator plugins can add additional configuration names.
+    $configs = array('aggregator.settings' => array(
+      'fetcher' => 'aggregator_test_fetcher',
+      'parser' => 'aggregator_test_parser',
+      'processors' => array('aggregator_test_processor')
+    ));
+    $definitions = array(
+      'aggregator_test_fetcher' => array(
+        'title' => 'Test fetcher',
+        'description' => 'Test generic fetcher functionality.',
+      ),
+      'aggregator_test_parser' => array(
+        'title' => 'Test parser',
+        'description' => 'Test generic parser functionality.',
+      ),
+      'aggregator_test_processor' => array(
+        'title' => 'Test processor',
+        'description' => 'Test generic processor functionality.',
+      ),
+    );
+    $config_names = array('aggregator_test.settings');
+    $settings_form = $this->getSettingsForm($configs, $definitions, $config_names);
+    $expected = array('aggregator.settings', 'aggregator_test.settings');
+    $this->assertEquals($expected, array_values($settings_form->getEditableConfigNames()));
+
+    // Test that configuration names are not duplicated.
+    $config_names = array('aggregator.settings');
+    // Re-use $configs and $definitions.
+    $settings_form = $this->getSettingsForm($configs, $definitions, $config_names);
+    $expected = array('aggregator.settings');
+    $this->assertEquals($expected, array_values($settings_form->getEditableConfigNames()));
+
+  }
+
+  /**
+   * Returns an aggregator settings form.
+   *
+   * @param array $configs
+   *   An array of configuration values as expected by
+   *   UnitTestCase::getConfigFactoryStub().
+   * @param array $definitions
+   *   An array of aggregator plugin definitions.
+   * @param array $config_names
+   *   An array of configuration names that the plugins should return.
+   *
+   * @return \Drupal\aggregator\Form\SettingsForm
+   *   The aggregator settings form.
+   */
+  protected function getSettingsForm(array $configs, array $definitions, array $config_names = NULL) {
+    $plugin_manager = $this->getMockBuilder('Drupal\aggregator\Plugin\AggregatorPluginManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $plugin_manager
+      ->expects($this->exactly(3))
+      ->method('getDefinitions')
+      ->will($this->returnValue($definitions));
+
+    $settings_form = new SettingsForm(
+      $this->getConfigFactoryStub($configs),
+      $this->getMock('Drupal\Core\Config\Context\ContextInterface'),
+      $plugin_manager,
+      $plugin_manager,
+      $plugin_manager,
+      $this->getMock('Drupal\Core\StringTranslation\TranslationInterface')
+    );
+
+    if (!empty($config_names)) {
+      $plugin = $this->getMock('Drupal\Core\Plugin\PluginConfigFormInterface');
+      $plugin
+        ->expects($this->exactly(3))
+        ->method('getConfigNames')
+        ->will($this->returnValue($config_names));
+      $plugin_manager
+        ->expects($this->exactly(3))
+        ->method('createInstance')
+        ->will($this->returnValueMap(array(
+          array('aggregator_test_fetcher', array(), $plugin),
+          array('aggregator_test_parser', array(), $plugin),
+          array('aggregator_test_processor', array(), $plugin)
+        )));
+    }
+
+    return $settings_form;
+  }
+
+}
diff --git a/core/modules/book/src/Form/BookSettingsForm.php b/core/modules/book/src/Form/BookSettingsForm.php
index 0db0dc5..17f113e 100644
--- a/core/modules/book/src/Form/BookSettingsForm.php
+++ b/core/modules/book/src/Form/BookSettingsForm.php
@@ -25,7 +25,7 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return ['book.settings'];
   }
 
diff --git a/core/modules/config_translation/config_translation.module b/core/modules/config_translation/config_translation.module
index 3655d49..b7e57a1 100644
--- a/core/modules/config_translation/config_translation.module
+++ b/core/modules/config_translation/config_translation.module
@@ -102,28 +102,22 @@ function config_translation_entity_type_alter(array &$entity_types) {
  * Implements hook_config_translation_info().
  */
 function config_translation_config_translation_info(&$info) {
-  $entity_manager = \Drupal::entityManager();
-  $route_provider = \Drupal::service('router.route_provider');
-
-  // If field UI is not enabled, the base routes of the type
-  // "entity.field_config.{$entity_type}_field_edit_form" are not defined.
-  if (\Drupal::moduleHandler()->moduleExists('field_ui')) {
-    // Add fields entity mappers to all fieldable entity types defined.
-    foreach ($entity_manager->getDefinitions() as $entity_type_id => $entity_type) {
-      // Make sure entity type has field UI enabled and has a base route.
-      if ($entity_type->get('field_ui_base_route')) {
-        $info[$entity_type_id . '_fields'] = array(
-          'base_route_name' => "entity.field_config.{$entity_type_id}_field_edit_form",
-          'entity_type' => 'field_config',
-          'title' => '!label field',
-          'class' => '\Drupal\config_translation\ConfigFieldMapper',
-          'base_entity_type' => $entity_type_id,
-          'weight' => 10,
-        );
-      }
-    }
+  // Register mappers for configuration forms.
+  // @see \Drupal\config_translation\Routing\RouteSubscriber
+  $config_form_routes = \Drupal::state()
+    ->get('config_translation.config_form_routes', []);
+  foreach ($config_form_routes as $route_name => $mapper_info) {
+    list($config_names, $title) = $mapper_info;
+    $info[$route_name] = [
+      'base_route_name' => $route_name,
+      'config_names' => $config_names,
+      'title' => $title,
+    ];
   }
 
+  $entity_manager = \Drupal::entityManager();
+  $field_ui_exists = \Drupal::moduleHandler()->moduleExists('field_ui');
+
   // Discover configuration entities automatically.
   foreach ($entity_manager->getDefinitions() as $entity_type_id => $entity_type) {
     // Determine base path for entities automatically if provided via the
@@ -145,10 +139,23 @@ function config_translation_config_translation_info(&$info) {
       'class' => '\Drupal\config_translation\ConfigEntityMapper',
       'base_route_name' => $base_route_name,
       'title' => '!label !entity_type',
-      'names' => array(),
       'entity_type' => $entity_type_id,
       'weight' => 10,
     );
+
+    // Make sure entity type has field UI enabled and has a base route. If field
+    // UI is not enabled, the base routes of the type
+    // "entity.field_config.{$entity_type}_field_edit_form" are not defined.
+    if ($field_ui_exists && $entity_type->get('field_ui_base_route')) {
+      $info[$entity_type_id . '_fields'] = array(
+        'base_route_name' => "entity.field_config.{$entity_type_id}_field_edit_form",
+        'entity_type' => 'field_config',
+        'title' => '!label field',
+        'class' => 'Drupal\config_translation\ConfigFieldMapper',
+        'base_entity_type' => $entity_type_id,
+        'weight' => 10,
+      );
+    }
   }
 }
 
diff --git a/core/modules/config_translation/config_translation.services.yml b/core/modules/config_translation/config_translation.services.yml
index e6c00b6..d75c7e1 100644
--- a/core/modules/config_translation/config_translation.services.yml
+++ b/core/modules/config_translation/config_translation.services.yml
@@ -1,7 +1,7 @@
 services:
   config_translation.route_subscriber:
     class: Drupal\config_translation\Routing\RouteSubscriber
-    arguments: ['@plugin.manager.config_translation.mapper']
+    arguments: ['@plugin.manager.config_translation.mapper', '@state', '@class_resolver']
     tags:
       - { name: event_subscriber }
 
diff --git a/core/modules/config_translation/src/ConfigMapperManagerInterface.php b/core/modules/config_translation/src/ConfigMapperManagerInterface.php
index 8f72f07..70a4766 100644
--- a/core/modules/config_translation/src/ConfigMapperManagerInterface.php
+++ b/core/modules/config_translation/src/ConfigMapperManagerInterface.php
@@ -7,13 +7,14 @@
 
 namespace Drupal\config_translation;
 
+use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface;
 use Drupal\Component\Plugin\PluginManagerInterface;
 use Symfony\Component\Routing\RouteCollection;
 
 /**
  * Provides a common interface for config mapper managers.
  */
-interface ConfigMapperManagerInterface extends PluginManagerInterface {
+interface ConfigMapperManagerInterface extends PluginManagerInterface, CachedDiscoveryInterface {
 
   /**
    * Returns an array of all mappers.
diff --git a/core/modules/config_translation/src/Routing/RouteSubscriber.php b/core/modules/config_translation/src/Routing/RouteSubscriber.php
index d9d56ad..d41501f 100644
--- a/core/modules/config_translation/src/Routing/RouteSubscriber.php
+++ b/core/modules/config_translation/src/Routing/RouteSubscriber.php
@@ -7,9 +7,13 @@
 
 namespace Drupal\config_translation\Routing;
 
+use Drupal\Core\DependencyInjection\ClassResolverInterface;
+use Drupal\Core\Form\ConfigFormInterface;
 use Drupal\Core\Routing\RouteSubscriberBase;
 use Drupal\config_translation\ConfigMapperManagerInterface;
 use Drupal\Core\Routing\RoutingEvents;
+use Drupal\Core\State\StateInterface;
+use Symfony\Component\EventDispatcher\Event;
 use Symfony\Component\Routing\RouteCollection;
 
 /**
@@ -25,22 +29,103 @@ class RouteSubscriber extends RouteSubscriberBase {
   protected $mapperManager;
 
   /**
+   * The state service.
+   *
+   * @var \Drupal\Core\State\StateInterface
+   */
+  protected $state;
+
+  /**
+   * The controller resolver.
+   *
+   * @var \Drupal\Core\DependencyInjection\ClassResolverInterface
+   */
+  protected $classResolver;
+
+  /**
+   * The key used to store form route information in state.
+   *
+   * @var string
+   */
+  protected static $stateKey = 'config_translation.config_form_routes';
+
+  /**
+   * Configuration form route information fetched from state.
+   *
+   * The list is keyed by route name and each value is an array where the first
+   * value is an array of configuration names and the second value is the route
+   * title (or an empty string if no title could be determined).
+   *
+   * @var array
+   */
+  protected $storedFormRoutes;
+
+  /**
+   * Configuration form route information gathered from rebuilt routes.
+   *
+   * The list is keyed by route name and each value is an array where the first
+   * value is an array of configuration names and the second value is the route
+   * title (or an empty string if no title could be determined).
+   *
+   * @var array
+   */
+  protected $processedFormRoutes;
+
+  /**
    * Constructs a new RouteSubscriber.
    *
    * @param \Drupal\config_translation\ConfigMapperManagerInterface $mapper_manager
    *   The mapper plugin discovery service.
+   * @param \Drupal\Core\State\StateInterface $state
+   *   The state service.
+   * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
+   *   The controller resolver.
    */
-  public function __construct(ConfigMapperManagerInterface $mapper_manager) {
+  public function __construct(ConfigMapperManagerInterface $mapper_manager, StateInterface $state, ClassResolverInterface $class_resolver) {
     $this->mapperManager = $mapper_manager;
+    $this->state = $state;
+    $this->classResolver = $class_resolver;
   }
 
   /**
    * {@inheritdoc}
    */
   protected function alterRoutes(RouteCollection $collection) {
-    $mappers = $this->mapperManager->getMappers($collection);
+    if (!isset($this->storedFormRoutes)) {
+      $this->storedFormRoutes = $this->state->get(static::$stateKey, []);
+    }
 
-    foreach ($mappers as $mapper) {
+    $update_stored_definitions = FALSE;
+    foreach ($collection as $route_name => $route) {
+      /** @var \Symfony\Component\Routing\Route $route */
+      if ($route->hasDefault('_form')) {
+        $form_object = $this->classResolver->getInstanceFromDefinition($route->getDefault('_form'));
+        if ($form_object instanceof ConfigFormInterface) {
+          $config_names = $form_object->getEditableConfigNames();
+          foreach ($config_names as $config_name) {
+            if ($this->mapperManager->hasTranslatable($config_name)) {
+              $this->processedFormRoutes[$route_name] = [
+                $config_names,
+                $route->getDefault('_title') ?: '',
+              ];
+
+              // Make sure that newly found routes are processed later.
+              if (!isset($this->storedFormRoutes[$route_name])) {
+                $this->storedFormRoutes[$route_name] = $this->processedFormRoutes[$route_name];
+                $update_stored_definitions = TRUE;
+              }
+            }
+          }
+        }
+      }
+    }
+
+    if ($update_stored_definitions) {
+      $this->state->set(static::$stateKey, $this->storedFormRoutes);
+      $this->mapperManager->clearCachedDefinitions();
+    }
+
+    foreach ($this->mapperManager->getMappers($collection) as $mapper) {
       $collection->add($mapper->getOverviewRouteName(), $mapper->getOverviewRoute());
       $collection->add($mapper->getAddRouteName(), $mapper->getAddRoute());
       $collection->add($mapper->getEditRouteName(), $mapper->getEditRoute());
@@ -49,11 +134,29 @@ protected function alterRoutes(RouteCollection $collection) {
   }
 
   /**
+   * Delegates the route altering to self::alterRoutes().
+   *
+   * @param \Drupal\Core\Routing\RouteBuildEvent $event
+   *   The route build event.
+   */
+  public function onFinished(Event $event) {
+    $removed_routes = array_diff_key($this->processedFormRoutes, $this->storedFormRoutes);
+    if ($removed_routes) {
+      $this->state->set(static::$stateKey, $this->storedFormRoutes);
+    }
+
+    // Reset internal state in case route rebuilding happens multiple time in a
+    // single request.
+    unset($this->storedFormRoutes, $this->processedFormRoutes);
+  }
+
+  /**
    * {@inheritdoc}
    */
   public static function getSubscribedEvents() {
     // Come after field_ui.
     $events[RoutingEvents::ALTER] = array('onAlterRoutes', -110);
+    $events[RoutingEvents::FINISHED] = array('onFinished');
     return $events;
   }
 
diff --git a/core/modules/forum/src/ForumSettingsForm.php b/core/modules/forum/src/ForumSettingsForm.php
index f2b43c4..42338ff 100644
--- a/core/modules/forum/src/ForumSettingsForm.php
+++ b/core/modules/forum/src/ForumSettingsForm.php
@@ -25,7 +25,7 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return ['forum.settings'];
   }
 
diff --git a/core/modules/language/src/Form/NegotiationBrowserForm.php b/core/modules/language/src/Form/NegotiationBrowserForm.php
index 14ebb62..120977a 100644
--- a/core/modules/language/src/Form/NegotiationBrowserForm.php
+++ b/core/modules/language/src/Form/NegotiationBrowserForm.php
@@ -56,7 +56,7 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return ['language.mappings'];
   }
 
diff --git a/core/modules/language/src/Form/NegotiationConfigureForm.php b/core/modules/language/src/Form/NegotiationConfigureForm.php
index d9cf420..20a5a13 100644
--- a/core/modules/language/src/Form/NegotiationConfigureForm.php
+++ b/core/modules/language/src/Form/NegotiationConfigureForm.php
@@ -121,7 +121,7 @@ public function getFormID() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return ['language.types'];
   }
 
diff --git a/core/modules/language/src/Form/NegotiationSelectedForm.php b/core/modules/language/src/Form/NegotiationSelectedForm.php
index 57194f9..7c5d57b 100644
--- a/core/modules/language/src/Form/NegotiationSelectedForm.php
+++ b/core/modules/language/src/Form/NegotiationSelectedForm.php
@@ -26,7 +26,7 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return ['language.negotiation'];
   }
 
diff --git a/core/modules/language/src/Form/NegotiationSessionForm.php b/core/modules/language/src/Form/NegotiationSessionForm.php
index fff1dd5..27e5b72 100644
--- a/core/modules/language/src/Form/NegotiationSessionForm.php
+++ b/core/modules/language/src/Form/NegotiationSessionForm.php
@@ -25,7 +25,7 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return ['language.negotiation'];
   }
 
diff --git a/core/modules/language/src/Form/NegotiationUrlForm.php b/core/modules/language/src/Form/NegotiationUrlForm.php
index fa1cd3e..4089e44 100644
--- a/core/modules/language/src/Form/NegotiationUrlForm.php
+++ b/core/modules/language/src/Form/NegotiationUrlForm.php
@@ -59,7 +59,7 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return ['language.negotiation'];
   }
 
diff --git a/core/modules/locale/src/Form/LocaleSettingsForm.php b/core/modules/locale/src/Form/LocaleSettingsForm.php
index 64460cc..b9c873b 100644
--- a/core/modules/locale/src/Form/LocaleSettingsForm.php
+++ b/core/modules/locale/src/Form/LocaleSettingsForm.php
@@ -24,7 +24,7 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return ['locale.settings'];
   }
 
diff --git a/core/modules/search/src/SearchPageListBuilder.php b/core/modules/search/src/SearchPageListBuilder.php
index 62aa99e..f59c1cb 100644
--- a/core/modules/search/src/SearchPageListBuilder.php
+++ b/core/modules/search/src/SearchPageListBuilder.php
@@ -12,7 +12,7 @@
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
-use Drupal\Core\Form\FormInterface;
+use Drupal\Core\Form\ConfigFormInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Url;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -22,7 +22,7 @@
  *
  * @see \Drupal\search\Entity\SearchPage
  */
-class SearchPageListBuilder extends DraggableListBuilder implements FormInterface {
+class SearchPageListBuilder extends DraggableListBuilder implements ConfigFormInterface {
 
   /**
    * The entities being listed.
@@ -85,6 +85,14 @@ public function getFormID() {
   /**
    * {@inheritdoc}
    */
+  public function getEditableConfigNames() {
+    return ['search.settings'];
+  }
+
+
+  /**
+   * {@inheritdoc}
+   */
   public function buildHeader() {
     $header['label'] = array(
       'data' => $this->t('Label'),
diff --git a/core/modules/simpletest/src/Form/SimpletestSettingsForm.php b/core/modules/simpletest/src/Form/SimpletestSettingsForm.php
index e6d4d4d..519c44d 100644
--- a/core/modules/simpletest/src/Form/SimpletestSettingsForm.php
+++ b/core/modules/simpletest/src/Form/SimpletestSettingsForm.php
@@ -25,7 +25,7 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return ['simpletest.settings'];
   }
 
diff --git a/core/modules/statistics/src/StatisticsSettingsForm.php b/core/modules/statistics/src/StatisticsSettingsForm.php
index 2213bef..2d2a45c 100644
--- a/core/modules/statistics/src/StatisticsSettingsForm.php
+++ b/core/modules/statistics/src/StatisticsSettingsForm.php
@@ -58,7 +58,7 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return ['statistics.settings'];
   }
 
diff --git a/core/modules/system/src/Form/CronForm.php b/core/modules/system/src/Form/CronForm.php
index f906c70..167097a 100644
--- a/core/modules/system/src/Form/CronForm.php
+++ b/core/modules/system/src/Form/CronForm.php
@@ -83,7 +83,7 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return ['system.cron'];
   }
 
diff --git a/core/modules/system/src/Form/FileSystemForm.php b/core/modules/system/src/Form/FileSystemForm.php
index 368f318..225f144 100644
--- a/core/modules/system/src/Form/FileSystemForm.php
+++ b/core/modules/system/src/Form/FileSystemForm.php
@@ -74,7 +74,7 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return ['system.file'];
   }
 
diff --git a/core/modules/system/src/Form/ImageToolkitForm.php b/core/modules/system/src/Form/ImageToolkitForm.php
index 0d9bec9..ec9f8bd 100644
--- a/core/modules/system/src/Form/ImageToolkitForm.php
+++ b/core/modules/system/src/Form/ImageToolkitForm.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Form\ConfigFormBase;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Form\PluginConfigFormInterface;
 use Drupal\Core\ImageToolkit\ImageToolkitManager;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -61,8 +62,16 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
-    return ['system.image'];
+  public function getEditableConfigNames() {
+    $config_names = ['system.image'];
+
+    foreach ($this->availableToolkits as $toolkit) {
+      if ($toolkit instanceof PluginConfigFormInterface) {
+        $config_names = array_merge($config_names, $toolkit->getEditableConfigNames());
+      }
+    }
+
+    return array_unique($config_names);
   }
 
   /**
diff --git a/core/modules/system/src/Form/LoggingForm.php b/core/modules/system/src/Form/LoggingForm.php
index cfbbddc..5b6e8be 100644
--- a/core/modules/system/src/Form/LoggingForm.php
+++ b/core/modules/system/src/Form/LoggingForm.php
@@ -25,7 +25,7 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return ['system.logging'];
   }
 
diff --git a/core/modules/system/src/Form/PerformanceForm.php b/core/modules/system/src/Form/PerformanceForm.php
index 9049c78..6df6ff2 100644
--- a/core/modules/system/src/Form/PerformanceForm.php
+++ b/core/modules/system/src/Form/PerformanceForm.php
@@ -93,7 +93,7 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return ['system.performance'];
   }
 
diff --git a/core/modules/system/src/Form/RegionalForm.php b/core/modules/system/src/Form/RegionalForm.php
index 827f74f..c72afd9 100644
--- a/core/modules/system/src/Form/RegionalForm.php
+++ b/core/modules/system/src/Form/RegionalForm.php
@@ -58,7 +58,7 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return ['system.date'];
   }
 
diff --git a/core/modules/system/src/Form/RssFeedsForm.php b/core/modules/system/src/Form/RssFeedsForm.php
index cde9964..1045108 100644
--- a/core/modules/system/src/Form/RssFeedsForm.php
+++ b/core/modules/system/src/Form/RssFeedsForm.php
@@ -25,7 +25,7 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return ['system.rss'];
   }
 
diff --git a/core/modules/system/src/Form/SiteInformationForm.php b/core/modules/system/src/Form/SiteInformationForm.php
index 71a0fbd..edd6b4b 100644
--- a/core/modules/system/src/Form/SiteInformationForm.php
+++ b/core/modules/system/src/Form/SiteInformationForm.php
@@ -71,7 +71,7 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return ['system.site'];
   }
 
diff --git a/core/modules/system/src/Form/SiteMaintenanceModeForm.php b/core/modules/system/src/Form/SiteMaintenanceModeForm.php
index cde1f57..eefa02f 100644
--- a/core/modules/system/src/Form/SiteMaintenanceModeForm.php
+++ b/core/modules/system/src/Form/SiteMaintenanceModeForm.php
@@ -57,7 +57,7 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return ['system.maintenance'];
   }
 
diff --git a/core/modules/system/src/Form/ThemeSettingsForm.php b/core/modules/system/src/Form/ThemeSettingsForm.php
index 75983f0..40e7464 100644
--- a/core/modules/system/src/Form/ThemeSettingsForm.php
+++ b/core/modules/system/src/Form/ThemeSettingsForm.php
@@ -82,7 +82,7 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return $this->editableConfig;
   }
 
diff --git a/core/modules/system/system.config_translation.yml b/core/modules/system/system.config_translation.yml
deleted file mode 100644
index 87cc98c..0000000
--- a/core/modules/system/system.config_translation.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-system.site_maintenance_mode:
-  title: 'System maintenance'
-  base_route_name: system.site_maintenance_mode
-  names:
-    - system.maintenance
-
-system.site_information_settings:
-  title: 'System information'
-  base_route_name: system.site_information_settings
-  names:
-    - system.site
-
-system.rss_feeds_settings:
-  title: 'RSS publishing'
-  base_route_name: system.rss_feeds_settings
-  names:
-    - system.rss
diff --git a/core/modules/system/tests/modules/form_test/src/SystemConfigFormTestForm.php b/core/modules/system/tests/modules/form_test/src/SystemConfigFormTestForm.php
index 243a81b..b666c44 100644
--- a/core/modules/system/tests/modules/form_test/src/SystemConfigFormTestForm.php
+++ b/core/modules/system/tests/modules/form_test/src/SystemConfigFormTestForm.php
@@ -24,7 +24,7 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return [];
   }
 
diff --git a/core/modules/update/src/UpdateSettingsForm.php b/core/modules/update/src/UpdateSettingsForm.php
index 7e4664d..a599845 100644
--- a/core/modules/update/src/UpdateSettingsForm.php
+++ b/core/modules/update/src/UpdateSettingsForm.php
@@ -25,7 +25,7 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return ['update.settings'];
   }
 
diff --git a/core/modules/user/src/AccountSettingsForm.php b/core/modules/user/src/AccountSettingsForm.php
index 2b91877..4f8d38b 100644
--- a/core/modules/user/src/AccountSettingsForm.php
+++ b/core/modules/user/src/AccountSettingsForm.php
@@ -59,7 +59,7 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return [
       'system.site',
       'user.mail',
diff --git a/core/modules/user/user.config_translation.yml b/core/modules/user/user.config_translation.yml
deleted file mode 100644
index 8e1b5e0..0000000
--- a/core/modules/user/user.config_translation.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-entity.user.admin_form:
-  title: 'Account settings'
-  base_route_name: entity.user.admin_form
-  names:
-    - user.settings
-    - user.mail
diff --git a/core/modules/views_ui/src/Form/AdvancedSettingsForm.php b/core/modules/views_ui/src/Form/AdvancedSettingsForm.php
index d8cd1e2..1d0c411 100644
--- a/core/modules/views_ui/src/Form/AdvancedSettingsForm.php
+++ b/core/modules/views_ui/src/Form/AdvancedSettingsForm.php
@@ -26,7 +26,7 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return ['views.settings'];
   }
 
diff --git a/core/modules/views_ui/src/Form/BasicSettingsForm.php b/core/modules/views_ui/src/Form/BasicSettingsForm.php
index 1732c9a..acf0c9d 100644
--- a/core/modules/views_ui/src/Form/BasicSettingsForm.php
+++ b/core/modules/views_ui/src/Form/BasicSettingsForm.php
@@ -59,7 +59,7 @@ public function getFormId() {
   /**
    * {@inheritdoc}
    */
-  protected function getEditableConfigNames() {
+  public function getEditableConfigNames() {
     return ['views.settings'];
   }
 
