Refactor SMS and Coupon resources to utilize localization for labels and messages, enhancing internationalization support. Update .env.example to change APP_LOCALE to 'tk'.

This commit is contained in:
Mekan1206
2026-06-03 22:10:17 +05:00
parent 96eefd70ba
commit 6f3069f619
75 changed files with 2830 additions and 47 deletions

View File

@@ -30,9 +30,9 @@ class SendSms extends Page
{
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedChatBubbleLeftRight;
protected static ?string $navigationLabel = 'Send SMS';
protected static ?string $navigationLabel = null;
protected static ?string $title = 'Send SMS';
protected static ?string $title = null;
protected static ?int $navigationSort = 2;
@@ -47,6 +47,16 @@ class SendSms extends Page
public bool $campaignNotificationSent = false;
public static function getNavigationLabel(): string
{
return __('filament.send_sms.navigation');
}
public function getTitle(): string
{
return __('filament.send_sms.title');
}
public function mount(): void
{
$this->form->fill([
@@ -68,38 +78,37 @@ class SendSms extends Page
{
return $schema
->components([
Section::make('Message')
->description('Write the SMS exactly as recipients should see it.')
Section::make(__('filament.send_sms.sections.message'))
->schema([
Textarea::make('message')
->label('SMS message')
->label(__('filament.send_sms.fields.message'))
->required()
->rows(8)
->maxLength(1000)
->live(debounce: 300)
->helperText(fn (Get $get): string => app(SmsMessageAnalyzer::class)->summary((string) ($get('message') ?? ''))),
]),
Section::make('Recipients')
->description('Send to everyone, pick specific coupon holders, or exclude numbers from a broadcast.')
Section::make(__('filament.send_sms.sections.recipients'))
->description(__('filament.send_sms.sections.recipients_description'))
->schema([
Checkbox::make('send_to_all')
->label('Send to all coupon holders')
->helperText('When enabled, every coupon holder is included. Manual selection is disabled.')
->label(__('filament.send_sms.fields.send_to_all'))
->helperText(__('filament.send_sms.fields.send_to_all_helper'))
->live(),
Select::make('coupon_ids')
->label('Select recipients')
->label(__('filament.send_sms.fields.coupon_ids'))
->multiple()
->searchable()
->disabled(fn (Get $get): bool => (bool) $get('send_to_all'))
->dehydrated(fn (Get $get): bool => ! (bool) $get('send_to_all'))
->helperText('Search by phone, coupon code, or ID.')
->helperText(__('filament.send_sms.fields.coupon_ids_helper'))
->getSearchResultsUsing(fn (string $search): array => $this->searchCoupons($search))
->getOptionLabelsUsing(fn (array $values): array => $this->couponLabels($values)),
Select::make('excluded_coupon_ids')
->label('Exclude recipients')
->label(__('filament.send_sms.fields.excluded_coupon_ids'))
->multiple()
->searchable()
->helperText('Excluded numbers will not receive this message, even when sending to all.')
->helperText(__('filament.send_sms.fields.excluded_coupon_ids_helper'))
->getSearchResultsUsing(fn (string $search): array => $this->searchCoupons($search))
->getOptionLabelsUsing(fn (array $values): array => $this->couponLabels($values)),
]),
@@ -116,12 +125,12 @@ class SendSms extends Page
->footer([
Actions::make([
Action::make('send')
->label('Send SMS')
->label(__('filament.send_sms.actions.send'))
->icon(Heroicon::OutlinedPaperAirplane)
->requiresConfirmation()
->modalHeading('Send SMS now?')
->modalHeading(__('filament.send_sms.actions.send_confirm_heading'))
->modalDescription(fn (): string => $this->confirmationDescription())
->modalSubmitActionLabel('Send')
->modalSubmitActionLabel(__('filament.send_sms.actions.send_confirm_submit'))
->action('sendCampaign')
->disabled(fn (): bool => $this->activeCampaignId !== null && ! $this->isCampaignComplete()),
]),
@@ -133,7 +142,7 @@ class SendSms extends Page
{
if ($this->activeCampaignId !== null && ! $this->isCampaignComplete()) {
Notification::make()
->title('A campaign is still sending')
->title(__('filament.send_sms.notifications.campaign_still_sending'))
->warning()
->send();
@@ -167,8 +176,8 @@ class SendSms extends Page
if ($willReceive === 0) {
Notification::make()
->title('No valid recipients')
->body('Adjust your selection or exclusions — no coupon holders with valid phone numbers will receive this message.')
->title(__('filament.send_sms.notifications.no_valid_recipients'))
->body(__('filament.send_sms.notifications.no_valid_recipients_body'))
->danger()
->send();
@@ -190,8 +199,8 @@ class SendSms extends Page
$this->notifyCampaignFinished($campaign);
} else {
Notification::make()
->title('SMS campaign queued')
->body("Sending to {$campaign->recipient_count} recipient(s) in the background.")
->title(__('filament.send_sms.notifications.queued'))
->body(__('filament.send_sms.notifications.queued_body', ['count' => $campaign->recipient_count]))
->success()
->send();
}
@@ -227,20 +236,24 @@ class SendSms extends Page
if ($campaign->failed_count > 0 && $campaign->sent_count === 0) {
Notification::make()
->title('SMS campaign failed')
->body("{$campaign->failed_count} message(s) failed to send.")
->title(__('filament.send_sms.notifications.failed'))
->body(__('filament.send_sms.notifications.failed_body', ['count' => $campaign->failed_count]))
->danger()
->send();
} elseif ($campaign->failed_count > 0) {
Notification::make()
->title('SMS campaign finished with errors')
->body("Sent {$campaign->sent_count}, failed {$campaign->failed_count}, skipped {$campaign->skipped_count}.")
->title(__('filament.send_sms.notifications.finished_with_errors'))
->body(__('filament.send_sms.notifications.finished_with_errors_body', [
'sent' => $campaign->sent_count,
'failed' => $campaign->failed_count,
'skipped' => $campaign->skipped_count,
]))
->warning()
->send();
} else {
Notification::make()
->title('SMS campaign completed')
->body("Successfully sent {$campaign->sent_count} message(s).")
->title(__('filament.send_sms.notifications.completed'))
->body(__('filament.send_sms.notifications.completed_body', ['count' => $campaign->sent_count]))
->success()
->send();
}
@@ -264,7 +277,10 @@ class SendSms extends Page
$analyzer = app(SmsMessageAnalyzer::class);
$willReceive = $this->getWillReceiveCount();
return "Send to {$willReceive} recipient(s)? {$analyzer->summary($message)}";
return __('filament.send_sms.confirmation', [
'count' => $willReceive,
'summary' => $analyzer->summary($message),
]);
}
public function getWillReceiveCount(): int
@@ -304,7 +320,11 @@ class SendSms extends Page
return '';
}
return "{$campaign->processedCount()} / {$campaign->recipient_count} processed · {$campaign->sent_count} sent";
return __('filament.send_sms.progress.processed', [
'processed' => $campaign->processedCount(),
'total' => $campaign->recipient_count,
'sent' => $campaign->sent_count,
]);
}
/**

View File

@@ -19,12 +19,17 @@ class CouponResource extends Resource
protected static ?string $recordTitleAttribute = 'phone';
protected static ?string $navigationLabel = 'Coupons';
protected static ?string $navigationLabel = null;
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedTicket;
protected static ?int $navigationSort = 1;
public static function getNavigationLabel(): string
{
return __('filament.coupons.navigation');
}
public static function infolist(Schema $schema): Schema
{
return CouponInfolist::configure($schema);

View File

@@ -12,13 +12,17 @@ class CouponInfolist
return $schema
->components([
TextEntry::make('phone')
->label(__('filament.fields.phone'))
->formatStateUsing(fn (string $state): string => format_phone($state))
->copyable(),
TextEntry::make('code')
->label(__('filament.fields.code'))
->copyable(),
TextEntry::make('created_at')
->label(__('filament.fields.created_at'))
->dateTime(),
TextEntry::make('updated_at')
->label(__('filament.fields.updated_at'))
->dateTime(),
]);
}

View File

@@ -3,8 +3,8 @@
namespace App\Filament\Resources\Coupons\Tables;
use App\Filament\Tables\Filters\CreatedAtDateFilter;
use Filament\Actions\ViewAction;
use Filament\Actions\DeleteAction;
use Filament\Actions\ViewAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
@@ -16,19 +16,24 @@ class CouponsTable
->defaultSort('created_at', 'desc')
->columns([
TextColumn::make('id')
->label(__('filament.fields.id'))
->sortable(),
TextColumn::make('phone')
->label(__('filament.fields.phone'))
->formatStateUsing(fn (string $state): string => format_phone($state))
->searchable()
->sortable()
->copyable(),
TextColumn::make('code')
->label(__('filament.fields.code'))
->sortable()
->copyable(),
TextColumn::make('created_at')
->label(__('filament.fields.created_at'))
->dateTime()
->sortable(),
TextColumn::make('updated_at')
->label(__('filament.fields.updated_at'))
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),

View File

@@ -19,16 +19,31 @@ class PhoneVerificationResource extends Resource
protected static ?string $recordTitleAttribute = 'phone';
protected static ?string $navigationLabel = 'OTP Verifications';
protected static ?string $navigationLabel = null;
protected static ?string $modelLabel = 'OTP verification';
protected static ?string $modelLabel = null;
protected static ?string $pluralModelLabel = 'OTP verifications';
protected static ?string $pluralModelLabel = null;
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedKey;
protected static ?int $navigationSort = 2;
public static function getNavigationLabel(): string
{
return __('filament.phone_verifications.navigation');
}
public static function getModelLabel(): string
{
return __('filament.phone_verifications.model');
}
public static function getPluralModelLabel(): string
{
return __('filament.phone_verifications.plural');
}
public static function infolist(Schema $schema): Schema
{
return PhoneVerificationInfolist::configure($schema);

View File

@@ -13,15 +13,20 @@ class PhoneVerificationInfolist
return $schema
->components([
TextEntry::make('phone')
->label(__('filament.fields.phone'))
->formatStateUsing(fn (string $state): string => format_phone($state))
->copyable(),
TextEntry::make('expires_at')
->label(__('filament.fields.expires_at'))
->dateTime(),
IconEntry::make('verified')
->label(__('filament.fields.verified'))
->boolean(),
TextEntry::make('created_at')
->label(__('filament.fields.created_at'))
->dateTime(),
TextEntry::make('updated_at')
->label(__('filament.fields.updated_at'))
->dateTime(),
]);
}

View File

@@ -16,22 +16,28 @@ class PhoneVerificationsTable
->defaultSort('created_at', 'desc')
->columns([
TextColumn::make('id')
->label(__('filament.fields.id'))
->sortable(),
TextColumn::make('phone')
->label(__('filament.fields.phone'))
->formatStateUsing(fn (string $state): string => format_phone($state))
->searchable()
->sortable()
->copyable(),
TextColumn::make('expires_at')
->label(__('filament.fields.expires_at'))
->dateTime()
->sortable(),
IconColumn::make('verified')
->label(__('filament.fields.verified'))
->boolean()
->sortable(),
TextColumn::make('created_at')
->label(__('filament.fields.created_at'))
->dateTime()
->sortable(),
TextColumn::make('updated_at')
->label(__('filament.fields.updated_at'))
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),

View File

@@ -13,12 +13,12 @@ class CreatedAtDateFilter
public static function make(): Filter
{
return Filter::make('created_at')
->label('Date')
->label(__('filament.filters.date'))
->schema([
DatePicker::make('from')
->label('From'),
->label(__('filament.filters.from')),
DatePicker::make('until')
->label('Until'),
->label(__('filament.filters.until')),
])
->query(function (Builder $query, array $data): Builder {
return $query
@@ -35,12 +35,16 @@ class CreatedAtDateFilter
$indicators = [];
if ($data['from'] ?? null) {
$indicators[] = Indicator::make('From '.Carbon::parse($data['from'])->toFormattedDateString())
$indicators[] = Indicator::make(__('filament.filters.from_indicator', [
'date' => Carbon::parse($data['from'])->toFormattedDateString(),
]))
->removeField('from');
}
if ($data['until'] ?? null) {
$indicators[] = Indicator::make('Until '.Carbon::parse($data['until'])->toFormattedDateString())
$indicators[] = Indicator::make(__('filament.filters.until_indicator', [
'date' => Carbon::parse($data['until'])->toFormattedDateString(),
]))
->removeField('until');
}