diff --git a/core/lib/Drupal/Core/Annotation/PluralTranslation.php b/core/lib/Drupal/Core/Annotation/PluralTranslation.php
new file mode 100644
index 0000000000000000000000000000000000000000..ba60d8785b70a1911e698ea0948312716c07d1be
--- /dev/null
+++ b/core/lib/Drupal/Core/Annotation/PluralTranslation.php
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Annotation\PluralTranslation.
+ */
+
+namespace Drupal\Core\Annotation;
+
+use Drupal\Component\Annotation\AnnotationBase;
+
+/**
+ * Defines an annotation object for strings that require plural forms.
+ *
+ * Note that the return values for both 'singular' and 'plural' keys needs to be
+ * passed to
+ * \Drupal\Core\StringTranslation\TranslationInterface::formatPlural().
+ *
+ * For example, the annotation can look like this:
+ * @code
+ *   label_count = @ PluralTranslation(
+ *     singular = "@count item",
+ *     plural = "@count items",
+ *     context = "cart_items",
+ *   ),
+ * @endcode
+ * Remove spaces after @ in your actual plugin - these are put into this sample
+ * code so that it is not recognized as annotation.
+ *
+ * Code samples that make use of this annotation class and the definition sample
+ * above:
+ * @code
+ *   // Returns: 1 item
+ *   $entity_type->getCountLabel(1);
+ *
+ *   // Returns: 5 items
+ *   $entity_type->getCountLabel(5);
+ * @endcode
+ *
+ * @see \Drupal\Core\Entity\EntityType::getSingularLabel()
+ * @see \Drupal\Core\Entity\EntityType::getPluralLabel()
+ * @see \Drupal\Core\Entity\EntityType::getCountLabel()
+ *
+ * @ingroup plugin_translatable
+ *
+ * @Annotation
+ */
+class PluralTranslation extends AnnotationBase {
+
+  /**
+   * The string for the singular case.
+   *
+   * @var string
+   */
+  protected $singular;
+
+  /**
+   * The string for the plural case.
+   *
+   * @var string
+   */
+  protected $plural;
+
+  /**
+   * The context the source strings belong to.
+   *
+   * @var string
+   */
+  protected $context;
+
+  /**
+   * Constructs a new class instance.
+   *
+   * @param array $values
+   *   An associative array with the following keys:
+   *   - singular: The string for the singular case.
+   *   - plural: The string for the plural case.
+   *   - context: The context the source strings belong to.
+   *
+   * @throws \InvalidArgumentException
+   *   Thrown when the keys 'singular' or 'plural' are missing from the $values
+   *   array.
+   */
+  public function __construct(array $values) {
+    if (!isset($values['singular'])) {
+      throw new \InvalidArgumentException('Missing "singular" value in the PluralTranslation annotation');
+    }
+    if (!isset($values['plural'])) {
+      throw new \InvalidArgumentException('Missing "plural" value in the PluralTranslation annotation');
+    }
+
+    $this->singular = $values['singular'];
+    $this->plural = $values['plural'];
+    if (isset($values['context'])) {
+      $this->context = $values['context'];
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function get() {
+    return [
+      'singular' => $this->singular,
+      'plural' => $this->plural,
+      'context' => $this->context,
+    ];
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityType.php b/core/lib/Drupal/Core/Entity/EntityType.php
index a7d11341797739b30c0072802f80275d66fd0e66..2fd5984cb836af8fcc90e2d9fad23f6866174d99 100644
--- a/core/lib/Drupal/Core/Entity/EntityType.php
+++ b/core/lib/Drupal/Core/Entity/EntityType.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Entity\Exception\EntityTypeIdLengthException;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 
 /**
  * Provides an implementation of an entity type and its metadata.
@@ -185,6 +186,29 @@ class EntityType implements EntityTypeInterface {
    */
   protected $label = '';
 
+  /**
+   * The indefinite singular name of the type.
+   *
+   * @var string
+   */
+  protected $label_singular = '';
+
+  /**
+   * The indefinite plural name of the type.
+   *
+   * @var string
+   */
+  protected $label_plural = '';
+
+  /**
+   * A definite singular/plural name of the type.
+   *
+   * Needed keys: "singular" and "plural".
+   *
+   * @var string[]
+   */
+  protected $label_count = [];
+
   /**
    * A callable that can be used to provide the entity URI.
    *
@@ -290,7 +314,6 @@ public function __construct($definition) {
     if (empty($this->list_cache_tags)) {
       $this->list_cache_tags = [$definition['id'] . '_list'];
     }
-
   }
 
   /**
@@ -720,6 +743,39 @@ public function getLowercaseLabel() {
     return Unicode::strtolower($this->getLabel());
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getSingularLabel() {
+    if (empty($this->label_singular)) {
+      $lowercase_label = $this->getLowercaseLabel();
+      $this->label_singular = $lowercase_label;
+    }
+    return $this->label_singular;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPluralLabel() {
+    if (empty($this->label_plural)) {
+      $lowercase_label = $this->getLowercaseLabel();
+      $this->label_plural = new TranslatableMarkup('@label entities', ['@label' => $lowercase_label], [], $this->getStringTranslation());
+    }
+    return $this->label_plural;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCountLabel($count) {
+    if (empty($this->label_count)) {
+      return $this->formatPlural($count, '@count @label', '@count @label entities', ['@label' => $this->getLowercaseLabel()], ['context' => 'Entity type label']);
+    }
+    $context = isset($this->label_count['context']) ? $this->label_count['context'] : 'Entity type label';
+    return $this->formatPlural($count, $this->label_count['singular'], $this->label_count['plural'], ['context' => $context]);
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
index 1120eff1967ccde98d551e4dd3a004f46a2728a9..f2d693a6af689f2bd01cde67d1793d7f9b20c232 100644
--- a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
@@ -630,6 +630,33 @@ public function getLabel();
    */
   public function getLowercaseLabel();
 
+  /**
+   * Gets the singular label of the entity type.
+   *
+   * @return string
+   *   The singular label.
+   */
+  public function getSingularLabel();
+
+  /**
+   * Gets the plural label of the entity type.
+   *
+   * @return string
+   *   The plural label.
+   */
+  public function getPluralLabel();
+
+  /**
+   * Gets the count label of the entity type
+   *
+   * @param int $count
+   *   The item count to display if the plural form was requested.
+   *
+   * @return string
+   *   The count label.
+   */
+  public function getCountLabel($count);
+
   /**
    * Gets a callable that can be used to provide the entity URI.
    *
diff --git a/core/modules/node/src/Entity/Node.php b/core/modules/node/src/Entity/Node.php
index 7b20a3b82105138fa5c419ceaa8795a06bdff82d..090ddd9b26db157c52fe72e5a063a91e475809e4 100644
--- a/core/modules/node/src/Entity/Node.php
+++ b/core/modules/node/src/Entity/Node.php
@@ -22,6 +22,12 @@
  * @ContentEntityType(
  *   id = "node",
  *   label = @Translation("Content"),
+ *   label_singular = @Translation("content item"),
+ *   label_plural = @Translation("content items"),
+ *   label_count = @PluralTranslation(
+ *     singular = "@count content item",
+ *     plural = "@count content items"
+ *   ),
  *   bundle_label = @Translation("Content type"),
  *   handlers = {
  *     "storage" = "Drupal\node\NodeStorage",
diff --git a/core/modules/system/src/Tests/Plugin/Discovery/AnnotatedClassDiscoveryTest.php b/core/modules/system/src/Tests/Plugin/Discovery/AnnotatedClassDiscoveryTest.php
index d038dbf2ce2e8d43b28379536c75d4765cbb442a..d79db4da8a1ba1aaedb2ed3c467cabffd619838b 100644
--- a/core/modules/system/src/Tests/Plugin/Discovery/AnnotatedClassDiscoveryTest.php
+++ b/core/modules/system/src/Tests/Plugin/Discovery/AnnotatedClassDiscoveryTest.php
@@ -32,6 +32,11 @@ protected function setUp() {
         'color' => 'yellow',
         'uses' => array(
           'bread' => t('Banana bread'),
+          'loaf' => array(
+            'singular' => '@count loaf',
+            'plural' => '@count loaves',
+            'context' => NULL,
+          ),
         ),
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Banana',
         'provider' => 'plugin_test',
diff --git a/core/modules/system/src/Tests/Plugin/Discovery/CustomDirectoryAnnotatedClassDiscoveryTest.php b/core/modules/system/src/Tests/Plugin/Discovery/CustomDirectoryAnnotatedClassDiscoveryTest.php
index 39e7caf22345fc74ee308a0bed8fddba970c3505..2d33070495b4ecc990577b19b71137e7314be80e 100644
--- a/core/modules/system/src/Tests/Plugin/Discovery/CustomDirectoryAnnotatedClassDiscoveryTest.php
+++ b/core/modules/system/src/Tests/Plugin/Discovery/CustomDirectoryAnnotatedClassDiscoveryTest.php
@@ -46,6 +46,11 @@ protected function setUp() {
         'color' => 'yellow',
         'uses' => array(
           'bread' => t('Banana bread'),
+          'loaf' => array(
+            'singular' => '@count loaf',
+            'plural' => '@count loaves',
+            'context' => NULL,
+          ),
         ),
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Banana',
         'provider' => 'plugin_test',
diff --git a/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/fruit/Banana.php b/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/fruit/Banana.php
index 5e4d180e442e12993726c94b549b8d259925f5ca..467db5f47c42a5e947bcc64c10978e8939bb20c9 100644
--- a/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/fruit/Banana.php
+++ b/core/modules/system/tests/modules/plugin_test/src/Plugin/plugin_test/fruit/Banana.php
@@ -13,7 +13,11 @@
  *   label = "Banana",
  *   color = "yellow",
  *   uses = {
- *     "bread" = @Translation("Banana bread")
+ *     "bread" = @Translation("Banana bread"),
+ *     "loaf" = @PluralTranslation(
+ *       singular = "@count loaf",
+ *       plural = "@count loaves"
+ *     )
  *   }
  * )
  */
diff --git a/core/tests/Drupal/Tests/Core/Annotation/PluralTranslationTest.php b/core/tests/Drupal/Tests/Core/Annotation/PluralTranslationTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e62c9b8cde90869a90dc8d38a1c8aed0f3bfc322
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Annotation/PluralTranslationTest.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Annotation\PluralTranslationTest.
+ */
+
+namespace Drupal\Tests\Core\Annotation;
+
+use Drupal\Core\Annotation\PluralTranslation;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Annotation\PluralTranslation
+ * @group Annotation
+ */
+class PluralTranslationTest extends UnitTestCase {
+
+  /**
+   * @covers ::get
+   *
+   * @dataProvider providerTestGet
+   */
+  public function testGet(array $values) {
+    $annotation = new PluralTranslation($values);
+
+    $default_values = [
+      'context' => NULL,
+    ];
+    $this->assertEquals($values + $default_values, $annotation->get());
+  }
+
+  /**
+   * Provides data to self::testGet().
+   */
+  public function providerTestGet() {
+    $data = [];
+    $data[] = [
+      [
+        'singular' => $this->randomMachineName(),
+        'plural' => $this->randomMachineName(),
+        'context' => $this->randomMachineName(),
+      ],
+    ];
+    $data[] = [
+      [
+        'singular' => $this->randomMachineName(),
+        'plural' => $this->randomMachineName(),
+      ],
+    ];
+
+    return $data;
+  }
+
+  /**
+   * @dataProvider providerTestMissingData
+   */
+  public function testMissingData($data) {
+    $this->setExpectedException(\InvalidArgumentException::class);
+    new PluralTranslation($data);
+  }
+
+  public function providerTestMissingData() {
+    $data = [];
+    $data['all-missing'] = [[]];
+    $data['singular-missing'] = [['plural' => 'muh']];
+    $data['plural-missing'] = [['singular' => 'muh']];
+    return $data;
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php
index 41dcceec4a0ac298f0f39af9f8bc619caa598c57..b650ddcc5bc3571d4439314c39674545fb5bfb7f 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php
@@ -314,6 +314,66 @@ public function testGetGroupLabel() {
     $this->assertSame($default_label, $entity_type->getGroupLabel());
   }
 
+  /**
+   * @covers ::getSingularLabel
+   */
+  public function testGetSingularLabel() {
+    $translatable_label = new TranslatableMarkup('entity test singular', [], [], $this->getStringTranslationStub());
+    $entity_type = $this->setUpEntityType(['label_singular' => $translatable_label]);
+    $entity_type->setStringTranslation($this->getStringTranslationStub());
+    $this->assertEquals('entity test singular', $entity_type->getSingularLabel());
+  }
+
+  /**
+   * @covers ::getSingularLabel
+   */
+  public function testGetSingularLabelDefault() {
+    $entity_type = $this->setUpEntityType(['label' => 'Entity test Singular']);
+    $entity_type->setStringTranslation($this->getStringTranslationStub());
+    $this->assertEquals('entity test singular', $entity_type->getSingularLabel());
+  }
+
+  /**
+   * @covers ::getPluralLabel
+   */
+  public function testGetPluralLabel() {
+    $translatable_label = new TranslatableMarkup('entity test plural', [], [], $this->getStringTranslationStub());
+    $entity_type = $this->setUpEntityType(['label_plural' => $translatable_label]);
+    $entity_type->setStringTranslation($this->getStringTranslationStub());
+    $this->assertEquals('entity test plural', $entity_type->getPluralLabel());
+  }
+
+  /**
+   * @covers ::getPluralLabel
+   */
+  public function testGetPluralLabelDefault() {
+    $entity_type = $this->setUpEntityType(['label' => 'Entity test Plural']);
+    $entity_type->setStringTranslation($this->getStringTranslationStub());
+    $this->assertEquals('entity test plural entities', $entity_type->getPluralLabel());
+  }
+
+  /**
+   * @covers ::getCountLabel
+   */
+  public function testGetCountLabel() {
+    $entity_type = $this->setUpEntityType(['label_count' => ['singular' => 'one entity test', 'plural' => '@count entity test']]);
+    $entity_type->setStringTranslation($this->getStringTranslationStub());
+    $this->assertEquals('one entity test', $entity_type->getCountLabel(1));
+    $this->assertEquals('2 entity test', $entity_type->getCountLabel(2));
+    $this->assertEquals('200 entity test', $entity_type->getCountLabel(200));
+  }
+
+  /**
+   * @covers ::getCountLabel
+   */
+  public function testGetCountLabelDefault() {
+    $entity_type = $this->setUpEntityType(['label' => 'Entity test Plural']);
+    $entity_type->setStringTranslation($this->getStringTranslationStub());
+    $this->assertEquals('1 entity test plural', $entity_type->getCountLabel(1));
+    $this->assertEquals('2 entity test plural entities', $entity_type->getCountLabel(2));
+    $this->assertEquals('200 entity test plural entities', $entity_type->getCountLabel(200));
+  }
+
   /**
    * Gets a mock controller class name.
    *
diff --git a/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php b/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php
index 180fd88db7ad21966145fe63e6edc1ad332c7aab..dab993a5e56ded2c9a5977bb712aa72686396005 100644
--- a/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php
@@ -49,6 +49,10 @@ protected function setUp() {
         'color' => 'yellow',
         'uses' => array(
           'bread' => 'Banana bread',
+          'loaf' => array(
+            'singular' => '@count loaf',
+            'plural' => '@count loaves',
+          ),
         ),
         'class' => 'Drupal\plugin_test\Plugin\plugin_test\fruit\Banana',
       ),