byrcsc/laravel-approval · 1.x
Notification content.
Reword, translate, or restructure the built-in approval notifications by publishing the translations and the mail views.
The shipped notifications say sensible but generic things — "You have been asked to approve Purchase Order #41." Changing that does not require replacing the notification classes. There are three ways in, and the cheapest one that fits is the right one:
| You want to | Publish |
|---|---|
| Reword or translate the messages | approval-translations |
| Restructure the emails, add a link into your UI | approval-views |
| Change who receives them, or the whole message | Neither — see Notifications |
Nothing here is required. The package ships working English messages and mail views, and an application that never publishes either still sends mail.
Rewording and translating
Every user-facing string lives in one file:
php artisan vendor:publish --tag="approval-translations"That writes lang/vendor/approval/en/approval.php:
return [
'requested' => [
'subject' => 'Approval needed: :subject',
'body' => 'You have been asked to approve :subject.',
],
'approved' => [
'subject' => 'Approved: :subject',
'body' => ':subject has been approved.',
],
// rejected, returned, assigned, overdue, conflicted
'reason' => 'Reason: :reason',
'reference' => 'Reference: :reference.',
'deleted_record' => 'a deleted record',
];Each event has a subject and a body. conflicted also has an unresolved
line, and reason, reference, and deleted_record are shared across
messages.
Edit it in place to reword, or copy the directory to es, fr, and so on to
translate. Messages are resolved at send time through trans(), so they follow
the application locale — a request routed to a French-speaking approver reads in
French with no work here, provided lang/vendor/approval/fr/approval.php
exists.
Placeholders may sit anywhere in the line, which is the point of translating through this file rather than concatenating strings:
'requested' => [
'subject' => 'Approbation requise : :subject',
'body' => 'Il vous est demandé d\'approuver :subject.',
],:subject is a readable name for the record — its name, title,
reference, or label attribute, falling back to ClassName #key. See
Notifications for the full resolution order and what a
deleted record reads as.
A key with no translation renders as the key itself rather than throwing, so a half-finished locale file produces visibly wrong mail instead of a failed queue job.
Restructuring the emails
To add a button into your own UI, change the layout, or drop a line entirely:
php artisan vendor:publish --tag="approval-views"That writes resources/views/vendor/approval/mail/, one Blade file per message:
approved.blade.php rejected.blade.php conflicted.blade.php
requested.blade.php returned.blade.php overdue.blade.php
assigned.blade.phpEach is an ordinary markdown mail view. The shipped files carry only the message and reference lines; a published copy can add whatever your UI needs — a review button, say:
<x-mail::message>
{{ __('approval::approval.requested.body', ['subject' => $subject]) }}
<x-mail::button :url="route('approvals.show', $request)">
{{ __('Review it') }}
</x-mail::button>
{{ __('approval::approval.reference', ['reference' => $reference]) }}
</x-mail::message>What each view receives
| Variable | |
|---|---|
$subject | The readable record name used in the subject line. |
$reference | The request's public ULID. |
$request | The ApprovalRequest. |
$approvable | The record, or null if it has been deleted since. |
$notification | The notification instance, for anything else you need. |
The rejected and returned views also receive $reason, which is nullable —
both shipped views guard it with @if ($reason !== null). Keep that guard, or a
decision recorded without a comment renders an empty "Reason:" line.
$approvable deserves the same care. A record hard-deleted between the decision
and the send leaves the notification with a request but no record, so
route('orders.show', $approvable) in a published view will fail on exactly the
mail you least want to fail.
Publishing one does not freeze the other
The shipped views read their wording through __('approval::approval.…') rather
than hardcoding English. Publish the views and the translations still apply;
publish the translations and the views pick them up. Publishing both is fine
too — they meet at the translation key.
This only holds if your published views keep reading translations. A view that inlines its text works, but takes that message out of the locale system, so mail to a French approver will be part French and part whatever you typed.
Replacing a message with your own
When the audience is wrong and not just the words, point the event at your own notification class — see Notifications for the map and the recipient helpers.
A replacement class does not lose access to the published content. Extend
ApprovalNotification and call the protected mailFor(), which builds the
MailMessage from the same translation keys and views:
use ByRcsc\LaravelApproval\Events\ApprovalEvent;
use ByRcsc\LaravelApproval\Notifications\ApprovalNotification;
use Illuminate\Notifications\Messages\MailMessage;
final class PurchaseOrderNeedsApproval extends ApprovalNotification
{
public static function recipients(ApprovalEvent $event): iterable
{
return self::activeApprovers($event);
}
public function toMail(object $notifiable): MailMessage
{
return $this->mailFor('requested');
}
}recipients() is abstract on the base class, so every replacement declares
its audience — that is the point.
mailFor($key, $with = []) reads approval::approval.{$key}.subject for the
subject, renders approval::mail.{$key}, and passes the five variables above.
Anything in $with is merged in, which is how the shipped rejected and returned
notifications supply $reason:
return $this->mailFor('rejected', ['reason' => $this->reason()]);So a class that only needs different recipients can keep the published wording entirely, and one that needs a different message can pass extra variables to a view of its own.
Both of these are the mail channel only
Publishing translations and views changes what the mail channel sends. Neither reaches the others.
database and broadcast render toArray(), which carries structured data
rather than prose:
[
'request' => '01JG3…', // the public ULID
'state' => 'pending',
'subject' => 'Purchase Order #41',
]There is nothing to translate there — subject is the record's readable name,
not a translated line — so rewording approval.php leaves a database
notification exactly as it was. Applications rendering an in-app inbox from
these rows write their own wording against state, which is where it belongs:
the row is read long after it was written, and should read in the locale of
whoever is looking at it rather than the one in effect when it was stored.
To change the payload itself, override toArray() on a replacement class.