feat: add i18n support for workspace connections disconnected email

This commit is contained in:
Paulo Castellano 2026-01-25 17:23:16 -03:00
parent 1aba8792ec
commit 59e837fd47
5 changed files with 307 additions and 0 deletions

View file

@ -0,0 +1,73 @@
<?php
namespace App\Mail;
use App\Models\SocialAccount;
use App\Models\Workspace;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection;
class WorkspaceConnectionsDisconnected extends Mailable implements ShouldQueue
{
use Queueable, SerializesModels;
/**
* @param Collection<int, SocialAccount> $disconnectedAccounts
*/
public function __construct(
public Workspace $workspace,
public Collection $disconnectedAccounts
) {
$this->locale = $this->workspace->owner?->language?->code ?? 'en';
}
public function envelope(): Envelope
{
$count = $this->disconnectedAccounts->count();
return new Envelope(
subject: trans_choice('mail.workspace_connections_disconnected.subject', $count, [
'count' => $count,
'workspace' => $this->workspace->name,
], $this->locale),
);
}
public function content(): Content
{
$count = $this->disconnectedAccounts->count();
$workspaceName = $this->workspace->name;
$locale = $this->locale;
return new Content(
view: 'mail.workspace-connections-disconnected',
with: [
'title' => __('mail.workspace_connections_disconnected.title', [], $locale),
'previewText' => trans_choice('mail.workspace_connections_disconnected.subject', $count, [
'count' => $count,
'workspace' => $workspaceName,
], $locale),
'intro' => __('mail.workspace_connections_disconnected.intro', ['workspace' => $workspaceName], $locale),
'reasonsTitle' => __('mail.workspace_connections_disconnected.reasons_title', [], $locale),
'reasonExpired' => __('mail.workspace_connections_disconnected.reason_expired', [], $locale),
'reasonRevoked' => __('mail.workspace_connections_disconnected.reason_revoked', [], $locale),
'reasonChanged' => __('mail.workspace_connections_disconnected.reason_changed', [], $locale),
'reconnectCta' => __('mail.workspace_connections_disconnected.reconnect_cta', [], $locale),
'buttonText' => __('mail.workspace_connections_disconnected.button', [], $locale),
'workspace' => $this->workspace,
'disconnectedAccounts' => $this->disconnectedAccounts,
'url' => route('accounts'),
],
);
}
public function attachments(): array
{
return [];
}
}

15
lang/en/mail.php Normal file
View file

@ -0,0 +1,15 @@
<?php
return [
'workspace_connections_disconnected' => [
'subject' => '{1} :count account needs to be reconnected in :workspace|[2,*] :count accounts need to be reconnected in :workspace',
'title' => 'Accounts Need Reconnection',
'intro' => 'The following social accounts in your <strong>:workspace</strong> workspace have been disconnected and need to be reconnected:',
'reasons_title' => 'This may have happened because:',
'reason_expired' => 'Access tokens expired',
'reason_revoked' => 'You revoked access to TryPost on the platform',
'reason_changed' => 'The platform changed their authentication requirements',
'reconnect_cta' => 'Please reconnect these accounts to continue scheduling and publishing posts.',
'button' => 'Reconnect Accounts',
],
];

15
lang/pt-BR/mail.php Normal file
View file

@ -0,0 +1,15 @@
<?php
return [
'workspace_connections_disconnected' => [
'subject' => '{1} :count conta precisa ser reconectada em :workspace|[2,*] :count contas precisam ser reconectadas em :workspace',
'title' => 'Contas Precisam ser Reconectadas',
'intro' => 'As seguintes contas de redes sociais no seu workspace <strong>:workspace</strong> foram desconectadas e precisam ser reconectadas:',
'reasons_title' => 'Isso pode ter acontecido porque:',
'reason_expired' => 'Os tokens de acesso expiraram',
'reason_revoked' => 'Você revogou o acesso ao TryPost na plataforma',
'reason_changed' => 'A plataforma mudou os requisitos de autenticação',
'reconnect_cta' => 'Por favor, reconecte essas contas para continuar agendando e publicando posts.',
'button' => 'Reconectar Contas',
],
];

View file

@ -0,0 +1,66 @@
<x-main>
<div class="bg-zinc-50 sm:px-4 font-sans">
<table align="center">
<tr>
<td class="w-[552px] max-w-full">
<x-header />
<table class="w-full">
<tr>
<td class="p-12 sm:px-6 text-base text-zinc-700 bg-white rounded shadow-sm">
<h1 class="m-0 mb-6 text-2xl sm:leading-8 text-black font-semibold">
@{{ $title }}
</h1>
<p class="m-0 leading-6">
{!! $intro !!}
</p>
<table class="w-full mt-4" style="border-collapse: collapse">
@foreach($disconnectedAccounts as $account)
<tr>
<td class="py-3" style="border-bottom: 1px solid #e4e4e7">
<div style="display: flex; align-items: center">
<div style="width: 8px; height: 8px; border-radius: 50%; background-color: @{{ $account->platform->color() }}; margin-right: 12px"></div>
<div>
<strong class="text-zinc-900">@{{ $account->platform->label() }}</strong>
@if($account->display_name || $account->username)
<span class="text-zinc-500"> - @{{ $account->display_name ?? $account->username }}</span>
@endif
</div>
</div>
</td>
</tr>
@endforeach
</table>
<p class="m-0 mt-6 leading-6">
@{{ $reasonsTitle }}
</p>
<ul class="m-0 mt-2 pl-5 leading-6">
<li>@{{ $reasonExpired }}</li>
<li>@{{ $reasonRevoked }}</li>
<li>@{{ $reasonChanged }}</li>
</ul>
<p class="m-0 mt-4 leading-6">
@{{ $reconnectCta }}
</p>
<x-spacer height="24px" />
<div class="flex items-center justify-center">
<x-button href="@{{ $url }}">
@{{ $buttonText }} &rarr;
</x-button>
</div>
</td>
</tr>
</table>
<x-footer />
</td>
</tr>
</table>
</div>
</x-main>

View file

@ -0,0 +1,138 @@
<!DOCTYPE html>
<html lang="en" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<meta charset="utf-8">
<meta name="x-apple-disable-message-reformatting">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no, url=no">
<meta name="color-scheme" content="light">
<meta name="supported-color-schemes" content="light">
<!--[if mso]>
<noscript>
<xml>
<o:OfficeDocumentSettings xmlns:o="urn:schemas-microsoft-com:office:office">
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
</noscript>
<style>
td,th,div,p,a,h1,h2,h3,h4,h5,h6 {font-family: "Segoe UI", sans-serif; mso-line-height-rule: exactly;}
</style>
<![endif]-->
@if(isset($title))
<title>{{ $title }}</title>
@endif
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet" media="screen">
<style>
.hover-i-text-decoration-underline:hover {
text-decoration: underline !important
}
@media (max-width: 600px) {
.sm-my-8 {
margin-top: 32px !important;
margin-bottom: 32px !important
}
.sm-px-4 {
padding-left: 16px !important;
padding-right: 16px !important
}
.sm-px-6 {
padding-left: 24px !important;
padding-right: 24px !important
}
.sm-leading-8 {
line-height: 32px !important
}
}
</style>
</head>
<body style="margin: 0; width: 100%; padding: 0; -webkit-font-smoothing: antialiased; word-break: break-word">
@if(isset($previewText))
<div style="display: none">
{{ $previewText }}
&#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847; &#8199;&#65279;&#847;
</div>
@endif
<div role="article" aria-roledescription="email" aria-label="{{ $title }}" lang="en">
<div class="sm-px-4" style="background-color: #fafafa; font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif">
<table align="center" cellpadding="0" cellspacing="0" role="none">
<tr>
<td style="width: 552px; max-width: 100%">
<div class="sm-my-8" style="margin-top: 48px; margin-bottom: 48px; text-align: center">
<a href="https://trypost.it" target="_blank">
<img src="{{ asset('/images/emails/logo-header.png') }}" width="160" alt="Trypost" style="max-width: 100%; vertical-align: middle">
</a>
</div>
<table style="width: 100%" cellpadding="0" cellspacing="0" role="none">
<tr>
<td class="sm-px-6" style="border-radius: 4px; background-color: #fffffe; padding: 48px; font-size: 16px; color: #3f3f46; box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05)">
<h1 class="sm-leading-8" style="margin: 0 0 24px; font-size: 24px; font-weight: 600; color: #000001">
{{ $title }}
</h1>
<p style="margin: 0; line-height: 24px">
{!! $intro !!}
</p>
<table style="border-collapse: collapse; margin-top: 16px; width: 100%" cellpadding="0" cellspacing="0" role="none">
@foreach($disconnectedAccounts as $account)
<tr>
<td style="border-bottom: 1px solid #e4e4e7; padding-top: 12px; padding-bottom: 12px">
<div style="display: flex; align-items: center">
<div style="width: 8px; height: 8px; border-radius: 50%; background-color: {{ $account->platform->color() }}; margin-right: 12px"></div>
<div>
<strong style="color: #18181b">{{ $account->platform->label() }}</strong>
@if($account->display_name || $account->username)
<span style="color: #71717a"> - {{ $account->display_name ?? $account->username }}</span>
@endif
</div>
</div>
</td>
</tr>
@endforeach
</table>
<p style="margin: 24px 0 0; line-height: 24px">
{{ $reasonsTitle }}
</p>
<ul style="margin: 8px 0 0; padding-left: 20px; line-height: 24px">
<li>{{ $reasonExpired }}</li>
<li>{{ $reasonRevoked }}</li>
<li>{{ $reasonChanged }}</li>
</ul>
<p style="margin: 16px 0 0; line-height: 24px">
{{ $reconnectCta }}
</p>
<div role="separator" style="line-height: 24px">&zwj;</div>
<div style="display: flex; align-items: center; justify-content: center">
<div>
<a href="{{ $url }}" style="display: inline-block; text-decoration: none; padding: 16px 24px; font-size: 16px; line-height: 1; border-radius: 8px; background-color: #262626; color: #ffffff">
<!--[if mso]><i style="mso-font-width: 150%; mso-text-raise: 31px" hidden>&emsp;</i><![endif]-->
<span style="mso-text-raise: 16px">{{ $buttonText }} &rarr;</span>
<!--[if mso]><i hidden style="mso-font-width: 150%">&emsp;&#8203;</i><![endif]-->
</a>
</div>
</div>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center" style="padding: 24px; text-align: center; font-size: 12px; color: #52525b">
<p style="margin: 0 0 8px">
Open-source social media scheduling tool
</p>
@if(isset($unsubscribe_url))
<p style="margin: 8px 0 0">
<a href="{{ unsubscribe_url }}" target="_blank" class="hover-i-text-decoration-underline" style="color: #52525b; text-decoration: none">
Unsubscribe
</a>
</p>
@endif
</td>
</tr>
</table>
</div>
</div>
</body>
</html>