dorfteich/packages/shared/i18n/de/errors.json
Claude Opus 4.8 4d48d72c40
All checks were successful
CD / Build and push images (push) Successful in 3m3s
CI / Lint, typecheck, test (push) Successful in 2m21s
CI / Auth e2e pack (push) Successful in 2m58s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m13s
CD / Promote to Int (push) Successful in 11s
Add grant model and shared permission-resolution algorithm (#51)
The heart of the security model: one algorithm, implemented once, for API,
collab, and UI (permissions.md — authoritative).

- shared `permissions/`: pure resolution (`resolvePageCapability`) exactly per
  permissions.md — specificity page > label (incl. ancestor labels) > pond,
  deny wins within a level, default-closed, Site Admin bypass — plus the trash
  rule (`canAccessPage` / `canAccessTrashedPage`, ADR 0013). `grantValidationError`
  enforces the structural constraints. Documented, I/O-free signatures for
  API/collab reuse.
- prisma: `RoleGrant` (+ grant enums) per data-model.md, unique on
  (pond, subject, role, scope); migration adds a CHECK backstop that a
  POND_ADMIN grant is pond-scope + user-subject.
- api `grants/`: `GrantsService.createGrant` validates before insert (structural
  + no extra admin on a personal pond), rejects duplicates; `grantsForPond`
  returns the shared resolver model (what #52/#53 consume); enum mappers between
  the DB and the shared model. Interim "who may manage grants" stays until #52.
- tests: exhaustive table-driven resolver suite — every worked example from
  permissions.md §Resolution, edge cases (multi-label deny-wins, ancestor
  inheritance, anonymous/public, most-specific-allow-beats-less-specific-deny,
  trash) and a property test (a less-specific grant never overrides a
  more-specific decision); validation unit tests; grants db test proving
  write-time rejection of invalid grants.
- i18n: grant error codes (de + en).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PGdhRiwU1WRL4XxJfZYipY
2026-07-09 14:02:07 +02:00

62 lines
3.5 KiB
JSON

{
"bad_request": "Die Anfrage ist ungültig.",
"unauthorized": "Bitte melde dich an, um fortzufahren.",
"forbidden": "Dir fehlt die Berechtigung für diese Aktion.",
"not_found": "Die angeforderte Ressource existiert nicht.",
"conflict": "Die Anfrage steht im Widerspruch zum aktuellen Zustand.",
"gone": "Diese Ressource ist nicht mehr verfügbar.",
"payload_too_large": "Die übermittelten Daten sind zu groß.",
"rate_limited": "Zu viele Anfragen — bitte versuche es später erneut.",
"internal_error": "Interner Serverfehler.",
"registration_closed": "Die Registrierung ist auf dieser Instanz derzeit geschlossen.",
"token_invalid": "Dieser Link ist ungültig oder abgelaufen.",
"login_failed": "Benutzername/E-Mail oder Passwort ist falsch.",
"login_backoff": "Zu viele Fehlversuche — bitte warte ein paar Minuten.",
"email_unverified": "Bitte bestätige zuerst deine E-Mail-Adresse.",
"account_disabled": "Dieses Konto wurde deaktiviert.",
"password_incorrect": "Das aktuelle Passwort ist falsch.",
"csrf_origin_mismatch": "Die Anfrage kam von einer unerwarteten Herkunft.",
"cannot_revoke_current_session": "Beende deine aktuelle Sitzung über die Abmeldung.",
"personal_pond_undeletable": "Der persönliche Teich kann nicht gelöscht werden.",
"quota_exceeded": "Das Kontingent ist erreicht (Limit: {{limit}}).",
"slug_taken": "Dieser Adressname ist in diesem Teich bereits vergeben.",
"page_document_too_large": "Die Seite ist zu groß (Limit: {{limitBytes}} Bytes).",
"invalid_page_state": "Der übermittelte Seiteninhalt ist ungültig.",
"page_trashed": "Diese Seite wurde in den Papierkorb verschoben.",
"label_name_taken": "Ein Label mit diesem Namen existiert auf dieser Ebene bereits.",
"label_cycle": "Ein Label kann nicht in seinen eigenen Teilbaum verschoben werden.",
"label_depth_exceeded": "Labels lassen sich höchstens {{max}} Ebenen tief verschachteln.",
"label_has_pages": "Diesem Label sind noch Seiten zugeordnet; bitte bestätigen, um sie zu lösen.",
"label_wrong_pond": "Dieses Label gehört zu einem anderen Teich.",
"unsupported_file_type": "Dieser Dateityp wird nicht unterstützt.",
"file_too_large": "Die Datei ist zu groß (Limit: {{limitBytes}} Bytes).",
"network": "Der Server war nicht erreichbar.",
"grant_exists": "Diese Berechtigung existiert bereits.",
"grant_pond_admin_scope": "Eine Teich-Admin-Berechtigung muss für den ganzen Teich und eine bestimmte Person gelten.",
"grant_pond_admin_personal_pond": "Der einzige Administrator eines persönlichen Teichs ist dessen Eigentümer.",
"grant_subject_id_mismatch": "Das Subjekt der Berechtigung ist widersprüchlich.",
"grant_scope_id_mismatch": "Der Geltungsbereich der Berechtigung ist widersprüchlich.",
"validation": {
"required": "Dieses Feld ist erforderlich.",
"taken": "Dieser Wert ist bereits vergeben.",
"username": {
"tooShort": "Der Benutzername braucht mindestens 3 Zeichen.",
"tooLong": "Der Benutzername darf höchstens 32 Zeichen haben.",
"charset": "Erlaubt sind nur Buchstaben, Ziffern und Bindestriche."
},
"password": {
"tooShort": "Das Passwort braucht mindestens 10 Zeichen.",
"tooLong": "Das Passwort darf höchstens 128 Zeichen haben.",
"tooCommon": "Dieses Passwort ist zu verbreitet."
},
"email": {
"invalid": "Bitte gib eine gültige E-Mail-Adresse ein."
},
"displayName": {
"required": "Bitte gib einen Anzeigenamen ein."
},
"labelColor": "Bitte gib eine Farbe wie #a1b2c3 ein.",
"tooLong": "Die Eingabe ist zu lang."
}
}