fix(cabana): translate hook ValidationError phrase keys in the request locale
- One shared localizer for ValidationError and ForbiddenError, tested in writeCRUDError's order; replaces the ForbiddenError-only one. - Applied on the CRUD and relation service transactions, the detached bulk branch, runAction, the record read (ShowRecord), and the reorder, singleton and relation-options routes, which localized neither type. - Only strings containing "::" are looked up, so translated text passes through unchanged and logs no missing-key warning. - Copy-on-write: the plugin's error value is never modified. - README and backend docs describe the localized ValidationError.
This commit is contained in:
@@ -269,13 +269,15 @@ A hook or an action stops a write by returning an error. Which error decides wha
|
||||
|
||||
| Error | Answer |
|
||||
|-------|--------|
|
||||
| `cabana.ValidationError` | 422 `validation_failed`, with its `Details` as messages per field. |
|
||||
| `cabana.ValidationError` | 422 `validation_failed`, with its `Details` as messages per field; each message that is a translation key is translated in the request locale. |
|
||||
| `cabana.ForbiddenError` | 403 `forbidden`, with its `Message` and its `Details`. |
|
||||
| any other error | The opaque 500. The error is logged on the server and its text never reaches the client. |
|
||||
|
||||
Return a `cabana.ForbiddenError` when the signed-in administrator may open the screen but may not make this particular change, for example editing a record that needs a higher permission. `Message` is a translation key or text; cabana translates it, and every `Details` message, in the request locale. `Details` maps a field name to a list of messages, as a `cabana.ValidationError` does, and may be left out. With an empty `Message` the admin shows its own text.
|
||||
|
||||
Every one of these errors rolls the write's transaction back, so a refused write changes nothing: a bulk action that refuses on its third record leaves the first two untouched. The admin SPA shows a refused save as a banner above the form and keeps what the administrator typed; a refused delete or action is a toast. A `cabana.ForbiddenError` works the same from the form hooks, the relation hooks, and bulk, record, toolbar and widget actions.
|
||||
A `cabana.ValidationError` message may be a translation key too. A hook that returns `acme.blog::lang.errors.slug_taken` on the `slug` field shows the administrator the sentence in their language; text that is not a key, such as an already translated message, is sent as written. cabana translates a copy, so the error can be a shared package variable.
|
||||
|
||||
Every one of these errors rolls the write's transaction back, so a refused write changes nothing: a bulk action that refuses on its third record leaves the first two untouched. The admin SPA shows a refused save as a banner above the form and keeps what the administrator typed; a refused delete or action is a toast. Both errors work the same, translated in the request locale, from the form hooks, the relation hooks, bulk, record, toolbar and widget actions, the reorder hook (`pact.ReorderAfterSave`) and the singleton record (`pact.AdminSingleton`).
|
||||
|
||||
## Toolbar actions
|
||||
|
||||
@@ -441,7 +443,7 @@ The admin SPA shows the declared actions in a "Bulk actions" menu next to the se
|
||||
|
||||
- The ids are resolved and row-locked through the controller's `pact.ListExtendQuery` scope inside one transaction. `Run` receives the loaded records in `pact.AdminBulkActionInput`, never the ids, so an id outside the scope cannot reach plugin code.
|
||||
- A selection that matches no row in the scope answers `affected: 0` without calling `Run`. A selection of which only a part is in the scope answers 409 `conflict` and changes nothing.
|
||||
- `Run` writes through `cabana.TxFromContext(ctx)`. Any error rolls every row back; a `cabana.ValidationError` is a 422 and any other error the opaque 500.
|
||||
- `Run` writes through `cabana.TxFromContext(ctx)`. Any error rolls every row back; a `cabana.ValidationError` is a 422 with its messages translated in the request locale, and any other error the opaque 500.
|
||||
- The action's `Permissions` are checked on top of the controller's. An administrator who may not run an action does not get it in the list schema, and posting it answers 403.
|
||||
- `pact.AdminBulkActionResult` carries an optional `Message` (a phrase key or text) and `Affected`, the number of records the action changed. The answer is a `cabana.BulkActionResult`.
|
||||
- An action with `Detached: true` runs outside the framework transaction. Its records are loaded through the same scope, with the same empty and 409 rules, but without row locks, and `cabana.TxFromContext` reports no transaction. `Run` opens the short transactions it needs (`lagoon.Transaction`). Set it on an action that calls an external service, so that no transaction or row lock is held across the call. What the action saved before a later failure stays saved.
|
||||
|
||||
Reference in New Issue
Block a user