Booking units¶
A hall offers places, a refuge offers nights, a bike shop offers bikes. What a booking takes has no single name, so the engine does not pick one: the noun is a plugin, and a resource says which one it offers.
Why this is a plugin and not a label¶
Three simpler designs were tried against the same requirement and each fails it.
One universal word. Whatever word the engine picked would be wrong somewhere. "Place" is wrong for everything but a seat map, and "unit" is wrong for a refuge that offers nights.
A label typed on the resource. Config Translation cannot express a plural, so
a site typing its own noun gets "4 place" in French. The wording has to live in
code, where it goes through a module's .po like every other string.
A fixed list. A site offering something the engine never thought of could not add it. Anything a site can extend has to be a plugin here.
So a booking unit is a plugin that owns both of its forms. That is what keeps a plural out of every call site: nothing anywhere writes "@count places", because only the unit knows how its own noun pluralizes.
The units that ship¶
| Unit | Ships in | Reads as |
|---|---|---|
unit |
yoyaku |
1 unit, 4 units |
place |
yoyaku_placement |
1 place, 4 places |
The engine ships only the neutral unit. The place unit lives in
yoyaku_placement, beside the entity it names, so the word leaves the engine
entirely: a place is one specific bookable among many, and the module named after
it is where it belongs.
The neutral unit is never the best word and never the wrong one, which is what the engine needs as a default: a hall and a refuge both read correctly only once they say what they offer, and until then neither is misnamed.
Which unit a resource gets¶
Four widths, narrowest first. The first one that names a unit wins.
- The resource, in its Booking unit field.
- Its resource type, for every resource of that type.
- A module that recognizes the resource.
yoyaku_placementanswersplacefor a resource running in a venue, so a venue reads "4 places" on its own seat map with nothing configured. - The neutral unit, when nothing above said anything.
Leaving the field empty is what asks for the next width, exactly as the booking mode already works. An operator who names a unit always wins over a module default, so a hall offering its boxes by the table can say so.
A unit that has gone (a module removed, a plugin id changed) reads as the neutral unit rather than breaking the page. Unlike a missing constraint policy, a missing noun cannot overbook, so failing open is safe here: the worst outcome is a card reading "4 units" until somebody picks the noun again.
Where the unit is used, and where it is not¶
A call site holding a resource asks it for its unit. One that does not stays neutral, and that is a design rule rather than an oversight:
- A base field description is written once for the entity type and cannot vary per resource, so "Number of units this booking consumes" is the one description no resource's unit can reach.
- A policy attached to a resource type is handed bookings from several resources that need not share a unit, so its refusals name units rather than claiming one.
- The stepper's live region names no noun at all: the quantity field beside it is labeled with the unit already, and repeating it would be the only place a browser had to know what a resource offers.
Writing your own unit¶
Two methods, and only one of them writes a plural.
namespace Drupal\my_module\Plugin\BookingUnit;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\yoyaku\Attribute\BookingUnit;
use Drupal\yoyaku\BookingUnitBase;
#[BookingUnit(
id: 'night',
label: new TranslatableMarkup('Nights'),
description: new TranslatableMarkup('A night of a bed or a room.'),
)]
final class Night extends BookingUnitBase {
public function formatQuantityInUnits(int $quantity, ?string $langcode = NULL): TranslatableMarkup {
return $this->formatPlural($quantity, '1 night', '@count nights', [], $langcode === NULL ? [] : ['langcode' => $langcode]);
}
}
label is the plural, capitalized, as a column heading or a select option prints
it. formatQuantityInUnits() is the only place the noun's plural is written, so
a translation carries whatever plural rules its language has. Do not put the
plural in the attribute: an attribute argument is extracted as a single string,
so it would never reach a msgid_plural.
To name the unit of resources your own module already understands, implement
BookingUnitDefaultInterface and tag the service
yoyaku.booking_unit_default. Answer NULL for a resource you do not recognize,
or you will name the unit of every resource on the site:
final class MyUnitDefault implements BookingUnitDefaultInterface {
public function unitFor(BookingResourceInterface $resource): ?string {
return $this->isARefuge($resource) ? 'night' : NULL;
}
}
Providers are asked in priority order and the first with an answer decides, as the assignment provider seam works and unlike the availability bound seam where every provider is asked and the lowest answer wins. A provider suggesting a unit nothing defines is skipped rather than trusted, so one stale suggestion cannot silence the providers behind it.