diff --git a/core/lib/Drupal/Core/Annotation/Translation.php b/core/lib/Drupal/Core/Annotation/Translation.php
index e1cc845..372000b 100644
--- a/core/lib/Drupal/Core/Annotation/Translation.php
+++ b/core/lib/Drupal/Core/Annotation/Translation.php
@@ -73,20 +73,17 @@ class Translation extends AnnotationBase {
    * @param array $values
    *   Possible array keys:
    *   - value (required): the string that is to be translated.
-   *   - arguments (optional): an array with placeholder replacements, keyed by
-   *     placeholder.
    *   - context (optional): a string that describes the context of "value";
    */
   public function __construct(array $values) {
     $string = $values['value'];
-    $arguments = isset($values['arguments']) ? $values['arguments'] : array();
     $options = array();
     if (!empty($values['context'])) {
       $options = array(
         'context' => $values['context'],
       );
     }
-    $this->translation = new TranslationWrapper($string, $arguments, $options);
+    $this->translation = new TranslationWrapper($string, $options);
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Entity/Annotation/ConfigEntityType.php b/core/lib/Drupal/Core/Entity/Annotation/ConfigEntityType.php
index 4720b35..0df0c2e 100644
--- a/core/lib/Drupal/Core/Entity/Annotation/ConfigEntityType.php
+++ b/core/lib/Drupal/Core/Entity/Annotation/ConfigEntityType.php
@@ -37,7 +37,7 @@ class ConfigEntityType extends EntityType {
    * {@inheritdoc}
    */
   public function get() {
-    $this->definition['group_label'] = new TranslationWrapper('Configuration', array(), array('context' => 'Entity type group'));
+    $this->definition['group_label'] = new TranslationWrapper('Configuration', array('context' => 'Entity type group'));
 
     return parent::get();
   }
diff --git a/core/lib/Drupal/Core/Entity/Annotation/ContentEntityType.php b/core/lib/Drupal/Core/Entity/Annotation/ContentEntityType.php
index 34363a5..3e4f9d8 100644
--- a/core/lib/Drupal/Core/Entity/Annotation/ContentEntityType.php
+++ b/core/lib/Drupal/Core/Entity/Annotation/ContentEntityType.php
@@ -37,7 +37,7 @@ class ContentEntityType extends EntityType {
    * {@inheritdoc}
    */
   public function get() {
-    $this->definition['group_label'] = new TranslationWrapper('Content', array(), array('context' => 'Entity type group'));
+    $this->definition['group_label'] = new TranslationWrapper('Content', array('context' => 'Entity type group'));
 
     return parent::get();
   }
diff --git a/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php b/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php
index 3a36db5..8f6236b 100644
--- a/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php
+++ b/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php
@@ -33,7 +33,7 @@ class TranslationWrapper implements SafeStringInterface {
    *
    * @var array
    */
-  protected $arguments;
+  protected $arguments = [];
 
   /**
    * The translation options.
@@ -50,14 +50,11 @@ class TranslationWrapper implements SafeStringInterface {
    *
    * @param string $string
    *   The string that is to be translated.
-   * @param array $arguments
-   *   (optional) An array with placeholder replacements, keyed by placeholder.
    * @param array $options
    *   (optional) An array of additional options.
    */
-  public function __construct($string, array $arguments = array(), array $options = array()) {
+  public function __construct($string, array $options = []) {
     $this->string = $string;
-    $this->arguments = $arguments;
     $this->options = $options;
   }
 
@@ -72,6 +69,16 @@ public function getUntranslatedString() {
   }
 
   /**
+   * Gets all arguments from this translation wrapper.
+   *
+   * @return string[]
+   *   The array of arguments.
+   */
+  public function getArguments() {
+    return $this->arguments;
+  }
+
+  /**
    * Gets a specific option from this translation wrapper.
    *
    * @param $name
@@ -85,6 +92,16 @@ public function getOption($name) {
   }
 
   /**
+   * Gets all options from this translation wrapper.
+   *
+   * @return array
+   *   The array of options.
+   */
+  public function getOptions() {
+    return $this->options;
+  }
+
+  /**
    * Implements the magic __toString() method.
    */
   public function __toString() {
@@ -92,6 +109,23 @@ public function __toString() {
   }
 
   /**
+   * Creates a new object with updated arguments.
+   *
+   * @param string[] $arguments
+   *   An array with placeholder replacements, keyed by placeholder. The values
+   *   will be merged with and replace any existing arguments with the same
+   *   keys.
+   *
+   * @return \Drupal\Core\StringTranslation\TranslationWrapper
+   *   The new object.
+   */
+  public function withArguments(array $arguments) {
+    $with = new static($this->string, $this->options);
+    $with->arguments = $arguments + $this->arguments;
+    return $with;
+  }
+
+  /**
    * Renders the object as a string.
    *
    * @return string
diff --git a/core/modules/locale/src/LocaleConfigManager.php b/core/modules/locale/src/LocaleConfigManager.php
index d96c758..f0e9620 100644
--- a/core/modules/locale/src/LocaleConfigManager.php
+++ b/core/modules/locale/src/LocaleConfigManager.php
@@ -173,7 +173,7 @@ protected function getTranslatableData(TypedDataInterface $element) {
         if (isset($definition['translation context'])) {
           $options['context'] = $definition['translation context'];
         }
-        return new TranslationWrapper($element->getValue(), array(), $options);
+        return new TranslationWrapper($element->getValue(), $options);
       }
     }
     return $translatable;
diff --git a/core/tests/Drupal/Tests/Core/Annotation/TranslationTest.php b/core/tests/Drupal/Tests/Core/Annotation/TranslationTest.php
index be8cca3..4536d27 100644
--- a/core/tests/Drupal/Tests/Core/Annotation/TranslationTest.php
+++ b/core/tests/Drupal/Tests/Core/Annotation/TranslationTest.php
@@ -41,13 +41,12 @@ public function testGet(array $values, $expected) {
     $container->set('string_translation', $this->translationManager);
     \Drupal::setContainer($container);
 
-    $arguments = isset($values['arguments']) ? $values['arguments'] : array();
     $options = isset($values['context']) ? array(
       'context' => $values['context'],
     ) : array();
     $this->translationManager->expects($this->once())
       ->method('translate')
-      ->with($values['value'], $arguments, $options);
+      ->with($values['value'], [], $options);
 
     $annotation = new Translation($values);
 
@@ -65,19 +64,12 @@ public function providerTestGet() {
       ),
       'Foo'
     );
-    $random = $this->randomMachineName();
-    $random_html_entity = '&' . $random;
     $data[] = array(
       array(
-        'value' => 'Foo !bar @baz %qux',
-        'arguments' => array(
-          '!bar' => $random,
-          '@baz' => $random_html_entity,
-          '%qux' => $random_html_entity,
-        ),
+        'value' => 'Foo',
         'context' => $this->randomMachineName(),
       ),
-      'Foo ' . $random . ' &amp;' . $random . ' <em class="placeholder">&amp;' . $random . '</em>',
+      'Foo',
     );
 
     return $data;
diff --git a/core/tests/Drupal/Tests/Core/StringTranslation/TranslationWrapperTest.php b/core/tests/Drupal/Tests/Core/StringTranslation/TranslationWrapperTest.php
new file mode 100644
index 0000000..4099742
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/StringTranslation/TranslationWrapperTest.php
@@ -0,0 +1,91 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\StringTranslation\TranslationWrapperTest.
+ */
+
+namespace Drupal\Tests\Core\StringTranslation;
+
+use Drupal\Tests\UnitTestCase;
+use Drupal\Core\StringTranslation\TranslationWrapper;
+
+/**
+ * @coversDefaultClass \Drupal\Core\StringTranslation\TranslationWrapper
+ * @group StringTranslation
+ */
+class TranslationWrapperTest extends UnitTestCase {
+
+  /**
+   * @covers ::getArguments
+   */
+  public function testGetArguments() {
+    $base = new TranslationWrapper('hey @ho @merry');
+    $this->assertArrayEquals([], $base->getArguments());
+    $wrapper = $base->withArguments(['@ho' => 'ho', '@merry' => 'go']);
+    $this->assertArrayEquals(['@ho' => 'ho', '@merry' => 'go'], $wrapper->getArguments());
+  }
+
+  /**
+   * @covers ::getOption
+   */
+  public function testGetOption() {
+    $wrapper = new TranslationWrapper('hey @ho @merry', ['context' => 'interface']);
+    $this->assertSame('interface', $wrapper->getOption('context'));
+  }
+
+  /**
+   * @covers ::getOptions
+   */
+  public function testGetOptions() {
+    $wrapper = new TranslationWrapper('hey @ho @merry', ['context' => 'interface']);
+    $this->assertArrayEquals(['context' => 'interface'], $wrapper->getOptions());
+  }
+
+  /**
+   * @covers ::getUntranslatedString
+   */
+  public function testGetUntranslatedString() {
+    $wrapper = new TranslationWrapper('hey @ho @merry', ['context' => 'interface']);
+    $this->assertSame('hey @ho @merry', $wrapper->getUntranslatedString());
+  }
+
+  /**
+   * @covers ::withArguments
+   */
+  public function testWithArguments() {
+    $base = new TranslationWrapper('hey @ho @merry');
+    $wrapper = $base->withArguments(['@ho' => 'ho', '@merry' => 'go']);
+    // Replace no arguments.
+    $with_args = $wrapper->withArguments([]);
+    // The method must return a new object.
+    $this->assertNotEquals(spl_object_hash($wrapper), spl_object_hash($with_args));
+    $last_object_hash = spl_object_hash($with_args);
+    $with_args->setStringTranslation($this->getStringTranslationStub());
+    $this->assertSame('hey ho go', (string) $with_args);
+    // Replace one argument.
+    $with_args = $wrapper->withArguments(['@ho' => 'llama']);
+    // The method must return a new object.
+    $this->assertNotEquals($last_object_hash, spl_object_hash($with_args));
+    $this->assertNotEquals(spl_object_hash($wrapper), spl_object_hash($with_args));
+    $with_args->setStringTranslation($this->getStringTranslationStub());
+    $this->assertSame('hey llama go', (string) $with_args);
+    $this->assertSame('hey @ho @merry', $with_args->getUntranslatedString());
+    // Replace two arguments.
+    $with_args = $wrapper->withArguments(['@ho' => 'llama', '@merry' => 'new year']);
+    $with_args->setStringTranslation($this->getStringTranslationStub());
+    $this->assertSame('hey llama new year', (string) $with_args);
+    // Replace an argument that is not used.
+    $with_args = $wrapper->withArguments(['@horse' => 'llama']);
+    $with_args->setStringTranslation($this->getStringTranslationStub());
+    $this->assertSame('hey ho go', (string) $with_args);
+    $this->assertArrayEquals(['@ho' => 'ho', '@merry' => 'go', '@horse' => 'llama'], $with_args->getArguments());
+    // Start with no arguments in the original.
+    $wrapper = new TranslationWrapper('hey @ho @merry', []);
+    // Add one argument.
+    $with_args = $wrapper->withArguments(['@ho' => 'llama']);
+    $with_args->setStringTranslation($this->getStringTranslationStub());
+    // The missing argument is not replaced.
+    $this->assertSame('hey llama @merry', (string) $with_args);
+  }
+}
