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 @@ +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 @@ + 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', ),