diff --git a/core/lib/Drupal/Core/Menu/MenuLinkDefault.php b/core/lib/Drupal/Core/Menu/MenuLinkDefault.php
index c224142..c17fe79 100644
--- a/core/lib/Drupal/Core/Menu/MenuLinkDefault.php
+++ b/core/lib/Drupal/Core/Menu/MenuLinkDefault.php
@@ -67,26 +67,14 @@ public static function create(ContainerInterface $container, array $configuratio
    * {@inheritdoc}
    */
   public function getTitle() {
-    // Subclasses may pull in the request or specific attributes as parameters.
-    $options = array();
-    if (!empty($this->pluginDefinition['title_context'])) {
-      $options['context'] = $this->pluginDefinition['title_context'];
-    }
-    $args = array();
-    if (isset($this->pluginDefinition['title_arguments']) && $title_arguments = $this->pluginDefinition['title_arguments']) {
-      $args = (array) $title_arguments;
-    }
-    return $this->t($this->pluginDefinition['title'], $args, $options);
+    return (string) $this->pluginDefinition['title'];
   }
 
   /**
    * {@inheritdoc}
    */
   public function getDescription() {
-    if ($this->pluginDefinition['description']) {
-      return $this->t($this->pluginDefinition['description']);
-    }
-    return '';
+    return (string) $this->pluginDefinition['description'];
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Menu/MenuLinkManager.php b/core/lib/Drupal/Core/Menu/MenuLinkManager.php
index b4c7760..44eeeb3 100644
--- a/core/lib/Drupal/Core/Menu/MenuLinkManager.php
+++ b/core/lib/Drupal/Core/Menu/MenuLinkManager.php
@@ -41,14 +41,9 @@ class MenuLinkManager implements MenuLinkManagerInterface {
     'route_parameters' => array(),
     // The external URL if this link has one (required if route_name is empty).
     'url' => '',
-    // The static title for the menu link. You can specify placeholders like on
-    // any translatable string and the values in title_arguments.
+    // The static title for the menu link. If this came from a YAML definition
+    // or other safe source this may be a TranslationWrapper object.
     'title' => '',
-    // The values for the menu link placeholders.
-    'title_arguments' => array(),
-    // A context for the title string.
-    // @see \Drupal\Core\StringTranslation\TranslationInterface::translate()
-    'title_context' => '',
     // The description.
     'description' => '',
     // The plugin ID of the parent link (or NULL for a top-level link).
@@ -148,8 +143,10 @@ protected function processDefinition(array &$definition, $plugin_id) {
    */
   protected function getDiscovery() {
     if (!isset($this->discovery)) {
-      $this->discovery = new YamlDiscovery('links.menu', $this->moduleHandler->getModuleDirectories());
-      $this->discovery = new ContainerDerivativeDiscoveryDecorator($this->discovery);
+      $yaml_discovery = new YamlDiscovery('links.menu', $this->moduleHandler->getModuleDirectories());
+      $yaml_discovery->addTranslatableProperty('title', 'title_context');
+      $yaml_discovery->addTranslatableProperty('description', 'description_context');
+      $this->discovery = new ContainerDerivativeDiscoveryDecorator($yaml_discovery);
     }
     return $this->discovery;
   }
diff --git a/core/lib/Drupal/Core/Menu/MenuTreeStorage.php b/core/lib/Drupal/Core/Menu/MenuTreeStorage.php
index 659d6be..7f2dc0c 100644
--- a/core/lib/Drupal/Core/Menu/MenuTreeStorage.php
+++ b/core/lib/Drupal/Core/Menu/MenuTreeStorage.php
@@ -95,8 +95,6 @@ class MenuTreeStorage implements MenuTreeStorageInterface {
     'route_parameters',
     'url',
     'title',
-    'title_arguments',
-    'title_context',
     'description',
     'parent',
     'weight',
@@ -359,6 +357,16 @@ protected function preSave(array &$link, array $original) {
         $link[$name] = $default;
       }
     }
+
+    if (isset($link['title'])) {
+      $link['title_serialized'] = $link['title'];
+      unset($link['title']);
+    }
+    if (isset($link['description'])) {
+      $link['description_serialized'] = $link['description'];
+      unset($link['description']);
+    }
+
     $fields = array_intersect_key($link, $schema_fields);
     // Sort the route parameters so that the query string will be the same.
     asort($fields['route_parameters']);
@@ -367,7 +375,9 @@ protected function preSave(array &$link, array $original) {
     $fields['route_param_key'] = $fields['route_parameters'] ? UrlHelper::buildQuery($fields['route_parameters']) : '';
 
     foreach ($this->serializedFields() as $name) {
-      $fields[$name] = serialize($fields[$name]);
+      if (isset($fields[$name])) {
+        $fields[$name] = serialize($fields[$name]);
+      }
     }
     $this->setParents($fields, $parent, $original);
 
@@ -619,8 +629,17 @@ protected function updateParentalStatus(array $link) {
    */
   protected function prepareLink(array $link, $intersect = FALSE) {
     foreach ($this->serializedFields() as $name) {
-      $link[$name] = unserialize($link[$name]);
+      if (isset($link[$name])) {
+        $link[$name] = unserialize($link[$name]);
+      }
+    }
+    if (isset($link['title_serialized'])) {
+      $link['title'] = $link['title_serialized'];
+    }
+    if (isset($link['description_serialized'])) {
+      $link['description'] = $link['description_serialized'];
     }
+
     if ($intersect) {
       $link = array_intersect_key($link, array_flip($this->definitionFields()));
     }
@@ -633,7 +652,7 @@ protected function prepareLink(array $link, $intersect = FALSE) {
    */
   public function loadByProperties(array $properties) {
     $query = $this->connection->select($this->table, $this->options);
-    $query->fields($this->table, $this->definitionFields());
+    $query->fields($this->table, $this->definitionFields(TRUE));
     foreach ($properties as $name => $value) {
       if (!in_array($name, $this->definitionFields(), TRUE)) {
         $fields = implode(', ', $this->definitionFields());
@@ -660,7 +679,7 @@ public function loadByRoute($route_name, array $route_parameters = array(), $men
     //   in place of system path. https://www.drupal.org/node/2302139
     $param_key = $route_parameters ? UrlHelper::buildQuery($route_parameters) : '';
     $query = $this->connection->select($this->table, $this->options);
-    $query->fields($this->table, $this->definitionFields());
+    $query->fields($this->table, $this->definitionFields(TRUE));
     $query->condition('route_name', $route_name);
     $query->condition('route_param_key', $param_key);
     if ($menu_name) {
@@ -685,7 +704,7 @@ public function loadMultiple(array $ids) {
 
     if ($missing_ids) {
       $query = $this->connection->select($this->table, $this->options);
-      $query->fields($this->table, $this->definitionFields());
+      $query->fields($this->table, $this->definitionFields(TRUE));
       $query->condition('id', $missing_ids, 'IN');
       $loaded = $this->safeExecuteSelect($query)->fetchAllAssoc('id', \PDO::FETCH_ASSOC);
       foreach ($loaded as $id => $link) {
@@ -736,7 +755,9 @@ protected function loadFullMultiple(array $ids) {
     $loaded = $this->safeExecuteSelect($query)->fetchAllAssoc('id', \PDO::FETCH_ASSOC);
     foreach ($loaded as &$link) {
       foreach ($this->serializedFields() as $name) {
-        $link[$name] = unserialize($link[$name]);
+        if (isset($link[$name])) {
+          $link[$name] = unserialize($link[$name]);
+        }
       }
     }
     return $loaded;
@@ -926,16 +947,20 @@ protected function loadLinks($menu_name, MenuTreeParameters $parameters) {
     // Add custom query conditions, if any were passed.
     if (!empty($parameters->conditions)) {
       // Only allow conditions that are testing definition fields.
-      $parameters->conditions = array_intersect_key($parameters->conditions, array_flip($this->definitionFields()));
+      $parameters->conditions = array_intersect_key($parameters->conditions, array_flip($this->definitionFields(TRUE)));
+      $serialized = array_flip($this->serializedFields());
       foreach ($parameters->conditions as $column => $value) {
-        if (!is_array($value)) {
-          $query->condition($column, $value);
-        }
-        else {
+        if (is_array($value)) {
           $operator = $value[1];
           $value = $value[0];
-          $query->condition($column, $value, $operator);
         }
+        else {
+          $operator = '=';
+        }
+        if (isset($serialized[$column])) {
+          $value = serialize($value);
+        }
+        $query->condition($column, $value, $operator);
       }
     }
 
@@ -1177,8 +1202,18 @@ protected function serializedFields() {
    * @return array
    *   The list of the subset of fields that are part of the plugin definition.
    */
-  protected function definitionFields() {
-    return $this->definitionFields;
+  protected function definitionFields($db_fields = FALSE) {
+    $fields = $this->definitionFields;
+
+    if ($db_fields) {
+      $fields = array_flip($fields);
+      unset($fields['title'], $fields['description']);
+      $fields = array_flip($fields);
+      $fields[] = 'title_serialized';
+      $fields[] = 'description_serialized';
+    }
+
+    return $fields;
   }
 
   /**
@@ -1241,31 +1276,19 @@ protected static function schemaDefinition() {
           'not null' => TRUE,
           'default' => '',
         ),
-        'title' => array(
-          'description' => 'The text displayed for the link.',
-          'type' => 'varchar',
-          'length' => 255,
-          'not null' => TRUE,
-          'default' => '',
-        ),
-        'title_arguments' => array(
-          'description' => 'A serialized array of arguments to be passed to t() (if this plugin uses it).',
+        'title_serialized' => array(
+          'description' => 'The title for the link. May be a serialized TranslationWrapper',
           'type' => 'blob',
           'size' => 'big',
           'not null' => FALSE,
           'serialize' => TRUE,
         ),
-        'title_context' => array(
-          'description' => 'The translation context for the link title.',
-          'type' => 'varchar',
-          'length' => 255,
-          'not null' => TRUE,
-          'default' => '',
-        ),
-        'description' => array(
+        'description_serialized' => array(
           'description' => 'The description of this link - used for admin pages and title attribute.',
-          'type' => 'text',
+          'type' => 'blob',
+          'size' => 'big',
           'not null' => FALSE,
+          'serialize' => TRUE,
         ),
         'class' => array(
           'description' => 'The class for this link plugin.',
diff --git a/core/modules/menu_link_content/src/Tests/MenuLinkContentDeriverTest.php b/core/modules/menu_link_content/src/Tests/MenuLinkContentDeriverTest.php
index c7a1ae1..7ea61d0 100644
--- a/core/modules/menu_link_content/src/Tests/MenuLinkContentDeriverTest.php
+++ b/core/modules/menu_link_content/src/Tests/MenuLinkContentDeriverTest.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\menu_link_content\Tests;
 
+use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Menu\MenuTreeParameters;
+use Drupal\Core\StringTranslation\TranslationWrapper;
 use Drupal\menu_link_content\Entity\MenuLinkContent;
 use Drupal\simpletest\KernelTestBase;
 use Symfony\Component\Routing\Route;
@@ -45,7 +47,7 @@ public function testRediscover() {
 
     // Set up a custom menu link pointing to a specific path.
     MenuLinkContent::create([
-      'title' => 'Example',
+      'title' => '<script>alert("Welcome to the discovered jungle!")</script>',
       'link' => [['uri' => 'internal:/example-path']],
       'menu_name' => 'tools',
     ])->save();
@@ -67,6 +69,10 @@ public function testRediscover() {
     /** @var \Drupal\Core\Menu\MenuLinkTreeElement $tree_element */
     $tree_element = reset($menu_tree);
     $this->assertEqual('route_name_2', $tree_element->link->getRouteName());
+    $title = (string) $tree_element->link->getTitle();
+    $this->assertFalse($title instanceof TranslationWrapper);
+    $this->assertIdentical('<script>alert("Welcome to the discovered jungle!")</script>', $title);
+    $this->assertFalse(SafeMarkup::isSafe($title));
   }
 
 }
diff --git a/core/modules/system/src/Tests/Menu/MenuTreeStorageTest.php b/core/modules/system/src/Tests/Menu/MenuTreeStorageTest.php
index 1df4050..57f1f2a 100644
--- a/core/modules/system/src/Tests/Menu/MenuTreeStorageTest.php
+++ b/core/modules/system/src/Tests/Menu/MenuTreeStorageTest.php
@@ -354,7 +354,6 @@ protected function addMenuLink($id, $parent = '', $route_name = 'test', $route_p
       'menu_name' => $menu_name,
       'route_name' => $route_name,
       'route_parameters' => $route_parameters,
-      'title_arguments' => array(),
       'title' => 'test',
       'parent' => $parent,
       'options' => array(),
diff --git a/core/modules/system/src/Tests/Update/UpdatePathTestBase.php b/core/modules/system/src/Tests/Update/UpdatePathTestBase.php
index fb365f0..7b19bf0 100644
--- a/core/modules/system/src/Tests/Update/UpdatePathTestBase.php
+++ b/core/modules/system/src/Tests/Update/UpdatePathTestBase.php
@@ -185,7 +185,14 @@ protected function runUpdates() {
       $this->fail('Missing zlib requirement for upgrade tests.');
       return FALSE;
     }
-    $this->drupalLogin($this->rootUser);
+
+    // The site might be broken at the time so logging in using the UI might
+    // not work, so we use the API itself.
+    drupal_rewrite_settings(['settings' => ['update_free_access' => (object) [
+      'value' => TRUE,
+      'required' => TRUE,
+    ]]]);
+
     $this->drupalGet($this->updateUrl);
     $this->clickLink(t('Continue'));
 
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index a0849b1..c5a5623 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -8,6 +8,7 @@
 use Drupal\Component\Utility\Crypt;
 use Drupal\Component\Utility\Environment;
 use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\StringTranslation\TranslationWrapper;
 use Drupal\Core\Url;
 use Drupal\Core\Database\Database;
 use Drupal\Core\DrupalKernel;
@@ -1085,3 +1086,83 @@ function system_schema() {
 
   return $schema;
 }
+
+/**
+ * Change two fields on the default menu link storage.
+ */
+function system_update_8001() {
+  if (db_table_exists('menu_tree')) {
+    $spec = array(
+      'description' => 'The title for the link. May be a serialized TranslationWrapper',
+      'type' => 'blob',
+      'size' => 'big',
+      'not null' => FALSE,
+      'serialize' => TRUE,
+    );
+    db_add_field('menu_tree', 'title_serialized', $spec);
+    $spec = array(
+      'description' => 'The description of this link - used for admin pages and title attribute.',
+      'type' => 'blob',
+      'size' => 'big',
+      'not null' => FALSE,
+      'serialize' => TRUE,
+    );
+    db_add_field('menu_tree', 'description_serialized', $spec);
+  }
+}
+
+/**
+ * Update the {menu_tree} table to convert the serialized data.
+ */
+function system_update_8002(&$sandbox = NULL) {
+  $database = \Drupal::database();
+  if (!isset($sandbox['progress'])) {
+    $sandbox['progress'] = 0;
+    // Start at 1 to skip the anonymous user.
+    $sandbox['current'] = 1;
+    $sandbox['max'] = $database->query('SELECT COUNT(*) FROM {menu_tree}')->fetchField();
+  }
+
+  $menu_links = $database->queryRange('SELECT * FROM {menu_tree} ORDER BY mlid ASC', $sandbox['current'], $sandbox['current'] + 10)->fetchAllAssoc('mlid');
+
+  $return = [];
+  foreach ($menu_links as $menu_link) {
+    $menu_link = (array) $menu_link;
+    $menu_link['title_arguments'] = unserialize($menu_link['title_arguments']);
+    $menu_link['title_context'] = unserialize($menu_link['title_context']);
+
+    // Convert static defined plugins to use t().
+    if ($menu_link['class'] === 'Drupal\Core\Menu\MenuLinkDefault') {
+      $menu_link['title_serialized'] = serialize(new TranslationWrapper($menu_link['title'], $menu_link['title_arguments'], ['context' => $menu_link['title_context']]));
+    }
+    else {
+      $menu_link['title_serialized'] = serialize($menu_link['title']);
+    }
+    $menu_link['description_serialized'] = serialize($menu_link['description']);
+
+    unset($menu_link['title_arguments']);
+    unset($menu_link['title_context']);
+
+    $database->update('menu_tree')
+      ->fields($menu_link)
+      ->condition('mlid', $menu_link['mlid'])
+      ->execute();
+
+    $sandbox['progress']++;
+    $sandbox['current']++;
+  }
+
+  $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['current'] / $sandbox['max']);
+
+  return t('Menu links converted');
+}
+
+/**
+ * Drop unnecessary fields from {menu_tree}.
+ */
+function system_update_8003() {
+  db_drop_field('menu_tree', 'title_arguments');
+  db_drop_field('menu_tree', 'title_context');
+  db_drop_field('menu_tree', 'title');
+  db_drop_field('menu_tree', 'description');
+}
diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml
index 9d806bb..a656ab3 100644
--- a/core/modules/system/system.routing.yml
+++ b/core/modules/system/system.routing.yml
@@ -454,6 +454,8 @@ system.db_update:
     _title: 'Drupal database update'
     _controller: '\Drupal\system\Controller\DbUpdateController::handle'
     op: 'info'
+  options:
+    _maintenance_access: TRUE
   requirements:
     _access_system_update: 'TRUE'
 
