diff --git a/core/lib/Drupal/Core/Template/TwigExtension.php b/core/lib/Drupal/Core/Template/TwigExtension.php
index 38bdc2e..df2d020 100644
--- a/core/lib/Drupal/Core/Template/TwigExtension.php
+++ b/core/lib/Drupal/Core/Template/TwigExtension.php
@@ -543,18 +543,18 @@ public function renderVar($arg) {
    *   This value is expected to be safe for output and user provided data
    *   should never be used as a glue.
    *
-   * @return string
-   *   The strings joined together.
+   * @return \Drupal\Core\Template\TwigMarkup
+   *   The created imploded string, which is now markup.
    */
   public function safeJoin(\Twig_Environment $env, $value, $glue = '') {
     if ($value instanceof \Traversable) {
       $value = iterator_to_array($value, FALSE);
     }
 
-    return implode($glue, array_map(function($item) use ($env) {
+    return new TwigMarkup(implode($glue, array_map(function($item) use ($env) {
       // If $item is not marked safe then it will be escaped.
       return $this->escapeFilter($env, $item, 'html', NULL, TRUE);
-    }, (array) $value));
+    }, (array) $value)), $env->getCharset());
   }
 
 }
diff --git a/core/lib/Drupal/Core/Template/TwigMarkup.php b/core/lib/Drupal/Core/Template/TwigMarkup.php
new file mode 100644
index 0000000..d642183
--- /dev/null
+++ b/core/lib/Drupal/Core/Template/TwigMarkup.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Template\TwigMarkup.
+ */
+
+namespace Drupal\Core\Template;
+
+use Drupal\Component\Render\MarkupInterface;
+
+/**
+ * A class that defines Twig markup class for Drupal.
+ */
+class TwigMarkup extends \Twig_Markup implements MarkupInterface {
+
+  /**
+   * Constructs an TwigMarkup object.
+   *
+   * @param string $string
+   *   The string containing markup to wrap as an object.
+   * @param string $charset
+   *   The character set used when counting, defaulted to utf-8 for convenience.
+   */
+  public function __construct($string, $charset = 'utf-8') {
+    parent::__construct($string, $charset);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function jsonSerialize() {
+    return $this->__toString();
+  }
+
+}
diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc
index f9c49c7..f1f7215 100644
--- a/core/modules/system/system.admin.inc
+++ b/core/modules/system/system.admin.inc
@@ -207,8 +207,7 @@ function template_preprocess_system_modules_details(&$variables) {
     $module['id'] = $id;
     $module['enable_id'] = $module['enable']['#id'];
 
-    // @todo Remove early rendering and use safe_join in the Twig template once
-    //   https://www.drupal.org/node/2579091 is fixed.
+    // @todo Remove early rendering.
     $renderer = \Drupal::service('renderer');
     $machine_name_render = [
       '#prefix' => '<span dir="ltr" class="table-filter-text-source">',
@@ -218,24 +217,10 @@ function template_preprocess_system_modules_details(&$variables) {
     $module['machine_name'] = $renderer->render($machine_name_render);
 
     if (!empty($module['#requires'])) {
-      $requires = [
-        '#theme' => 'item_list',
-        '#items' => $module['#requires'],
-        '#context' => ['list_style' => 'comma-list'],
-      ];
-      $module['requires'] = $renderer->render($requires);
+      $module['requires'] = $module['#requires'];
     }
     if (!empty($module['#required_by'])) {
-      $required_by = [
-        '#theme' => 'item_list',
-        '#items' => $module['#required_by'],
-        '#context' => ['list_style' => 'comma-list'],
-      ];
-      $module['required_by'] = $renderer->render($required_by);
-    }
-
-    if (!empty($module['version'])) {
-      $module['version'] = $renderer->render($module['version']);
+      $module['required_by'] = $module['#required_by'];
     }
 
     $module['attributes'] = new Attribute($module['#attributes']);
diff --git a/core/modules/system/templates/system-modules-details.html.twig b/core/modules/system/templates/system-modules-details.html.twig
index e431c8e..5ad7812 100644
--- a/core/modules/system/templates/system-modules-details.html.twig
+++ b/core/modules/system/templates/system-modules-details.html.twig
@@ -53,10 +53,10 @@
                     <div class="admin-requirements">{{ 'Version: @module-version'|t({'@module-version': module.version }) }}</div>
                   {% endif %}
                   {% if module.requires %}
-                    <div class="admin-requirements">{{ 'Requires: @module-list'|t({'@module-list': module.requires }) }}</div>
+                    <div class="admin-requirements">{{ 'Requires: @module-list'|t({'@module-list': module.requires|safe_join(', ') }) }}</div>
                   {% endif %}
                   {% if module.required_by %}
-                    <div class="admin-requirements">{{ 'Required by: @module-list'|t({'@module-list': module.required_by }) }}</div>
+                    <div class="admin-requirements">{{ 'Required by: @module-list'|t({'@module-list': module.required_by|safe_join(', ') }) }}</div>
                   {% endif %}
                 </div>
                 {% if module.links %}
diff --git a/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php b/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php
index cb09022..846d7ee 100644
--- a/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php
+++ b/core/tests/Drupal/Tests/Core/Template/TwigExtensionTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\Core\Template;
 
+use Drupal\Component\Render\FormattableMarkup;
 use Drupal\Core\Render\RenderableInterface;
 use Drupal\Core\Render\RendererInterface;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
@@ -186,7 +187,12 @@ public function testSafeJoin() {
     $renderer = $renderer->reveal();
 
     $twig_extension = new TwigExtension($renderer);
-    $twig_environment = $this->prophesize(TwigEnvironment::class)->reveal();
+    $twig_environment = new \Twig_Environment(NULL, array(
+      'debug' => TRUE,
+      'cache' => FALSE,
+      'autoescape' => 'html',
+      'optimizations' => 0
+    ));
 
     // Simulate t().
     $markup = $this->prophesize(TranslatableMarkup::class);
@@ -210,6 +216,11 @@ public function testSafeJoin() {
     $result = $twig_extension->safeJoin($twig_environment, $items, ', ');
     $this->assertEquals('&lt;em&gt;will be escaped&lt;/em&gt;, <em>will be markup</em>, <strong>will be rendered</strong>', $result);
 
+    // Ensure that result of safe join can be used in t() and string formatting
+    // without double escaping.
+    $result = new FormattableMarkup('Add some kittens @text', ['@text' => $result]);
+    $this->assertEquals('Add some kittens &lt;em&gt;will be escaped&lt;/em&gt;, <em>will be markup</em>, <strong>will be rendered</strong>', (string) $result);
+
     // Ensure safe_join Twig filter supports empty variables.
     $items = NULL;
     $result = $twig_extension->safeJoin($twig_environment, $items, '<br>');
diff --git a/core/themes/stable/stable.theme b/core/themes/stable/stable.theme
index 456a4ad..a8e993e 100644
--- a/core/themes/stable/stable.theme
+++ b/core/themes/stable/stable.theme
@@ -23,3 +23,33 @@ function stable_preprocess_links(&$variables) {
     }
   }
 }
+
+/**
+ * Implements hook_preprocess_system_modules_details().
+ */
+function stable_preprocess_system_modules_details(&$variables) {
+  $renderer = \Drupal::service('renderer');
+  foreach ($variables['modules'] as $key => $value) {
+    if (!empty($variables['modules'][$key]['requires'])) {
+      $requires = [
+        '#theme' => 'item_list',
+        '#items' => $variables['modules'][$key]['requires'],
+        '#context' => ['list_style' => 'comma-list'],
+      ];
+      $variables['modules'][$key]['requires'] = $renderer->render($requires);
+    }
+
+    if (!empty($variables['modules'][$key]['required_by'])) {
+      $required_by = [
+        '#theme' => 'item_list',
+        '#items' => $variables['modules'][$key]['required_by'],
+        '#context' => ['list_style' => 'comma-list'],
+      ];
+      $variables['modules'][$key]['required_by'] = $renderer->render($required_by);
+    }
+
+    if (!empty($variables['module'][$key]['version'])) {
+      $variables['modules'][$key]['version'] = $renderer->render($variables['modules'][$key]['version']);
+    }
+  }
+}
