diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php
index e039341..5298c35 100644
--- a/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php
+++ b/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php
@@ -122,25 +122,25 @@ function testUILanguageNegotiation() {
       'string' => $default_string,
       'langcode' => $langcode_browser_fallback,
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $textarea = current($this->xpath('//textarea'));
     $lid = (string) $textarea[0]['name'];
     $edit = array(
       $lid => $language_browser_fallback_string,
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Save translations'));
+    $this->drupalPost('admin/config/regional/translate', $edit, t('Save translations'));
 
     $search = array(
       'string' => $default_string,
       'langcode' => $langcode,
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $textarea = current($this->xpath('//textarea'));
     $lid = (string) $textarea[0]['name'];
     $edit = array(
       $lid => $language_string,
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Save translations'));
+    $this->drupalPost('admin/config/regional/translate', $edit, t('Save translations'));
 
     // Configure URL language rewrite.
     variable_set('language_negotiation_url_type', Language::TYPE_INTERFACE);
diff --git a/core/modules/locale/lib/Drupal/locale/Controller/LocaleController.php b/core/modules/locale/lib/Drupal/locale/Controller/LocaleController.php
index 71bbd6c..f8c0baf 100644
--- a/core/modules/locale/lib/Drupal/locale/Controller/LocaleController.php
+++ b/core/modules/locale/lib/Drupal/locale/Controller/LocaleController.php
@@ -1,4 +1,5 @@
 <?php
+
 /**
  * @file
  * Contains \Drupal\locale\Controller\LocaleController.
@@ -6,43 +7,14 @@
 
 namespace Drupal\locale\Controller;
 
-use Drupal\Core\Controller\ControllerInterface;
-use Drupal\Core\Routing\UrlGeneratorInterface;
-use Drupal\Core\Extension\ModuleHandlerInterface;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-use Symfony\Component\HttpFoundation\RedirectResponse;
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\locale\Form\TranslateEditForm;
+use Drupal\locale\Form\TranslateFilterForm;
+
 /**
  * Return response for manual check translations.
  */
-class LocaleController implements ControllerInterface {
-
-  /**
-   * The module handler.
-   *
-   * @var \Drupal\Core\Extension\ModuleHandlerInterface
-   */
-  protected $moduleHandler;
-
-  /**
-   * Constructs a \Drupal\locale\Controller\LocaleController object.
-   *
-   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
-   *   The module handler.
-   */
-  public function __construct(ModuleHandlerInterface $module_handler, UrlGeneratorInterface $url_generator) {
-    $this->moduleHandler = $module_handler;
-    $this->urlGenerator = $url_generator;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static(
-      $container->get('module_handler'),
-      $container->get('url_generator')
-    );
-  }
+class LocaleController extends ControllerBase {
 
   /**
    * Checks for translation updates and displays the translations status.
@@ -53,11 +25,11 @@ public static function create(ContainerInterface $container) {
    *   A redirection to translations reports page.
    */
   public function checkTranslation() {
-    $this->moduleHandler->loadInclude('locale', 'inc', 'locale.compare');
+    $this->moduleHandler()->loadInclude('locale', 'inc', 'locale.compare');
 
     // Check translation status of all translatable project in all languages.
     // First we clear the cached list of projects. Although not strictly
-    // nescessary, this is helpfull in case the project list is out of sync.
+    // nescessary, this is helpful in case the project list is out of sync.
     locale_translation_flush_projects();
     locale_translation_check_projects();
 
@@ -67,6 +39,20 @@ public function checkTranslation() {
       return batch_process('admin/reports/translations');
     }
 
-    return new RedirectResponse($this->urlGenerator->generateFromPath('admin/reports/translations', array('absolute' => TRUE)));
+    return $this->redirect($this->urlGenerator()->generateFromPath('admin/reports/translations', array('absolute' => TRUE)));
   }
+
+  /**
+   * Shows the string search screen.
+   *
+   * @return array
+   *   The render array for the string search screen.
+   */
+  public function translatePage() {
+    return array(
+      'filter' => drupal_get_form(TranslateFilterForm::create($this->container)),
+      'form' => drupal_get_form(TranslateEditForm::create($this->container)),
+    );
+  }
+
 }
diff --git a/core/modules/locale/lib/Drupal/locale/Form/TranslateEditForm.php b/core/modules/locale/lib/Drupal/locale/Form/TranslateEditForm.php
new file mode 100644
index 0000000..45e81c2
--- /dev/null
+++ b/core/modules/locale/lib/Drupal/locale/Form/TranslateEditForm.php
@@ -0,0 +1,244 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\locale\Form\TranslateEditForm.
+ */
+
+namespace Drupal\locale\Form;
+
+use Drupal\Component\Utility\String;
+use Drupal\locale\SourceString;
+
+/**
+ * Defines a translation edit form.
+ */
+class TranslateEditForm extends TranslateFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormID() {
+    return 'locale_translate_edit_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, array &$form_state) {
+    $filter_values = $this->translateFilterValues();
+    $langcode = $filter_values['langcode'];
+
+    drupal_static_reset('language_list');
+    $languages = language_list();
+
+    $langname = isset($langcode) ? $languages[$langcode]->name : "- None -";
+
+    $path = drupal_get_path('module', 'locale');
+    $form['#attached']['css'] = array(
+      $path . '/css/locale.admin.css',
+    );
+    $form['#attached']['library'][] = array('locale', 'drupal.locale.admin');
+
+    $form['langcode'] = array(
+      '#type' => 'value',
+      '#value' => $filter_values['langcode'],
+    );
+
+    $form['strings'] = array(
+      '#type' => 'item',
+      '#tree' => TRUE,
+      '#language' => $langname,
+      '#theme' => 'locale_translate_edit_form_strings',
+    );
+
+    if (isset($langcode)) {
+      $strings = $this->translateFilterLoadStrings();
+
+      $plural_formulas = $this->state->get('locale.translation.plurals') ?: array();
+
+      foreach ($strings as $string) {
+        // Cast into source string, will do for our purposes.
+        $source = new SourceString($string);
+        // Split source to work with plural values.
+        $source_array = $source->getPlurals();
+        $translation_array = $string->getPlurals();
+        if (count($source_array) == 1) {
+          // Add original string value and mark as non-plural.
+          $form['strings'][$string->lid]['plural'] = array(
+            '#type' => 'value',
+            '#value' => 0,
+          );
+          $form['strings'][$string->lid]['original'] = array(
+            '#type' => 'item',
+            '#title' => $this->t('Source string (@language)', array('@language' => $this->t('Built-in English'))),
+            '#title_display' => 'invisible',
+            '#markup' => '<span lang="en">' . String::checkPlain($source_array[0]) . '</span>',
+          );
+        }
+        else {
+          // Add original string value and mark as plural.
+          $form['strings'][$string->lid]['plural'] = array(
+            '#type' => 'value',
+            '#value' => 1,
+          );
+          $form['strings'][$string->lid]['original_singular'] = array(
+            '#type' => 'item',
+            '#title' => $this->t('Singular form'),
+            '#markup' => '<span lang="en">' . String::checkPlain($source_array[0]) . '</span>',
+            '#prefix' => '<span class="visually-hidden">' . $this->t('Source string (@language)', array('@language' => $this->t('Built-in English'))) . '</span>'
+          );
+          $form['strings'][$string->lid]['original_plural'] = array(
+            '#type' => 'item',
+            '#title' => $this->t('Plural form'),
+            '#markup' => '<span lang="en">' . String::checkPlain($source_array[1]) . '</span>',
+          );
+        }
+        if (!empty($string->context)) {
+          $form['strings'][$string->lid]['context'] = array(
+            '#type' => 'value',
+            '#value' => '<span lang="en">' . String::checkPlain($string->context) . '</span>',
+          );
+        }
+        // Approximate the number of rows to use in the default textarea.
+        $rows = min(ceil(str_word_count($source_array[0]) / 12), 10);
+        if (empty($form['strings'][$string->lid]['plural']['#value'])) {
+          $form['strings'][$string->lid]['translations'][0] = array(
+            '#type' => 'textarea',
+            '#title' => $this->t('Translated string (@language)', array('@language' => $langname)),
+            '#title_display' => 'invisible',
+            '#rows' => $rows,
+            '#default_value' => $translation_array[0],
+            '#attributes' => array('lang' => $langcode),
+          );
+        }
+        else {
+          // Dealing with plural strings.
+          if (isset($plural_formulas[$langcode]['plurals']) && $plural_formulas[$langcode]['plurals'] > 2) {
+            // Add a textarea for each plural variant.
+            for ($i = 0; $i < $plural_formulas[$langcode]['plurals']; $i++) {
+              $form['strings'][$string->lid]['translations'][$i] = array(
+                '#type' => 'textarea',
+                '#title' => ($i == 0 ? $this->t('Singular form') : format_plural($i, 'First plural form', '@count. plural form')),
+                '#rows' => $rows,
+                '#default_value' => isset($translation_array[$i]) ? $translation_array[$i] : '',
+                '#attributes' => array('lang' => $langcode),
+                '#prefix' => $i == 0 ? ('<span class="visually-hidden">' . $this->t('Translated string (@language)',  array('@language' => $langname)) . '</span>') : '',
+              );
+            }
+          }
+          else {
+            // Fallback for unknown number of plurals.
+            $form['strings'][$string->lid]['translations'][0] = array(
+              '#type' => 'textarea',
+              '#title' => $this->t('Singular form'),
+              '#rows' => $rows,
+              '#default_value' => $translation_array[0],
+              '#attributes' => array('lang' => $langcode),
+              '#prefix' => '<span class="visually-hidden">' . $this->t('Translated string (@language)',  array('@language' => $langname)) . '</span>',
+            );
+            $form['strings'][$string->lid]['translations'][1] = array(
+              '#type' => 'textarea',
+              '#title' => $this->t('Plural form'),
+              '#rows' => $rows,
+              '#default_value' => isset($translation_array[1]) ? $translation_array[1] : '',
+              '#attributes' => array('lang' => $langcode),
+            );
+          }
+        }
+      }
+      if (count(element_children($form['strings']))) {
+        $form['actions'] = array('#type' => 'actions');
+        $form['actions']['submit'] = array(
+          '#type' => 'submit',
+          '#value' => $this->t('Save translations'),
+        );
+      }
+    }
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, array &$form_state) {
+    $langcode = $form_state['values']['langcode'];
+    foreach ($form_state['values']['strings'] as $lid => $translations) {
+      foreach ($translations['translations'] as $key => $value) {
+        if (!locale_string_is_safe($value)) {
+          form_set_error("strings][$lid][translations][$key", $this->t('The submitted string contains disallowed HTML: %string', array('%string' => $value)));
+          form_set_error("translations][$langcode][$key", $this->t('The submitted string contains disallowed HTML: %string', array('%string' => $value)));
+          watchdog('locale', 'Attempted submission of a translation string with disallowed HTML: %string', array('%string' => $value), WATCHDOG_WARNING);
+        }
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, array &$form_state) {
+    $langcode = $form_state['values']['langcode'];
+    $updated = array();
+
+    // Preload all translations for strings in the form.
+    $lids = array_keys($form_state['values']['strings']);
+    $existing_translation_objects = array();
+    foreach ($this->localeStorage->getTranslations(array('lid' => $lids, 'language' => $langcode, 'translated' => TRUE)) as $existing_translation_object) {
+      $existing_translation_objects[$existing_translation_object->lid] = $existing_translation_object;
+    }
+
+    foreach ($form_state['values']['strings'] as $lid => $new_translation) {
+      $existing_translation = isset($existing_translation_objects[$lid]);
+
+      // Plural translations are saved in a delimited string. To be able to
+      // compare the new strings with the existing strings a string in the same format is created.
+      $new_translation_string_delimited = implode(LOCALE_PLURAL_DELIMITER, $new_translation['translations']);
+
+      // Generate an imploded string without delimiter, to be able to run
+      // empty() on it.
+      $new_translation_string = implode('', $new_translation['translations']);
+
+      $is_changed = FALSE;
+
+      if ($existing_translation && $existing_translation_objects[$lid]->translation != $new_translation_string_delimited) {
+        // If there is an existing translation in the DB and the new translation
+        // is not the same as the existing one.
+        $is_changed = TRUE;
+      }
+      elseif (!$existing_translation && !empty($new_translation_string)) {
+        // Newly entered translation.
+        $is_changed = TRUE;
+      }
+
+      if ($is_changed) {
+        // Only update or insert if we have a value to use.
+        $target = isset($existing_translation_objects[$lid]) ? $existing_translation_objects[$lid] : $this->localeStorage->createTranslation(array('lid' => $lid, 'language' => $langcode));
+        $target->setPlurals($new_translation['translations'])
+          ->setCustomized()
+          ->save();
+        $updated[] = $target->getId();
+      }
+      if (empty($new_translation_string) && isset($existing_translation_objects[$lid])) {
+        // Empty new translation entered: remove existing entry from database.
+        $existing_translation_objects[$lid]->delete();
+        $updated[] = $lid;
+      }
+    }
+
+    drupal_set_message($this->t('The strings have been saved.'));
+
+    // Keep the user on the current pager page.
+    $page = $this->getRequest()->query->get('page');
+    if (isset($page)) {
+      $form_state['redirect'] = array('admin/config/regional/translate', array('query' => array('page' => $page)));
+    }
+
+    if ($updated) {
+      // Clear cache and force refresh of JavaScript translations.
+      _locale_refresh_translations(array($langcode), $updated);
+      _locale_refresh_configuration(array($langcode), $updated);
+    }
+  }
+
+}
diff --git a/core/modules/locale/lib/Drupal/locale/Form/TranslateFilterForm.php b/core/modules/locale/lib/Drupal/locale/Form/TranslateFilterForm.php
new file mode 100644
index 0000000..4585602
--- /dev/null
+++ b/core/modules/locale/lib/Drupal/locale/Form/TranslateFilterForm.php
@@ -0,0 +1,105 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\locale\Form\TranslateFilterForm.
+ */
+
+namespace Drupal\locale\Form;
+
+/**
+ * Provides a filtered translation edit form.
+ */
+class TranslateFilterForm extends TranslateFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormID() {
+    return 'locale_translate_filter_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, array &$form_state) {
+    $filters = $this->translateFilters();
+    $filter_values = $this->translateFilterValues();
+
+    $form['#attached']['css'] = array(
+      drupal_get_path('module', 'locale') . '/css/locale.admin.css',
+    );
+
+    $form['filters'] = array(
+      '#type' => 'details',
+      '#title' => $this->t('Filter translatable strings'),
+      '#collapsed' => FALSE,
+    );
+    foreach ($filters as $key => $filter) {
+      // Special case for 'string' filter.
+      if ($key == 'string') {
+        $form['filters']['status']['string'] = array(
+          '#type' => 'search',
+          '#title' => $filter['title'],
+          '#description' => $filter['description'],
+          '#default_value' => $filter_values[$key],
+        );
+      }
+      else {
+        $empty_option = isset($filter['options'][$filter['default']]) ? $filter['options'][$filter['default']] : '- None -';
+        $form['filters']['status'][$key] = array(
+          '#title' => $filter['title'],
+          '#type' => 'select',
+          '#empty_value' => $filter['default'],
+          '#empty_option' => $empty_option,
+          '#size' => 0,
+          '#options' => $filter['options'],
+          '#default_value' => $filter_values[$key],
+        );
+        if (isset($filter['states'])) {
+          $form['filters']['status'][$key]['#states'] = $filter['states'];
+        }
+      }
+    }
+
+    $form['filters']['actions'] = array(
+      '#type' => 'actions',
+      '#attributes' => array('class' => array('container-inline')),
+    );
+    $form['filters']['actions']['submit'] = array(
+      '#type' => 'submit',
+      '#value' => $this->t('Filter'),
+    );
+    if (!empty($_SESSION['locale_translate_filter'])) {
+      $form['filters']['actions']['reset'] = array(
+        '#type' => 'submit',
+        '#value' => $this->t('Reset'),
+        '#submit' => array(array($this, 'resetForm')),
+      );
+    }
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, array &$form_state) {
+    $filters = $this->translateFilters();
+    foreach ($filters as $name => $filter) {
+      if (isset($form_state['values'][$name])) {
+        $_SESSION['locale_translate_filter'][$name] = $form_state['values'][$name];
+      }
+    }
+    $form_state['redirect'] = 'admin/config/regional/translate';
+  }
+
+  /**
+   * Provides a submit handler for the reset button.
+   */
+  public function resetForm(array &$form, array &$form_state) {
+    $_SESSION['locale_translate_filter'] = array();
+    $form_state['redirect'] = 'admin/config/regional/translate';
+  }
+
+}
diff --git a/core/modules/locale/lib/Drupal/locale/Form/TranslateFormBase.php b/core/modules/locale/lib/Drupal/locale/Form/TranslateFormBase.php
new file mode 100644
index 0000000..717f9d2
--- /dev/null
+++ b/core/modules/locale/lib/Drupal/locale/Form/TranslateFormBase.php
@@ -0,0 +1,220 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\locale\Form\TranslateFormBase.
+ */
+
+namespace Drupal\locale\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Language\Language;
+use Drupal\Core\Language\LanguageManager;
+use Drupal\locale\StringStorageInterface;
+use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Defines the locale user interface translation form base.
+ *
+ * Provides methods for searching and filtering strings.
+ */
+abstract class TranslateFormBase extends FormBase {
+
+  /**
+   * The locale storage.
+   *
+   * @var \Drupal\locale\StringStorageInterface
+   */
+  protected $localeStorage;
+
+  /**
+   * The state store.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
+   */
+  protected $state;
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManager
+   */
+  protected $languageManager;
+
+  /*
+   * Filter values. Shared between objects that inherit this class.
+   *
+   * @var array|null
+   */
+  protected static $filterValues;
+
+  /**
+   * Constructs a new TranslationFormBase object.
+   *
+   * @param \Drupal\locale\StringStorageInterface $locale_storage
+   *   The locale storage.
+   * @param \Drupal\Core\KeyValueStore\KeyValueStoreInterface $state
+   *   The state service.
+   * @param \Drupal\Core\Language\LanguageManager $language_manager
+   *   The language manager.
+   */
+  public function __construct(StringStorageInterface $locale_storage, KeyValueStoreInterface $state, LanguageManager $language_manager) {
+    $this->localeStorage = $locale_storage;
+    $this->state = $state;
+    $this->languageManager = $language_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('locale.storage'),
+      $container->get('state'),
+      $container->get('language_manager')
+    );
+  }
+
+  /**
+   * Builds a string search query and returns an array of string objects.
+   *
+   * @return array $filter_values
+   *   Array of \Drupal\locale\TranslationString objects.
+   */
+  protected function translateFilterLoadStrings() {
+    $filter_values = $this->translateFilterValues();
+
+    // Language is sanitized to be one of the possible options in
+    // translateFilterValues().
+    $conditions = array('language' => $filter_values['langcode']);
+    $options = array('pager limit' => 30, 'translated' => TRUE, 'untranslated' => TRUE);
+
+    // Add translation status conditions and options.
+    switch ($filter_values['translation']) {
+      case 'translated':
+        $conditions['translated'] = TRUE;
+        if ($filter_values['customized'] != 'all') {
+          $conditions['customized'] = $filter_values['customized'];
+        }
+        break;
+
+      case 'untranslated':
+        $conditions['translated'] = FALSE;
+        break;
+
+    }
+
+    if (!empty($filter_values['string'])) {
+      $options['filters']['source'] = $filter_values['string'];
+      if ($options['translated']) {
+        $options['filters']['translation'] = $filter_values['string'];
+      }
+    }
+
+    return $this->localeStorage->getTranslations($conditions, $options);
+  }
+
+  /**
+   * Builds an array out of search criteria specified in request variables.
+   *
+   * @param bool $reset
+   *   If the list of values should be reset.
+   *
+   * @return array $filter_values
+   *   The filter values.
+   */
+  protected function translateFilterValues($reset = FALSE) {
+    if (!$reset && static::$filterValues) {
+      return static::$filterValues;
+    }
+
+    $filter_values = array();
+    $filters = $this->translateFilters();
+    foreach ($filters as $key => $filter) {
+      $filter_values[$key] = $filter['default'];
+      // Let the filter defaults be overwritten by parameters in the URL.
+      if ($this->getRequest()->query->has($key)) {
+        // Only allow this value if it was among the options, or
+        // if there were no fixed options to filter for.
+        $value = $this->getRequest()->query->get($key);
+        if (!isset($filter['options']) || isset($filter['options'][$value])) {
+          $filter_values[$key] = $value;
+        }
+      }
+      elseif (isset($_SESSION['locale_translate_filter'][$key])) {
+        // Only allow this value if it was among the options, or
+        // if there were no fixed options to filter for.
+        if (!isset($filter['options']) || isset($filter['options'][$_SESSION['locale_translate_filter'][$key]])) {
+          $filter_values[$key] = $_SESSION['locale_translate_filter'][$key];
+        }
+      }
+    }
+
+    return static::$filterValues = $filter_values;
+  }
+
+  /**
+   * Lists locale translation filters that can be applied.
+   */
+  protected function translateFilters() {
+    $filters = array();
+
+    // Get all languages, except English.
+    drupal_static_reset('language_list');
+    $languages = language_list();
+    $language_options = array();
+    foreach ($languages as $langcode => $language) {
+      if ($langcode != 'en' || locale_translate_english()) {
+        $language_options[$langcode] = $language->name;
+      }
+    }
+
+    // Pick the current interface language code for the filter.
+    $default_langcode = $this->languageManager->getLanguage(Language::TYPE_INTERFACE)->id;
+    if (!isset($language_options[$default_langcode])) {
+      $available_langcodes = array_keys($language_options);
+      $default_langcode = array_shift($available_langcodes);
+    }
+
+    $filters['string'] = array(
+      'title' => $this->t('String contains'),
+      'description' => $this->t('Leave blank to show all strings. The search is case sensitive.'),
+      'default' => '',
+    );
+
+    $filters['langcode'] = array(
+      'title' => $this->t('Translation language'),
+      'options' => $language_options,
+      'default' => $default_langcode,
+    );
+
+    $filters['translation'] = array(
+      'title' => $this->t('Search in'),
+      'options' => array(
+        'all' => $this->t('Both translated and untranslated strings'),
+        'translated' => $this->t('Only translated strings'),
+        'untranslated' => $this->t('Only untranslated strings'),
+      ),
+      'default' => 'all',
+    );
+
+    $filters['customized'] = array(
+      'title' => $this->t('Translation type'),
+      'options' => array(
+        'all' => $this->t('All'),
+        LOCALE_NOT_CUSTOMIZED => $this->t('Non-customized translation'),
+        LOCALE_CUSTOMIZED => $this->t('Customized translation'),
+      ),
+      'states' => array(
+        'visible' => array(
+          ':input[name=translation]' => array('value' => 'translated'),
+        ),
+      ),
+      'default' => 'all',
+    );
+
+    return $filters;
+  }
+
+}
diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleConfigTranslationTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleConfigTranslationTest.php
index aa53fbb..8b825d0 100644
--- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleConfigTranslationTest.php
+++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleConfigTranslationTest.php
@@ -69,14 +69,14 @@ function testConfigTranslation() {
       'langcode' => $langcode,
       'translation' => 'all',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $textareas = $this->xpath('//textarea');
     $textarea = current($textareas);
     $lid = (string) $textarea[0]['name'];
     $edit = array(
       $lid => $site_name,
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Save translations'));
+    $this->drupalPost('admin/config/regional/translate', $edit, t('Save translations'));
 
     $wrapper = $this->container->get('locale.config.typed')->get('system.site');
 
@@ -115,13 +115,13 @@ function testConfigTranslation() {
       'langcode' => $langcode,
       'translation' => 'all',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $textarea = current($this->xpath('//textarea'));
     $lid = (string) $textarea[0]['name'];
     $edit = array(
       $lid => $image_style_label,
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Save translations'));
+    $this->drupalPost('admin/config/regional/translate', $edit, t('Save translations'));
 
     // Check the right single translation has been created.
     $translations = $this->storage->getTranslations(array('language' => $langcode, 'type' => 'configuration', 'name' => 'image.style.medium'));
diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleImportFunctionalTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleImportFunctionalTest.php
index f8fb4c4..c301fef 100644
--- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleImportFunctionalTest.php
+++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleImportFunctionalTest.php
@@ -111,7 +111,7 @@ function testStandalonePoFile() {
       'langcode' => 'fr',
       'translation' => 'translated',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $this->assertText(t('No strings available.'), 'String not overwritten by imported string.');
 
     // This import should not have changed number of plural forms.
@@ -133,7 +133,7 @@ function testStandalonePoFile() {
       'langcode' => 'fr',
       'translation' => 'translated',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $this->assertNoText(t('No strings available.'), 'String overwritten by imported string.');
     // This import should have changed number of plural forms.
     $locale_plurals = \Drupal::state()->get('locale.translation.plurals') ?: array();
@@ -169,7 +169,7 @@ function testStandalonePoFile() {
       'langcode' => 'fr',
       'translation' => 'translated',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $this->assertText(t('No strings available.'), 'Customized string not overwritten by imported string.');
 
     // Try importing a .po file with overriding strings, and ensure existing
@@ -188,7 +188,7 @@ function testStandalonePoFile() {
       'langcode' => 'fr',
       'translation' => 'translated',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $this->assertNoText(t('No strings available.'), 'Customized string overwritten by imported string.');
 
   }
@@ -233,7 +233,7 @@ function testEmptyMsgstr() {
       'langcode' => $langcode,
       'translation' => 'untranslated',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $this->assertText($str, 'Search found the string as untranslated.');
   }
 
@@ -287,7 +287,7 @@ function testConfigPoFile() {
         'langcode' => $langcode,
         'translation' => 'all',
       );
-      $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+      $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
       $this->assertText($config_string[1], format_string('Translation of @string found.', array('@string' => $config_string[0])));
     }
 
diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocalePluralFormatTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocalePluralFormatTest.php
index 15a1c55..25b6922 100644
--- a/core/modules/locale/lib/Drupal/locale/Tests/LocalePluralFormatTest.php
+++ b/core/modules/locale/lib/Drupal/locale/Tests/LocalePluralFormatTest.php
@@ -198,7 +198,7 @@ function testPluralEditExport() {
     $search = array(
       'langcode' => 'fr',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     // Plural values for the langcode fr.
     $this->assertText('@count heure');
     $this->assertText('@count heures');
@@ -221,7 +221,7 @@ function testPluralEditExport() {
       'string' => '1 day',
       'langcode' => 'fr',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
 
     // Save complete translations for the string in langcode fr.
     $edit = array(
@@ -235,7 +235,7 @@ function testPluralEditExport() {
       'string' => '1 day',
       'langcode' => 'hr',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
 
     $edit = array(
       "strings[$lid][translations][0]" => '@count dan',
diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleTranslationUiTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleTranslationUiTest.php
index e391022..9527adc 100644
--- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleTranslationUiTest.php
+++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleTranslationUiTest.php
@@ -39,7 +39,7 @@ function testEnglishTranslation() {
     $this->drupalLogin($admin_user);
 
     $this->drupalPost('admin/config/regional/language/edit/en', array('locale_translate_english' => TRUE), t('Save language'));
-    $this->assertLinkByHref('/admin/config/regional/translate/translate?langcode=en', 0, 'Enabled interface translation to English.');
+    $this->assertLinkByHref('/admin/config/regional/translate?langcode=en', 0, 'Enabled interface translation to English.');
   }
 
   /**
@@ -87,7 +87,7 @@ function testStringTranslation() {
       'langcode' => $langcode,
       'translation' => 'untranslated',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $this->assertText($name, 'Search found the string as untranslated.');
 
     // Assume this is the only result, given the random name.
@@ -105,7 +105,7 @@ function testStringTranslation() {
     $this->drupalPost('admin/config/regional/language/edit/en', array('locale_translate_english' => TRUE), t('Save language'));
     $this->drupalLogout();
     $this->drupalLogin($translate_user);
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $this->assertText($name, 'Search found the string as untranslated.');
 
     // Assume this is the only result, given the random name.
@@ -114,15 +114,15 @@ function testStringTranslation() {
     $edit = array(
       $lid => $translation,
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Save translations'));
+    $this->drupalPost('admin/config/regional/translate', $edit, t('Save translations'));
     $this->assertText(t('The strings have been saved.'), 'The strings have been saved.');
-    $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), 'Correct page redirection.');
+    $this->assertEqual($this->getUrl(), url('admin/config/regional/translate', array('absolute' => TRUE)), 'Correct page redirection.');
     $search = array(
       'string' => $name,
       'langcode' => $langcode,
       'translation' => 'translated',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $this->assertRaw($translation, 'Non-English translation properly saved.');
 
 
@@ -131,19 +131,19 @@ function testStringTranslation() {
       'langcode' => 'en',
       'translation' => 'untranslated',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $textarea = current($this->xpath('//textarea'));
     $lid = (string) $textarea[0]['name'];
     $edit = array(
       $lid => $translation_to_en,
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Save translations'));
+    $this->drupalPost('admin/config/regional/translate', $edit, t('Save translations'));
     $search = array(
       'string' => $name,
       'langcode' => 'en',
       'translation' => 'translated',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $this->assertRaw($translation_to_en, 'English translation properly saved.');
 
     // Reset the tag cache on the tester side in order to pick up the call to
@@ -164,7 +164,7 @@ function testStringTranslation() {
       'langcode' => 'en',
       'translation' => 'untranslated',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $this->assertText(t('No strings available.'), 'String is translated.');
 
     $this->drupalLogout();
@@ -190,14 +190,14 @@ function testStringTranslation() {
       'langcode' => 'en',
       'translation' => 'translated',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     // Assume this is the only result, given the random name.
     $textarea = current($this->xpath('//textarea'));
     $lid = (string) $textarea[0]['name'];
     $edit = array(
       $lid => '',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Save translations'));
+    $this->drupalPost('admin/config/regional/translate', $edit, t('Save translations'));
     $this->assertRaw($name, 'The strings have been saved.');
     $this->drupalLogin($translate_user);
     $search = array(
@@ -205,7 +205,7 @@ function testStringTranslation() {
       'langcode' => 'en',
       'translation' => 'untranslated',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $this->assertNoText(t('No strings available.'), 'The translation has been removed');
   }
 
@@ -248,14 +248,14 @@ function testJavaScriptTranslation() {
       'langcode' => $langcode,
       'translation' => 'all',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
 
     $textarea = current($this->xpath('//textarea'));
     $lid = (string) $textarea[0]['name'];
     $edit = array(
       $lid => $this->randomName(),
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Save translations'));
+    $this->drupalPost('admin/config/regional/translate', $edit, t('Save translations'));
 
     // Trigger JavaScript translation parsing and building.
     _locale_rebuild_js($langcode);
@@ -311,7 +311,7 @@ function testStringValidation() {
       'langcode' => $langcode,
       'translation' => 'all',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     // Find the edit path.
 
     $textarea = current($this->xpath('//textarea'));
@@ -320,7 +320,7 @@ function testStringValidation() {
       $edit = array(
         $lid => $translation,
       );
-      $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Save translations'));
+      $this->drupalPost('admin/config/regional/translate', $edit, t('Save translations'));
       // Check for a form error on the textarea.
       $form_class = $this->xpath('//form[@id="locale-translate-edit-form"]//textarea/@class');
       $this->assertNotIdentical(FALSE, strpos($form_class[0], 'error'), 'The string was rejected as unsafe.');
@@ -380,7 +380,7 @@ function testStringSearch() {
       'langcode' => $langcode,
       'translation' => 'all',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     // assertText() seems to remove the input field where $name always could be
     // found, so this is not a false assert. See how assertNoText succeeds
     // later.
@@ -393,7 +393,7 @@ function testStringSearch() {
       'langcode' => $langcode,
       'translation' => 'translated',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $this->assertText(t('No strings available.'), "Search didn't find the string.");
 
     // Ensure untranslated string appears if searching on 'only untranslated
@@ -403,7 +403,7 @@ function testStringSearch() {
       'langcode' => $langcode,
       'translation' => 'untranslated',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $this->assertNoText(t('No strings available.'), 'Search found the string.');
 
     // Add translation.
@@ -414,7 +414,7 @@ function testStringSearch() {
     $edit = array(
       $lid => $translation,
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Save translations'));
+    $this->drupalPost('admin/config/regional/translate', $edit, t('Save translations'));
 
     // Ensure translated string does appear if searching on 'only
     // translated strings'.
@@ -423,7 +423,7 @@ function testStringSearch() {
       'langcode' => $langcode,
       'translation' => 'translated',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $this->assertNoText(t('No strings available.'), 'Search found the translation.');
 
     // Ensure translated source string doesn't appear if searching on 'only
@@ -433,7 +433,7 @@ function testStringSearch() {
       'langcode' => $langcode,
       'translation' => 'untranslated',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $this->assertText(t('No strings available.'), "Search didn't find the source string.");
 
     // Ensure translated string doesn't appear if searching on 'only
@@ -443,7 +443,7 @@ function testStringSearch() {
       'langcode' => $langcode,
       'translation' => 'untranslated',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $this->assertText(t('No strings available.'), "Search didn't find the translation.");
 
     // Ensure translated string does appear if searching on the custom language.
@@ -452,7 +452,7 @@ function testStringSearch() {
       'langcode' => $langcode,
       'translation' => 'all',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $this->assertNoText(t('No strings available.'), 'Search found the translation.');
 
     // Ensure translated string doesn't appear if searching in System (English).
@@ -461,7 +461,7 @@ function testStringSearch() {
       'langcode' => 'yy',
       'translation' => 'all',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $this->assertText(t('No strings available.'), "Search didn't find the translation.");
 
     // Search for a string that isn't in the system.
@@ -471,7 +471,7 @@ function testStringSearch() {
       'langcode' => $langcode,
       'translation' => 'all',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $this->assertText(t('No strings available.'), "Search didn't find the invalid string.");
   }
 
@@ -508,7 +508,7 @@ function testUICustomizedStrings(){
       'translation' => 'translated',
       'customized' => '0',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
 
     $source = $this->assertText($translation->getString(), 'Translation is found in search result.');
 
@@ -518,7 +518,7 @@ function testUICustomizedStrings(){
     $edit = array(
       $lid => $translation->getString(),
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Save translations'));
+    $this->drupalPost('admin/config/regional/translate', $edit, t('Save translations'));
 
     // Ensure unchanged translation string does appear if searching non-customized translation.
     $search = array(
@@ -527,7 +527,7 @@ function testUICustomizedStrings(){
       'translation' => 'translated',
       'customized' => '0',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $source = $this->assertText($string->getString(), 'Translation is not marked as customized.');
 
     // Submit the translations with a new translation.
@@ -536,7 +536,7 @@ function testUICustomizedStrings(){
     $edit = array(
       $lid => $this->randomName(100),
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Save translations'));
+    $this->drupalPost('admin/config/regional/translate', $edit, t('Save translations'));
 
     // Ensure changed translation string does appear if searching customized translation.
     $search = array(
@@ -545,7 +545,7 @@ function testUICustomizedStrings(){
       'translation' => 'translated',
       'customized' => '1',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $this->assertText($string->getString(), "Translation is marked as customized.");
   }
 }
diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallTest.php
index 1cfb538..cf36a06 100644
--- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallTest.php
+++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUninstallTest.php
@@ -78,7 +78,7 @@ function testUninstallProcess() {
     // Build the JavaScript translation file for French.
     $user = $this->drupalCreateUser(array('translate interface', 'access administration pages'));
     $this->drupalLogin($user);
-    $this->drupalGet('admin/config/regional/translate/translate');
+    $this->drupalGet('admin/config/regional/translate');
     // Get any of the javascript strings to translate.
     $js_strings = $this->container->get('locale.storage')->getStrings(array('type' => 'javascript'));
     $string = reset($js_strings);
diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateTest.php
index 9f55abc..9c24d4f 100644
--- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateTest.php
+++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleUpdateTest.php
@@ -452,7 +452,7 @@ function testEnableCustomLanguage() {
       'langcode' => $langcode,
       'translation' => 'translated',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $this->assertNoText(t('No strings available.'), 'String successfully imported.');
 
     // Ensure the multiline string was imported.
@@ -461,7 +461,7 @@ function testEnableCustomLanguage() {
       'langcode' => $langcode,
       'translation' => 'all',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $this->assertText('Multiline translation string to make sure that import works with it.', 'String successfully imported.');
 
     // Ensure 'Allowed HTML source string' was imported but the translation for
@@ -472,7 +472,7 @@ function testEnableCustomLanguage() {
       'langcode' => $langcode,
       'translation' => 'all',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     $this->assertText('Allowed HTML source string', 'String successfully imported.');
     $this->assertNoText('Another allowed HTML source string', 'String with disallowed translation not imported.');
   }
diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module
index 9819f18..601bd83 100644
--- a/core/modules/locale/locale.module
+++ b/core/modules/locale/locale.module
@@ -173,9 +173,7 @@ function locale_menu() {
   $items['admin/config/regional/translate'] = array(
     'title' => 'User interface translation',
     'description' => 'Translate the built-in user interface.',
-    'page callback' => 'locale_translate_page',
-    'access arguments' => array('translate interface'),
-    'file' => 'locale.pages.inc',
+    'route_name' => 'locale_translate_page',
     'weight' => -5,
   );
   $items['admin/config/regional/translate/translate'] = array(
@@ -741,7 +739,7 @@ function locale_form_language_admin_overview_form_alter(&$form, &$form_state) {
             '@total' => $total_strings,
             '@ratio' => $stats[$langcode]['ratio'],
           )),
-          'admin/config/regional/translate/translate',
+          'admin/config/regional/translate',
           array('query' => array('langcode' => $langcode))
         ),
       );
diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc
index 15fefb6..9df236b 100644
--- a/core/modules/locale/locale.pages.inc
+++ b/core/modules/locale/locale.pages.inc
@@ -12,460 +12,27 @@
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
 /**
- * Page callback: Shows the string search screen.
+ * Page callback: Checks for translation updates and displays the status.
  *
- * @see locale_menu()
- */
-function locale_translate_page() {
-  return array(
-    'filter' => drupal_get_form('locale_translate_filter_form'),
-    'form' => drupal_get_form('locale_translate_edit_form'),
-  );
-}
-
-/**
- * Builds a string search query and returns an array of string objects.
- *
- * @return array
- *   Array of Drupal\locale\TranslationString objects.
- */
-function locale_translate_filter_load_strings() {
-  $filter_values = locale_translate_filter_values();
-
-  // Language is sanitized to be one of the possible options in
-  // locale_translate_filter_values().
-  $conditions = array('language' => $filter_values['langcode']);
-  $options = array('pager limit' => 30, 'translated' => TRUE, 'untranslated' => TRUE);
-
-  // Add translation status conditions and options.
-  switch ($filter_values['translation']) {
-    case 'translated':
-      $conditions['translated'] = TRUE;
-      if ($filter_values['customized'] != 'all') {
-        $conditions['customized'] = $filter_values['customized'];
-      }
-      break;
-
-    case 'untranslated':
-      $conditions['translated'] = FALSE;
-      break;
-
-  }
-
-  if (!empty($filter_values['string'])) {
-    $options['filters']['source'] = $filter_values['string'];
-    if ($options['translated']) {
-      $options['filters']['translation'] = $filter_values['string'];
-    }
-  }
-
-  return Drupal::service('locale.storage')->getTranslations($conditions, $options);
-}
-
-/**
- * Build array out of search criteria specified in request variables.
- */
-function locale_translate_filter_values() {
-  $filter_values = &drupal_static(__FUNCTION__);
-  if (!isset($filter_values)) {
-    $filter_values = array();
-    $filters = locale_translate_filters();
-    foreach ($filters as $key => $filter) {
-      $filter_values[$key] = $filter['default'];
-      // Let the filter defaults be overwritten by parameters in the URL.
-      if (isset($_GET[$key])) {
-        // Only allow this value if it was among the options, or
-        // if there were no fixed options to filter for.
-        if (!isset($filter['options']) || isset($filter['options'][$_GET[$key]])) {
-          $filter_values[$key] = $_GET[$key];
-        }
-      }
-      elseif (isset($_SESSION['locale_translate_filter'][$key])) {
-        // Only allow this value if it was among the options, or
-        // if there were no fixed options to filter for.
-        if (!isset($filter['options']) || isset($filter['options'][$_SESSION['locale_translate_filter'][$key]])) {
-          $filter_values[$key] = $_SESSION['locale_translate_filter'][$key];
-        }
-      }
-    }
-  }
-  return $filter_values;
-}
-
-/**
- * List locale translation filters that can be applied.
- */
-function locale_translate_filters() {
-  $filters = array();
-
-  // Get all languages, except English.
-  drupal_static_reset('language_list');
-  $languages = language_list();
-  $language_options = array();
-  foreach ($languages as $langcode => $language) {
-    if ($langcode != 'en' || locale_translate_english()) {
-      $language_options[$langcode] = $language->name;
-    }
-  }
-
-  // Pick the current interface language code for the filter.
-  $default_langcode = language(Language::TYPE_INTERFACE)->id;
-  if (!isset($language_options[$default_langcode])) {
-    $available_langcodes = array_keys($language_options);
-    $default_langcode = array_shift($available_langcodes);
-  }
-
-  $filters['string'] = array(
-    'title' => t('String contains'),
-    'description' => t('Leave blank to show all strings. The search is case sensitive.'),
-    'default' => '',
-  );
-
-  $filters['langcode'] = array(
-    'title' => t('Translation language'),
-    'options' => $language_options,
-    'default' => $default_langcode,
-  );
-
-  $filters['translation'] = array(
-    'title' => t('Search in'),
-    'options' => array(
-      'all' => t('Both translated and untranslated strings'),
-      'translated' => t('Only translated strings'),
-      'untranslated' => t('Only untranslated strings'),
-    ),
-    'default' => 'all',
-  );
-
-  $filters['customized'] = array(
-    'title' => t('Translation type'),
-    'options' => array(
-      'all' => t('All'),
-      LOCALE_NOT_CUSTOMIZED => t('Non-customized translation'),
-      LOCALE_CUSTOMIZED => t('Customized translation'),
-    ),
-    'states' => array(
-      'visible' => array(
-        ':input[name=translation]' => array('value' => 'translated'),
-      ),
-    ),
-    'default' => 'all',
-  );
-
-  return $filters;
-}
-
-/**
- * Return form for locale translation filters.
- *
- * @ingroup forms
- */
-function locale_translate_filter_form($form, &$form_state) {
-  $filters = locale_translate_filters();
-  $filter_values = locale_translate_filter_values();
-
-  $form['#attached']['css'] = array(
-    drupal_get_path('module', 'locale') . '/css/locale.admin.css',
-  );
-
-  $form['filters'] = array(
-    '#type' => 'details',
-    '#title' => t('Filter translatable strings'),
-    '#collapsed' => FALSE,
-  );
-  foreach ($filters as $key => $filter) {
-    // Special case for 'string' filter.
-    if ($key == 'string') {
-      $form['filters']['status']['string'] = array(
-        '#type' => 'search',
-        '#title' => $filter['title'],
-        '#description' => $filter['description'],
-        '#default_value' => $filter_values[$key],
-      );
-    }
-    else {
-      $empty_option = isset($filter['options'][$filter['default']]) ? $filter['options'][$filter['default']] : '- None -';
-      $form['filters']['status'][$key] = array(
-        '#title' => $filter['title'],
-        '#type' => 'select',
-        '#empty_value' => $filter['default'],
-        '#empty_option' => $empty_option,
-        '#size' => 0,
-        '#options' => $filter['options'],
-        '#default_value' => $filter_values[$key],
-      );
-      if (isset($filter['states'])) {
-        $form['filters']['status'][$key]['#states'] = $filter['states'];
-      }
-    }
-  }
-
-  $form['filters']['actions'] = array(
-    '#type' => 'actions',
-    '#attributes' => array('class' => array('container-inline')),
-  );
-  $form['filters']['actions']['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Filter'),
-  );
-  if (!empty($_SESSION['locale_translate_filter'])) {
-    $form['filters']['actions']['reset'] = array(
-      '#type' => 'submit',
-      '#value' => t('Reset'),
-    );
-  }
-
-  return $form;
-}
-
-/**
- * Process result from locale translation filter form.
- */
-function locale_translate_filter_form_submit($form, &$form_state) {
-  $op = $form_state['values']['op'];
-  $filters = locale_translate_filters();
-  switch ($op) {
-    case t('Filter'):
-      foreach ($filters as $name => $filter) {
-        if (isset($form_state['values'][$name])) {
-          $_SESSION['locale_translate_filter'][$name] = $form_state['values'][$name];
-        }
-      }
-      break;
-
-    case t('Reset'):
-      $_SESSION['locale_translate_filter'] = array();
-      break;
-
-  }
-
-  $form_state['redirect'] = 'admin/config/regional/translate/translate';
-}
-
-/**
- * Form constructor for the string editing form.
+ * Manually checks the translation status without the use of cron.
  *
  * @see locale_menu()
- * @see locale_translate_edit_form_validate()
- * @see locale_translate_edit_form_submit()
- *
- * @ingroup forms
  */
-function locale_translate_edit_form($form, &$form_state) {
-  $filter_values = locale_translate_filter_values();
-  $langcode = $filter_values['langcode'];
-
-  drupal_static_reset('language_list');
-  $languages = language_list();
-
-  $langname = isset($langcode) ? $languages[$langcode]->name : "- None -";
-
-  $path = drupal_get_path('module', 'locale');
-  $form['#attached']['css'] = array(
-    $path . '/css/locale.admin.css',
-  );
-  $form['#attached']['library'][] = array('locale', 'drupal.locale.admin');
-
-  $form['langcode'] = array(
-    '#type' => 'value',
-    '#value' => $filter_values['langcode'],
-  );
-
-  $form['strings'] = array(
-    '#type' => 'item',
-    '#tree' => TRUE,
-    '#language' => $langname,
-    '#theme' => 'locale_translate_edit_form_strings',
-  );
-
-  if (isset($langcode)) {
-    $strings = locale_translate_filter_load_strings();
-
-    $plural_formulas = Drupal::state()->get('locale.translation.plurals') ?: array();
-
-    foreach ($strings as $string) {
-      // Cast into source string, will do for our purposes.
-      $source = new SourceString($string);
-      // Split source to work with plural values.
-      $source_array = $source->getPlurals();
-      $translation_array = $string->getPlurals();
-      if (count($source_array) == 1) {
-        // Add original string value and mark as non-plural.
-        $form['strings'][$string->lid]['plural'] = array(
-          '#type' => 'value',
-          '#value' => 0,
-        );
-        $form['strings'][$string->lid]['original'] = array(
-          '#type' => 'item',
-          '#title' => t('Source string (@language)', array('@language' => t('Built-in English'))),
-          '#title_display' => 'invisible',
-          '#markup' => '<span lang="en">' . check_plain($source_array[0]) . '</span>',
-        );
-      }
-      else {
-        // Add original string value and mark as plural.
-        $form['strings'][$string->lid]['plural'] = array(
-          '#type' => 'value',
-          '#value' => 1,
-        );
-        $form['strings'][$string->lid]['original_singular'] = array(
-          '#type' => 'item',
-          '#title' => t('Singular form'),
-          '#markup' => '<span lang="en">' . check_plain($source_array[0]) . '</span>',
-          '#prefix' => '<span class="visually-hidden">' . t('Source string (@language)', array('@language' => t('Built-in English'))) . '</span>',
-        );
-        $form['strings'][$string->lid]['original_plural'] = array(
-          '#type' => 'item',
-          '#title' => t('Plural form'),
-          '#markup' => '<span lang="en">' . check_plain($source_array[1]) . '</span>',
-        );
-      }
-      if (!empty($string->context)) {
-        $form['strings'][$string->lid]['context'] = array(
-          '#type' => 'value',
-          '#value' => '<span lang="en">' . check_plain($string->context) . '</span>',
-        );
-      }
-      // Approximate the number of rows to use in the default textarea.
-      $rows = min(ceil(str_word_count($source_array[0]) / 12), 10);
-      if (empty($form['strings'][$string->lid]['plural']['#value'])) {
-        $form['strings'][$string->lid]['translations'][0] = array(
-          '#type' => 'textarea',
-          '#title' => t('Translated string (@language)', array('@language' => $langname)),
-          '#title_display' => 'invisible',
-          '#rows' => $rows,
-          '#default_value' => $translation_array[0],
-          '#attributes' => array('lang' => $langcode),
-        );
-      }
-      else {
-        // Dealing with plural strings.
-        if (isset($plural_formulas[$langcode]['plurals']) && $plural_formulas[$langcode]['plurals'] > 2) {
-          // Add a textarea for each plural variant.
-          for ($i = 0; $i < $plural_formulas[$langcode]['plurals']; $i++) {
-            $form['strings'][$string->lid]['translations'][$i] = array(
-              '#type' => 'textarea',
-              '#title' => ($i == 0 ? t('Singular form') : format_plural($i, 'First plural form', '@count. plural form')),
-              '#rows' => $rows,
-              '#default_value' => isset($translation_array[$i]) ? $translation_array[$i] : '',
-              '#attributes' => array('lang' => $langcode),
-              '#prefix' => $i == 0 ? ('<span class="visually-hidden">' . t('Translated string (@language)',  array('@language' => $langname)) . '</span>') : '',
-            );
-          }
-        }
-        else {
-          // Fallback for unknown number of plurals.
-          $form['strings'][$string->lid]['translations'][0] = array(
-            '#type' => 'textarea',
-            '#title' => t('Singular form'),
-            '#rows' => $rows,
-            '#default_value' => $translation_array[0],
-            '#attributes' => array('lang' => $langcode),
-            '#prefix' => '<span class="visually-hidden">' . t('Translated string (@language)',  array('@language' => $langname)) . '</span>',
-          );
-          $form['strings'][$string->lid]['translations'][1] = array(
-            '#type' => 'textarea',
-            '#title' => t('Plural form'),
-            '#rows' => $rows,
-            '#default_value' => isset($translation_array[1]) ? $translation_array[1] : '',
-            '#attributes' => array('lang' => $langcode),
-          );
-        }
-      }
-    }
-    if (count(element_children($form['strings']))) {
-      $form['actions'] = array('#type' => 'actions');
-      $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save translations'));
-    }
-  }
-  return $form;
-}
-
-/**
- * Form validation handler for locale_translate_edit_form().
- *
- * @see locale_translate_edit_form_submit()
- */
-function locale_translate_edit_form_validate($form, &$form_state) {
-  $langcode = $form_state['values']['langcode'];
-  foreach ($form_state['values']['strings'] as $lid => $translations) {
-    foreach ($translations['translations'] as $key => $value) {
-      if (!locale_string_is_safe($value)) {
-        form_set_error("strings][$lid][translations][$key", t('The submitted string contains disallowed HTML: %string', array('%string' => $value)));
-        form_set_error("translations][$langcode][$key", t('The submitted string contains disallowed HTML: %string', array('%string' => $value)));
-        watchdog('locale', 'Attempted submission of a translation string with disallowed HTML: %string', array('%string' => $value), WATCHDOG_WARNING);
-      }
-    }
-  }
-}
-
-/**
- * Form submission handler for locale_translate_edit_form().
- *
- * @see locale_translate_edit_form_validate()
- */
-function locale_translate_edit_form_submit($form, &$form_state) {
-  $langcode = $form_state['values']['langcode'];
-  $updated = array();
-
-  // Preload all translations for strings in the form.
-  $lids = array_keys($form_state['values']['strings']);
-  $existing_translation_objects = array();
-  foreach (Drupal::service('locale.storage')->getTranslations(array('lid' => $lids, 'language' => $langcode, 'translated' => TRUE)) as $existing_translation_object) {
-    $existing_translation_objects[$existing_translation_object->lid] = $existing_translation_object;
-  }
-
-  foreach ($form_state['values']['strings'] as $lid => $new_translation) {
-    $existing_translation = isset($existing_translation_objects[$lid]);
-
-    // Plural translations are saved in a delimited string. To be able to
-    // compare the new strings with the existing strings a string in the same format is created.
-    $new_translation_string_delimited = implode(LOCALE_PLURAL_DELIMITER, $new_translation['translations']);
-
-    // Generate an imploded string without delimiter, to be able to run
-    // empty() on it.
-    $new_translation_string = implode('', $new_translation['translations']);
-
-    $is_changed = FALSE;
-
-    if ($existing_translation && $existing_translation_objects[$lid]->translation != $new_translation_string_delimited) {
-      // If there is an existing translation in the DB and the new translation
-      // is not the same as the existing one.
-      $is_changed = TRUE;
-    }
-    elseif (!$existing_translation && !empty($new_translation_string)) {
-      // Newly entered translation.
-      $is_changed = TRUE;
-    }
-
-    if ($is_changed) {
-      // Only update or insert if we have a value to use.
-      $target = isset($existing_translation_objects[$lid]) ? $existing_translation_objects[$lid] : Drupal::service('locale.storage')->createTranslation(array('lid' => $lid, 'language' => $langcode));
-      $target->setPlurals($new_translation['translations'])
-        ->setCustomized()
-        ->save();
-      $updated[] = $target->getId();
-    }
-    if (empty($new_translation_string) && isset($existing_translation_objects[$lid])) {
-      // Empty new translation entered: remove existing entry from database.
-      $existing_translation_objects[$lid]->delete();
-      $updated[] = $lid;
-    }
-  }
-
-  drupal_set_message(t('The strings have been saved.'));
+function locale_translation_manual_status() {
+  module_load_include('compare.inc', 'locale');
 
-  // Keep the user on the current pager page.
-  if (isset($_GET['page'])) {
-    $form_state['redirect'] = array('admin/config/regional/translate', array('query' => array('page' => $_GET['page'])));
-  }
+  // Check the translation status of all translatable projects in all languages.
+  // First we clear the cached list of projects. Although not strictly
+  // necessary, this is helpful in case the project list is out of sync.
+  locale_translation_flush_projects();
+  locale_translation_check_projects();
 
-  if ($updated) {
-    // Clear cache and refresh configuration and JavaScript translations.
-    _locale_refresh_translations(array($langcode), $updated);
-    _locale_refresh_configuration(array($langcode), $updated);
+  // Execute a batch if required. A batch is only used when remote files
+  // are checked.
+  if (batch_get()) {
+    return batch_process('admin/reports/translations');
   }
-
+  return new RedirectResponse(url('admin/reports/translations', array('absolute' => TRUE)));
 }
 
 /**
diff --git a/core/modules/locale/locale.routing.yml b/core/modules/locale/locale.routing.yml
index 2047229..4b7d50e 100644
--- a/core/modules/locale/locale.routing.yml
+++ b/core/modules/locale/locale.routing.yml
@@ -11,3 +11,10 @@ locale_check_translation:
     _controller: 'Drupal\locale\Controller\LocaleController::checkTranslation'
   requirements:
     _permission: 'translate interface'
+
+locale_translate_page:
+  pattern: '/admin/config/regional/translate'
+  defaults:
+    _content: 'Drupal\locale\Controller\LocaleController::translatePage'
+  requirements:
+    _permission: 'translate interface'
diff --git a/core/modules/toolbar/lib/Drupal/toolbar/Tests/ToolbarMenuTranslationTest.php b/core/modules/toolbar/lib/Drupal/toolbar/Tests/ToolbarMenuTranslationTest.php
index 02c01d4..949d565 100644
--- a/core/modules/toolbar/lib/Drupal/toolbar/Tests/ToolbarMenuTranslationTest.php
+++ b/core/modules/toolbar/lib/Drupal/toolbar/Tests/ToolbarMenuTranslationTest.php
@@ -59,7 +59,7 @@ function testToolbarClasses() {
       'langcode' => $langcode,
       'translation' => 'untranslated',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     // Make sure will be able to translate the menu item.
     $this->assertNoText('No strings available.', 'Search found the menu item as untranslated.');
 
@@ -74,7 +74,7 @@ function testToolbarClasses() {
     $edit = array(
       $lid => $menu_item_translated,
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Save translations'));
+    $this->drupalPost('admin/config/regional/translate', $edit, t('Save translations'));
 
     // Search for the translated menu item.
     $search = array(
@@ -82,7 +82,7 @@ function testToolbarClasses() {
       'langcode' => $langcode,
       'translation' => 'translated',
     );
-    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $this->drupalPost('admin/config/regional/translate', $search, t('Filter'));
     // Make sure the menu item string was translated.
     $this->assertText($menu_item_translated, 'Search found the menu item as translated: ' . $menu_item_translated . '.');
 
