diff --git a/composer.lock b/composer.lock
index c8f7f55c0f0945e9a5cdeb2fc0debda332b7a99d..54eded8f0c9c0667e3200b49586c1a5910b0715d 100644
--- a/composer.lock
+++ b/composer.lock
@@ -495,7 +495,7 @@
             "dist": {
                 "type": "path",
                 "url": "core",
-                "reference": "52e04d4f59d5d77f898e4a3d2a63c1d809f139f7"
+                "reference": "b8ae3e330a6035450fa1578a5d2d30388cb98314"
             },
             "require": {
                 "asm89/stack-cors": "^2.1",
@@ -540,7 +540,7 @@
                 "symfony/serializer": "^6.4",
                 "symfony/validator": "^6.4",
                 "symfony/yaml": "^6.4",
-                "twig/twig": "^3.5.0"
+                "twig/twig": "^3.9.3"
             },
             "conflict": {
                 "drush/drush": "<12.4.3"
@@ -4367,30 +4367,37 @@
         },
         {
             "name": "twig/twig",
-            "version": "v3.8.0",
+            "version": "v3.9.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/twigphp/Twig.git",
-                "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d"
+                "reference": "a842d75fed59cdbcbd3a3ad7fb9eb768fc350d58"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/twigphp/Twig/zipball/9d15f0ac07f44dc4217883ec6ae02fd555c6f71d",
-                "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d",
+                "url": "https://api.github.com/repos/twigphp/Twig/zipball/a842d75fed59cdbcbd3a3ad7fb9eb768fc350d58",
+                "reference": "a842d75fed59cdbcbd3a3ad7fb9eb768fc350d58",
                 "shasum": ""
             },
             "require": {
                 "php": ">=7.2.5",
+                "symfony/deprecation-contracts": "^2.5|^3",
                 "symfony/polyfill-ctype": "^1.8",
                 "symfony/polyfill-mbstring": "^1.3",
                 "symfony/polyfill-php80": "^1.22"
             },
             "require-dev": {
                 "psr/container": "^1.0|^2.0",
-                "symfony/phpunit-bridge": "^5.4.9|^6.3|^7.0"
+                "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0"
             },
             "type": "library",
             "autoload": {
+                "files": [
+                    "src/Resources/core.php",
+                    "src/Resources/debug.php",
+                    "src/Resources/escaper.php",
+                    "src/Resources/string_loader.php"
+                ],
                 "psr-4": {
                     "Twig\\": "src/"
                 }
@@ -4423,7 +4430,7 @@
             ],
             "support": {
                 "issues": "https://github.com/twigphp/Twig/issues",
-                "source": "https://github.com/twigphp/Twig/tree/v3.8.0"
+                "source": "https://github.com/twigphp/Twig/tree/v3.9.3"
             },
             "funding": [
                 {
@@ -4435,7 +4442,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-11-21T18:54:41+00:00"
+            "time": "2024-04-18T11:59:33+00:00"
         }
     ],
     "packages-dev": [
diff --git a/composer/Metapackage/CoreRecommended/composer.json b/composer/Metapackage/CoreRecommended/composer.json
index 86fe66380bae018904f513f20bd24483e7f23020..e8f497112ad148264304ba5c6374a23ac6fcfb12 100644
--- a/composer/Metapackage/CoreRecommended/composer.json
+++ b/composer/Metapackage/CoreRecommended/composer.json
@@ -61,6 +61,6 @@
         "symfony/var-dumper": "~v6.4.0",
         "symfony/var-exporter": "~v6.4.1",
         "symfony/yaml": "~v6.4.3",
-        "twig/twig": "~v3.8.0"
+        "twig/twig": "~v3.9.3"
     }
 }
diff --git a/core/composer.json b/core/composer.json
index 44fe4bada636e06c971a83f5c1bb3ba10babe3ce..c899706d59a97ad1fcaa578459b7830687f1bc7a 100644
--- a/core/composer.json
+++ b/core/composer.json
@@ -33,7 +33,7 @@
         "symfony/process": "^6.4",
         "symfony/polyfill-iconv": "^1.26",
         "symfony/yaml": "^6.4",
-        "twig/twig": "^3.5.0",
+        "twig/twig": "^3.9.3",
         "doctrine/annotations": "^1.14",
         "guzzlehttp/guzzle": "^7.5",
         "guzzlehttp/psr7": "^2.4.5",
diff --git a/core/lib/Drupal/Core/Template/TwigNodeTrans.php b/core/lib/Drupal/Core/Template/TwigNodeTrans.php
index 135593793597ff9486981cd133b509db2981b393..4660ba0063ff40a7c9c5a94cfd99bce619566bf8 100644
--- a/core/lib/Drupal/Core/Template/TwigNodeTrans.php
+++ b/core/lib/Drupal/Core/Template/TwigNodeTrans.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Core\Template;
 
+use Twig\Attribute\YieldReady;
 use Twig\Compiler;
 use Twig\Error\SyntaxError;
 use Twig\Node\CheckToStringNode;
@@ -25,6 +26,7 @@
  * @see https://twig-extensions.readthedocs.io/en/latest/i18n.html
  * @see https://github.com/fabpot/Twig-extensions
  */
+#[YieldReady]
 class TwigNodeTrans extends Node {
 
   /**
@@ -59,7 +61,7 @@ public function compile(Compiler $compiler) {
     }
 
     // Start writing with the function to be called.
-    $compiler->write('echo ' . (empty($plural) ? 't' : '\Drupal::translation()->formatPlural') . '(');
+    $compiler->write('yield ' . (empty($plural) ? 't' : '\Drupal::translation()->formatPlural') . '(');
 
     // Move the count to the beginning of the parameters list.
     if (!empty($plural)) {
diff --git a/core/lib/Drupal/Core/Template/TwigNodeVisitor.php b/core/lib/Drupal/Core/Template/TwigNodeVisitor.php
index e73afdce240c7c44eef271ac6f7ee91d5a2e3f10..a387cf4c4e209308aef677f11d7faf43bd3d65a4 100644
--- a/core/lib/Drupal/Core/Template/TwigNodeVisitor.php
+++ b/core/lib/Drupal/Core/Template/TwigNodeVisitor.php
@@ -7,7 +7,7 @@
 use Twig\Node\Expression\FunctionExpression;
 use Twig\Node\Node;
 use Twig\Node\PrintNode;
-use Twig\NodeVisitor\AbstractNodeVisitor;
+use Twig\NodeVisitor\NodeVisitorInterface;
 
 /**
  * Provides a TwigNodeVisitor to change the generated parse-tree.
@@ -18,7 +18,7 @@
  *
  * @see twig_render
  */
-class TwigNodeVisitor extends AbstractNodeVisitor {
+class TwigNodeVisitor implements NodeVisitorInterface {
 
   /**
    * Tracks whether there is a render array aware filter active already.
@@ -28,14 +28,14 @@ class TwigNodeVisitor extends AbstractNodeVisitor {
   /**
    * {@inheritdoc}
    */
-  protected function doEnterNode(Node $node, Environment $env) {
+  public function enterNode(Node $node, Environment $env): Node {
     return $node;
   }
 
   /**
    * {@inheritdoc}
    */
-  protected function doLeaveNode(Node $node, Environment $env) {
+  public function leaveNode(Node $node, Environment $env): ?Node {
     // We use this to inject a call to render_var -> TwigExtension->renderVar()
     // before anything is printed.
     if ($node instanceof PrintNode) {
diff --git a/core/lib/Drupal/Core/Template/TwigNodeVisitorCheckDeprecations.php b/core/lib/Drupal/Core/Template/TwigNodeVisitorCheckDeprecations.php
index c54ebb689be136f318410494d49b8b4ab2083ca7..1ee7efa5d8c3c3343766668894be87f7bcce3e01 100644
--- a/core/lib/Drupal/Core/Template/TwigNodeVisitorCheckDeprecations.php
+++ b/core/lib/Drupal/Core/Template/TwigNodeVisitorCheckDeprecations.php
@@ -7,7 +7,7 @@
 use Twig\Node\Expression\NameExpression;
 use Twig\Node\ModuleNode;
 use Twig\Node\Node;
-use Twig\NodeVisitor\AbstractNodeVisitor;
+use Twig\NodeVisitor\NodeVisitorInterface;
 
 /**
  * Provides a Node Visitor to trigger errors if deprecated variables are used.
@@ -18,7 +18,7 @@
  *
  * @see \Drupal\Core\Template\TwigNodeCheckDeprecations
  */
-class TwigNodeVisitorCheckDeprecations extends AbstractNodeVisitor {
+class TwigNodeVisitorCheckDeprecations implements NodeVisitorInterface {
 
   /**
    * The named variables used in the template from the context.
@@ -33,7 +33,7 @@ class TwigNodeVisitorCheckDeprecations extends AbstractNodeVisitor {
   /**
    * {@inheritdoc}
    */
-  protected function doEnterNode(Node $node, Environment $env) {
+  public function enterNode(Node $node, Environment $env): Node {
     if ($node instanceof ModuleNode) {
       $this->usedNames = [];
       $this->assignedNames = [];
@@ -55,7 +55,7 @@ protected function doEnterNode(Node $node, Environment $env) {
   /**
    * {@inheritdoc}
    */
-  protected function doLeaveNode(Node $node, Environment $env) {
+  public function leaveNode(Node $node, Environment $env): ?Node {
     // At the end of the template, check the used variables are not deprecated.
     if ($node instanceof ModuleNode) {
       if (!empty($this->usedNames)) {
diff --git a/core/modules/help/tests/modules/help_topics_twig_tester/src/HelpTestTwigNodeVisitor.php b/core/modules/help/tests/modules/help_topics_twig_tester/src/HelpTestTwigNodeVisitor.php
index b3070db7597e3afea37f7ad14a08d7e0aadb7c80..af323effb974461f45c96e008f9a48fd6e21a48c 100644
--- a/core/modules/help/tests/modules/help_topics_twig_tester/src/HelpTestTwigNodeVisitor.php
+++ b/core/modules/help/tests/modules/help_topics_twig_tester/src/HelpTestTwigNodeVisitor.php
@@ -9,7 +9,7 @@
 use Twig\Node\SetNode;
 use Twig\Node\TextNode;
 use Twig\Node\Expression\AbstractExpression;
-use Twig\NodeVisitor\AbstractNodeVisitor;
+use Twig\NodeVisitor\NodeVisitorInterface;
 
 /**
  * Defines a Twig node visitor for testing help topics.
@@ -17,7 +17,7 @@
  * See static::setStateValue() for information on the special processing
  * this class can do.
  */
-class HelpTestTwigNodeVisitor extends AbstractNodeVisitor {
+class HelpTestTwigNodeVisitor implements NodeVisitorInterface {
 
   /**
    * Delimiter placed around single translated chunks.
@@ -32,14 +32,14 @@ class HelpTestTwigNodeVisitor extends AbstractNodeVisitor {
   /**
    * {@inheritdoc}
    */
-  protected function doEnterNode(Node $node, Environment $env) {
+  public function enterNode(Node $node, Environment $env): Node {
     return $node;
   }
 
   /**
    * {@inheritdoc}
    */
-  protected function doLeaveNode(Node $node, Environment $env) {
+  public function leaveNode(Node $node, Environment $env): ?Node {
     $processing = static::getState();
     if (!$processing['manner']) {
       return $node;
diff --git a/core/modules/system/tests/modules/sdc_other_node_visitor/src/Twig/Profiler/EnterProfileNode.php b/core/modules/system/tests/modules/sdc_other_node_visitor/src/Twig/Profiler/EnterProfileNode.php
index 7e55b192682f59264288a56546a848232ae0d40e..b9d8ce46290b328081ccd8ad9a7cd221c422a4da 100644
--- a/core/modules/system/tests/modules/sdc_other_node_visitor/src/Twig/Profiler/EnterProfileNode.php
+++ b/core/modules/system/tests/modules/sdc_other_node_visitor/src/Twig/Profiler/EnterProfileNode.php
@@ -2,12 +2,14 @@
 
 namespace Drupal\sdc_other_node_visitor\Twig\Profiler;
 
+use Twig\Attribute\YieldReady;
 use Twig\Compiler;
 use Twig\Node\Node;
 
 /**
  * Represents a profile enter node.
  */
+#[YieldReady]
 class EnterProfileNode extends Node {
 
   public function __construct(string $extensionName, string $varName) {
diff --git a/core/modules/system/tests/modules/sdc_other_node_visitor/src/Twig/Profiler/LeaveProfileNode.php b/core/modules/system/tests/modules/sdc_other_node_visitor/src/Twig/Profiler/LeaveProfileNode.php
index 52e86a4c9c939abef2d8f2fe79341658185c3fb4..0cee31e52b6cc92c96192f2a7bebbfbbd99688b4 100644
--- a/core/modules/system/tests/modules/sdc_other_node_visitor/src/Twig/Profiler/LeaveProfileNode.php
+++ b/core/modules/system/tests/modules/sdc_other_node_visitor/src/Twig/Profiler/LeaveProfileNode.php
@@ -2,12 +2,14 @@
 
 namespace Drupal\sdc_other_node_visitor\Twig\Profiler;
 
+use Twig\Attribute\YieldReady;
 use Twig\Compiler;
 use Twig\Node\Node;
 
 /**
  * Represents a profile leave node.
  */
+#[YieldReady]
 class LeaveProfileNode extends Node {
 
   public function __construct(string $varName) {