diff --git a/core/config/schema/core.entity.schema.yml b/core/config/schema/core.entity.schema.yml index 8850d40ef7..aa64a5655b 100644 --- a/core/config/schema/core.entity.schema.yml +++ b/core/config/schema/core.entity.schema.yml @@ -375,6 +375,24 @@ field.formatter.settings.entity_reference_label: type: boolean label: 'Link label to the referenced entity' +bundle_entity_with_plural_labels: + type: config_entity + label: 'Bundle' + mapping: + label_singular: + type: label + label: 'Indefinite singular name' + label_plural: + type: label + label: 'Indefinite plural name' + label_count: + type: sequence + nullable: true + label: 'Count labels' + sequence: + type: plural_label + label: 'Count label' + block.settings.field_block:*:*:*: type: block_settings mapping: diff --git a/core/lib/Drupal/Core/Config/Entity/EntityBundleWithPluralLabelsInterface.php b/core/lib/Drupal/Core/Config/Entity/EntityBundleWithPluralLabelsInterface.php new file mode 100644 index 0000000000..1be187211c --- /dev/null +++ b/core/lib/Drupal/Core/Config/Entity/EntityBundleWithPluralLabelsInterface.php @@ -0,0 +1,40 @@ + "1 article\x03@count article", + * 'items_found' => "1 article was found\x03@count articles were found", + * ] + * @endcode + * Note that the context ('default', 'items_found') is an arbitrary string + * identifier used to retrieve the desired version. If the array keys are + * missed, the array item integer index is used as variant ID: + * @code + * [ + * "1 item\x03@count items", + * ] + * @endcode + * Each value is a definite singular/plural count label with the plural + * variants separated by ETX (PoItem::DELIMITER). + * + * @var string[]|null + * + * @see \Drupal\Component\Gettext\PoItem::DELIMITER + */ + protected $label_count; + + /** + * {@inheritdoc} + */ + public function setSingularLabel(string $singular_label): EntityBundleWithPluralLabelsInterface { + $this->label_singular = $singular_label; + return $this; + } + + /** + * {@inheritdoc} + */ + public function setPluralLabel(string $plural_label): EntityBundleWithPluralLabelsInterface { + $this->label_plural = $plural_label; + return $this; + } + + /** + * {@inheritdoc} + */ + public function setCountLabel(array $count_label): EntityBundleWithPluralLabelsInterface { + $this->label_count = $count_label; + return $this; + } + +} diff --git a/core/lib/Drupal/Core/Entity/EntityTypeBundleInfo.php b/core/lib/Drupal/Core/Entity/EntityTypeBundleInfo.php index aa917c845c..4f268cc96d 100644 --- a/core/lib/Drupal/Core/Entity/EntityTypeBundleInfo.php +++ b/core/lib/Drupal/Core/Entity/EntityTypeBundleInfo.php @@ -2,9 +2,12 @@ namespace Drupal\Core\Entity; +use Drupal\Component\Gettext\PoItem; +use Drupal\Component\Render\FormattableMarkup; use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Cache\UseCacheBackendTrait; +use Drupal\Core\Config\Entity\EntityBundleWithPluralLabelsInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Language\LanguageManagerInterface; use Drupal\Core\TypedData\TypedDataManagerInterface; @@ -98,6 +101,13 @@ public function getAllBundleInfo() { if ($bundle_entity_type = $entity_type->getBundleEntityType()) { foreach ($this->entityTypeManager->getStorage($bundle_entity_type)->loadMultiple() as $entity) { $this->bundleInfo[$type][$entity->id()]['label'] = $entity->label(); + if ($entity instanceof EntityBundleWithPluralLabelsInterface) { + $this->bundleInfo[$type][$entity->id()] += [ + 'label_singular' => $entity->get('label_singular'), + 'label_plural' => $entity->get('label_plural'), + 'label_count' => $entity->get('label_count'), + ]; + } } } // If entity type bundles are not supported and @@ -125,4 +135,62 @@ public function clearCachedBundles() { $this->typedDataManager->clearCachedDefinitions(); } + /** + * {@inheritdoc} + */ + public function getBundleCountLabel(string $entity_type_id, string $bundle, int $count, $variant): ?string { + if (!$bundles_info = $this->getBundleInfo($entity_type_id)) { + throw new \InvalidArgumentException("The '{$entity_type_id}' doesn't exist."); + } + $bundle_info = $bundles_info[$bundle] ?? NULL; + if (!$bundle_info) { + throw new \InvalidArgumentException("The '{$entity_type_id}' entity type bundle '{$bundle}' doesn't exist."); + } + + if (!empty($bundle_info['label_count'])) { + if (empty($bundle_info['label_count'][$variant])) { + throw new \InvalidArgumentException("There's no variant '{$variant}' defined in label_count for bundle '{$bundle}' of '{$entity_type_id}' entity type."); + } + + $index = static::getPluralIndex($count); + if ($index === -1) { + // If the index cannot be computed, fallback to a single plural variant. + $index = $count > 1 ? 1 : 0; + } + + $label_count = explode(PoItem::DELIMITER, $bundle_info['label_count'][$variant]); + if (!empty($label_count[$index])) { + return new FormattableMarkup($label_count[$index], ['@count' => $count]); + } + } + + return NULL; + } + + /** + * Gets the plural index through the gettext formula. + * + * @param int $count + * Number to return plural for. + * + * @return int + * The numeric index of the plural variant to use for the current language + * and the given $count number or -1 if the language was not found or does + * not have a plural formula. + * + * @todo Remove this method when https://www.drupal.org/node/2766857 gets in. + */ + protected static function getPluralIndex(int $count): int { + // We have to test both if the function and the service exist since in + // certain situations it is possible that locale code might be loaded but + // the service does not exist. For example, where the parent test site has + // locale installed but the child site does not. + // @todo Refactor in https://www.drupal.org/node/2660338 so this code does + // not depend on knowing that the Locale module exists. + if (function_exists('locale_get_plural') && \Drupal::hasService('locale.plural.formula')) { + return locale_get_plural($count); + } + return -1; + } + } diff --git a/core/lib/Drupal/Core/Entity/EntityTypeBundleInfoInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeBundleInfoInterface.php index 2789b651e5..7baea06b5e 100644 --- a/core/lib/Drupal/Core/Entity/EntityTypeBundleInfoInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityTypeBundleInfoInterface.php @@ -37,4 +37,30 @@ public function getBundleInfo($entity_type_id); */ public function clearCachedBundles(); + /** + * Gets the count label for a given bundle. + * + * @param string $entity_type_id + * The bundle's entity type ID. + * @param string $bundle + * The bundle. + * @param int $count + * The item count to display if the plural form was requested. + * @param string|int $variant + * The variant of the count label. This is actually the, string or integer, + * array item key corresponding to the count label variant. A bundle can + * define unlimited definite singular/plural count labels in order to cover + * various contexts where they are used. Pass the variant, as a string or + * integer identifier, to get the appropriate version of the count label. + * + * @return string|null + * The count label. NULL is returned in one of the following cases: + * - The bundle didn't define a 'label_count' variant list. + * - There's no plural formula for the given $count. + * + * @throws \InvalidArgumentException + * If the passed entity type, bundle or count label variant doesn't exist. + */ + public function getBundleCountLabel(string $entity_type_id, string $bundle, int $count, $variant): ?string; + } diff --git a/core/lib/Drupal/Core/Entity/entity.api.php b/core/lib/Drupal/Core/Entity/entity.api.php index 0b163dc3a0..b47d3a2a93 100644 --- a/core/lib/Drupal/Core/Entity/entity.api.php +++ b/core/lib/Drupal/Core/Entity/entity.api.php @@ -840,6 +840,31 @@ function hook_entity_view_mode_info_alter(&$view_modes) { * An associative array of all entity bundles, keyed by the entity * type name, and then the bundle name, with the following keys: * - label: The human-readable name of the bundle. + * - label_singular: (optional) A translated string with the indefinite + * singular label of the bundle. This is typically a lowercase label but it + * can be also translated as a capitalized label for languages such as + * German. For instance, given a bundle with the ID 'fruit', the singular + * label would be 'fruit' but for German translation will be capitalized as + * 'Obst'. + * - label_plural: (optional) A translated string with the indefinite plural + * label of the bundle. The same capitalization rules should be applied as + * for 'label_singular'. + * - label_count: (optional) An array containing one or more count label + * variants for this bundle. Depending on context a site might need + * different versions of the count label. For instance, a site will define + * the following variants, depending on the use-case: + * - On a regular page: @code 1 article\x03@count articles @endcode. + * - On a search results page: @code 1 article was found\x03@count articles + * were found @encode. Note that we cannot build this version by deriving + * the first one because of the was/were forms. + * - On a different page: @code 1 + * article\x03@count articles @endcode. + * - On other pages the count might be displayed in a different @code
+ * @encode or even in a different theme hook, so it needs a variant + * without the count part: @code Article\x03Articles @encode. + * A meaningful key may be added to each variant in order to allow a more + * developer friendly identification. If the key is missed, the array index + * value should be used to identify which count plural variant to be used. * - uri_callback: (optional) The same as the 'uri_callback' key defined for * the entity type in the EntityTypeManager, but for the bundle only. When * determining the URI of an entity, if a 'uri_callback' is defined for both @@ -852,6 +877,16 @@ function hook_entity_view_mode_info_alter(&$view_modes) { */ function hook_entity_bundle_info() { $bundles['user']['user']['label'] = t('User'); + $bundles['user']['user']['label_singular'] = t('user'); + $bundles['user']['user']['label_plural'] = t('users'); + $bundles['user']['user']['label_count'] = [ + // This variant misses a key and is identifiable by its array delta. + t('1 user\x03@count users'), + // The following variants have a meaningful identifier as key. + 'search results' => t('1 user is found\x03@count users were found'), + 'with_markup' => t('1 user\x03@count users'), + 'no count' => t('User\x03Users'), + ]; return $bundles; } @@ -859,13 +894,16 @@ function hook_entity_bundle_info() { * Alter the bundles for entity types. * * @param array $bundles - * An array of bundles, keyed first by entity type, then by bundle name. + * An array of bundles, keyed first by entity type, then by bundle name. Each + * value is an array with the same structure as the hook_entity_bundle_info() + * return array element. * * @see Drupal\Core\Entity\EntityTypeBundleInfo::getBundleInfo() * @see hook_entity_bundle_info() */ function hook_entity_bundle_info_alter(&$bundles) { $bundles['user']['user']['label'] = t('Full account'); + $bundles['user']['user']['label_count']['search results'] = t('1 user is registered\x03@count users are registered'); } /** diff --git a/core/modules/book/config/optional/node.type.book.yml b/core/modules/book/config/optional/node.type.book.yml index 0c07a79e8f..ada815bb6d 100644 --- a/core/modules/book/config/optional/node.type.book.yml +++ b/core/modules/book/config/optional/node.type.book.yml @@ -11,3 +11,7 @@ help: '' new_revision: true preview_mode: 1 display_submitted: true +label_singular: 'book page' +label_plural: 'book pages' +label_count: + - "1 book page\x03@count book pages" diff --git a/core/modules/forum/config/optional/node.type.forum.yml b/core/modules/forum/config/optional/node.type.forum.yml index 8ed965df3f..5e0db03df4 100644 --- a/core/modules/forum/config/optional/node.type.forum.yml +++ b/core/modules/forum/config/optional/node.type.forum.yml @@ -11,3 +11,7 @@ help: '' new_revision: false preview_mode: 1 display_submitted: true +label_singular: 'forum topic' +label_plural: 'forum topics' +label_count: + - "1 forum topic\x03@count forum topics" diff --git a/core/modules/jsonapi/tests/src/Functional/NodeTypeTest.php b/core/modules/jsonapi/tests/src/Functional/NodeTypeTest.php index c7bc3e2223..85a9afe77b 100644 --- a/core/modules/jsonapi/tests/src/Functional/NodeTypeTest.php +++ b/core/modules/jsonapi/tests/src/Functional/NodeTypeTest.php @@ -91,6 +91,9 @@ protected function getExpectedDocument() { 'display_submitted' => TRUE, 'help' => NULL, 'langcode' => 'en', + 'label_count' => NULL, + 'label_plural' => NULL, + 'label_singular' => NULL, 'name' => 'Camelids', 'new_revision' => TRUE, 'preview_mode' => 1, diff --git a/core/modules/node/config/schema/node.schema.yml b/core/modules/node/config/schema/node.schema.yml index 2a831d7678..0fa84e78be 100644 --- a/core/modules/node/config/schema/node.schema.yml +++ b/core/modules/node/config/schema/node.schema.yml @@ -9,7 +9,7 @@ node.settings: label: 'Use administration theme when editing or creating content' node.type.*: - type: config_entity + type: bundle_entity_with_plural_labels label: 'Content type' mapping: name: diff --git a/core/modules/node/migrations/d6_node_type.yml b/core/modules/node/migrations/d6_node_type.yml index 3afe4bc475..063fb16fb8 100644 --- a/core/modules/node/migrations/d6_node_type.yml +++ b/core/modules/node/migrations/d6_node_type.yml @@ -8,6 +8,7 @@ source: constants: preview: 1 # DRUPAL_OPTIONAL create_body: false + null_value: null process: type: type name: name @@ -23,5 +24,8 @@ process: create_body_label: body_label 'third_party_settings/menu_ui/available_menus': available_menus 'third_party_settings/menu_ui/parent': parent + label_singular: 'constants/null_value' + label_plural: 'constants/null_value' + label_count: 'constants/null_value' destination: plugin: entity:node_type diff --git a/core/modules/node/migrations/d7_node_type.yml b/core/modules/node/migrations/d7_node_type.yml index 1be9cd1ccc..23deba9c82 100644 --- a/core/modules/node/migrations/d7_node_type.yml +++ b/core/modules/node/migrations/d7_node_type.yml @@ -7,6 +7,7 @@ source: plugin: d7_node_type constants: preview: 1 # DRUPAL_OPTIONAL + null_value: null process: type: type name: name @@ -20,5 +21,8 @@ process: create_body_label: body_label 'third_party_settings/menu_ui/available_menus': available_menus 'third_party_settings/menu_ui/parent': parent + label_singular: 'constants/null_value' + label_plural: 'constants/null_value' + label_count: 'constants/null_value' destination: plugin: entity:node_type diff --git a/core/modules/node/node.post_update.php b/core/modules/node/node.post_update.php index 913137757c..ab24dc40f7 100644 --- a/core/modules/node/node.post_update.php +++ b/core/modules/node/node.post_update.php @@ -5,7 +5,9 @@ * Post update functions for Node. */ +use Drupal\Core\Config\Entity\ConfigEntityUpdater; use Drupal\Core\Entity\Entity\EntityFormDisplay; +use Drupal\node\NodeTypeInterface; /** * Load all form displays for nodes, add status with these settings, save. @@ -34,3 +36,16 @@ function node_post_update_configure_status_field_widget() { function node_post_update_node_revision_views_data() { // Empty post-update hook. } + +/** + * Add plural label variants to node-type entities. + */ +function node_post_update_plural_variants(array &$sandbox): void { + \Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'node_type', function (NodeTypeInterface $node_type): bool { + $node_type + ->set('label_singular', NULL) + ->set('label_plural', NULL) + ->set('label_count', NULL); + return TRUE; + }); +} diff --git a/core/modules/node/src/Entity/NodeType.php b/core/modules/node/src/Entity/NodeType.php index 60bfc7df77..69c187ecf9 100644 --- a/core/modules/node/src/Entity/NodeType.php +++ b/core/modules/node/src/Entity/NodeType.php @@ -3,6 +3,7 @@ namespace Drupal\node\Entity; use Drupal\Core\Config\Entity\ConfigEntityBundleBase; +use Drupal\Core\Config\Entity\EntityBundleWithPluralLabelsTrait; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\node\NodeTypeInterface; @@ -48,11 +49,16 @@ * "new_revision", * "preview_mode", * "display_submitted", + * "label_singular", + * "label_plural", + * "label_count", * } * ) */ class NodeType extends ConfigEntityBundleBase implements NodeTypeInterface { + use EntityBundleWithPluralLabelsTrait; + /** * The machine name of this node type. * diff --git a/core/modules/node/src/NodeTypeInterface.php b/core/modules/node/src/NodeTypeInterface.php index 17dcd63db2..39b7b6f154 100644 --- a/core/modules/node/src/NodeTypeInterface.php +++ b/core/modules/node/src/NodeTypeInterface.php @@ -3,12 +3,13 @@ namespace Drupal\node; use Drupal\Core\Config\Entity\ConfigEntityInterface; +use Drupal\Core\Config\Entity\EntityBundleWithPluralLabelsInterface; use Drupal\Core\Entity\RevisionableEntityBundleInterface; /** * Provides an interface defining a node type entity. */ -interface NodeTypeInterface extends ConfigEntityInterface, RevisionableEntityBundleInterface { +interface NodeTypeInterface extends ConfigEntityInterface, RevisionableEntityBundleInterface, EntityBundleWithPluralLabelsInterface { /** * Determines whether the node type is locked. diff --git a/core/modules/node/tests/src/Functional/Rest/NodeTypeResourceTestBase.php b/core/modules/node/tests/src/Functional/Rest/NodeTypeResourceTestBase.php index d74824dd13..dbc36cfc08 100644 --- a/core/modules/node/tests/src/Functional/Rest/NodeTypeResourceTestBase.php +++ b/core/modules/node/tests/src/Functional/Rest/NodeTypeResourceTestBase.php @@ -59,6 +59,9 @@ protected function getExpectedNormalizedEntity() { 'description' => 'Camelids are large, strictly herbivorous animals with slender necks and long legs.', 'display_submitted' => TRUE, 'help' => NULL, + 'label_count' => NULL, + 'label_plural' => NULL, + 'label_singular' => NULL, 'langcode' => 'en', 'name' => 'Camelids', 'new_revision' => TRUE, diff --git a/core/modules/node/tests/src/Functional/Update/NodeUpdateBundlePluralLabelsTest.php b/core/modules/node/tests/src/Functional/Update/NodeUpdateBundlePluralLabelsTest.php new file mode 100644 index 0000000000..b828451048 --- /dev/null +++ b/core/modules/node/tests/src/Functional/Update/NodeUpdateBundlePluralLabelsTest.php @@ -0,0 +1,47 @@ +databaseDumpFiles[] = __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.8.0.bare.standard.php.gz'; + } + + /** + * Tests node_post_update_plural_variants(). + * + * @see node_post_update_plural_variants() + */ + public function testPostUpdatePluralVariants() { + $properties = ['label_singular', 'label_plural', 'label_count']; + + // Check that plural label variant properties are not present before update. + $node_type = $this->config('node.type.page')->getRawData(); + foreach ($properties as $property) { + $this->assertArrayNotHasKey($property, $node_type); + } + + $this->runUpdates(); + + // Check that plural label variant properties were added as NULL. + $node_type = $this->config('node.type.page')->getRawData(); + foreach ($properties as $property) { + $this->assertArrayHasKey($property, $node_type); + $this->assertNull($node_type[$property]); + } + } + +} diff --git a/core/modules/system/tests/modules/entity_bundle_test/entity_bundle_test.info.yml b/core/modules/system/tests/modules/entity_bundle_test/entity_bundle_test.info.yml new file mode 100644 index 0000000000..660bf0c41e --- /dev/null +++ b/core/modules/system/tests/modules/entity_bundle_test/entity_bundle_test.info.yml @@ -0,0 +1,4 @@ +name: 'Entity bundle test' +type: module +description: 'Provides support for testing entity bundles.' +package: Testing diff --git a/core/modules/system/tests/modules/entity_bundle_test/entity_bundle_test.module b/core/modules/system/tests/modules/entity_bundle_test/entity_bundle_test.module new file mode 100644 index 0000000000..bc85eb9027 --- /dev/null +++ b/core/modules/system/tests/modules/entity_bundle_test/entity_bundle_test.module @@ -0,0 +1,60 @@ +set('bundle_entity_type', 'entity_test_bundle_plural_labels'); +} + +/** + * Implements hook_entity_bundle_info(). + */ +function entity_bundle_test_entity_bundle_info(): array { + return [ + 'entity_test' => [ + 'artist' => [ + 'label' => t('Artist'), + 'label_singular' => t('artist'), + 'label_plural' => t('artists'), + 'label_count' => [ + "1 artist\x03@count artists", + 'search results' => "1 artist was awarded\x03@count artists were awarded", + ], + ], + ], + ]; +} + +/** + * Implements hook_entity_bundle_info_alter(). + */ +function entity_bundle_test_entity_bundle_info_alter(array &$bundles): void { + // Allow the test to trigger altering. + // @see \Drupal\KernelTests\Core\Entity\EntityTypeBundleInfoPluralLabelTest + if (\Drupal::state()->get('entity_bundle_test.allow_alter', FALSE)) { + // Bundle defined via config entity. + $bundles['entity_test_with_bundle']['article']['label_singular'] = 'article item'; + $bundles['entity_test_with_bundle']['article']['label_plural'] = 'article items'; + $bundles['entity_test_with_bundle']['article']['label_count'] = [ + "1 article item\x03@count article items", + 'search results' => "1 article item was found\x03@count article items were found", + ]; + + // Bundle defined via hook_entity_bundle_info(). + $bundles['entity_test']['artist']['label_singular'] = 'creator'; + $bundles['entity_test']['artist']['label_plural'] = 'creators'; + $bundles['entity_test']['artist']['label_count'] = [ + "1 creator\x03@count creators", + 'search results' => "1 creator was awarded\x03@count creators were awarded", + ]; + } +} diff --git a/core/modules/system/tests/modules/entity_test/config/schema/entity_test.schema.yml b/core/modules/system/tests/modules/entity_test/config/schema/entity_test.schema.yml index d34d94940d..2c57636cbb 100644 --- a/core/modules/system/tests/modules/entity_test/config/schema/entity_test.schema.yml +++ b/core/modules/system/tests/modules/entity_test/config/schema/entity_test.schema.yml @@ -27,3 +27,13 @@ entity_test.entity_test_bundle.*: description: type: text label: 'Description' + +entity_test.entity_test_bundle_plural_labels.*: + type: bundle_entity_with_plural_labels + mapping: + label: + type: label + label: 'Label' + id: + type: string + label: 'Machine-readable name' diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestBundleWithPluralLabels.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestBundleWithPluralLabels.php new file mode 100644 index 0000000000..93a7a19349 --- /dev/null +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestBundleWithPluralLabels.php @@ -0,0 +1,33 @@ +bundleInfo = $this->container->get('entity_type.bundle.info'); + } + + /** + * Tests singular, plural and count labels for bundles as config entities. + * + * @covers ::getAllBundleInfo + */ + public function testLabelsForBundlesAsConfigEntities(): void { + // Test bundles stored in config entities. + $this->setBundle('entity_test_with_bundle', 'article'); + + $bundle_entity = EntityTestBundleWithPluralLabels::create([ + 'id' => 'article', + 'label' => 'Article', + ]); + $bundle_entity->save(); + + // Check that bundle info returns no label with bundle undefined labels. + $this->assertSingularLabel(NULL); + $this->assertPluralLabel(NULL); + $this->assertCountLabel(1, 0, NULL); + $this->assertCountLabel(100, 0, NULL); + $this->assertCountLabel(1, 'search results', NULL); + $this->assertCountLabel(100, 'search results', NULL); + + // Set singular, plural and count labels on the bundle entity. + $bundle_entity + ->setSingularLabel('article') + ->setPluralLabel('articles') + ->setCountLabel([ + "1 article\x03@count articles", + 'search results' => "1 article was found\x03@count articles were found", + ]) + ->save(); + + // Check that labels are correctly returned. + $this->assertSingularLabel('article'); + $this->assertPluralLabel('articles'); + $this->assertCountLabel(1, 0, '1 article'); + $this->assertCountLabel(100, 0, '100 articles'); + $this->assertCountLabel(1, 'search results', '1 article was found'); + $this->assertCountLabel(100, 'search results', '100 articles were found'); + + // Allow altering the labels via hook_entity_bundle_info_alter(). + // @see \entity_bundle_test_entity_bundle_info_alter() + \Drupal::state()->set('entity_bundle_test.allow_alter', TRUE); + // Also, clear the bundle info cache, to get fresh bundle definitions. + $this->bundleInfo->clearCachedBundles(); + + // Check that altered labels are correctly returned. + $this->assertSingularLabel('article item'); + $this->assertPluralLabel('article items'); + $this->assertCountLabel(1, 0, '1 article item'); + $this->assertCountLabel(100, 0, '100 article items'); + $this->assertCountLabel(1, 'search results', '1 article item was found'); + $this->assertCountLabel(100, 'search results', '100 article items were found'); + } + + /** + * Tests singular, plural and count labels for bundles defined in code. + * + * @covers ::getAllBundleInfo + */ + public function testLabelsForBundlesWithoutConfigEntities(): void { + // Test entities with bundles not stored in config entities. + // @see entity_bundle_test_entity_bundle_info() + $this->setBundle('entity_test', 'artist'); + + // Check that labels are correctly returned. + $this->assertSingularLabel('artist'); + $this->assertPluralLabel('artists'); + $this->assertCountLabel(1, 0, '1 artist'); + $this->assertCountLabel(100, 0, '100 artists'); + $this->assertCountLabel(1, 'search results', '1 artist was awarded'); + $this->assertCountLabel(100, 'search results', '100 artists were awarded'); + + // Allow altering the labels via hook_entity_bundle_info_alter(). + // @see \entity_bundle_test_entity_bundle_info_alter() + \Drupal::state()->set('entity_bundle_test.allow_alter', TRUE); + // Also, clear the bundle info cache, to get fresh bundle definitions. + $this->bundleInfo->clearCachedBundles(); + + // Check that altered labels are correctly returned. + $this->assertSingularLabel('creator'); + $this->assertPluralLabel('creators'); + $this->assertCountLabel(1, 0, '1 creator'); + $this->assertCountLabel(100, 0, '100 creators'); + $this->assertCountLabel(1, 'search results', '1 creator was awarded'); + $this->assertCountLabel(100, 'search results', '100 creators were awarded'); + } + + /** + * Test passing a wrong entity type to ::getBundleCountLabel(). + * + * @covers ::getBundleCountLabel + */ + public function testGetBundleCountLabelWithWrongEntityType(): void { + $this->expectExceptionObject(new \InvalidArgumentException("The 'nonexistent_entity_type' doesn't exist.")); + $this->bundleInfo->getBundleCountLabel('nonexistent_entity_type', 'article', 123, 'default'); + } + + /** + * Test passing a wrong bundle type to ::getBundleCountLabel(). + * + * @covers ::getBundleCountLabel + */ + public function testGetBundleCountLabelWithWrongBundle(): void { + $this->expectExceptionObject(new \InvalidArgumentException("The 'entity_test' entity type bundle 'nonexistent_bundle' doesn't exist.")); + $this->bundleInfo->getBundleCountLabel('entity_test', 'nonexistent_bundle', 123, 'default'); + } + + /** + * Test passing a wrong bundle count label variant to ::getBundleCountLabel(). + * + * @covers ::getBundleCountLabel + */ + public function testGetBundleCountLabelWithWrongVariant(): void { + EntityTestBundleWithPluralLabels::create([ + 'id' => 'article', + 'label' => 'Article', + 'label_count' => [ + 'default' => "1 item\x03@count items", + ], + ])->save(); + + $this->expectExceptionObject(new \InvalidArgumentException("There's no variant 'nonexistent_variant' defined in label_count for bundle 'article' of 'entity_test_with_bundle' entity type.")); + $this->bundleInfo->getBundleCountLabel('entity_test_with_bundle', 'article', 123, 'nonexistent_variant'); + } + + /** + * Asserts that a given bundle has an expected singular label. + * + * @param string|null $expected_singular_label + * The expected bundle singular label. + */ + protected function assertSingularLabel(?string $expected_singular_label): void { + $bundle_info = $this->bundleInfo->getBundleInfo($this->entityTypeId)[$this->bundle]; + $this->assertEquals($expected_singular_label, $bundle_info['label_singular']); + } + + /** + * Asserts that a given bundle has an expected plural label. + * + * @param string|null $expected_plural_label + * The expected bundle plural label. + * + * @throws \Exception + */ + protected function assertPluralLabel(?string $expected_plural_label): void { + $bundle_info = $this->bundleInfo->getBundleInfo($this->entityTypeId)[$this->bundle]; + $this->assertEquals($expected_plural_label, $bundle_info['label_plural']); + } + + /** + * Asserts an expected count label on a given bundle, count and variant. + * + * @param int $count + * The count. + * @param string|int $variant + * The count label variant ID. + * @param string|null $expected_count_label + * The expected count label. + */ + protected function assertCountLabel(int $count, $variant, ?string $expected_count_label): void { + $actual_count_label = $this->bundleInfo->getBundleCountLabel($this->entityTypeId, $this->bundle, $count, $variant); + $this->assertEquals($expected_count_label, $actual_count_label); + } + + /** + * Sets the bundle being tested. + * + * @param string $entity_type_id + * The bundle's entity type ID. + * @param string $bundle + * The bundle. + */ + protected function setBundle(string $entity_type_id, string $bundle): void { + $this->entityTypeId = $entity_type_id; + $this->bundle = $bundle; + } + +}