diff --git a/core/lib/Drupal/Component/Render/FormattableMarkup.php b/core/lib/Drupal/Component/Render/FormattableMarkup.php
index ec42e068a7934fa14ec4cb9d9d0ce3ca3966e645..52d6da66fe64cae40f147be6b9caeae020110581 100644
--- a/core/lib/Drupal/Component/Render/FormattableMarkup.php
+++ b/core/lib/Drupal/Component/Render/FormattableMarkup.php
@@ -65,7 +65,7 @@
  * @see \Drupal\Core\StringTranslation\PluralTranslatableMarkup
  * @see \Drupal\Component\Render\FormattableMarkup::placeholderFormat()
  */
-class FormattableMarkup implements MarkupInterface {
+class FormattableMarkup implements MarkupInterface, \Countable {
 
   /**
    * The arguments to replace placeholders with.
diff --git a/core/lib/Drupal/Component/Render/HtmlEscapedText.php b/core/lib/Drupal/Component/Render/HtmlEscapedText.php
index fd1e69d94ea6f941145eb37b6c555f8639bdf37e..f12e4e9b7a7c3cfa062115ae1c35c7527d513dc1 100644
--- a/core/lib/Drupal/Component/Render/HtmlEscapedText.php
+++ b/core/lib/Drupal/Component/Render/HtmlEscapedText.php
@@ -18,7 +18,7 @@
  *
  * @ingroup sanitization
  */
-class HtmlEscapedText implements MarkupInterface {
+class HtmlEscapedText implements MarkupInterface, \Countable {
 
   /**
    * The string to escape.
diff --git a/core/lib/Drupal/Core/GeneratedLink.php b/core/lib/Drupal/Core/GeneratedLink.php
index d7e96e1297a0e461a69c56903470ea9b0fdd65d4..a612f665256e707cb22b4046599267fb0bb838ac 100644
--- a/core/lib/Drupal/Core/GeneratedLink.php
+++ b/core/lib/Drupal/Core/GeneratedLink.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core;
 
 use Drupal\Component\Render\MarkupInterface;
+use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Render\BubbleableMetadata;
 
 /**
@@ -16,7 +17,7 @@
  * Note: not to be confused with \Drupal\Core\Link, which is for passing around
  *   ungenerated links (typically link text + route name + route parameters).
  */
-class GeneratedLink extends BubbleableMetadata implements MarkupInterface {
+class GeneratedLink extends BubbleableMetadata implements MarkupInterface, \Countable {
 
   /**
    * The HTML string value containing a link.
@@ -61,4 +62,11 @@ public function jsonSerialize() {
     return $this->__toString();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function count() {
+    return Unicode::strlen($this->__toString());
+  }
+
 }
diff --git a/core/lib/Drupal/Core/StringTranslation/TranslatableMarkup.php b/core/lib/Drupal/Core/StringTranslation/TranslatableMarkup.php
index 024c3a42a30386da9da2d3e715cbe9476d95e3eb..a0111eac8de726e9818a6dc2c105cabf34703f31 100644
--- a/core/lib/Drupal/Core/StringTranslation/TranslatableMarkup.php
+++ b/core/lib/Drupal/Core/StringTranslation/TranslatableMarkup.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Component\Utility\ToStringTrait;
+use Drupal\Component\Utility\Unicode;
 
 /**
  * Provides translatable markup class.
@@ -231,4 +232,14 @@ protected function getStringTranslation() {
     return $this->stringTranslation;
   }
 
+  /**
+   * Returns the string length.
+   *
+   * @return int
+   *   The length of the string.
+   */
+  public function count() {
+    return Unicode::strlen($this->render());
+  }
+
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Theme/TwigMarkupInterfaceTest.php b/core/tests/Drupal/KernelTests/Core/Theme/TwigMarkupInterfaceTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..ed11a315746f29b5b0817e98689f727d3e1f4751
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Theme/TwigMarkupInterfaceTest.php
@@ -0,0 +1,112 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\KernelTests\Core\Theme\ThemeRenderAndAutoescapeTest.
+ */
+
+namespace Drupal\KernelTests\Core\Theme;
+
+use Drupal\Component\Render\FormattableMarkup;
+use Drupal\Component\Render\MarkupInterface;
+use Drupal\Component\Render\MarkupTrait;
+use Drupal\Core\GeneratedLink;
+use Drupal\Core\Render\RenderContext;
+use Drupal\Core\Render\Markup;
+use Drupal\Core\Site\Settings;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Tests Twig with MarkupInterface objects.
+ *
+ * @group Theme
+ */
+class TwigMarkupInterfaceTest extends KernelTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = [
+    'language'
+  ];
+
+  /**
+   * @dataProvider providerTestMarkupInterfaceEmpty
+   */
+  public function testMarkupInterfaceEmpty($expected, $variable) {
+    $this->assertEquals($expected, $this->renderObjectWithTwig($variable));
+  }
+
+  /**
+   * Provide test examples.
+   */
+  public function providerTestMarkupInterfaceEmpty() {
+    return [
+      'empty TranslatableMarkup' => ['', new TranslatableMarkup('')],
+      'non-empty TranslatableMarkup' => ['<span>test</span>', new TranslatableMarkup('test')],
+      'empty FormattableMarkup' => ['', new FormattableMarkup('', ['@foo' => 'bar'])],
+      'non-empty FormattableMarkup' => ['<span>bar</span>', new FormattableMarkup('@foo', ['@foo' => 'bar'])],
+      'non-empty Markup' => ['<span>test</span>', Markup::create('test')],
+      'empty GeneratedLink' => ['', new GeneratedLink()],
+      'non-empty GeneratedLink' => ['<span><a hef="http://www.example.com">test</a></span>', (new GeneratedLink())->setGeneratedLink('<a hef="http://www.example.com">test</a>')],
+      // Test objects that do not implement \Countable.
+      'empty SafeMarkupTestMarkup' => ['<span></span>', SafeMarkupTestMarkup::create('')],
+      'non-empty SafeMarkupTestMarkup' => ['<span>test</span>', SafeMarkupTestMarkup::create('test')],
+    ];
+  }
+
+  /**
+   * Tests behaviour if a string is translated to become an empty string.
+   */
+  public function testEmptyTranslation() {
+    $settings = Settings::getAll();
+    $settings['locale_custom_strings_en'] = ['' => ['test' => '']];
+    // Recreate the settings static.
+    new Settings($settings);
+
+    $variable = new TranslatableMarkup('test');
+    $this->assertEquals('', $this->renderObjectWithTwig($variable));
+
+    $variable = new TranslatableMarkup('test', [], ['langcode' => 'de']);
+    $this->assertEquals('<span>test</span>', $this->renderObjectWithTwig($variable));
+  }
+
+  /**
+   * @return \Drupal\Component\Render\MarkupInterface
+   *   The rendered HTML.
+   */
+  protected function renderObjectWithTwig($variable) {
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+    $context = new RenderContext();
+    return $renderer->executeInRenderContext($context, function () use ($renderer, $variable) {
+      $elements = [
+        '#type' => 'inline_template',
+        '#template' => '{%- if variable is not empty -%}<span>{{ variable }}</span>{%- endif -%}',
+        '#context' => array('variable' => $variable),
+      ];
+      return $renderer->render($elements);
+    });
+  }
+
+}
+
+/**
+ * Implements MarkupInterface without implementing \Countable
+ */
+class SafeMarkupTestMarkup implements MarkupInterface {
+  use MarkupTrait;
+
+  /**
+   * Overrides MarkupTrait::create() to allow creation with empty strings.
+   */
+  public static function create($string) {
+    $object = new static();
+    $object->string = $string;
+    return $object;
+  }
+
+}