refactor: use english for routes, parameters and internal messages
This commit is contained in:
1 parent
bce8f58be3
commit
a88ff7ff24
22 files changed
+106
-135
No files matched your search
@@ -98,11 +98,11 @@ final readonly class BoardPresenter
|
||||
*/
|
||||
public static function urlParameters(ResourceType $type, \DateTimeImmutable $date, TimeRange $time): array
|
||||
{
|
||||
$parameters = ['type' => $type->slug(), 'tag' => $date->format('Y-m-d')];
|
||||
$parameters = ['type' => $type->value, 'date' => $date->format('Y-m-d')];
|
||||
|
||||
if (!$time->isAllDay()) {
|
||||
$parameters['von'] = $time->start();
|
||||
$parameters['bis'] = $time->end();
|
||||
$parameters['from'] = $time->start();
|
||||
$parameters['to'] = $time->end();
|
||||
}
|
||||
|
||||
return $parameters;
|
||||
|
||||
@@ -21,11 +21,11 @@ final readonly class TimeRange implements \Stringable
|
||||
public int $endMinute,
|
||||
) {
|
||||
if ($startMinute < self::DAY_START || $endMinute > self::DAY_END) {
|
||||
throw new \InvalidArgumentException('Eine Buchung muss innerhalb eines Tages liegen.');
|
||||
throw new \InvalidArgumentException('A time range must stay within a single day.');
|
||||
}
|
||||
|
||||
if ($startMinute >= $endMinute) {
|
||||
throw new \InvalidArgumentException('Das Ende muss nach dem Beginn liegen.');
|
||||
throw new \InvalidArgumentException('A time range must end after it starts.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ final class SeedCommand extends Command
|
||||
|
||||
$this->em->flush();
|
||||
|
||||
$io->success(\sprintf('%d neue Datensätze angelegt.', $created));
|
||||
$io->success(\sprintf('%d new records created.', $created));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
@@ -29,11 +29,11 @@ final class BoardController extends AbstractController
|
||||
{
|
||||
}
|
||||
|
||||
#[Route('/buchen/{type}', name: 'app_board', requirements: ['type' => '[a-z]+'], methods: ['GET'])]
|
||||
#[Route('/book/{type}', name: 'app_board', requirements: ['type' => '[a-z]+'], methods: ['GET'])]
|
||||
public function index(Request $request, string $type): Response
|
||||
{
|
||||
$resourceType = $this->resourceType($type);
|
||||
$date = DateParam::parseOrToday($request->query->getString('tag'));
|
||||
$date = DateParam::parseOrToday($request->query->getString('date'));
|
||||
|
||||
return $this->render('board/index.html.twig', $this->board->context(
|
||||
$resourceType,
|
||||
@@ -51,7 +51,7 @@ final class BoardController extends AbstractController
|
||||
* client-side event choreography. Requests without the htmx header get a plain redirect
|
||||
* rather than a bare pair of fragments, so the URL stays shareable.
|
||||
*/
|
||||
#[Route('/buchen/{type}/tag/{date}', name: 'app_board_day', requirements: ['type' => '[a-z]+', 'date' => DateParam::PATTERN], methods: ['GET'])]
|
||||
#[Route('/book/{type}/day/{date}', name: 'app_board_day', requirements: ['type' => '[a-z]+', 'date' => DateParam::PATTERN], methods: ['GET'])]
|
||||
public function day(Request $request, string $type, string $date): Response
|
||||
{
|
||||
$resourceType = $this->resourceType($type);
|
||||
@@ -76,19 +76,17 @@ final class BoardController extends AbstractController
|
||||
*/
|
||||
public static function timeOf(Request $request): TimeRange
|
||||
{
|
||||
return TimeRange::parse($request->query->getString('von'), $request->query->getString('bis'))
|
||||
return TimeRange::parse($request->query->getString('from'), $request->query->getString('to'))
|
||||
?? TimeRange::wholeDay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Route requirements cannot call a method, so the slug is matched loosely and resolved here.
|
||||
* Route requirements cannot enumerate the enum, so {type} is matched loosely and resolved
|
||||
* here -- an unknown kind is a 404 rather than a 500.
|
||||
*/
|
||||
private function resourceType(string $slug): ResourceType
|
||||
private function resourceType(string $value): ResourceType
|
||||
{
|
||||
try {
|
||||
return ResourceType::fromSlug($slug);
|
||||
} catch (\ValueError $e) {
|
||||
throw $this->createNotFoundException($e->getMessage(), $e);
|
||||
}
|
||||
return ResourceType::tryFrom($value)
|
||||
?? throw $this->createNotFoundException(\sprintf('Unknown bookable kind "%s".', $value));
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ final class BookingController extends AbstractController
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('/buchungen', name: 'app_booking', methods: ['GET'])]
|
||||
#[Route('/bookings', name: 'app_booking', methods: ['GET'])]
|
||||
public function index(): Response
|
||||
{
|
||||
$today = new \DateTimeImmutable('today');
|
||||
@@ -60,11 +60,11 @@ final class BookingController extends AbstractController
|
||||
* out-of-band board blocks, so the modal's outerHTML target is swapped with an empty
|
||||
* fragment and the modal simply disappears.
|
||||
*/
|
||||
#[Route('/buchungen/neu/{id}/{date}', name: 'app_booking_new', requirements: ['date' => DateParam::PATTERN], methods: ['GET', 'POST'])]
|
||||
#[Route('/bookings/new/{id}/{date}', name: 'app_booking_new', requirements: ['date' => DateParam::PATTERN], methods: ['GET', 'POST'])]
|
||||
public function create(Request $request, Resource $resource, string $date): Response
|
||||
{
|
||||
if (!$resource->isActive()) {
|
||||
throw $this->createNotFoundException('Diese Ressource wird nicht mehr angeboten.');
|
||||
throw $this->createNotFoundException('This resource has been retired.');
|
||||
}
|
||||
|
||||
$selected = DateParam::parseOrToday($date);
|
||||
@@ -110,7 +110,7 @@ final class BookingController extends AbstractController
|
||||
* Moves one occurrence, which is what an "override" of a series is: the other occurrences
|
||||
* are untouched and this one is flagged as differing from its series.
|
||||
*/
|
||||
#[Route('/buchungen/{id}/bearbeiten', name: 'app_booking_edit', methods: ['GET', 'POST'])]
|
||||
#[Route('/bookings/{id}/edit', name: 'app_booking_edit', methods: ['GET', 'POST'])]
|
||||
public function edit(Request $request, Booking $booking): Response
|
||||
{
|
||||
$this->denyUnlessOwn($booking);
|
||||
@@ -145,7 +145,7 @@ final class BookingController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/buchungen/{id}/freigeben', name: 'app_booking_release', methods: ['POST'])]
|
||||
#[Route('/bookings/{id}/release', name: 'app_booking_release', methods: ['POST'])]
|
||||
public function release(Request $request, Booking $booking): Response
|
||||
{
|
||||
$this->denyUnlessOwn($booking);
|
||||
@@ -169,11 +169,11 @@ final class BookingController extends AbstractController
|
||||
/**
|
||||
* Ends a series from today onwards. Past occurrences are history and stay put.
|
||||
*/
|
||||
#[Route('/serien/{id}/beenden', name: 'app_series_end', methods: ['POST'])]
|
||||
#[Route('/series/{id}/end', name: 'app_series_end', methods: ['POST'])]
|
||||
public function endSeries(Request $request, BookingSeries $series): Response
|
||||
{
|
||||
if ($series->getUser()->getId() !== $this->currentUser()->getId()) {
|
||||
throw $this->createAccessDeniedException('Fremde Serien können nicht beendet werden.');
|
||||
throw $this->createAccessDeniedException('A series may only be ended by the person who created it.');
|
||||
}
|
||||
|
||||
$this->denyUnlessCsrfValid($request, 'end-series-'.$series->getId());
|
||||
@@ -252,20 +252,20 @@ final class BookingController extends AbstractController
|
||||
*/
|
||||
private function timeParameters(TimeRange $time): array
|
||||
{
|
||||
return $time->isAllDay() ? [] : ['von' => $time->start(), 'bis' => $time->end()];
|
||||
return $time->isAllDay() ? [] : ['from' => $time->start(), 'to' => $time->end()];
|
||||
}
|
||||
|
||||
private function denyUnlessOwn(Booking $booking): void
|
||||
{
|
||||
if (!$booking->belongsTo($this->currentUser())) {
|
||||
throw $this->createAccessDeniedException('Fremde Buchungen können nicht geändert werden.');
|
||||
throw $this->createAccessDeniedException('A booking may only be changed by the person holding it.');
|
||||
}
|
||||
}
|
||||
|
||||
private function denyUnlessCsrfValid(Request $request, string $tokenId): void
|
||||
{
|
||||
if (!$this->isCsrfTokenValid($tokenId, $request->request->getString('_token'))) {
|
||||
throw $this->createAccessDeniedException('Ungültiges CSRF-Token.');
|
||||
throw $this->createAccessDeniedException('Invalid CSRF token.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,13 +8,14 @@ namespace App\Entity;
|
||||
* The kinds of thing that can be booked.
|
||||
*
|
||||
* **This enum is the extension point.** The dashboard builds one tile per case, the board route
|
||||
* accepts one slug per case, and the floor plan is looked up by convention from the case's
|
||||
* value. Adding a third bookable kind is therefore: a case here, a
|
||||
* accepts one backing value per case, and the floor plan is looked up by convention from that
|
||||
* same value. Adding a third bookable kind is therefore: a case here, a
|
||||
* `templates/board/plan/_<value>.html.twig` with its geometry, and rows in `app:seed`. Nothing
|
||||
* else needs editing — in particular no template lists the kinds by hand.
|
||||
*
|
||||
* The backing values are persisted in `resource.type`, which is why they stay English while the
|
||||
* URLs do not: `slug()` is what appears in a path, so the routes read German like the rest.
|
||||
* The backing value is both what is persisted in `resource.type` and what appears in a URL, so
|
||||
* there is no slug mapping to keep in sync. The label, plural and description are the only
|
||||
* things here anyone sees, which is why they are the only German ones.
|
||||
*/
|
||||
enum ResourceType: string
|
||||
{
|
||||
@@ -52,24 +53,4 @@ enum ResourceType: string
|
||||
self::PARKING => 'lucide:car',
|
||||
};
|
||||
}
|
||||
|
||||
/** What appears in a URL. */
|
||||
public function slug(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::DESK => 'schreibtische',
|
||||
self::PARKING => 'parkplaetze',
|
||||
};
|
||||
}
|
||||
|
||||
public static function fromSlug(string $slug): self
|
||||
{
|
||||
foreach (self::cases() as $case) {
|
||||
if ($case->slug() === $slug) {
|
||||
return $case;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \ValueError(\sprintf('"%s" ist kein bekannter Buchungsbereich.', $slug));
|
||||
}
|
||||
}
|
||||
@@ -74,11 +74,11 @@ class EpClient
|
||||
|
||||
$code = $request->query->get('code');
|
||||
if (null === $code || '' === $code) {
|
||||
throw new AuthorizationRequestException('Die Antwort von myep enthielt keinen Code.');
|
||||
throw new AuthorizationRequestException('The callback from myep carried no authorization code.');
|
||||
}
|
||||
|
||||
if (null === $expectedState || $request->query->get('state') !== $expectedState) {
|
||||
throw new AuthorizationRequestException('Der state-Parameter passt nicht zur Sitzung.');
|
||||
throw new AuthorizationRequestException('The state parameter does not match the one this session started with.');
|
||||
}
|
||||
|
||||
return $this->getProvider()->getAccessToken('authorization_code', ['code' => $code]);
|
||||
@@ -107,13 +107,13 @@ class EpClient
|
||||
['headers' => ['Accept' => 'application/json']],
|
||||
));
|
||||
} catch (ClientExceptionInterface $e) {
|
||||
throw new UserinfoUnavailableException('userinfo konnte nicht abgerufen werden.', previous: $e);
|
||||
throw new UserinfoUnavailableException('The userinfo endpoint could not be reached.', previous: $e);
|
||||
}
|
||||
|
||||
$userinfo = json_decode((string) $response->getBody(), true);
|
||||
|
||||
if (!\is_array($userinfo)) {
|
||||
throw new UserinfoUnavailableException('userinfo lieferte keine lesbare Antwort.');
|
||||
throw new UserinfoUnavailableException('The userinfo endpoint returned an unreadable payload.');
|
||||
}
|
||||
|
||||
return $userinfo;
|
||||
|
||||
Reference in new issue
Block a user