diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7f4018ab..bd3d31b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,6 +52,43 @@ jobs: - name: Lancer la suite de tests run: php artisan test + static-analysis: + name: Analyse statique (PHPStan) + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Configurer PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + extensions: pdo_sqlite, sqlite3, mbstring, zip, gd, intl, bcmath + coverage: none + + # `.env` doit exister AVANT composer install : le script post-autoload-dump + # (`package:discover`) boote l'application. + - name: Préparer l'environnement + run: | + cp .env.example .env + touch database/database.sqlite + + - name: Cache des dépendances Composer + uses: actions/cache@v4 + with: + path: vendor + key: composer-${{ hashFiles('composer.lock') }} + restore-keys: composer- + + - name: Installer les dépendances + run: composer install --no-interaction --prefer-dist --no-progress + + - name: Générer la clé applicative + run: php artisan key:generate + + - name: Lancer PHPStan + run: composer analyse + frontend: name: Front (types & build) runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b44e4cc..10233476 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,30 @@ Toutes les évolutions notables de **Dalibi** sont consignées ici. Le format suit [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/) et le versionnage respecte [SemVer](https://semver.org/lang/fr/). +## [1.0.2] — 2026-11-13 + +### Ajouté +- **Internationalisation (i18n)** : bascule complète **français / anglais** de l'ensemble + des modules (interface, écrans et composants partagés), avec fichiers de langue dédiés + et partage des traductions côté client via Inertia. + +### Sécurité +- Montée des dépendances résorbant **67 avis de sécurité** (`composer audit` désormais + vierge), dont le **bypass d'authentification TokenGuard** de Laravel + ([CVE-2026-114509](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2026-114509), + critique — pertinent pour l'auth par jeton du portail) : Laravel 12.72, Symfony 7.4.23, + Guzzle 7.16, CommonMark 2.10, dompdf 3.1.6, ainsi que les montées majeures + phpoffice/phpspreadsheet 5 et maatwebsite/excel 4. + +### Qualité & documentation +- **Analyse statique** : intégration de **PHPStan / Larastan niveau 5** avec étape CI + dédiée, puis résorption **intégrale de la dette** (463 → 0 erreur, sans fichier + *baseline*) — typage générique des relations Eloquent, formes des résultats d'agrégats, + authentification multi-modèle du portail (élève / tuteur). +- Corrections de **bugs réels** mis au jour par l'analyse : contrainte `withSum` ignorée + sur les totaux de caisse, libellé d'archive d'élève vide, positionnement de colonne en + migration, comparaison d'identifiant UUID, accès nullsafe rétablis sur les seeders. + ## [1.0.1] — 2026-10-11 ### Ajouté diff --git a/app/Concerns/Auditable.php b/app/Concerns/Auditable.php index 5cbb81f2..ae1a097b 100644 --- a/app/Concerns/Auditable.php +++ b/app/Concerns/Auditable.php @@ -3,7 +3,6 @@ namespace App\Concerns; use App\Models\AuditLog; -use Illuminate\Database\Eloquent\Model; /** * Enregistre automatiquement les créations / modifications / suppressions @@ -16,9 +15,9 @@ trait Auditable { public static function bootAuditable(): void { - static::created(fn (Model $model) => $model->recordAudit('created', null, $model->auditableAttributes())); + static::created(fn (self $model) => $model->recordAudit('created', null, $model->auditableAttributes())); - static::updated(function (Model $model): void { + static::updated(function (self $model): void { $changed = array_keys($model->getChanges()); $keys = array_diff($changed, $model->auditExcluded()); if ($keys === []) { @@ -31,7 +30,7 @@ public static function bootAuditable(): void $model->recordAudit('updated', $old, $new); }); - static::deleted(fn (Model $model) => $model->recordAudit('deleted', $model->auditableAttributes(), null)); + static::deleted(fn (self $model) => $model->recordAudit('deleted', $model->auditableAttributes(), null)); } /** Champs exclus du journal (secrets et bruit). */ diff --git a/app/Console/Commands/GenerateStudentMatricule.php b/app/Console/Commands/GenerateStudentMatricule.php index e8f82061..8ead8a84 100644 --- a/app/Console/Commands/GenerateStudentMatricule.php +++ b/app/Console/Commands/GenerateStudentMatricule.php @@ -3,8 +3,8 @@ namespace App\Console\Commands; use App\Models\Student; -use App\Services\MatriculeService; use Illuminate\Console\Command; +use Illuminate\Database\Eloquent\Collection; class GenerateStudentMatricule extends Command { @@ -13,10 +13,10 @@ class GenerateStudentMatricule extends Command * * @var string */ - protected $signature = 'matricule:generate-student + protected $signature = 'matricule:generate-student {--class-id= : ID de la classe} {--all : Générer pour tous les élèves} - {--registration : Générer aussi les numéros d\'enregistrement} + {--matricule : Générer aussi les matricules des élèves} {--force : Forcer la régénération}'; /** @@ -31,10 +31,9 @@ class GenerateStudentMatricule extends Command */ public function handle(): int { - $matriculeService = app(MatriculeService::class); + $withStudentMatricule = (bool) $this->option('matricule'); if ($this->option('all')) { - // Générer pour tous les élèves $students = Student::all(); if ($students->isEmpty()) { @@ -44,28 +43,7 @@ public function handle(): int } $this->info("Génération des matricules pour {$students->count()} élèves..."); - - $progressBar = $this->output->createProgressBar($students->count()); - $progressBar->start(); - - foreach ($students as $student) { - // Générer matricule de l'utilisateur - if (! $student->user->natricule) { - $student->user->natricule = $student->user->generateMatricule(); - $student->user->save(); - } - - // Générer numéro d'enregistrement - if (! $student->registration_number) { - $student->registration_number = $student->generateRegistrationNumber(); - $student->save(); - } - - $progressBar->advance(); - } - - $progressBar->finish(); - $this->newLine(); + $this->backfillMatricules($students, $withStudentMatricule); $this->info('✅ Matricules générés avec succès!'); return 0; @@ -88,18 +66,32 @@ public function handle(): int } $this->info("Génération pour {$students->count()} élèves..."); + $this->backfillMatricules($students, $withStudentMatricule); + $this->info('✅ Matricules générés avec succès!'); + return 0; + } + + /** + * Génère les matricules manquants pour un lot d'élèves. + * + * @param Collection $students + */ + private function backfillMatricules(Collection $students, bool $withStudentMatricule): void + { $progressBar = $this->output->createProgressBar($students->count()); $progressBar->start(); foreach ($students as $student) { + // Matricule du compte utilisateur associé. if (! $student->user->natricule) { $student->user->natricule = $student->user->generateMatricule(); $student->user->save(); } - if (! $student->registration_number) { - $student->registration_number = $student->generateRegistrationNumber(); + // Matricule de l'élève (optionnel, via --matricule). + if ($withStudentMatricule && empty($student->matricule)) { + $student->matricule = $student->generateMatricule(); $student->save(); } @@ -108,8 +100,5 @@ public function handle(): int $progressBar->finish(); $this->newLine(); - $this->info('✅ Matricules générés avec succès!'); - - return 0; } } diff --git a/app/Constants/Locales.php b/app/Constants/Locales.php new file mode 100644 index 00000000..20786f3b --- /dev/null +++ b/app/Constants/Locales.php @@ -0,0 +1,60 @@ + + */ + public static function all(): array + { + return [ + 'fr' => ['label' => 'Français', 'flag' => '🇫🇷'], + 'en' => ['label' => 'English', 'flag' => '🇬🇧'], + ]; + } + + /** Codes valides (pour la validation). */ + public static function codes(): array + { + return array_keys(self::all()); + } + + /** Le code est-il une langue prise en charge ? */ + public static function isValid(?string $code): bool + { + return $code !== null && array_key_exists($code, self::all()); + } + + /** Libellé lisible d'un code (repli sur le code). */ + public static function label(?string $code): string + { + $code = $code ?: self::DEFAULT; + + return self::all()[$code]['label'] ?? $code; + } + + /** + * Options prêtes pour un sélecteur côté frontend. + * + * @return array + */ + public static function options(): array + { + $out = []; + foreach (self::all() as $code => $def) { + $out[] = ['code' => $code, 'label' => $def['label'], 'flag' => $def['flag']]; + } + + return $out; + } +} diff --git a/app/Exports/StatisticsExport.php b/app/Exports/StatisticsExport.php index 2a237645..6a12fc4f 100644 --- a/app/Exports/StatisticsExport.php +++ b/app/Exports/StatisticsExport.php @@ -3,10 +3,11 @@ namespace App\Exports; use Illuminate\Support\Collection; +use Maatwebsite\Excel\Concerns\Export; use Maatwebsite\Excel\Concerns\WithMultipleSheets; /** Construit un classeur Excel multi-feuilles à partir des données d'une section. */ -class StatisticsExport implements WithMultipleSheets +class StatisticsExport implements Export, WithMultipleSheets { private const METHOD_LABELS = [ 'CASH' => 'Espèces', diff --git a/app/Http/Controllers/Administration/SubjectAssignmentController.php b/app/Http/Controllers/Administration/SubjectAssignmentController.php index 08112f6e..707d88bf 100644 --- a/app/Http/Controllers/Administration/SubjectAssignmentController.php +++ b/app/Http/Controllers/Administration/SubjectAssignmentController.php @@ -91,8 +91,8 @@ public function statistics(Request $request): Response $assignments = $selected ? $this->teacherAssignments($teacherId, $yearFilter)->map(fn ($a) => [ 'id' => $a->id, - 'subject' => $a->subject?->name ?? '—', - 'classroom' => $a->classroom?->name ?? '—', + 'subject' => $a->subject->name ?? '—', + 'classroom' => $a->classroom->name ?? '—', 'active' => (bool) $a->active, ]) : collect(); @@ -143,7 +143,7 @@ public function exportStatistics(Request $request) 'assignments' => $this->teacherAssignments($teacher->id, $yearId), ])->setPaper('a4', 'portrait'); - return $pdf->stream('affectations-'.Str::slug(trim($teacher->firstname.' '.$teacher->lastname).'-'.($year?->year ?? '')).'.pdf'); + return $pdf->stream('affectations-'.Str::slug(trim($teacher->firstname.' '.$teacher->lastname).'-'.($year->year ?? '')).'.pdf'); } /** Identifiant de l'année académique active (la plus récente marquée active). */ @@ -170,7 +170,7 @@ private function teacherAssignments(string $teacherId, string $yearId): Collecti ->where('teacher_id', $teacherId) ->where('academic_year_id', $yearId) ->get() - ->sortBy(fn ($a) => ($a->subject?->name ?? '').' '.($a->classroom?->name ?? '')) + ->sortBy(fn ($a) => ($a->subject->name ?? '').' '.($a->classroom->name ?? '')) ->values(); } diff --git a/app/Http/Controllers/Api/ApiController.php b/app/Http/Controllers/Api/ApiController.php index 9dd0da72..48783eb4 100644 --- a/app/Http/Controllers/Api/ApiController.php +++ b/app/Http/Controllers/Api/ApiController.php @@ -18,6 +18,9 @@ abstract class ApiController extends Controller */ protected function accessibleStudents(Request $request): Collection { + // Ces routes API authentifient un élève ou un tuteur (gardes Sanctum + // dédiées), jamais le User par défaut supposé par l'analyse statique. + /** @var Student|Guardian|null $user */ $user = $request->user(); if ($user instanceof Student) { @@ -38,6 +41,7 @@ protected function accessibleStudents(Request $request): Collection */ protected function resolveStudent(Request $request, string $studentId): Student { + /** @var Student|Guardian|null $user */ $user = $request->user(); if ($user instanceof Student) { diff --git a/app/Http/Controllers/Api/V1/AuthController.php b/app/Http/Controllers/Api/V1/AuthController.php index 7b8962fe..e8da0508 100644 --- a/app/Http/Controllers/Api/V1/AuthController.php +++ b/app/Http/Controllers/Api/V1/AuthController.php @@ -39,6 +39,8 @@ public function login(LoginRequest $request): JsonResponse public function me(Request $request): JsonResponse { + // Portail parent/élève : le principal authentifié est un tuteur ou un élève. + /** @var Guardian|Student $user */ $user = $request->user(); $type = $user instanceof Guardian ? 'guardian' : 'student'; @@ -47,7 +49,9 @@ public function me(Request $request): JsonResponse public function logout(Request $request): JsonResponse { - $request->user()->currentAccessToken()->delete(); + /** @var Guardian|Student $user */ + $user = $request->user(); + $user->currentAccessToken()->delete(); return response()->json(['message' => 'Déconnecté.']); } diff --git a/app/Http/Controllers/Api/V1/ChildrenController.php b/app/Http/Controllers/Api/V1/ChildrenController.php index 58edffca..b72c98f1 100644 --- a/app/Http/Controllers/Api/V1/ChildrenController.php +++ b/app/Http/Controllers/Api/V1/ChildrenController.php @@ -16,6 +16,7 @@ public function index(Request $request): AnonymousResourceCollection { $withActiveEnrollment = ['enrollments' => fn ($q) => $q->where('status', Enrollment::STATUS_ACTIVE)->with('classroom:id,name')]; + /** @var Student|Guardian $user */ $user = $request->user(); if ($user instanceof Student) { diff --git a/app/Http/Controllers/Api/V1/DashboardController.php b/app/Http/Controllers/Api/V1/DashboardController.php index 90a3101e..7ca42517 100644 --- a/app/Http/Controllers/Api/V1/DashboardController.php +++ b/app/Http/Controllers/Api/V1/DashboardController.php @@ -45,7 +45,7 @@ public function index(Request $request): JsonResponse 'id' => $e->id, 'title' => $e->title, 'type' => $e->type, - 'start_date' => $e->start_date?->format('Y-m-d'), + 'start_date' => $e->start_date->format('Y-m-d'), ]); return response()->json([ diff --git a/app/Http/Controllers/Archives/ArchiveController.php b/app/Http/Controllers/Archives/ArchiveController.php index 6eadf886..3ead58ee 100644 --- a/app/Http/Controllers/Archives/ArchiveController.php +++ b/app/Http/Controllers/Archives/ArchiveController.php @@ -16,8 +16,6 @@ class ArchiveController extends Controller { - private const TYPES = ['student' => Student::class, 'classroom' => Classroom::class]; - private function authorizeManage(Request $request): void { // Socle de sécurité : les routes portent déjà la permission fine (create/edit/ @@ -234,9 +232,12 @@ private function linkLabel(ArchivedDocument $d): ?string return null; } - return match ($d->documentable_type) { - Student::class => 'Élève : '.$d->documentable->name, - Classroom::class => 'Classe : '.$d->documentable->name, + $linked = $d->documentable; + + return match (true) { + // Un élève n'a pas de colonne « name » : on compose nom + prénom. + $linked instanceof Student => 'Élève : '.trim($linked->firstname.' '.$linked->lastname), + $linked instanceof Classroom => 'Classe : '.$linked->name, default => null, }; } diff --git a/app/Http/Controllers/CalendarEventController.php b/app/Http/Controllers/CalendarEventController.php index 60c48a39..7a8bce3d 100644 --- a/app/Http/Controllers/CalendarEventController.php +++ b/app/Http/Controllers/CalendarEventController.php @@ -28,7 +28,7 @@ public function index(Request $request): Response 'title' => $e->title, 'description' => $e->description, 'type' => $e->type, - 'start_date' => $e->start_date?->format('Y-m-d'), + 'start_date' => $e->start_date->format('Y-m-d'), 'end_date' => $e->end_date?->format('Y-m-d'), 'all_day' => $e->all_day, 'start_time' => $e->start_time, diff --git a/app/Http/Controllers/Comptabilite/CashAccountController.php b/app/Http/Controllers/Comptabilite/CashAccountController.php index e982222a..a70b40df 100644 --- a/app/Http/Controllers/Comptabilite/CashAccountController.php +++ b/app/Http/Controllers/Comptabilite/CashAccountController.php @@ -15,7 +15,7 @@ class CashAccountController extends Controller public function index(): Response { $accounts = CashAccount::withCount('payments') - ->withSum('transactions as total_income', 'amount', fn ($q) => $q->where('type', 'INCOME')) + ->withSum(['transactions as total_income' => fn ($q) => $q->where('type', 'INCOME')], 'amount') ->orderBy('type') ->orderBy('name') ->get(); diff --git a/app/Http/Controllers/Comptabilite/InvoiceController.php b/app/Http/Controllers/Comptabilite/InvoiceController.php index e870f9cf..33378cb0 100644 --- a/app/Http/Controllers/Comptabilite/InvoiceController.php +++ b/app/Http/Controllers/Comptabilite/InvoiceController.php @@ -169,7 +169,7 @@ public function verifyReceipt(Request $request): Response 'valid' => true, 'receipt_number' => $receipt->receipt_number, 'amount' => (float) $receipt->payment->amount, - 'paid_at' => $receipt->payment->paid_at?->format('d/m/Y'), + 'paid_at' => $receipt->payment->paid_at->format('d/m/Y'), 'student' => $receipt->payment->invoice?->enrollment?->student ? $receipt->payment->invoice->enrollment->student->lastname.' '.$receipt->payment->invoice->enrollment->student->firstname : null, diff --git a/app/Http/Controllers/Comptabilite/SituationController.php b/app/Http/Controllers/Comptabilite/SituationController.php index aa2d8643..6a0a856c 100644 --- a/app/Http/Controllers/Comptabilite/SituationController.php +++ b/app/Http/Controllers/Comptabilite/SituationController.php @@ -93,10 +93,10 @@ classes.code AS cl 'lastname' => $e->student?->lastname, 'matricule' => $e->student?->matricule, 'invoice_number' => $e->invoice?->invoice_number, - 'total' => (float) ($e->invoice?->total ?? 0), - 'amount_paid' => (float) ($e->invoice?->amount_paid ?? 0), - 'amount_remaining' => (float) ($e->invoice?->amount_remaining ?? 0), - 'status' => $e->invoice?->status ?? 'ISSUED', + 'total' => (float) ($e->invoice->total ?? 0), + 'amount_paid' => (float) ($e->invoice->amount_paid ?? 0), + 'amount_remaining' => (float) ($e->invoice->amount_remaining ?? 0), + 'status' => $e->invoice->status ?? 'ISSUED', ]); } @@ -153,14 +153,14 @@ public function export(Request $request) foreach ($enrollments as $e) { $inv = $e->invoice; fputcsv($out, [ - $e->student?->matricule ?? '—', - $e->student?->lastname ?? '—', - $e->student?->firstname ?? '—', - $e->classroom?->name ?? '—', - $inv?->invoice_number ?? '—', - number_format((float) ($inv?->total ?? 0), 0, ',', ' '), - number_format((float) ($inv?->amount_paid ?? 0), 0, ',', ' '), - number_format((float) ($inv?->amount_remaining ?? 0), 0, ',', ' '), + $e->student->matricule ?? '—', + $e->student->lastname ?? '—', + $e->student->firstname ?? '—', + $e->classroom->name ?? '—', + $inv->invoice_number ?? '—', + number_format((float) ($inv->total ?? 0), 0, ',', ' '), + number_format((float) ($inv->amount_paid ?? 0), 0, ',', ' '), + number_format((float) ($inv->amount_remaining ?? 0), 0, ',', ' '), $this->statusLabel($inv?->status), ], ';'); } diff --git a/app/Http/Controllers/Dashboard/DashboardController.php b/app/Http/Controllers/Dashboard/DashboardController.php index f683f97a..01dfcb8b 100644 --- a/app/Http/Controllers/Dashboard/DashboardController.php +++ b/app/Http/Controllers/Dashboard/DashboardController.php @@ -115,12 +115,12 @@ public function index(Request $request): Response 'id' => $p->id, 'amount' => (float) $p->amount, 'payment_method' => $p->payment_method, - 'paid_at' => $p->paid_at?->toDateString(), + 'paid_at' => $p->paid_at->toDateString(), 'cash_account' => $p->cashAccount?->name, 'student_name' => $p->invoice?->enrollment?->student ? $p->invoice->enrollment->student->firstname.' '.$p->invoice->enrollment->student->lastname : '—', - 'class_name' => $p->invoice?->enrollment?->classroom?->name ?? '—', + 'class_name' => $p->invoice?->enrollment?->classroom->name ?? '—', ]); /* Élèves avec aucun paiement (status ISSUED) — top 5 restant le + élevé */ @@ -193,14 +193,14 @@ public function index(Request $request): Response ->map(fn ($e) => [ 'id' => $e->id, 'enrollment_code' => $e->enrollment_code, - 'enrollment_date' => $e->enrollment_date?->toDateString(), + 'enrollment_date' => $e->enrollment_date->toDateString(), 'status' => $e->status, 'student_name' => $e->student ? $e->student->firstname.' '.$e->student->lastname : '—', 'matricule' => $e->student?->matricule, - 'class_name' => $e->classroom?->name ?? '—', - 'class_code' => $e->classroom?->code ?? '—', + 'class_name' => $e->classroom->name ?? '—', + 'class_code' => $e->classroom->code ?? '—', ]); /* Répartition des inscriptions par classe (pour le graphe) */ @@ -265,8 +265,8 @@ public function index(Request $request): Response 'id' => $p->id, 'student_name' => $p->student ? $p->student->lastname.' '.$p->student->firstname : '—', 'reason' => $p->reason, - 'start_date' => $p->start_date?->format('d/m/Y'), - 'end_date' => $p->end_date?->format('d/m/Y'), + 'start_date' => $p->start_date->format('d/m/Y'), + 'end_date' => $p->end_date->format('d/m/Y'), ]); /* Prochains examens officiels */ @@ -307,6 +307,8 @@ public function index(Request $request): Response ->first(['id', 'name']); // Bulletins figés (locked_at) de l'année (et de la période courante le cas échéant). + // Agrégat sans GROUP BY : toujours une ligne, colonnes calculées. + /** @var object{validated: int|string, avg_general: float|int|string|null} $rc */ $rc = ReportCard::query() ->whereNotNull('locked_at') ->when($yearId, fn ($q) => $q->where('academic_year_id', $yearId)) @@ -332,10 +334,10 @@ public function index(Request $request): Response $myAssignments = $assignments->map(fn ($a) => [ 'id' => $a->id, - 'subject' => $a->subject?->name ?? '—', + 'subject' => $a->subject->name ?? '—', 'class_id' => $a->class_id, - 'class_name' => $a->classroom?->name ?? '—', - 'class_code' => $a->classroom?->code ?? '—', + 'class_name' => $a->classroom->name ?? '—', + 'class_code' => $a->classroom->code ?? '—', ]); /* Emploi du temps du jour (1=lundi … 6=samedi) */ @@ -350,8 +352,8 @@ public function index(Request $request): Response 'id' => $s->id, 'start_time' => substr((string) $s->start_time, 0, 5), 'end_time' => substr((string) $s->end_time, 0, 5), - 'subject' => $s->subject?->name ?? '—', - 'class_name' => $s->classroom?->name ?? '—', + 'subject' => $s->subject->name ?? '—', + 'class_name' => $s->classroom->name ?? '—', 'room' => $s->room, ]); @@ -378,9 +380,9 @@ public function index(Request $request): Response ->get() ->map(fn ($e) => [ 'id' => $e->id, - 'name' => $e->template?->name ?? 'Évaluation', - 'subject' => $e->classSubject?->subject?->name ?? '—', - 'class_name' => $e->classSubject?->class?->name ?? '—', + 'name' => $e->template->name ?? 'Évaluation', + 'subject' => $e->classSubject?->subject->name ?? '—', + 'class_name' => $e->classSubject?->class->name ?? '—', 'date' => $e->date?->format('d/m/Y'), ]); diff --git a/app/Http/Controllers/Eleves/EnrollmentController.php b/app/Http/Controllers/Eleves/EnrollmentController.php index 9fc5fc3c..f2216fa2 100644 --- a/app/Http/Controllers/Eleves/EnrollmentController.php +++ b/app/Http/Controllers/Eleves/EnrollmentController.php @@ -86,6 +86,9 @@ public function index(Request $request): Response ->where('invoices.status', '!=', 'CANCELLED'); $applyFilters($aggQuery); + // Agrégat sans GROUP BY : renvoie toujours exactement une ligne de synthèse + // dont les colonnes sont calculées (invisibles à l'analyse statique). + /** @var object{billed: int|float|string, collected: int|float|string, remaining: int|float|string, unpaid_count: int|float|string, unpaid_amount: int|float|string} $agg */ $agg = $aggQuery->selectRaw(' COALESCE(SUM(invoices.total), 0) as billed, COALESCE(SUM(invoices.amount_paid), 0) as collected, diff --git a/app/Http/Controllers/Eleves/RosterController.php b/app/Http/Controllers/Eleves/RosterController.php index b84b80a0..92a2d041 100644 --- a/app/Http/Controllers/Eleves/RosterController.php +++ b/app/Http/Controllers/Eleves/RosterController.php @@ -30,7 +30,7 @@ public function index(Request $request): Response // Année par défaut : celle demandée, sinon l'année active if ($yearId === '') { - $yearId = $activeYear?->id ?? ''; + $yearId = $activeYear->id ?? ''; } $classrooms = Classroom::where('active', true)->orderBy('name')->get(['id', 'name', 'code']); @@ -59,7 +59,7 @@ public function index(Request $request): Response 'student_name' => $e->student ? $e->student->lastname.' '.$e->student->firstname : '—', 'matricule' => $e->student?->matricule, 'gender' => $e->student?->gender, - 'payment_status' => $e->invoice?->status ?? 'NONE', + 'payment_status' => $e->invoice->status ?? 'NONE', 'academic_status' => $e->academic_status ?? 'en_cours', 'status_reason' => $e->status_reason, ])->values(); diff --git a/app/Http/Controllers/Eleves/StudentController.php b/app/Http/Controllers/Eleves/StudentController.php index 555f6b56..f7893beb 100644 --- a/app/Http/Controllers/Eleves/StudentController.php +++ b/app/Http/Controllers/Eleves/StudentController.php @@ -219,7 +219,7 @@ public function show(Student $student): Response 'reference_number' => $i->reference_number, 'template_name' => $i->template?->name, 'issued_by' => $i->issuedBy?->name, - 'issued_at' => $i->issued_at?->format('d/m/Y H:i'), + 'issued_at' => $i->issued_at->format('d/m/Y H:i'), ]); // Inscription de l'année active (pour la réaffectation de classe) @@ -244,7 +244,7 @@ public function show(Student $student): Response 'class_id' => $currentEnrollment->class_id, 'class_name' => $currentEnrollment->classroom?->name, 'class_code' => $currentEnrollment->classroom?->code, - 'year' => $activeYear?->year, + 'year' => $activeYear->year, ] : null, 'classrooms' => Classroom::where('active', true)->orderBy('name')->get(['id', 'name', 'code']), 'documents' => $student->documents()->with('uploadedBy:id,firstname,lastname')->latest()->get()->map(fn ($d) => [ @@ -271,13 +271,13 @@ public function changeClass(Request $request, Student $student): RedirectRespons ]); $activeYear = AcademicYear::where('active', true)->first(['id']); - abort_unless($activeYear, 422, 'Aucune année académique active.'); + abort_unless($activeYear !== null, 422, 'Aucune année académique active.'); $enrollment = Enrollment::where('student_id', $student->id) ->where('academic_year_id', $activeYear->id) ->first(); - abort_unless($enrollment, 422, 'Cet élève n\'a pas d\'inscription pour l\'année active.'); + abort_unless($enrollment !== null, 422, 'Cet élève n\'a pas d\'inscription pour l\'année active.'); $enrollment->update(['class_id' => $validated['class_id']]); @@ -305,9 +305,9 @@ public function history(Student $student): Response 'code' => $enrollment->classroom?->code, ], 'academic_year' => $enrollment->academicYear?->year, - 'enrollment_date' => $enrollment->enrollment_date?->format('Y-m-d'), + 'enrollment_date' => $enrollment->enrollment_date->format('Y-m-d'), 'enrollment_code' => $enrollment->enrollment_code, - 'status' => $enrollment->invoice?->status ?? 'NONE', + 'status' => $enrollment->invoice->status ?? 'NONE', 'academic_status' => $enrollment->academic_status ?? 'en_cours', 'academic_status_label' => Enrollment::ACADEMIC_STATUSES[$enrollment->academic_status ?? 'en_cours'] ?? '—', 'status_reason' => $enrollment->status_reason, diff --git a/app/Http/Controllers/Eleves/StudentStatsController.php b/app/Http/Controllers/Eleves/StudentStatsController.php index bf611729..410b6322 100644 --- a/app/Http/Controllers/Eleves/StudentStatsController.php +++ b/app/Http/Controllers/Eleves/StudentStatsController.php @@ -8,6 +8,7 @@ use App\Models\Student; use Illuminate\Http\Request; use Illuminate\Support\Carbon; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; use Inertia\Inertia; @@ -48,7 +49,8 @@ public function index(Request $request): Response ]; // Répartition par nationalité (top 6, cohorte) - $byNationality = Student::query() + /** @var Collection $natRows */ + $natRows = Student::query() ->whereIn('id', $studentIds) ->select('nationality', DB::raw('COUNT(*) as count')) ->whereNotNull('nationality') @@ -56,8 +58,8 @@ public function index(Request $request): Response ->groupBy('nationality') ->orderByDesc('count') ->limit(6) - ->get() - ->map(fn ($r) => ['label' => $r->nationality, 'count' => (int) $r->count]); + ->get(); + $byNationality = $natRows->map(fn ($r) => ['label' => $r->nationality, 'count' => (int) $r->count]); // Répartition par tranche d'âge (cohorte) $brackets = [ @@ -89,6 +91,7 @@ public function index(Request $request): Response // Sur-âge (retard scolaire) : âge de l'élève >= âge attendu de sa classe + 2. $overAgeThreshold = 2; + /** @var Collection $ageRows */ $ageRows = Enrollment::query() ->join('classes', 'classes.id', '=', 'enrollments.class_id') ->join('students', 'students.id', '=', 'enrollments.student_id') @@ -102,7 +105,8 @@ public function index(Request $request): Response // Effectifs par classe (année sélectionnée, scolarité active), enrichis du // cycle, du sexe et de la capacité pour permettre filtre et empilage côté client. - $byClass = $selectedYearId + /** @var Collection $byClassRows */ + $byClassRows = $selectedYearId ? Enrollment::query() ->join('classes', 'classes.id', '=', 'enrollments.class_id') ->join('students', 'students.id', '=', 'enrollments.student_id') @@ -118,16 +122,16 @@ classes.expected_age AS level, ->orderByRaw('classes.expected_age NULLS LAST') ->orderBy('classes.name') ->get() - ->map(fn ($r) => [ - 'label' => $r->label, - 'cycle' => $this->cycleLabel($r->cycle), - 'capacity' => (int) $r->capacity, - 'level' => $r->level !== null ? (int) $r->level : null, - 'count' => (int) $r->total, - 'male' => (int) $r->male, - 'female' => (int) $r->female, - ]) : collect(); + $byClass = $byClassRows->map(fn ($r) => [ + 'label' => $r->label, + 'cycle' => $this->cycleLabel($r->cycle), + 'capacity' => (int) $r->capacity, + 'level' => $r->level !== null ? (int) $r->level : null, + 'count' => (int) $r->total, + 'male' => (int) $r->male, + 'female' => (int) $r->female, + ]); return Inertia::render('Eleves/Students/Stats', [ 'summary' => [ diff --git a/app/Http/Controllers/Eleves/TimetableController.php b/app/Http/Controllers/Eleves/TimetableController.php index a0802ebe..df4b6e79 100644 --- a/app/Http/Controllers/Eleves/TimetableController.php +++ b/app/Http/Controllers/Eleves/TimetableController.php @@ -125,7 +125,7 @@ public function teacherExport(Request $request) return $this->schedulePdf( $this->teacherSlots($teacher->id, $activeYearId), title: 'EMPLOI DU TEMPS — ENSEIGNANT', - subtitle: $teacher->name.' — Année : '.($year?->year ?? '—'), + subtitle: $teacher->name.' — Année : '.($year->year ?? '—'), filename: 'emploi-du-temps-'.Str::slug($teacher->name), ); } @@ -211,15 +211,19 @@ private function teacherSlots(string $teacherId, ?string $yearId): Collection */ private function buildScheduleGrid(Collection $slots): array { - $range = fn ($s) => substr($s->start_time, 0, 5).'-'.substr($s->end_time, 0, 5); + $range = fn (TimetableSlot $s): string => substr($s->start_time, 0, 5).'-'.substr($s->end_time, 0, 5); + /** @var array> $grid */ $grid = []; foreach ($slots as $slot) { $grid[$range($slot)][$slot->day_of_week] = $slot; } + /** @var Collection $timeRanges */ + $timeRanges = $slots->map($range)->unique()->sort()->values(); + return [ - 'timeRanges' => $slots->map($range)->unique()->sort()->values(), + 'timeRanges' => $timeRanges, 'grid' => $grid, ]; } diff --git a/app/Http/Controllers/Examens/MarkController.php b/app/Http/Controllers/Examens/MarkController.php index 43ff2381..900a57e1 100644 --- a/app/Http/Controllers/Examens/MarkController.php +++ b/app/Http/Controllers/Examens/MarkController.php @@ -61,7 +61,7 @@ public function index(Evaluation $evaluation): Response 'student' => $enrollment->student, 'mark_id' => $mark?->id, 'score' => $mark?->score !== null ? (float) $mark->score : null, - 'absent' => $mark?->absent ?? false, + 'absent' => $mark->absent ?? false, 'comments' => $mark?->comments, ]; })->values(); @@ -97,7 +97,7 @@ public function store(Request $request, Evaluation $evaluation) return back()->withErrors(['locked' => $decision->message()]); } - $maxScore = (float) ($evaluation->template?->max_score ?? 20); + $maxScore = (float) ($evaluation->template->max_score ?? 20); // L'élève noté doit être inscrit (activement) dans la classe de l'évaluation : // `exists:students,id` seul laisserait noter un élève d'une autre classe. diff --git a/app/Http/Controllers/Examens/OfficialExamController.php b/app/Http/Controllers/Examens/OfficialExamController.php index 33400cd8..7507d459 100644 --- a/app/Http/Controllers/Examens/OfficialExamController.php +++ b/app/Http/Controllers/Examens/OfficialExamController.php @@ -26,7 +26,7 @@ public function index(Request $request): Response $years = AcademicYear::orderByDesc('start_date')->get(['id', 'year', 'active']); // Année par défaut = année académique active - $yearId = $request->string('academic_year_id')->toString() ?: ($activeYear?->id ?? ''); + $yearId = $request->string('academic_year_id')->toString() ?: ($activeYear->id ?? ''); $type = $request->string('type')->toString(); $session = $request->string('session')->toString(); $status = $request->string('status')->toString(); diff --git a/app/Http/Controllers/Notes/NoteReclamationController.php b/app/Http/Controllers/Notes/NoteReclamationController.php index 251c1b41..43db1d88 100644 --- a/app/Http/Controllers/Notes/NoteReclamationController.php +++ b/app/Http/Controllers/Notes/NoteReclamationController.php @@ -55,7 +55,7 @@ public function index(Request $request): Response ]); } - public function create(Request $request): Response + public function create(Request $request): Response|RedirectResponse { abort_unless($request->user()->can('create_note_reclamations'), 403); @@ -152,7 +152,7 @@ public function review(ReviewNoteReclamationRequest $request, NoteReclamation $n $data = $request->validated(); $evaluation = $noteReclamation->evaluation()->with('template:id,max_score')->first(); - $maxScore = (float) ($evaluation?->template?->max_score ?? 9999); + $maxScore = (float) ($evaluation?->template->max_score ?? 9999); if ($data['status'] === 'approved' && isset($data['corrected_score']) && $data['corrected_score'] > $maxScore) { return back()->withErrors(['corrected_score' => "La note corrigée ne peut pas dépasser {$maxScore}."]); diff --git a/app/Http/Controllers/Parametres/BulletinTemplateController.php b/app/Http/Controllers/Parametres/BulletinTemplateController.php index c105df61..024894bd 100644 --- a/app/Http/Controllers/Parametres/BulletinTemplateController.php +++ b/app/Http/Controllers/Parametres/BulletinTemplateController.php @@ -21,8 +21,8 @@ public function edit(Request $request): Response $template = BulletinTemplate::where('school_id', $school->id) ->whereNull('classroom_type_id')->first(); - $columns = $template?->columns ?? BulletinTemplate::defaultColumns(); - $options = $template?->options ?? BulletinTemplate::defaultOptions(); + $columns = $template->columns ?? BulletinTemplate::defaultColumns(); + $options = $template->options ?? BulletinTemplate::defaultOptions(); return Inertia::render('Parametres/BulletinTemplates/Edit', [ 'columns' => $columns, @@ -87,7 +87,7 @@ private function presets(): array private function activeSchool(): School { $school = School::query()->first(); - abort_unless($school, 404, 'Aucune école configurée.'); + abort_unless($school !== null, 404, 'Aucune école configurée.'); return $school; } diff --git a/app/Http/Controllers/Parametres/DocumentHeaderController.php b/app/Http/Controllers/Parametres/DocumentHeaderController.php index 040c7a34..57268cda 100644 --- a/app/Http/Controllers/Parametres/DocumentHeaderController.php +++ b/app/Http/Controllers/Parametres/DocumentHeaderController.php @@ -29,7 +29,7 @@ public function edit(Request $request): Response return Inertia::render('Parametres/Documents/HeaderDesigner', [ 'header' => $config, - 'preset' => $header?->preset ?? 'ministeriel', + 'preset' => $header->preset ?? 'ministeriel', 'presets' => DocumentHeader::PRESETS, 'default' => DocumentHeader::defaultLayout($school), 'watermarkImageUrl' => $this->mediaUrl($config['watermark']['image_path'] ?? null), @@ -97,7 +97,7 @@ private function sanitizeLayout(array $layout): array private function activeSchool(): School { $school = School::query()->first(); - abort_unless($school, 404, 'Aucune école configurée.'); + abort_unless($school !== null, 404, 'Aucune école configurée.'); return $school; } diff --git a/app/Http/Controllers/Parametres/DocumentTemplateController.php b/app/Http/Controllers/Parametres/DocumentTemplateController.php index f089a667..33e61a22 100644 --- a/app/Http/Controllers/Parametres/DocumentTemplateController.php +++ b/app/Http/Controllers/Parametres/DocumentTemplateController.php @@ -83,7 +83,7 @@ public function registry(Request $request): Response 'student_name' => $i->student ? $i->student->lastname.' '.$i->student->firstname : '—', 'student_matricule' => $i->student?->matricule, 'issued_by' => $i->issuedBy?->name, - 'issued_at' => $i->issued_at?->format('d/m/Y H:i'), + 'issued_at' => $i->issued_at->format('d/m/Y H:i'), ]); return Inertia::render('Parametres/Documents/Registry', [ diff --git a/app/Http/Controllers/Presences/AttendanceController.php b/app/Http/Controllers/Presences/AttendanceController.php index e128571c..3a429a50 100644 --- a/app/Http/Controllers/Presences/AttendanceController.php +++ b/app/Http/Controllers/Presences/AttendanceController.php @@ -77,7 +77,7 @@ public function index(Request $request): Response 'student_id' => $enrollment->student_id, 'student' => $enrollment->student, 'record_id' => $record?->id, - 'status' => $record?->status ?? ($permission ? 'excused' : 'present'), + 'status' => $record->status ?? ($permission ? 'excused' : 'present'), 'minutes_late' => $record?->minutes_late, 'comment' => $record?->comment, 'has_permission' => (bool) $permission, diff --git a/app/Http/Controllers/Settings/LanguageController.php b/app/Http/Controllers/Settings/LanguageController.php new file mode 100644 index 00000000..e0793396 --- /dev/null +++ b/app/Http/Controllers/Settings/LanguageController.php @@ -0,0 +1,28 @@ +validate([ + 'locale' => ['required', 'string', Rule::in(Locales::codes())], + ]); + + $request->user()?->update(['locale' => $data['locale']]); + $request->session()->put('locale', $data['locale']); + + return back(); + } +} diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index cc18a373..8a85a29f 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -3,8 +3,10 @@ namespace App\Http\Middleware; use App\Constants\Currencies; +use App\Constants\Locales; use App\Models\School; use Illuminate\Http\Request; +use Illuminate\Support\Facades\App; use Inertia\Middleware; class HandleInertiaRequests extends Middleware @@ -53,9 +55,47 @@ public function share(Request $request): array 'settings' => [ 'currency' => $user ? $this->currency() : self::defaultCurrency(), ], + // Internationalisation : langue active, langues disponibles et dictionnaire + // de la langue active (la langue par défaut sert de repli pour les clés absentes). + 'locale' => App::getLocale(), + 'locales' => Locales::options(), + 'translations' => $this->translations(App::getLocale()), ]; } + /** + * Dictionnaire de traduction d'une langue : chaque fichier `lang/{code}/*.php` + * devient un groupe. La langue par défaut est fusionnée dessous comme repli. + * + * @return array + */ + private function translations(string $locale): array + { + $fallback = $this->loadLang(config('app.fallback_locale', Locales::DEFAULT)); + $active = $this->loadLang($locale); + + return array_replace_recursive($fallback, $active); + } + + /** + * Charge tous les groupes de `lang/{locale}/*.php`. + * + * @return array + */ + private function loadLang(string $locale): array + { + $out = []; + $dir = lang_path($locale); + + if (is_dir($dir)) { + foreach (glob($dir.'/*.php') as $file) { + $out[basename($file, '.php')] = require $file; + } + } + + return $out; + } + /** * Monnaie de l'établissement (code + symbole) pour le formatage des montants côté front. * diff --git a/app/Http/Middleware/SetLocale.php b/app/Http/Middleware/SetLocale.php new file mode 100644 index 00000000..24cf854e --- /dev/null +++ b/app/Http/Middleware/SetLocale.php @@ -0,0 +1,29 @@ +user()->locale + ?? $request->session()->get('locale') + ?? config('app.locale'); + + $locale = Locales::isValid($candidate) ? $candidate : Locales::DEFAULT; + + App::setLocale($locale); + + return $next($request); + } +} diff --git a/app/Http/Resources/Api/CalendarEventResource.php b/app/Http/Resources/Api/CalendarEventResource.php index cc3b310a..f148d845 100644 --- a/app/Http/Resources/Api/CalendarEventResource.php +++ b/app/Http/Resources/Api/CalendarEventResource.php @@ -2,9 +2,13 @@ namespace App\Http\Resources\Api; +use App\Models\CalendarEvent; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; +/** + * @mixin CalendarEvent + */ class CalendarEventResource extends JsonResource { public function toArray(Request $request): array @@ -13,7 +17,7 @@ public function toArray(Request $request): array 'id' => $this->id, 'title' => $this->title, 'type' => $this->type, - 'start_date' => $this->start_date?->format('Y-m-d'), + 'start_date' => $this->start_date->format('Y-m-d'), 'end_date' => $this->end_date?->format('Y-m-d'), 'all_day' => $this->all_day, 'start_time' => $this->start_time, diff --git a/app/Http/Resources/Api/ReportCardResource.php b/app/Http/Resources/Api/ReportCardResource.php index 72db4cbb..03014029 100644 --- a/app/Http/Resources/Api/ReportCardResource.php +++ b/app/Http/Resources/Api/ReportCardResource.php @@ -2,9 +2,13 @@ namespace App\Http\Resources\Api; +use App\Models\ReportCard; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; +/** + * @mixin ReportCard + */ class ReportCardResource extends JsonResource { public function toArray(Request $request): array diff --git a/app/Http/Resources/Api/StudentResource.php b/app/Http/Resources/Api/StudentResource.php index c44af472..74558f1a 100644 --- a/app/Http/Resources/Api/StudentResource.php +++ b/app/Http/Resources/Api/StudentResource.php @@ -2,9 +2,13 @@ namespace App\Http\Resources\Api; +use App\Models\Student; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; +/** + * @mixin Student + */ class StudentResource extends JsonResource { public function toArray(Request $request): array diff --git a/app/Models/AbsencePermission.php b/app/Models/AbsencePermission.php index b11fd806..ecd60384 100644 --- a/app/Models/AbsencePermission.php +++ b/app/Models/AbsencePermission.php @@ -32,21 +32,33 @@ class AbsencePermission extends Model 'reviewed_at' => 'datetime', ]; + /** + * @return BelongsTo + */ public function student(): BelongsTo { return $this->belongsTo(Student::class); } + /** + * @return BelongsTo + */ public function requestedBy(): BelongsTo { return $this->belongsTo(User::class, 'requested_by'); } + /** + * @return BelongsTo + */ public function reviewedBy(): BelongsTo { return $this->belongsTo(User::class, 'reviewed_by'); } + /** + * @return HasMany + */ public function attendanceRecords(): HasMany { return $this->hasMany(AttendanceRecord::class, 'permission_id'); diff --git a/app/Models/AcademicPeriod.php b/app/Models/AcademicPeriod.php index 12bd7037..02cacda9 100644 --- a/app/Models/AcademicPeriod.php +++ b/app/Models/AcademicPeriod.php @@ -16,12 +16,12 @@ namespace App\Models; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Concerns\HasUuids; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; -use Illuminate\Support\Collection; class AcademicPeriod extends Model { @@ -49,6 +49,8 @@ class AcademicPeriod extends Model /** * Get the academic year that owns the period. + * + * @return BelongsTo */ public function academicYear(): BelongsTo { @@ -57,6 +59,8 @@ public function academicYear(): BelongsTo /** * Get the class type this period is scoped to (null = toutes les classes). + * + * @return BelongsTo */ public function classType(): BelongsTo { @@ -65,6 +69,8 @@ public function classType(): BelongsTo /** * Get evaluations attached to this academic period. + * + * @return HasMany */ public function evaluations(): HasMany { @@ -75,7 +81,7 @@ public function evaluations(): HasMany * Périodes applicables à un type de classe pour une année donnée. * Priorité aux périodes spécifiques au type ; sinon, périodes globales (class_type_id null). * - * @return Collection + * @return Collection */ public static function forClassType(?string $academicYearId, ?string $classTypeId) { diff --git a/app/Models/AcademicYear.php b/app/Models/AcademicYear.php index dc68093f..a1877c35 100644 --- a/app/Models/AcademicYear.php +++ b/app/Models/AcademicYear.php @@ -48,6 +48,8 @@ public function getNameAttribute(): string /** * Get all student scholarships for this academic year. + * + * @return HasMany */ public function studentScholarships(): HasMany { diff --git a/app/Models/AccountingTransaction.php b/app/Models/AccountingTransaction.php index 3d7b9e47..1980a69e 100644 --- a/app/Models/AccountingTransaction.php +++ b/app/Models/AccountingTransaction.php @@ -34,11 +34,17 @@ class AccountingTransaction extends Model /* Relations */ /* ------------------------------------------------------------------ */ + /** + * @return BelongsTo + */ public function cashAccount(): BelongsTo { return $this->belongsTo(CashAccount::class); } + /** + * @return BelongsTo + */ public function createdBy(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); diff --git a/app/Models/ArchivedDocument.php b/app/Models/ArchivedDocument.php index 87a079de..c368913b 100644 --- a/app/Models/ArchivedDocument.php +++ b/app/Models/ArchivedDocument.php @@ -37,11 +37,17 @@ class ArchivedDocument extends Model 'autre' => 'Autre', ]; + /** + * @return BelongsToMany + */ public function tags(): BelongsToMany { return $this->belongsToMany(DocumentTag::class, 'archived_document_tag'); } + /** + * @return BelongsTo + */ public function archivedBy(): BelongsTo { return $this->belongsTo(User::class, 'archived_by'); diff --git a/app/Models/Attendance.php b/app/Models/Attendance.php index 6c1e744f..f57a5ac8 100644 --- a/app/Models/Attendance.php +++ b/app/Models/Attendance.php @@ -25,21 +25,33 @@ class Attendance extends Model 'date' => 'date', ]; + /** + * @return BelongsTo + */ public function classroom(): BelongsTo { return $this->belongsTo(Classroom::class, 'class_id'); } + /** + * @return BelongsTo + */ public function academicPeriod(): BelongsTo { return $this->belongsTo(AcademicPeriod::class); } + /** + * @return BelongsTo + */ public function recordedBy(): BelongsTo { return $this->belongsTo(User::class, 'recorded_by'); } + /** + * @return HasMany + */ public function records(): HasMany { return $this->hasMany(AttendanceRecord::class); diff --git a/app/Models/AttendanceRecord.php b/app/Models/AttendanceRecord.php index 6b9a9980..c45ba1ea 100644 --- a/app/Models/AttendanceRecord.php +++ b/app/Models/AttendanceRecord.php @@ -24,16 +24,25 @@ class AttendanceRecord extends Model 'minutes_late' => 'integer', ]; + /** + * @return BelongsTo + */ public function attendance(): BelongsTo { return $this->belongsTo(Attendance::class); } + /** + * @return BelongsTo + */ public function student(): BelongsTo { return $this->belongsTo(Student::class); } + /** + * @return BelongsTo + */ public function permission(): BelongsTo { return $this->belongsTo(AbsencePermission::class, 'permission_id'); diff --git a/app/Models/AuditLog.php b/app/Models/AuditLog.php index 23d40591..73c7fceb 100644 --- a/app/Models/AuditLog.php +++ b/app/Models/AuditLog.php @@ -31,6 +31,9 @@ class AuditLog extends Model 'new_values' => 'array', ]; + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); diff --git a/app/Models/Backup.php b/app/Models/Backup.php index 213021a1..caae059f 100644 --- a/app/Models/Backup.php +++ b/app/Models/Backup.php @@ -22,11 +22,17 @@ class Backup extends Model 'includes_media' => 'boolean', ]; + /** + * @return BelongsTo + */ public function createdBy(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); } + /** + * @return BelongsTo + */ public function academicYear(): BelongsTo { return $this->belongsTo(AcademicYear::class, 'academic_year_id'); diff --git a/app/Models/BulletinTemplate.php b/app/Models/BulletinTemplate.php index 5d3fc6d8..62db6de7 100644 --- a/app/Models/BulletinTemplate.php +++ b/app/Models/BulletinTemplate.php @@ -45,11 +45,17 @@ class BulletinTemplate extends Model 'moyenne' => 'Moyenne de période', ]; + /** + * @return BelongsTo + */ public function school(): BelongsTo { return $this->belongsTo(School::class); } + /** + * @return BelongsTo + */ public function classroomType(): BelongsTo { return $this->belongsTo(ClassroomType::class, 'classroom_type_id'); diff --git a/app/Models/CalendarEvent.php b/app/Models/CalendarEvent.php index bb562af6..9c5dd8e7 100644 --- a/app/Models/CalendarEvent.php +++ b/app/Models/CalendarEvent.php @@ -40,11 +40,17 @@ class CalendarEvent extends Model 'other' => 'Autre', ]; + /** + * @return BelongsTo + */ public function academicYear(): BelongsTo { return $this->belongsTo(AcademicYear::class); } + /** + * @return BelongsTo + */ public function createdBy(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); diff --git a/app/Models/CashAccount.php b/app/Models/CashAccount.php index 757c4e32..1f610cae 100644 --- a/app/Models/CashAccount.php +++ b/app/Models/CashAccount.php @@ -28,11 +28,17 @@ class CashAccount extends Model /* Relations */ /* ------------------------------------------------------------------ */ + /** + * @return HasMany + */ public function transactions(): HasMany { return $this->hasMany(AccountingTransaction::class); } + /** + * @return HasMany + */ public function payments(): HasMany { return $this->hasMany(Payment::class); @@ -44,11 +50,11 @@ public function payments(): HasMany public function typeLabel(): string { + // Colonne enum en base : les trois valeurs couvrent tous les cas. return match ($this->type) { 'CASH' => 'Espèces', 'MOBILE_MONEY' => 'Mobile Money', 'BANK' => 'Banque', - default => $this->type, }; } } diff --git a/app/Models/ClassSubject.php b/app/Models/ClassSubject.php index fd34eeb1..c5ad7dc3 100644 --- a/app/Models/ClassSubject.php +++ b/app/Models/ClassSubject.php @@ -46,6 +46,8 @@ class ClassSubject extends Model /** * Get the class this assignment belongs to. + * + * @return BelongsTo */ public function class(): BelongsTo { @@ -54,6 +56,8 @@ public function class(): BelongsTo /** * Get the subject for this assignment. + * + * @return BelongsTo */ public function subject(): BelongsTo { @@ -62,6 +66,8 @@ public function subject(): BelongsTo /** * Get the academic year for this class-subject assignment. + * + * @return BelongsTo */ public function academicYear(): BelongsTo { @@ -70,6 +76,8 @@ public function academicYear(): BelongsTo /** * Get all grades for this class-subject combination. + * + * @return HasMany */ public function grades(): HasMany { @@ -78,6 +86,8 @@ public function grades(): HasMany /** * Get all evaluations for this class-subject combination. + * + * @return HasMany */ public function evaluations(): HasMany { diff --git a/app/Models/Classroom.php b/app/Models/Classroom.php index 614d9efe..43d98075 100644 --- a/app/Models/Classroom.php +++ b/app/Models/Classroom.php @@ -45,6 +45,8 @@ class Classroom extends Model /** * Get the classroom type for this classroom. + * + * @return BelongsTo */ public function type(): BelongsTo { @@ -53,6 +55,8 @@ public function type(): BelongsTo /** * Alias for type() relationship. + * + * @return BelongsTo */ public function classroomType(): BelongsTo { @@ -61,6 +65,8 @@ public function classroomType(): BelongsTo /** * Get all attendances for this class. + * + * @return HasMany */ public function attendances(): HasMany { @@ -69,6 +75,8 @@ public function attendances(): HasMany /** * Get all class subjects for this class. + * + * @return HasMany */ public function classSubjects(): HasMany { @@ -77,6 +85,8 @@ public function classSubjects(): HasMany /** * Get all evaluations for this class. + * + * @return HasMany */ public function evaluations(): HasMany { diff --git a/app/Models/ClassroomType.php b/app/Models/ClassroomType.php index efc89cd5..0a8c4ab1 100644 --- a/app/Models/ClassroomType.php +++ b/app/Models/ClassroomType.php @@ -55,13 +55,19 @@ public function periodsCount(): int /** * Get all classrooms with this type. + * + * @return HasMany */ public function classrooms(): HasMany { return $this->hasMany(Classroom::class, 'classroom_type_id'); } - /** Écoles qui proposent ce type de classe. */ + /** + * Écoles qui proposent ce type de classe. + * + * @return BelongsToMany + */ public function schools(): BelongsToMany { return $this->belongsToMany(School::class, 'class_type_school'); diff --git a/app/Models/DocumentHeader.php b/app/Models/DocumentHeader.php index 53c56c01..7434e8d1 100644 --- a/app/Models/DocumentHeader.php +++ b/app/Models/DocumentHeader.php @@ -32,6 +32,9 @@ class DocumentHeader extends Model /** Largeur de référence du canevas = largeur utile d'une page A4 portrait (px @96dpi). */ public const CANVAS_WIDTH = 760; + /** + * @return BelongsTo + */ public function school(): BelongsTo { return $this->belongsTo(School::class); diff --git a/app/Models/DocumentIssuance.php b/app/Models/DocumentIssuance.php index 2393a774..8fb86115 100644 --- a/app/Models/DocumentIssuance.php +++ b/app/Models/DocumentIssuance.php @@ -24,16 +24,25 @@ class DocumentIssuance extends Model 'issued_at' => 'datetime', ]; + /** + * @return BelongsTo + */ public function template(): BelongsTo { return $this->belongsTo(DocumentTemplate::class, 'template_id'); } + /** + * @return BelongsTo + */ public function student(): BelongsTo { return $this->belongsTo(Student::class); } + /** + * @return BelongsTo + */ public function issuedBy(): BelongsTo { return $this->belongsTo(User::class, 'issued_by'); diff --git a/app/Models/DocumentTag.php b/app/Models/DocumentTag.php index 1efa59b8..67cbdd0e 100644 --- a/app/Models/DocumentTag.php +++ b/app/Models/DocumentTag.php @@ -13,6 +13,9 @@ class DocumentTag extends Model protected $fillable = ['name', 'slug', 'color']; + /** + * @return BelongsToMany + */ public function documents(): BelongsToMany { return $this->belongsToMany(ArchivedDocument::class, 'archived_document_tag'); diff --git a/app/Models/DocumentTemplate.php b/app/Models/DocumentTemplate.php index fa5e21b9..a0854cda 100644 --- a/app/Models/DocumentTemplate.php +++ b/app/Models/DocumentTemplate.php @@ -82,11 +82,17 @@ class DocumentTemplate extends Model ], ]; + /** + * @return BelongsTo + */ public function school(): BelongsTo { return $this->belongsTo(School::class); } + /** + * @return HasMany + */ public function issuances(): HasMany { return $this->hasMany(DocumentIssuance::class, 'template_id'); diff --git a/app/Models/EmployeeAllowance.php b/app/Models/EmployeeAllowance.php index 9f9f9d77..15fbffc4 100644 --- a/app/Models/EmployeeAllowance.php +++ b/app/Models/EmployeeAllowance.php @@ -36,11 +36,17 @@ class EmployeeAllowance extends Model 'active' => 'boolean', ]; + /** + * @return BelongsTo + */ public function employeeProfile(): BelongsTo { return $this->belongsTo(EmployeeProfile::class); } + /** + * @return BelongsTo + */ public function createdBy(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); diff --git a/app/Models/EmployeeProfile.php b/app/Models/EmployeeProfile.php index 3fc50111..b55f2475 100644 --- a/app/Models/EmployeeProfile.php +++ b/app/Models/EmployeeProfile.php @@ -37,22 +37,35 @@ class EmployeeProfile extends Model 'end_date' => 'date', ]; + /** + * @return BelongsTo + */ public function user(): BelongsTo { return $this->belongsTo(User::class); } + /** + * @return HasMany + */ public function payslips(): HasMany { return $this->hasMany(Payslip::class); } + /** + * @return BelongsTo + */ public function salaryGrade(): BelongsTo { return $this->belongsTo(SalaryGrade::class); } - /** Primes / retenues récurrentes propres à l'employé (tracées). */ + /** + * Primes / retenues récurrentes propres à l'employé (tracées). + * + * @return HasMany + */ public function allowances(): HasMany { return $this->hasMany(EmployeeAllowance::class); @@ -61,7 +74,7 @@ public function allowances(): HasMany /** Nom complet de l'employé (via le compte utilisateur). */ public function fullName(): string { - return trim(($this->user?->firstname ?? '').' '.($this->user?->lastname ?? '')); + return trim(($this->user->firstname ?? '').' '.($this->user->lastname ?? '')); } /** @@ -70,6 +83,6 @@ public function fullName(): string */ public function effectiveBaseSalary(): float { - return (float) ($this->salaryGrade?->base_amount ?? $this->base_salary); + return (float) ($this->salaryGrade->base_amount ?? $this->base_salary); } } diff --git a/app/Models/Enrollment.php b/app/Models/Enrollment.php index ebff4edf..e1450af4 100644 --- a/app/Models/Enrollment.php +++ b/app/Models/Enrollment.php @@ -79,31 +79,49 @@ public function scopeActive(Builder $query): Builder /** Statuts considérés comme « scolarité active » (présents à l'appel). */ public const ACTIVE_ACADEMIC_STATUSES = ['en_cours', 'valide', 'non_valide']; + /** + * @return BelongsTo + */ public function school(): BelongsTo { return $this->belongsTo(School::class); } + /** + * @return BelongsTo + */ public function student(): BelongsTo { return $this->belongsTo(Student::class); } + /** + * @return BelongsTo + */ public function classroom(): BelongsTo { return $this->belongsTo(Classroom::class, 'class_id'); } + /** + * @return BelongsTo + */ public function academicYear(): BelongsTo { return $this->belongsTo(AcademicYear::class, 'academic_year_id'); } + /** + * @return BelongsTo + */ public function enrolledBy(): BelongsTo { return $this->belongsTo(User::class, 'enrolled_by'); } + /** + * @return HasOne + */ public function invoice(): HasOne { return $this->hasOne(Invoice::class); diff --git a/app/Models/Evaluation.php b/app/Models/Evaluation.php index 5e16b4c3..173e6fe1 100644 --- a/app/Models/Evaluation.php +++ b/app/Models/Evaluation.php @@ -32,16 +32,25 @@ class Evaluation extends Model 'locked_at' => 'datetime', ]; + /** + * @return BelongsTo + */ public function template(): BelongsTo { return $this->belongsTo(EvaluationTemplate::class, 'evaluation_template_id'); } + /** + * @return BelongsTo + */ public function classSubject(): BelongsTo { return $this->belongsTo(ClassSubject::class); } + /** + * @return HasMany + */ public function marks(): HasMany { return $this->hasMany(Mark::class); diff --git a/app/Models/EvaluationTemplate.php b/app/Models/EvaluationTemplate.php index 68bdd951..c8a1209a 100644 --- a/app/Models/EvaluationTemplate.php +++ b/app/Models/EvaluationTemplate.php @@ -34,21 +34,33 @@ class EvaluationTemplate extends Model 'date' => 'date', ]; + /** + * @return BelongsTo + */ public function academicPeriod(): BelongsTo { return $this->belongsTo(AcademicPeriod::class); } + /** + * @return BelongsTo + */ public function classType(): BelongsTo { return $this->belongsTo(ClassroomType::class, 'class_type_id'); } + /** + * @return BelongsTo + */ public function evaluationType(): BelongsTo { return $this->belongsTo(EvaluationType::class); } + /** + * @return HasMany + */ public function evaluations(): HasMany { return $this->hasMany(Evaluation::class); diff --git a/app/Models/EvaluationType.php b/app/Models/EvaluationType.php index 1879788c..29babdeb 100644 --- a/app/Models/EvaluationType.php +++ b/app/Models/EvaluationType.php @@ -39,6 +39,8 @@ class EvaluationType extends Model /** * Get all evaluations for this type. + * + * @return HasMany */ public function evaluations(): HasMany { diff --git a/app/Models/FeeStructure.php b/app/Models/FeeStructure.php index 9e6825d1..58785a37 100644 --- a/app/Models/FeeStructure.php +++ b/app/Models/FeeStructure.php @@ -20,6 +20,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; class FeeStructure extends Model { @@ -38,6 +39,8 @@ class FeeStructure extends Model /** * Get the academic year that owns the fee structure. + * + * @return BelongsTo */ public function academicYear(): BelongsTo { @@ -46,6 +49,8 @@ public function academicYear(): BelongsTo /** * Get the fee category that owns the fee structure. + * + * @return BelongsTo */ public function feeCategory(): BelongsTo { @@ -54,6 +59,8 @@ public function feeCategory(): BelongsTo /** * Get the classroom that owns the fee structure. + * + * @return BelongsTo */ public function classroom(): BelongsTo { @@ -62,8 +69,10 @@ public function classroom(): BelongsTo /** * Get the installments for the fee structure. + * + * @return HasMany */ - public function installments() + public function installments(): HasMany { return $this->hasMany(Installment::class); } diff --git a/app/Models/Grade.php b/app/Models/Grade.php index 495d88bd..89ddc7e4 100644 --- a/app/Models/Grade.php +++ b/app/Models/Grade.php @@ -41,6 +41,8 @@ class Grade extends Model /** * Get the student for this grade. + * + * @return BelongsTo */ public function student(): BelongsTo { @@ -49,6 +51,8 @@ public function student(): BelongsTo /** * Get the class subject for this grade. + * + * @return BelongsTo */ public function classSubject(): BelongsTo { @@ -57,6 +61,8 @@ public function classSubject(): BelongsTo /** * Get the academic period for this grade. + * + * @return BelongsTo */ public function academicPeriod(): BelongsTo { diff --git a/app/Models/GradingConfig.php b/app/Models/GradingConfig.php index 6e932ba7..36b22091 100644 --- a/app/Models/GradingConfig.php +++ b/app/Models/GradingConfig.php @@ -33,12 +33,19 @@ class GradingConfig extends Model 'mentions' => 'array', ]; + /** + * @return BelongsTo + */ public function school(): BelongsTo { return $this->belongsTo(School::class); } - /** Type de classe ciblé (null = configuration par défaut de l'école). */ + /** + * Type de classe ciblé (null = configuration par défaut de l'école). + * + * @return BelongsTo + */ public function classroomType(): BelongsTo { return $this->belongsTo(ClassroomType::class, 'classroom_type_id'); diff --git a/app/Models/Guardian.php b/app/Models/Guardian.php index b68a76a2..c8d8d883 100644 --- a/app/Models/Guardian.php +++ b/app/Models/Guardian.php @@ -34,7 +34,11 @@ class Guardian extends Authenticatable 'password' => 'hashed', ]; - /** Enfants liés à ce tuteur. */ + /** + * Enfants liés à ce tuteur. + * + * @return BelongsToMany + */ public function children(): BelongsToMany { return $this->belongsToMany(Student::class, 'guardian_student')->withPivot('relationship'); diff --git a/app/Models/Installment.php b/app/Models/Installment.php index 1aca32fb..096864a2 100644 --- a/app/Models/Installment.php +++ b/app/Models/Installment.php @@ -31,16 +31,25 @@ class Installment extends Model /* Relations */ /* ------------------------------------------------------------------ */ + /** + * @return BelongsTo + */ public function feeStructure(): BelongsTo { return $this->belongsTo(FeeStructure::class); } + /** + * @return BelongsTo + */ public function academicPeriod(): BelongsTo { return $this->belongsTo(AcademicPeriod::class); } + /** + * @return HasMany + */ public function payments(): HasMany { return $this->hasMany(Payment::class); diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index 6d557b00..4d04c238 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -40,16 +40,25 @@ class Invoice extends Model /* Relations */ /* ------------------------------------------------------------------ */ + /** + * @return BelongsTo + */ public function enrollment(): BelongsTo { return $this->belongsTo(Enrollment::class); } + /** + * @return HasMany + */ public function items(): HasMany { return $this->hasMany(InvoiceItem::class)->orderBy('sort_order'); } + /** + * @return HasMany + */ public function payments(): HasMany { return $this->hasMany(Payment::class)->latest('paid_at'); diff --git a/app/Models/InvoiceItem.php b/app/Models/InvoiceItem.php index 8ae749fd..2a996004 100644 --- a/app/Models/InvoiceItem.php +++ b/app/Models/InvoiceItem.php @@ -24,6 +24,9 @@ class InvoiceItem extends Model 'sort_order' => 'integer', ]; + /** + * @return BelongsTo + */ public function invoice(): BelongsTo { return $this->belongsTo(Invoice::class); diff --git a/app/Models/Mark.php b/app/Models/Mark.php index ee3259f4..09e56158 100644 --- a/app/Models/Mark.php +++ b/app/Models/Mark.php @@ -30,16 +30,25 @@ class Mark extends Model 'absent' => 'boolean', ]; + /** + * @return BelongsTo + */ public function evaluation(): BelongsTo { return $this->belongsTo(Evaluation::class); } + /** + * @return BelongsTo + */ public function student(): BelongsTo { return $this->belongsTo(Student::class); } + /** + * @return BelongsTo + */ public function createdBy(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); diff --git a/app/Models/NoteReclamation.php b/app/Models/NoteReclamation.php index b40e3ca2..426d7f84 100644 --- a/app/Models/NoteReclamation.php +++ b/app/Models/NoteReclamation.php @@ -31,21 +31,33 @@ class NoteReclamation extends Model 'reviewed_at' => 'datetime', ]; + /** + * @return BelongsTo + */ public function evaluation(): BelongsTo { return $this->belongsTo(Evaluation::class); } + /** + * @return BelongsTo + */ public function student(): BelongsTo { return $this->belongsTo(Student::class); } + /** + * @return BelongsTo + */ public function requestedBy(): BelongsTo { return $this->belongsTo(User::class, 'requested_by'); } + /** + * @return BelongsTo + */ public function reviewedBy(): BelongsTo { return $this->belongsTo(User::class, 'reviewed_by'); diff --git a/app/Models/OfficialExam.php b/app/Models/OfficialExam.php index 48f6d061..1511909f 100644 --- a/app/Models/OfficialExam.php +++ b/app/Models/OfficialExam.php @@ -7,6 +7,10 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; +/** + * @property-read int|null $registrations_count Agrégat withCount('registrations'). + * @property-read int|null $admis_count Agrégat withCount alias (status = 'admis'). + */ class OfficialExam extends Model { use HasUuids; @@ -37,21 +41,33 @@ class OfficialExam extends Model 'termine' => 'Terminé', ]; + /** + * @return BelongsTo + */ public function school(): BelongsTo { return $this->belongsTo(School::class); } + /** + * @return BelongsTo + */ public function academicYear(): BelongsTo { return $this->belongsTo(AcademicYear::class); } + /** + * @return BelongsTo + */ public function classroom(): BelongsTo { return $this->belongsTo(Classroom::class, 'class_id'); } + /** + * @return HasMany + */ public function registrations(): HasMany { return $this->hasMany(OfficialExamRegistration::class); diff --git a/app/Models/OfficialExamRegistration.php b/app/Models/OfficialExamRegistration.php index 6d548198..e3dd3814 100644 --- a/app/Models/OfficialExamRegistration.php +++ b/app/Models/OfficialExamRegistration.php @@ -32,11 +32,17 @@ class OfficialExamRegistration extends Model 'tres_bien' => 'Très bien', ]; + /** + * @return BelongsTo + */ public function officialExam(): BelongsTo { return $this->belongsTo(OfficialExam::class); } + /** + * @return BelongsTo + */ public function student(): BelongsTo { return $this->belongsTo(Student::class); diff --git a/app/Models/PayRun.php b/app/Models/PayRun.php index ea61a65a..76908247 100644 --- a/app/Models/PayRun.php +++ b/app/Models/PayRun.php @@ -46,16 +46,25 @@ class PayRun extends Model 'paid_at' => 'datetime', ]; + /** + * @return HasMany + */ public function payslips(): HasMany { return $this->hasMany(Payslip::class); } + /** + * @return BelongsTo + */ public function cashAccount(): BelongsTo { return $this->belongsTo(CashAccount::class); } + /** + * @return BelongsTo + */ public function createdBy(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); diff --git a/app/Models/Payment.php b/app/Models/Payment.php index 077c25e8..13784785 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -34,31 +34,49 @@ class Payment extends Model /* Relations */ /* ------------------------------------------------------------------ */ + /** + * @return BelongsTo + */ public function invoice(): BelongsTo { return $this->belongsTo(Invoice::class); } + /** + * @return BelongsTo + */ public function installment(): BelongsTo { return $this->belongsTo(Installment::class); } + /** + * @return HasOne + */ public function receipt(): HasOne { return $this->hasOne(Receipt::class); } + /** + * @return BelongsTo + */ public function createdBy(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); } + /** + * @return BelongsTo + */ public function cashAccount(): BelongsTo { return $this->belongsTo(CashAccount::class); } + /** + * @return HasOne + */ public function accountingTransaction(): HasOne { return $this->hasOne(AccountingTransaction::class, 'reference_id'); diff --git a/app/Models/Payslip.php b/app/Models/Payslip.php index cc216f88..a21f24e6 100644 --- a/app/Models/Payslip.php +++ b/app/Models/Payslip.php @@ -29,11 +29,17 @@ class Payslip extends Model 'payload' => 'array', ]; + /** + * @return BelongsTo + */ public function payRun(): BelongsTo { return $this->belongsTo(PayRun::class); } + /** + * @return BelongsTo + */ public function employeeProfile(): BelongsTo { return $this->belongsTo(EmployeeProfile::class); diff --git a/app/Models/Receipt.php b/app/Models/Receipt.php index 5050f738..a6094a71 100644 --- a/app/Models/Receipt.php +++ b/app/Models/Receipt.php @@ -23,11 +23,17 @@ class Receipt extends Model 'printed_at' => 'datetime', ]; + /** + * @return BelongsTo + */ public function payment(): BelongsTo { return $this->belongsTo(Payment::class); } + /** + * @return BelongsTo + */ public function printedBy(): BelongsTo { return $this->belongsTo(User::class, 'printed_by'); diff --git a/app/Models/ReportCard.php b/app/Models/ReportCard.php index f5d4cc31..1eb8daf1 100644 --- a/app/Models/ReportCard.php +++ b/app/Models/ReportCard.php @@ -32,21 +32,33 @@ class ReportCard extends Model 'locked_at' => 'datetime', ]; + /** + * @return BelongsTo + */ public function student(): BelongsTo { return $this->belongsTo(Student::class); } + /** + * @return BelongsTo + */ public function academicPeriod(): BelongsTo { return $this->belongsTo(AcademicPeriod::class); } + /** + * @return BelongsTo + */ public function classroom(): BelongsTo { return $this->belongsTo(Classroom::class, 'class_id'); } + /** + * @return BelongsTo + */ public function generatedBy(): BelongsTo { return $this->belongsTo(User::class, 'generated_by'); diff --git a/app/Models/SalaryGrade.php b/app/Models/SalaryGrade.php index 0337d8af..12b5a680 100644 --- a/app/Models/SalaryGrade.php +++ b/app/Models/SalaryGrade.php @@ -27,6 +27,9 @@ class SalaryGrade extends Model 'sort_order' => 'integer', ]; + /** + * @return HasMany + */ public function employeeProfiles(): HasMany { return $this->hasMany(EmployeeProfile::class); diff --git a/app/Models/Scholarship.php b/app/Models/Scholarship.php index 86adea61..692e34b2 100644 --- a/app/Models/Scholarship.php +++ b/app/Models/Scholarship.php @@ -38,6 +38,8 @@ class Scholarship extends Model /** * Get all student scholarships for this scholarship. + * + * @return HasMany */ public function studentScholarships(): HasMany { diff --git a/app/Models/School.php b/app/Models/School.php index f410b075..39a88fad 100644 --- a/app/Models/School.php +++ b/app/Models/School.php @@ -75,24 +75,37 @@ public function getLogoUrlAttribute(): ?string /** * Get all subjects for the school. + * + * @return HasMany */ public function subjects(): HasMany { return $this->hasMany(Subject::class); } + /** + * @return HasMany + */ public function gradingConfigs(): HasMany { return $this->hasMany(GradingConfig::class); } - /** En-tête personnalisé des documents (certificats, attestations, bulletins). */ + /** + * En-tête personnalisé des documents (certificats, attestations, bulletins). + * + * @return HasOne + */ public function documentHeader(): HasOne { return $this->hasOne(DocumentHeader::class); } - /** Types de classe proposés par l'école. */ + /** + * Types de classe proposés par l'école. + * + * @return BelongsToMany + */ public function classTypes(): BelongsToMany { return $this->belongsToMany(ClassroomType::class, 'class_type_school'); diff --git a/app/Models/Student.php b/app/Models/Student.php index 5a198be0..df3cc6c4 100644 --- a/app/Models/Student.php +++ b/app/Models/Student.php @@ -28,9 +28,13 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; +use Illuminate\Database\Eloquent\Relations\Pivot; use Illuminate\Database\Eloquent\SoftDeletes; use Laravel\Sanctum\HasApiTokens; +/** + * @property-read Pivot&object{relationship: string} $pivot Pivot guardian_student (chargé via belongsToMany). + */ class Student extends Model implements AuthenticatableContract { use Auditable, AuthenticatableTrait, HasApiTokens, HasFactory, HasResetToken, HasUuids, SoftDeletes; @@ -68,13 +72,18 @@ class Student extends Model implements AuthenticatableContract 'reset_token', ]; - /** Tuteurs liés à cet élève. */ + /** Tuteurs liés à cet élève. * @return BelongsToMany + */ public function guardians(): BelongsToMany { return $this->belongsToMany(Guardian::class, 'guardian_student')->withPivot('relationship'); } - /** Inscriptions de l'élève. */ + /** + * Inscriptions de l'élève. + * + * @return HasMany + */ public function enrollments(): HasMany { return $this->hasMany(Enrollment::class); @@ -82,6 +91,8 @@ public function enrollments(): HasMany /** * Get the user account for this student. + * + * @return BelongsTo */ public function user(): BelongsTo { @@ -90,6 +101,8 @@ public function user(): BelongsTo /** * Get all attendances for this student. + * + * @return HasMany */ public function attendances(): HasMany { @@ -98,6 +111,8 @@ public function attendances(): HasMany /** * Get all grades for this student. + * + * @return HasMany */ public function grades(): HasMany { @@ -106,6 +121,8 @@ public function grades(): HasMany /** * Informations administratives de l'élève. + * + * @return HasOne */ public function information(): HasOne { @@ -114,6 +131,8 @@ public function information(): HasOne /** * Informations parentales de l'élève. + * + * @return HasOne */ public function parentInfo(): HasOne { @@ -122,6 +141,8 @@ public function parentInfo(): HasOne /** * Informations médicales de l'élève. + * + * @return HasOne */ public function medicalInfo(): HasOne { @@ -130,6 +151,8 @@ public function medicalInfo(): HasOne /** * Get all scholarships for this student. + * + * @return HasMany */ public function scholarships(): HasMany { @@ -138,6 +161,8 @@ public function scholarships(): HasMany /** * Get all documents (pièces justificatives) for this student. + * + * @return HasMany */ public function documents(): HasMany { diff --git a/app/Models/StudentDocument.php b/app/Models/StudentDocument.php index 49338d37..4bc56554 100644 --- a/app/Models/StudentDocument.php +++ b/app/Models/StudentDocument.php @@ -24,11 +24,17 @@ class StudentDocument extends Model 'size' => 'integer', ]; + /** + * @return BelongsTo + */ public function student(): BelongsTo { return $this->belongsTo(Student::class); } + /** + * @return BelongsTo + */ public function uploadedBy(): BelongsTo { return $this->belongsTo(User::class, 'uploaded_by'); diff --git a/app/Models/StudentInformation.php b/app/Models/StudentInformation.php index 99292eb4..a3574b5e 100644 --- a/app/Models/StudentInformation.php +++ b/app/Models/StudentInformation.php @@ -39,6 +39,9 @@ class StudentInformation extends Model 'birth_certificate_issue_date' => 'date', ]; + /** + * @return BelongsTo + */ public function student(): BelongsTo { return $this->belongsTo(Student::class); diff --git a/app/Models/StudentMedicalInfo.php b/app/Models/StudentMedicalInfo.php index f3fe2363..d86e45ac 100644 --- a/app/Models/StudentMedicalInfo.php +++ b/app/Models/StudentMedicalInfo.php @@ -34,6 +34,9 @@ class StudentMedicalInfo extends Model 'emergency_contact_phone', ]; + /** + * @return BelongsTo + */ public function student(): BelongsTo { return $this->belongsTo(Student::class); diff --git a/app/Models/StudentParent.php b/app/Models/StudentParent.php index da2271d0..09b811ce 100644 --- a/app/Models/StudentParent.php +++ b/app/Models/StudentParent.php @@ -38,6 +38,9 @@ class StudentParent extends Model 'email', ]; + /** + * @return BelongsTo + */ public function student(): BelongsTo { return $this->belongsTo(Student::class); diff --git a/app/Models/StudentScholarship.php b/app/Models/StudentScholarship.php index 20e74a03..78a8e190 100644 --- a/app/Models/StudentScholarship.php +++ b/app/Models/StudentScholarship.php @@ -28,6 +28,8 @@ class StudentScholarship extends Model /** * Get the student that owns the student scholarship. + * + * @return BelongsTo */ public function student(): BelongsTo { @@ -36,6 +38,8 @@ public function student(): BelongsTo /** * Get the scholarship that owns the student scholarship. + * + * @return BelongsTo */ public function scholarship(): BelongsTo { @@ -44,6 +48,8 @@ public function scholarship(): BelongsTo /** * Get the academic year that owns the student scholarship. + * + * @return BelongsTo */ public function academicYear(): BelongsTo { diff --git a/app/Models/Subject.php b/app/Models/Subject.php index 1fdfb5a2..1306ef7c 100644 --- a/app/Models/Subject.php +++ b/app/Models/Subject.php @@ -35,19 +35,29 @@ class Subject extends Model /** * Get all class subjects for this subject. + * + * @return HasMany */ public function classSubjects(): HasMany { return $this->hasMany(ClassSubject::class); } - /** Matière parente (si cette matière est une sous-matière). */ + /** + * Matière parente (si cette matière est une sous-matière). + * + * @return BelongsTo + */ public function parent(): BelongsTo { return $this->belongsTo(self::class, 'parent_id'); } - /** Sous-matières. */ + /** + * Sous-matières. + * + * @return HasMany + */ public function children(): HasMany { return $this->hasMany(self::class, 'parent_id'); diff --git a/app/Models/SubjectAssignment.php b/app/Models/SubjectAssignment.php index 55e2e6fc..179a491e 100644 --- a/app/Models/SubjectAssignment.php +++ b/app/Models/SubjectAssignment.php @@ -40,6 +40,8 @@ class SubjectAssignment extends Model /** * Get the subject for this assignment. + * + * @return BelongsTo */ public function subject(): BelongsTo { @@ -48,6 +50,8 @@ public function subject(): BelongsTo /** * Get the teacher (user) for this assignment. + * + * @return BelongsTo */ public function teacher(): BelongsTo { @@ -56,6 +60,8 @@ public function teacher(): BelongsTo /** * Get the academic year for this assignment. + * + * @return BelongsTo */ public function academicYear(): BelongsTo { @@ -64,6 +70,8 @@ public function academicYear(): BelongsTo /** * Get the class (classroom) for this assignment. + * + * @return BelongsTo */ public function classroom(): BelongsTo { diff --git a/app/Models/TimetableSlot.php b/app/Models/TimetableSlot.php index abb414be..8f9053d4 100644 --- a/app/Models/TimetableSlot.php +++ b/app/Models/TimetableSlot.php @@ -28,16 +28,25 @@ class TimetableSlot extends Model 6 => 'Samedi', ]; + /** + * @return BelongsTo + */ public function classroom(): BelongsTo { return $this->belongsTo(Classroom::class, 'class_id'); } + /** + * @return BelongsTo + */ public function subject(): BelongsTo { return $this->belongsTo(Subject::class); } + /** + * @return BelongsTo + */ public function teacher(): BelongsTo { return $this->belongsTo(User::class, 'teacher_id'); diff --git a/app/Models/User.php b/app/Models/User.php index fa842db5..b43d3128 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -23,6 +23,7 @@ use App\Traits\HasMatricule; use Database\Factories\UserFactory; use Illuminate\Database\Eloquent\Casts\Attribute; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Concerns\HasUuids; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -30,8 +31,12 @@ use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Laravel\Fortify\TwoFactorAuthenticatable; +use Spatie\Permission\Models\Role; use Spatie\Permission\Traits\HasRoles; +/** + * @property-read Collection $roles + */ class User extends Authenticatable { /** @use HasFactory */ @@ -55,6 +60,7 @@ class User extends Authenticatable 'password', 'is_demo', 'must_change_password', + 'locale', ]; /** @@ -97,27 +103,41 @@ protected function casts(): array /** * Get the user's full name + * + * @return Attribute */ protected function name(): Attribute { return Attribute::make( - get: fn () => "{$this->firstname} {$this->lastname}", + get: fn (): string => "{$this->firstname} {$this->lastname}", ); } - /** Profil RH (paie) de l'utilisateur, s'il est employé. */ + /** + * Profil RH (paie) de l'utilisateur, s'il est employé. + * + * @return HasOne + */ public function employeeProfile(): HasOne { return $this->hasOne(EmployeeProfile::class); } - /** Affectations matières où l'utilisateur est l'enseignant. */ + /** + * Affectations matières où l'utilisateur est l'enseignant. + * + * @return HasMany + */ public function subjectAssignments(): HasMany { return $this->hasMany(SubjectAssignment::class, 'teacher_id'); } - /** Créneaux d'emploi du temps où l'utilisateur est l'enseignant. */ + /** + * Créneaux d'emploi du temps où l'utilisateur est l'enseignant. + * + * @return HasMany + */ public function timetableSlots(): HasMany { return $this->hasMany(TimetableSlot::class, 'teacher_id'); @@ -156,7 +176,7 @@ public function generateMatricule(): string $matriculeService = app(MatriculeService::class); // Obtenir le rôle principal de l'utilisateur - $role = $this->roles?->first()?->name; + $role = $this->roles->first()?->name; if ($role) { return $matriculeService->generateUserMatricule($role); diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php index 2909403f..622c85cf 100644 --- a/app/Observers/UserObserver.php +++ b/app/Observers/UserObserver.php @@ -18,7 +18,7 @@ public function created(User $user): void $user->natricule, 'user', $user->id, - $user->roles->first()?->name ?? 'unknown' + $user->roles->first()->name ?? 'unknown' ); } } diff --git a/app/Rules/UniqueMatricule.php b/app/Rules/UniqueMatricule.php index c9c7de95..b186f315 100644 --- a/app/Rules/UniqueMatricule.php +++ b/app/Rules/UniqueMatricule.php @@ -11,9 +11,9 @@ class UniqueMatricule implements ValidationRule { protected MatriculeService $matriculeService; - protected ?int $ignoreId = null; + protected ?string $ignoreId = null; - public function __construct(?int $ignoreId = null) + public function __construct(?string $ignoreId = null) { $this->matriculeService = app(MatriculeService::class); $this->ignoreId = $ignoreId; diff --git a/app/Services/AccountingService.php b/app/Services/AccountingService.php index 31b08aba..fc54177c 100644 --- a/app/Services/AccountingService.php +++ b/app/Services/AccountingService.php @@ -112,7 +112,7 @@ public function recordScholarshipTransaction( $studentName = $student ? "{$student->firstname} {$student->lastname}" : 'Élève inconnu'; - $scholarshipName = $scholarship?->name ?? 'Bourse'; + $scholarshipName = $scholarship->name ?? 'Bourse'; return AccountingTransaction::create([ 'type' => 'EXPENSE', diff --git a/app/Services/BackupService.php b/app/Services/BackupService.php index 25f16e8c..7f009fe1 100644 --- a/app/Services/BackupService.php +++ b/app/Services/BackupService.php @@ -229,6 +229,9 @@ private function addDiskToZip(\ZipArchive $zip, string $diskName, string $prefix $zip->addFile($local, "{$prefix}/{$rel}"); } else { $t = tempnam(sys_get_temp_dir(), 'med_'); + if ($t === false) { + continue; // impossible de créer le fichier temporaire : on saute ce média + } file_put_contents($t, $disk->get($rel)); $temps[] = $t; $zip->addFile($t, "{$prefix}/{$rel}"); @@ -269,7 +272,7 @@ private function writeDump(string $format, string $tmp): string * (json[.gz], sql[.gz] ou dump PostgreSQL). Une sauvegarde de sécurité * (JSON) est générée au préalable. * - * @return array{format:string, tables:int, rows?:int} + * @return array{format:string, tables:int, rows?:int, media?:int} */ public function restore(UploadedFile $file, ?string $onlyTable = null): array { @@ -496,10 +499,8 @@ private function restoreTableBatched(string $table, array $rows): int $total = 0; foreach (array_chunk($rows, 500) as $chunk) { - if (! empty($chunk)) { - DB::transaction(fn () => DB::table($table)->insert($chunk)); - $total += count($chunk); - } + DB::transaction(fn () => DB::table($table)->insert($chunk)); + $total += count($chunk); } return $total; @@ -512,7 +513,7 @@ private function restoreTableBatched(string $table, array $rows): int private function restoreSql(string $contents): array { preg_match_all('/INSERT\s+INTO\s+"([^"]+)"/i', $contents, $matches); - $tables = array_values(array_unique($matches[1] ?? [])); + $tables = array_values(array_unique($matches[1])); try { DB::transaction(function () use ($contents, $tables): void { diff --git a/app/Services/BulletinRenderer.php b/app/Services/BulletinRenderer.php index 0c00f045..7c0ec693 100644 --- a/app/Services/BulletinRenderer.php +++ b/app/Services/BulletinRenderer.php @@ -235,7 +235,7 @@ private function infoBlock(array $p): string private function footBlock(array $p, array $options): string { $average = $this->num($p['average'] ?? null); - $rank = isset($p['rank']) && $p['rank'] !== null ? e($this->ordinal((int) $p['rank'])) : '—'; + $rank = isset($p['rank']) ? e($this->ordinal((int) $p['rank'])) : '—'; $effectif = e((string) ($p['effectif'] ?? '')); $mention = e($p['mention'] ?? '—'); $obs = e($p['observations'] ?? ''); diff --git a/app/Services/DocumentRenderer.php b/app/Services/DocumentRenderer.php index 309a3bef..9a9b48b1 100644 --- a/app/Services/DocumentRenderer.php +++ b/app/Services/DocumentRenderer.php @@ -90,16 +90,21 @@ public function resolveVariables( ]; if ($student) { + // $student peut être un modèle NON persisté (aperçu/test) : les colonnes + // NOT NULL en base peuvent alors être nulles. On lit via getAttribute() + // (type mixed) pour conserver les gardes défensives. + $birthDate = $student->getAttribute('birth_date'); + $vars += [ 'eleve.nom_complet' => trim(($student->lastname ?? '').' '.($student->firstname ?? '')), 'eleve.nom' => $student->lastname ?? '', 'eleve.prenom' => $student->firstname ?? '', 'eleve.matricule' => $student->matricule ?? '', - 'eleve.date_naissance' => $student->birth_date - ? Carbon::parse($student->birth_date)->locale('fr')->isoFormat('D MMMM YYYY') + 'eleve.date_naissance' => $birthDate + ? Carbon::parse($birthDate)->locale('fr')->isoFormat('D MMMM YYYY') : '', 'eleve.lieu_naissance' => $student->place_of_birth ?? '', - 'eleve.sexe' => match ($student->gender) { + 'eleve.sexe' => match ($student->getAttribute('gender')) { 'male' => 'Masculin', 'female' => 'Féminin', default => '', @@ -198,7 +203,7 @@ public function watermarkHtml(?School $school, array $variables): string protected function renderHeader(DocumentTemplate $template, array $variables): string { $header = $template->school?->documentHeader; - $preset = $header?->preset ?? 'ministeriel'; + $preset = $header->preset ?? 'ministeriel'; // Préréglage « personnalisé » : canevas glisser-déposer (si des éléments existent). // Sinon on rend le gabarit officiel ministériel (défaut). diff --git a/app/Services/GradingService.php b/app/Services/GradingService.php index f546dd30..5590bef3 100644 --- a/app/Services/GradingService.php +++ b/app/Services/GradingService.php @@ -424,7 +424,7 @@ public function periodAverageFromEvaluations(string $studentId, string $periodId /** * Attribue des rangs à une liste [{student_id, average}]. * - * @param Collection $rows + * @param Collection $rows * @return Collection */ public function rank(Collection $rows): Collection @@ -469,7 +469,7 @@ private function activeStudentIds(string $classId): Collection /** * Attribue les rangs (1, 1, 3…). Les élèves sans moyenne ne sont pas classés (rang null). * - * @param Collection $rows + * @param Collection $rows * @return Collection */ private function assignRanks(Collection $rows): Collection diff --git a/app/Services/PayrollService.php b/app/Services/PayrollService.php index d2f6b0ce..2e7eea71 100644 --- a/app/Services/PayrollService.php +++ b/app/Services/PayrollService.php @@ -137,7 +137,7 @@ private function buildLines(EmployeeProfile $employee, $components, int $month, $grossEarnings = 0.0; foreach ($lines as $l) { if (($l['type'] ?? '') !== SalaryComponent::DEDUCTION) { - $grossEarnings += (float) ($l['amount'] ?? 0); + $grossEarnings += (float) ($l['amount']); } } $statutory = $this->statutory($grossEarnings, $settings); @@ -197,7 +197,7 @@ private function progressiveTax(float $taxable, array $brackets): float $tax = 0.0; $prev = 0.0; foreach ($brackets as $b) { - $cap = isset($b['up_to']) && $b['up_to'] !== null ? (float) $b['up_to'] : INF; + $cap = isset($b['up_to']) ? (float) $b['up_to'] : INF; $rate = (float) ($b['rate'] ?? 0); if ($taxable <= $prev) { break; @@ -382,6 +382,8 @@ private function totals(array $lines): array private function refreshTotals(PayRun $run): void { + // Agrégat sans GROUP BY : toujours une ligne, colonnes calculées. + /** @var object{g: int|float|string, d: int|float|string, n: int|float|string} $sums */ $sums = $run->payslips()->selectRaw(' coalesce(sum(gross), 0) as g, coalesce(sum(total_deductions), 0) as d, diff --git a/app/Services/ReportCardBuilder.php b/app/Services/ReportCardBuilder.php index 555a1686..08c9e064 100644 --- a/app/Services/ReportCardBuilder.php +++ b/app/Services/ReportCardBuilder.php @@ -99,7 +99,7 @@ public function build( 'lowest' => $values->isNotEmpty() ? $values->min() : null, 'average' => $values->isNotEmpty() ? round($values->avg(), 2) : null, ]; - $periodSystem = $class->classroomType?->period_system ?? 'trimestre'; + $periodSystem = $class->classroomType->period_system ?? 'trimestre'; $retards = $this->retardsByStudent($class->id, $period); // Récapitulatif inter-périodes + annuel (classements précalculés une fois, depuis l'index). @@ -162,7 +162,7 @@ public function build( } $lines[] = [ - 'subject' => $cs->subject?->name ?? '', + 'subject' => $cs->subject->name ?? '', 'parent' => $cs->subject?->parent?->name, 'group' => $cs->group ?? 'obligatoire', 'coefficient' => $coeff, @@ -171,7 +171,7 @@ public function build( 'moyenne' => $cell['moy'], 'points' => $points, 'definitive' => $points, - 'by_type' => $cell['by_type'] ?? [], + 'by_type' => $cell['by_type'], 'rang' => $subjectRanks[$cs->id]->get($student->id)['rank'] ?? null, 'appreciation' => $comments[$student->id.'|'.$cs->id] ?? '', 'teacher' => $teachers[$cs->subject_id] ?? '', @@ -257,7 +257,7 @@ public function classSubjects(Classroom $class, ?string $yearId): Collection ->with(['subject:id,name,code,parent_id', 'subject.parent:id,name']) ->get() // Regroupe les sous-matières sous leur matière parente. - ->sortBy(fn ($cs) => ($cs->subject?->parent?->name ?? $cs->subject?->name).'~'.($cs->subject?->name)) + ->sortBy(fn ($cs) => ($cs->subject?->parent->name ?? $cs->subject->name).'~'.($cs->subject->name)) ->values(); } @@ -282,7 +282,7 @@ private function teachersBySubject(string $classId, ?string $yearId): array ->when($yearId, fn ($q) => $q->where('academic_year_id', $yearId)) ->with('teacher:id,firstname,lastname') ->get() - ->mapWithKeys(fn ($a) => [$a->subject_id => trim(($a->teacher?->firstname ?? '').' '.($a->teacher?->lastname ?? ''))]) + ->mapWithKeys(fn ($a) => [$a->subject_id => trim(($a->teacher->firstname ?? '').' '.($a->teacher->lastname ?? ''))]) ->toArray(); } diff --git a/app/Services/StatisticsService.php b/app/Services/StatisticsService.php index 9d515c80..353cef0b 100644 --- a/app/Services/StatisticsService.php +++ b/app/Services/StatisticsService.php @@ -334,10 +334,10 @@ public function successStats(array $filters): array 'moyenne_generale' => $rcStats->avg_moy !== null ? round((float) $rcStats->avg_moy, 2) : null, 'pass_rate' => $rcTotal > 0 ? round(((int) $rcStats->pass_count) / $rcTotal * 100, 1) : 0.0, 'mentions' => [ - 'passable' => (int) ($mentions['passable'] ?? 0), - 'assez_bien' => (int) ($mentions['assez_bien'] ?? 0), - 'bien' => (int) ($mentions['bien'] ?? 0), - 'tres_bien' => (int) ($mentions['tres_bien'] ?? 0), + 'passable' => (int) ($mentions['passable']), + 'assez_bien' => (int) ($mentions['assez_bien']), + 'bien' => (int) ($mentions['bien']), + 'tres_bien' => (int) ($mentions['tres_bien']), ], 'exams' => $exams, 'exams_summary' => [ @@ -395,6 +395,9 @@ public function attendanceStats(array $filters): array ->sortByDesc('absence_rate') ->values(); + // GROUP BY + HAVING : un ->count() SQL compterait les lignes par groupe, + // pas le nombre de groupes. On matérialise puis on compte en mémoire. + // @phpstan-ignore larastan.noUnnecessaryCollectionCall $chronic = (clone $base) ->where('ar.status', 'absent') ->groupBy('ar.student_id') diff --git a/app/Support/FrenchNumberSpeller.php b/app/Support/FrenchNumberSpeller.php index f0b8079c..a883b1ee 100644 --- a/app/Support/FrenchNumberSpeller.php +++ b/app/Support/FrenchNumberSpeller.php @@ -138,7 +138,7 @@ private static function tens(int $n, bool $isFinal): string ? ($isFinal ? 'quatre-vingts' : 'quatre-vingt') : 'quatre-vingt-'.self::UNITS[$unit], 90 => 'quatre-vingt-'.self::UNITS[10 + $unit], - default => self::UNITS[$n], // inatteignable, garde-fou + default => throw new \LogicException("Dizaine inattendue: {$base}"), // inatteignable pour n ∈ [20,99] }; } diff --git a/app/Traits/HasMatricule.php b/app/Traits/HasMatricule.php index 0dd83ade..f3914455 100644 --- a/app/Traits/HasMatricule.php +++ b/app/Traits/HasMatricule.php @@ -14,9 +14,10 @@ trait HasMatricule */ public static function bootHasMatricule() { - // Générer le matricule avant la création si absent - static::creating(function ($model) { - if (empty($model->natricule) && method_exists($model, 'generateMatricule')) { + // Générer le matricule avant la création si absent. + // Le modèle hôte expose generateMatricule() (cf. User::generateMatricule()). + static::creating(function (self $model) { + if (empty($model->natricule)) { $model->natricule = $model->generateMatricule(); } }); diff --git a/bootstrap/app.php b/bootstrap/app.php index 6f392011..15c3e254 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -3,6 +3,7 @@ use App\Http\Middleware\ForcePasswordChange; use App\Http\Middleware\HandleAppearance; use App\Http\Middleware\HandleInertiaRequests; +use App\Http\Middleware\SetLocale; use Illuminate\Foundation\Application; use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Middleware; @@ -24,6 +25,7 @@ $middleware->web(append: [ HandleAppearance::class, + SetLocale::class, HandleInertiaRequests::class, AddLinkHeadersForPreloadedAssets::class, ForcePasswordChange::class, diff --git a/composer.json b/composer.json index 838e5ffd..69119b1c 100644 --- a/composer.json +++ b/composer.json @@ -17,12 +17,13 @@ "laravel/sanctum": "^4.3", "laravel/tinker": "^2.10.1", "laravel/wayfinder": "^0.1.9", - "maatwebsite/excel": "*", + "maatwebsite/excel": "^4.0", "sentry/sentry-laravel": "*", "spatie/laravel-permission": "*" }, "require-dev": { "fakerphp/faker": "^1.23", + "larastan/larastan": "^3.0", "laravel/boost": "2.0", "laravel/pail": "^1.2.2", "laravel/pint": "^1.24", @@ -67,6 +68,9 @@ "test:lint": [ "pint --parallel --test" ], + "analyse": [ + "phpstan analyse --memory-limit=512M" + ], "test": [ "@php artisan config:clear --ansi", "@test:lint", diff --git a/composer.lock b/composer.lock index de56dedb..e5c6c0c9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f057d51d47393d2a1f68d2624b5e1a0e", + "content-hash": "8e8103316f56c22fe702f83ff4d555d9", "packages": [ { "name": "bacon/bacon-qr-code", - "version": "v3.0.3", + "version": "v3.1.1", "source": { "type": "git", "url": "https://github.com/Bacon/BaconQrCode.git", - "reference": "36a1cb2b81493fa5b82e50bf8068bf84d1542563" + "reference": "4da2233e72eeecd9be3b62e0dc2cc9ed8e2e31c2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/36a1cb2b81493fa5b82e50bf8068bf84d1542563", - "reference": "36a1cb2b81493fa5b82e50bf8068bf84d1542563", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/4da2233e72eeecd9be3b62e0dc2cc9ed8e2e31c2", + "reference": "4da2233e72eeecd9be3b62e0dc2cc9ed8e2e31c2", "shasum": "" }, "require": { @@ -57,9 +57,9 @@ "homepage": "https://github.com/Bacon/BaconQrCode", "support": { "issues": "https://github.com/Bacon/BaconQrCode/issues", - "source": "https://github.com/Bacon/BaconQrCode/tree/v3.0.3" + "source": "https://github.com/Bacon/BaconQrCode/tree/v3.1.1" }, - "time": "2025-11-19T17:15:36+00:00" + "time": "2026-04-05T21:06:35+00:00" }, { "name": "barryvdh/laravel-dompdf", @@ -200,16 +200,16 @@ }, { "name": "carbonphp/carbon-doctrine-types", - "version": "3.2.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", - "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d" + "reference": "5fa5eacafd9ef47c8c6ab9143fc901ba0194d3dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d", - "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/5fa5eacafd9ef47c8c6ab9143fc901ba0194d3dc", + "reference": "5fa5eacafd9ef47c8c6ab9143fc901ba0194d3dc", "shasum": "" }, "require": { @@ -224,6 +224,11 @@ "phpunit/phpunit": "^10.3" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, "autoload": { "psr-4": { "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" @@ -249,7 +254,7 @@ ], "support": { "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", - "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.1" }, "funding": [ { @@ -265,7 +270,7 @@ "type": "tidelift" } ], - "time": "2024-02-09T16:56:22+00:00" + "time": "2026-09-06T14:11:38+00:00" }, { "name": "composer/pcre", @@ -345,16 +350,16 @@ }, { "name": "composer/semver", - "version": "3.4.4", + "version": "3.6.0", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + "reference": "d92543b7ede9c4ec6b59853cefb061e39866d2cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", - "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "url": "https://api.github.com/repos/composer/semver/zipball/d92543b7ede9c4ec6b59853cefb061e39866d2cf", + "reference": "d92543b7ede9c4ec6b59853cefb061e39866d2cf", "shasum": "" }, "require": { @@ -396,7 +401,7 @@ "homepage": "http://robbast.nl" } ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", + "description": "Version comparison library that offers utilities, version constraint parsing and validation.", "keywords": [ "semantic", "semver", @@ -406,7 +411,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.4" + "source": "https://github.com/composer/semver/tree/3.6.0" }, "funding": [ { @@ -418,7 +423,7 @@ "type": "github" } ], - "time": "2025-08-20T19:15:30+00:00" + "time": "2026-10-29T09:13:34+00:00" }, { "name": "dasprid/enum", @@ -545,25 +550,73 @@ }, "time": "2024-07-08T12:26:09+00:00" }, + { + "name": "doctrine/deprecations", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "d2e16653a734c8784c1851ad68b02f997b53ca82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d2e16653a734c8784c1851ad68b02f997b53ca82", + "reference": "d2e16653a734c8784c1851ad68b02f997b53ca82", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=14" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.2.0" + }, + "time": "2026-11-11T10:04:49+00:00" + }, { "name": "doctrine/inflector", - "version": "2.1.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" + "reference": "46351b6812b7b6d52b82c3efa5dfb786a198ec88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", - "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/46351b6812b7b6d52b82c3efa5dfb786a198ec88", + "reference": "46351b6812b7b6d52b82c3efa5dfb786a198ec88", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^12.0 || ^13.0", + "doctrine/coding-standard": "^12.0 || ^14.0", "phpstan/phpstan": "^1.12 || ^2.0", "phpstan/phpstan-phpunit": "^1.4 || ^2.0", "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", @@ -617,7 +670,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.1.0" + "source": "https://github.com/doctrine/inflector/tree/2.1.1" }, "funding": [ { @@ -633,31 +686,29 @@ "type": "tidelift" } ], - "time": "2025-08-10T19:31:58+00:00" + "time": "2026-10-26T08:14:14+00:00" }, { "name": "doctrine/lexer", - "version": "3.0.1", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" + "reference": "71d305eaa6480d3d3460c7d053d1da7749fa9cca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", - "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/71d305eaa6480d3d3460c7d053d1da7749fa9cca", + "reference": "71d305eaa6480d3d3460c7d053d1da7749fa9cca", "shasum": "" }, "require": { "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^12", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^10.5", - "psalm/plugin-phpunit": "^0.18.3", - "vimeo/psalm": "^5.21" + "doctrine/coding-standard": "^14", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^10.5.58 || ^12.5.4" }, "type": "library", "autoload": { @@ -694,7 +745,7 @@ ], "support": { "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/3.0.1" + "source": "https://github.com/doctrine/lexer/tree/3.0.3" }, "funding": [ { @@ -710,20 +761,20 @@ "type": "tidelift" } ], - "time": "2024-02-05T11:56:58+00:00" + "time": "2026-10-01T06:59:29+00:00" }, { "name": "dompdf/dompdf", - "version": "v3.1.5", + "version": "v3.1.6", "source": { "type": "git", "url": "https://github.com/dompdf/dompdf.git", - "reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496" + "reference": "6d4b4eb8500f7a786da8868ba463a71b725a4005" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/dompdf/zipball/f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496", - "reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/6d4b4eb8500f7a786da8868ba463a71b725a4005", + "reference": "6d4b4eb8500f7a786da8868ba463a71b725a4005", "shasum": "" }, "require": { @@ -772,9 +823,9 @@ "homepage": "https://github.com/dompdf/dompdf", "support": { "issues": "https://github.com/dompdf/dompdf/issues", - "source": "https://github.com/dompdf/dompdf/tree/v3.1.5" + "source": "https://github.com/dompdf/dompdf/tree/v3.1.6" }, - "time": "2026-03-03T13:54:37+00:00" + "time": "2026-07-20T12:29:38+00:00" }, { "name": "dompdf/php-font-lib", @@ -869,16 +920,16 @@ }, { "name": "dragonmantank/cron-expression", - "version": "v3.6.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013" + "reference": "4189372ffeff09bf14f731f67a9de90c7b413ebe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/d61a8a9604ec1f8c3d150d09db6ce98b32675013", - "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/4189372ffeff09bf14f731f67a9de90c7b413ebe", + "reference": "4189372ffeff09bf14f731f67a9de90c7b413ebe", "shasum": "" }, "require": { @@ -889,8 +940,8 @@ }, "require-dev": { "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^1.12.32|^2.1.31", - "phpunit/phpunit": "^8.5.48|^9.0" + "phpstan/phpstan": "^2.1.31", + "phpunit/phpunit": "^11.5.43 || ^12.4" }, "type": "library", "extra": { @@ -921,7 +972,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v3.6.0" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.6.1" }, "funding": [ { @@ -929,7 +980,7 @@ "type": "github" } ], - "time": "2025-10-31T18:51:33+00:00" + "time": "2026-10-19T17:53:57+00:00" }, { "name": "egulias/email-validator", @@ -998,67 +1049,6 @@ ], "time": "2025-03-06T22:45:56+00:00" }, - { - "name": "ezyang/htmlpurifier", - "version": "v4.19.0", - "source": { - "type": "git", - "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "b287d2a16aceffbf6e0295559b39662612b77fcf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/b287d2a16aceffbf6e0295559b39662612b77fcf", - "reference": "b287d2a16aceffbf6e0295559b39662612b77fcf", - "shasum": "" - }, - "require": { - "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0" - }, - "require-dev": { - "cerdic/css-tidy": "^1.7 || ^2.0", - "simpletest/simpletest": "dev-master" - }, - "suggest": { - "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", - "ext-bcmath": "Used for unit conversion and imagecrash protection", - "ext-iconv": "Converts text to and from non-UTF-8 encodings", - "ext-tidy": "Used for pretty-printing HTML" - }, - "type": "library", - "autoload": { - "files": [ - "library/HTMLPurifier.composer.php" - ], - "psr-0": { - "HTMLPurifier": "library/" - }, - "exclude-from-classmap": [ - "/library/HTMLPurifier/Language/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL-2.1-or-later" - ], - "authors": [ - { - "name": "Edward Z. Yang", - "email": "admin@htmlpurifier.org", - "homepage": "http://ezyang.com" - } - ], - "description": "Standards compliant HTML filter written in PHP", - "homepage": "http://htmlpurifier.org/", - "keywords": [ - "html" - ], - "support": { - "issues": "https://github.com/ezyang/htmlpurifier/issues", - "source": "https://github.com/ezyang/htmlpurifier/tree/v4.19.0" - }, - "time": "2025-10-17T16:34:55+00:00" - }, { "name": "fruitcake/php-cors", "version": "v1.4.0", @@ -1132,24 +1122,24 @@ }, { "name": "graham-campbell/result-type", - "version": "v1.1.4", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b" + "reference": "adccca3324eece92ca35463648c12b9e6293c05b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b", - "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/adccca3324eece92ca35463648c12b9e6293c05b", + "reference": "adccca3324eece92ca35463648c12b9e6293c05b", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.5" + "phpoption/phpoption": "^1.10" }, "require-dev": { - "phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7" + "phpunit/phpunit": "^8.5.52 || ^9.6.34 || ^10.5.63 || ^11.5.55 || ^12.5.14" }, "type": "library", "autoload": { @@ -1178,7 +1168,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.2.0" }, "funding": [ { @@ -1190,29 +1180,30 @@ "type": "tidelift" } ], - "time": "2025-12-27T19:43:20+00:00" + "time": "2026-08-24T09:06:52+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "7.10.0", + "version": "7.16.1", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" + "reference": "81755d989b3007ca7f88a069857553cd08e7dc2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", - "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/81755d989b3007ca7f88a069857553cd08e7dc2e", + "reference": "81755d989b3007ca7f88a069857553cd08e7dc2e", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^2.3", - "guzzlehttp/psr7": "^2.8", + "guzzlehttp/promises": "^2.6", + "guzzlehttp/psr7": "^2.13.2", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php80": "^1.25" }, "provide": { "psr/http-client-implementation": "1.0" @@ -1220,9 +1211,10 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "guzzle/client-integration-tests": "3.0.2", + "guzzle/client-integration-tests": "3.0.3", + "guzzlehttp/test-server": "^0.7", "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "phpunit/phpunit": "^8.5.52 || ^9.6.34", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -1300,7 +1292,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.10.0" + "source": "https://github.com/guzzle/guzzle/tree/7.16.1" }, "funding": [ { @@ -1316,28 +1308,29 @@ "type": "tidelift" } ], - "time": "2025-08-23T22:36:01+00:00" + "time": "2026-10-25T21:35:21+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.3.0", + "version": "2.6.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "481557b130ef3790cf82b713667b43030dc9c957" + "reference": "3d88c829bd5f13d13847b39067dbf09a60af2154" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", - "reference": "481557b130ef3790cf82b713667b43030dc9c957", + "url": "https://api.github.com/repos/guzzle/promises/zipball/3d88c829bd5f13d13847b39067dbf09a60af2154", + "reference": "3d88c829bd5f13d13847b39067dbf09a60af2154", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0" + "php": "^7.2.5 || ^8.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.44 || ^9.6.25" + "phpunit/phpunit": "^8.5.52 || ^9.6.34" }, "type": "library", "extra": { @@ -1383,7 +1376,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.3.0" + "source": "https://github.com/guzzle/promises/tree/2.6.0" }, "funding": [ { @@ -1399,27 +1392,29 @@ "type": "tidelift" } ], - "time": "2025-08-22T14:34:08+00:00" + "time": "2026-10-18T12:46:59+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.9.0", + "version": "2.13.3", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884" + "reference": "6d3cd8285a97eaa352956a4ada92fa03ecce1c4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/7d0ed42f28e42d61352a7a79de682e5e67fec884", - "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/6d3cd8285a97eaa352956a4ada92fa03ecce1c4e", + "reference": "6d3cd8285a97eaa352956a4ada92fa03ecce1c4e", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", "psr/http-factory": "^1.0", "psr/http-message": "^1.1 || ^2.0", - "ralouphie/getallheaders": "^3.0" + "ralouphie/getallheaders": "^3.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php80": "^1.25" }, "provide": { "psr/http-factory-implementation": "1.0", @@ -1427,9 +1422,9 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "http-interop/http-factory-tests": "0.9.0", + "http-interop/http-factory-tests": "1.1.0", "jshttp/mime-db": "1.54.0.1", - "phpunit/phpunit": "^8.5.44 || ^9.6.25" + "phpunit/phpunit": "^8.5.52 || ^9.6.34" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -1500,7 +1495,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.9.0" + "source": "https://github.com/guzzle/psr7/tree/2.13.3" }, "funding": [ { @@ -1516,29 +1511,29 @@ "type": "tidelift" } ], - "time": "2026-03-10T16:41:02+00:00" + "time": "2026-11-12T15:57:42+00:00" }, { "name": "guzzlehttp/uri-template", - "version": "v1.0.5", + "version": "v1.0.11", "source": { "type": "git", "url": "https://github.com/guzzle/uri-template.git", - "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1" + "reference": "d0058dccf4299d70c3d9da3378b8908b32780368" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/uri-template/zipball/4f4bbd4e7172148801e76e3decc1e559bdee34e1", - "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/d0058dccf4299d70c3d9da3378b8908b32780368", + "reference": "d0058dccf4299d70c3d9da3378b8908b32780368", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "symfony/polyfill-php80": "^1.24" + "symfony/polyfill-php80": "^1.25" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.44 || ^9.6.25", + "phpunit/phpunit": "^8.5.52 || ^9.6.34", "uri-template/tests": "1.0.0" }, "type": "library", @@ -1586,7 +1581,7 @@ ], "support": { "issues": "https://github.com/guzzle/uri-template/issues", - "source": "https://github.com/guzzle/uri-template/tree/v1.0.5" + "source": "https://github.com/guzzle/uri-template/tree/v1.0.11" }, "funding": [ { @@ -1602,7 +1597,7 @@ "type": "tidelift" } ], - "time": "2025-08-22T14:27:06+00:00" + "time": "2026-08-24T09:15:32+00:00" }, { "name": "inertiajs/inertia-laravel", @@ -1736,29 +1731,31 @@ }, { "name": "laravel/fortify", - "version": "v1.36.1", + "version": "v1.42.0", "source": { "type": "git", "url": "https://github.com/laravel/fortify.git", - "reference": "cad8bfeb63f6818f173d40090725c565c92651d4" + "reference": "29041e37e581fb58af62b70ee9649ad9105db0eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/fortify/zipball/cad8bfeb63f6818f173d40090725c565c92651d4", - "reference": "cad8bfeb63f6818f173d40090725c565c92651d4", + "url": "https://api.github.com/repos/laravel/fortify/zipball/29041e37e581fb58af62b70ee9649ad9105db0eb", + "reference": "29041e37e581fb58af62b70ee9649ad9105db0eb", "shasum": "" }, "require": { "bacon/bacon-qr-code": "^3.0", "ext-json": "*", - "illuminate/console": "^10.0|^11.0|^12.0|^13.0", - "illuminate/support": "^10.0|^11.0|^12.0|^13.0", - "php": "^8.1", + "illuminate/console": "^11.0|^12.0|^13.0", + "illuminate/support": "^11.0|^12.0|^13.0", + "laravel/passkeys": "^0.2.0", + "php": "^8.2", "pragmarx/google2fa": "^9.0" }, "require-dev": { - "orchestra/testbench": "^8.36|^9.15|^10.8|^11.0", - "phpstan/phpstan": "^1.10" + "jasonmccreary/double": "^1.0", + "orchestra/testbench": "^9.15|^10.8|^11.0", + "phpstan/phpstan": "^2.2.6" }, "type": "library", "extra": { @@ -1795,20 +1792,20 @@ "issues": "https://github.com/laravel/fortify/issues", "source": "https://github.com/laravel/fortify" }, - "time": "2026-03-10T19:59:49+00:00" + "time": "2026-10-21T21:13:55+00:00" }, { "name": "laravel/framework", - "version": "v12.54.1", + "version": "v12.72.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "325497463e7599cd14224c422c6e5dd2fe832868" + "reference": "b089d3020e508f49b3625726ab4a2c897f1b78d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/325497463e7599cd14224c422c6e5dd2fe832868", - "reference": "325497463e7599cd14224c422c6e5dd2fe832868", + "url": "https://api.github.com/repos/laravel/framework/zipball/b089d3020e508f49b3625726ab4a2c897f1b78d5", + "reference": "b089d3020e508f49b3625726ab4a2c897f1b78d5", "shasum": "" }, "require": { @@ -1849,8 +1846,8 @@ "symfony/mailer": "^7.2.0", "symfony/mime": "^7.2.0", "symfony/polyfill-php83": "^1.33", - "symfony/polyfill-php84": "^1.33", - "symfony/polyfill-php85": "^1.33", + "symfony/polyfill-php84": "^1.34", + "symfony/polyfill-php85": "^1.34", "symfony/process": "^7.2.0", "symfony/routing": "^7.2.0", "symfony/uid": "^7.2.0", @@ -1921,10 +1918,10 @@ "league/flysystem-sftp-v3": "^3.25.1", "mockery/mockery": "^1.6.10", "opis/json-schema": "^2.4.1", - "orchestra/testbench-core": "^10.9.0", + "orchestra/testbench-core": "^10.15.0", "pda/pheanstalk": "^5.0.6|^7.0.0", "php-http/discovery": "^1.15", - "phpstan/phpstan": "^2.0", + "phpstan/phpstan": "2.2.13", "phpunit/phpunit": "^10.5.35|^11.5.3|^12.0.1", "predis/predis": "^2.3|^3.0", "resend/resend-php": "^0.10.0|^1.0", @@ -2017,20 +2014,88 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2026-03-10T20:25:56+00:00" + "time": "2026-11-10T15:45:11+00:00" + }, + { + "name": "laravel/passkeys", + "version": "v0.2.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/passkeys-server.git", + "reference": "a76656ada41b2b4a591f075eddae5ddc67e8ab9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/passkeys-server/zipball/a76656ada41b2b4a591f075eddae5ddc67e8ab9c", + "reference": "a76656ada41b2b4a591f075eddae5ddc67e8ab9c", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^11.0|^12.0|^13.0", + "illuminate/database": "^11.0|^12.0|^13.0", + "illuminate/http": "^11.0|^12.0|^13.0", + "illuminate/routing": "^11.0|^12.0|^13.0", + "illuminate/support": "^11.0|^12.0|^13.0", + "php": "^8.2", + "web-auth/webauthn-lib": "5.3.x" + }, + "require-dev": { + "laravel/pint": "^1.28.0", + "orchestra/testbench": "^9.0|^10.0|^11.0", + "pestphp/pest": "^3.0|^4.0", + "phpstan/phpstan": "^2.0", + "rector/rector": "^2.3" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Passkeys\\PasskeysServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Passkeys\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Passwordless authentication using WebAuthn/passkeys for Laravel", + "homepage": "https://github.com/laravel/passkeys-server", + "keywords": [ + "Authentication", + "Passwordless", + "laravel", + "passkeys", + "webauthn" + ], + "support": { + "issues": "https://github.com/laravel/passkeys-server/issues", + "source": "https://github.com/laravel/passkeys-server" + }, + "time": "2026-05-18T16:26:00+00:00" }, { "name": "laravel/prompts", - "version": "v0.3.14", + "version": "v0.3.27", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "9f0e371244eedfe2ebeaa72c79c54bb5df6e0176" + "reference": "abdfc75a1ae6c6f21522daed50da7124cbff09d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/9f0e371244eedfe2ebeaa72c79c54bb5df6e0176", - "reference": "9f0e371244eedfe2ebeaa72c79c54bb5df6e0176", + "url": "https://api.github.com/repos/laravel/prompts/zipball/abdfc75a1ae6c6f21522daed50da7124cbff09d2", + "reference": "abdfc75a1ae6c6f21522daed50da7124cbff09d2", "shasum": "" }, "require": { @@ -2073,23 +2138,22 @@ ], "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { - "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.14" + "source": "https://github.com/laravel/prompts/tree/v0.3.27" }, - "time": "2026-03-01T09:02:38+00:00" + "time": "2026-11-09T16:07:10+00:00" }, { "name": "laravel/sanctum", - "version": "v4.3.2", + "version": "v4.4.0", "source": { "type": "git", "url": "https://github.com/laravel/sanctum.git", - "reference": "2a9bccc18e9907808e0018dd15fa643937886b1e" + "reference": "a28ea5d4c2b123420562d6a3a233414f84b70774" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sanctum/zipball/2a9bccc18e9907808e0018dd15fa643937886b1e", - "reference": "2a9bccc18e9907808e0018dd15fa643937886b1e", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/a28ea5d4c2b123420562d6a3a233414f84b70774", + "reference": "a28ea5d4c2b123420562d6a3a233414f84b70774", "shasum": "" }, "require": { @@ -2102,6 +2166,7 @@ "symfony/console": "^7.0|^8.0" }, "require-dev": { + "jasonmccreary/double": "^1.0", "mockery/mockery": "^1.6", "orchestra/testbench": "^9.15|^10.8|^11.0", "phpstan/phpstan": "^1.10" @@ -2139,20 +2204,20 @@ "issues": "https://github.com/laravel/sanctum/issues", "source": "https://github.com/laravel/sanctum" }, - "time": "2026-04-30T11:46:25+00:00" + "time": "2026-11-02T15:42:32+00:00" }, { "name": "laravel/serializable-closure", - "version": "v2.0.10", + "version": "v2.2.0", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669" + "reference": "2bfb44e8af009b553ed2c060c94b0bc3c67b4769" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/870fc81d2f879903dfc5b60bf8a0f94a1609e669", - "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/2bfb44e8af009b553ed2c060c94b0bc3c67b4769", + "reference": "2bfb44e8af009b553ed2c060c94b0bc3c67b4769", "shasum": "" }, "require": { @@ -2200,7 +2265,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2026-02-20T19:59:49+00:00" + "time": "2026-10-14T21:20:29+00:00" }, { "name": "laravel/tinker", @@ -2270,16 +2335,16 @@ }, { "name": "laravel/wayfinder", - "version": "v0.1.14", + "version": "v0.1.20", "source": { "type": "git", "url": "https://github.com/laravel/wayfinder.git", - "reference": "57bbd154ae69b11513d459bb97a0db19a8b7c871" + "reference": "91d958a6f99fe9187c6eda84e62aa93d79330be5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/wayfinder/zipball/57bbd154ae69b11513d459bb97a0db19a8b7c871", - "reference": "57bbd154ae69b11513d459bb97a0db19a8b7c871", + "url": "https://api.github.com/repos/laravel/wayfinder/zipball/91d958a6f99fe9187c6eda84e62aa93d79330be5", + "reference": "91d958a6f99fe9187c6eda84e62aa93d79330be5", "shasum": "" }, "require": { @@ -2329,20 +2394,20 @@ "issues": "https://github.com/laravel/wayfinder/issues", "source": "https://github.com/laravel/wayfinder" }, - "time": "2026-02-24T12:51:07+00:00" + "time": "2026-05-12T01:44:17+00:00" }, { "name": "league/commonmark", - "version": "2.8.1", + "version": "2.10.7", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "84b1ca48347efdbe775426f108622a42735a6579" + "reference": "8eb53e906fd60c67038d22f1f1cab420ed58b8be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/84b1ca48347efdbe775426f108622a42735a6579", - "reference": "84b1ca48347efdbe775426f108622a42735a6579", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/8eb53e906fd60c67038d22f1f1cab420ed58b8be", + "reference": "8eb53e906fd60c67038d22f1f1cab420ed58b8be", "shasum": "" }, "require": { @@ -2364,8 +2429,8 @@ "github/gfm": "0.29.0", "michelf/php-markdown": "^1.4 || ^2.0", "nyholm/psr7": "^1.5", - "phpstan/phpstan": "^1.8.2", - "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", + "phpstan/phpstan": "^2.0.0", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0 || ^12.0.0 || ^13.0.0", "scrutinizer/ocular": "^1.8.1", "symfony/finder": "^5.3 | ^6.0 | ^7.0 || ^8.0", "symfony/process": "^5.4 | ^6.0 | ^7.0 || ^8.0", @@ -2379,7 +2444,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.9-dev" + "dev-main": "2.11-dev" } }, "autoload": { @@ -2436,7 +2501,7 @@ "type": "tidelift" } ], - "time": "2026-03-05T21:37:03+00:00" + "time": "2026-10-31T13:35:37+00:00" }, { "name": "league/config", @@ -2522,16 +2587,16 @@ }, { "name": "league/flysystem", - "version": "3.32.0", + "version": "3.36.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "254b1595b16b22dbddaaef9ed6ca9fdac4956725" + "reference": "f7fb152932f30072d573510cbd4dd657d6475b25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/254b1595b16b22dbddaaef9ed6ca9fdac4956725", - "reference": "254b1595b16b22dbddaaef9ed6ca9fdac4956725", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f7fb152932f30072d573510cbd4dd657d6475b25", + "reference": "f7fb152932f30072d573510cbd4dd657d6475b25", "shasum": "" }, "require": { @@ -2599,22 +2664,22 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.32.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.36.0" }, - "time": "2026-02-25T17:01:41+00:00" + "time": "2026-09-02T08:00:27+00:00" }, { "name": "league/flysystem-local", - "version": "3.31.0", + "version": "3.35.3", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079" + "reference": "a099b24dce160f3b2239043d13d47c4a1a214ea4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/2f669db18a4c20c755c2bb7d3a7b0b2340488079", - "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/a099b24dce160f3b2239043d13d47c4a1a214ea4", + "reference": "a099b24dce160f3b2239043d13d47c4a1a214ea4", "shasum": "" }, "require": { @@ -2648,22 +2713,22 @@ "local" ], "support": { - "source": "https://github.com/thephpleague/flysystem-local/tree/3.31.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.35.3" }, - "time": "2026-01-23T15:30:45+00:00" + "time": "2026-08-12T13:29:21+00:00" }, { "name": "league/mime-type-detection", - "version": "1.16.0", + "version": "1.17.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" + "reference": "f5f47eff7c48ed1003069a2ca67f316fb4021c76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", - "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/f5f47eff7c48ed1003069a2ca67f316fb4021c76", + "reference": "f5f47eff7c48ed1003069a2ca67f316fb4021c76", "shasum": "" }, "require": { @@ -2673,7 +2738,7 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^3.2", "phpstan/phpstan": "^0.12.68", - "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0 || ^11.0 || ^12.0" }, "type": "library", "autoload": { @@ -2694,7 +2759,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.17.0" }, "funding": [ { @@ -2706,24 +2771,25 @@ "type": "tidelift" } ], - "time": "2024-09-21T08:32:55+00:00" + "time": "2026-07-09T11:49:27+00:00" }, { "name": "league/uri", - "version": "7.8.0", + "version": "7.10.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri.git", - "reference": "4436c6ec8d458e4244448b069cc572d088230b76" + "reference": "be89e2382e37acdef232259071ade5180821397b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri/zipball/4436c6ec8d458e4244448b069cc572d088230b76", - "reference": "4436c6ec8d458e4244448b069cc572d088230b76", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/be89e2382e37acdef232259071ade5180821397b", + "reference": "be89e2382e37acdef232259071ade5180821397b", "shasum": "" }, "require": { - "league/uri-interfaces": "^7.8", + "ext-mbstring": "*", + "league/uri-interfaces": "^7.10.0", "php": "^8.1", "psr/http-factory": "^1" }, @@ -2796,7 +2862,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri/tree/7.8.0" + "source": "https://github.com/thephpleague/uri/tree/7.10.0" }, "funding": [ { @@ -2804,20 +2870,20 @@ "type": "github" } ], - "time": "2026-01-14T17:24:56+00:00" + "time": "2026-11-04T12:45:07+00:00" }, { "name": "league/uri-interfaces", - "version": "7.8.0", + "version": "7.10.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-interfaces.git", - "reference": "c5c5cd056110fc8afaba29fa6b72a43ced42acd4" + "reference": "ea66240cff4ae9a56ce85d577a04d038b771e51c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/c5c5cd056110fc8afaba29fa6b72a43ced42acd4", - "reference": "c5c5cd056110fc8afaba29fa6b72a43ced42acd4", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/ea66240cff4ae9a56ce85d577a04d038b771e51c", + "reference": "ea66240cff4ae9a56ce85d577a04d038b771e51c", "shasum": "" }, "require": { @@ -2880,7 +2946,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri-interfaces/tree/7.8.0" + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.10.0" }, "funding": [ { @@ -2888,34 +2954,42 @@ "type": "github" } ], - "time": "2026-01-15T06:54:53+00:00" + "time": "2026-11-04T12:45:07+00:00" }, { "name": "maatwebsite/excel", - "version": "3.1.69", + "version": "4.0.6", "source": { "type": "git", "url": "https://github.com/SpartnerNL/Laravel-Excel.git", - "reference": "ae5d65b7c9a2fac43bff4d44f796ac95d7a8e760" + "reference": "90b012ef8680e0f0e20208ce7f05400a798b1c0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/ae5d65b7c9a2fac43bff4d44f796ac95d7a8e760", - "reference": "ae5d65b7c9a2fac43bff4d44f796ac95d7a8e760", + "url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/90b012ef8680e0f0e20208ce7f05400a798b1c0b", + "reference": "90b012ef8680e0f0e20208ce7f05400a798b1c0b", "shasum": "" }, "require": { - "composer/semver": "^3.3", - "ext-json": "*", - "illuminate/support": "5.8.*||^6.0||^7.0||^8.0||^9.0||^10.0||^11.0||^12.0||^13.0", - "php": "^7.0||^8.0", - "phpoffice/phpspreadsheet": "^1.30.4", - "psr/simple-cache": "^1.0||^2.0||^3.0" + "composer/semver": "^3.4", + "illuminate/support": "^12.0 || ^13.0", + "php": "^8.3", + "phpoffice/phpspreadsheet": "^5.9", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" }, "require-dev": { - "laravel/scout": "^7.0||^8.0||^9.0||^10.0||^11.0", - "orchestra/testbench": "^6.0||^7.0||^8.0||^9.0||^10.0||^11.0", - "predis/predis": "^1.1" + "brianium/paratest": "^7.20", + "driftingly/rector-laravel": "^2.5", + "ext-sqlite3": "*", + "larastan/larastan": "^3.10", + "laravel/pint": "^1.29", + "laravel/scout": "^10.25 || ^11.2", + "orchestra/testbench": "^10.11 || ^11.1", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan-mockery": "^2.0", + "phpunit/phpunit": "^12.5 || ~13.1.14", + "predis/predis": "^2.3 || ^3.0", + "rector/rector": "^2.4.2" }, "type": "library", "extra": { @@ -2957,7 +3031,7 @@ ], "support": { "issues": "https://github.com/SpartnerNL/Laravel-Excel/issues", - "source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.69" + "source": "https://github.com/SpartnerNL/Laravel-Excel/tree/4.0.6" }, "funding": [ { @@ -2969,7 +3043,7 @@ "type": "github" } ], - "time": "2026-04-30T20:03:58+00:00" + "time": "2026-10-23T11:35:56+00:00" }, { "name": "maennchen/zipstream-php", @@ -3158,24 +3232,25 @@ }, { "name": "masterminds/html5", - "version": "2.10.0", + "version": "2.12.0", "source": { "type": "git", "url": "https://github.com/Masterminds/html5-php.git", - "reference": "fcf91eb64359852f00d921887b219479b4f21251" + "reference": "15e16663bd6f512352209d0e3a789c0a691c0d9b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", - "reference": "fcf91eb64359852f00d921887b219479b4f21251", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/15e16663bd6f512352209d0e3a789c0a691c0d9b", + "reference": "15e16663bd6f512352209d0e3a789c0a691c0d9b", "shasum": "" }, "require": { "ext-dom": "*", - "php": ">=5.3.0" + "ext-mbstring": "*", + "php": ">=7.4" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + "phpunit/phpunit": "^6 || ^7 || ^8 || ^9 || ^10" }, "type": "library", "extra": { @@ -3219,22 +3294,22 @@ ], "support": { "issues": "https://github.com/Masterminds/html5-php/issues", - "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" + "source": "https://github.com/Masterminds/html5-php/tree/2.12.0" }, - "time": "2025-07-25T09:04:22+00:00" + "time": "2026-10-25T08:57:58+00:00" }, { "name": "monolog/monolog", - "version": "3.10.0", + "version": "3.12.1", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0" + "reference": "d7059e4c691d05ea1222b5124bed0f961be46e15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0", - "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/d7059e4c691d05ea1222b5124bed0f961be46e15", + "reference": "d7059e4c691d05ea1222b5124bed0f961be46e15", "shasum": "" }, "require": { @@ -3260,6 +3335,7 @@ "phpstan/phpstan-strict-rules": "^2", "phpunit/phpunit": "^10.5.17 || ^11.0.7", "predis/predis": "^1.1 || ^2", + "psr/clock": "^1.0", "rollbar/rollbar": "^4.0", "ruflin/elastica": "^7 || ^8", "symfony/mailer": "^5.4 || ^6", @@ -3278,6 +3354,7 @@ "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "psr/clock": "Required to pass a clock to the Logger and control the timestamp of log records", "rollbar/rollbar": "Allow sending log messages to Rollbar", "ruflin/elastica": "Allow sending log messages to an Elastic Search server" }, @@ -3312,7 +3389,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.10.0" + "source": "https://github.com/Seldaek/monolog/tree/3.12.1" }, "funding": [ { @@ -3324,20 +3401,20 @@ "type": "tidelift" } ], - "time": "2026-01-02T08:56:05+00:00" + "time": "2026-09-29T19:21:12+00:00" }, { "name": "nesbot/carbon", - "version": "3.11.3", + "version": "3.15.1", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon.git", - "reference": "6a7e652845bb018c668220c2a545aded8594fbbf" + "reference": "ce0e5636fae7d1e99856eb70cae4c719a284aa08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/6a7e652845bb018c668220c2a545aded8594fbbf", - "reference": "6a7e652845bb018c668220c2a545aded8594fbbf", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/ce0e5636fae7d1e99856eb70cae4c719a284aa08", + "reference": "ce0e5636fae7d1e99856eb70cae4c719a284aa08", "shasum": "" }, "require": { @@ -3347,6 +3424,7 @@ "psr/clock": "^1.0", "symfony/clock": "^6.3.12 || ^7.0 || ^8.0", "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-time": "^1.0", "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0 || ^8.0" }, "provide": { @@ -3357,7 +3435,7 @@ "doctrine/orm": "^2.15.2 || ^3.0", "friendsofphp/php-cs-fixer": "^v3.87.1", "kylekatarnls/multi-tester": "^2.5.3", - "phpmd/phpmd": "^2.15.0", + "phpmd/phpmd": "^3.0@dev", "phpstan/extension-installer": "^1.4.3", "phpstan/phpstan": "^2.1.22", "phpunit/phpunit": "^10.5.53", @@ -3429,37 +3507,37 @@ "type": "tidelift" } ], - "time": "2026-03-11T17:23:39+00:00" + "time": "2026-10-25T14:32:48+00:00" }, { "name": "nette/schema", - "version": "v1.3.5", + "version": "v1.4.0", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002" + "reference": "af9fa8a86bfca9b02b10346220db26e62ef942ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/f0ab1a3cda782dbc5da270d28545236aa80c4002", - "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002", + "url": "https://api.github.com/repos/nette/schema/zipball/af9fa8a86bfca9b02b10346220db26e62ef942ec", + "reference": "af9fa8a86bfca9b02b10346220db26e62ef942ec", "shasum": "" }, "require": { "nette/utils": "^4.0", - "php": "8.1 - 8.5" + "php": "8.1 - 8.6" }, "require-dev": { - "nette/phpstan-rules": "^1.0", + "nette/phpstan-rules": "^1.0.7", "nette/tester": "^2.6", "phpstan/extension-installer": "^1.4@stable", - "phpstan/phpstan": "^2.1.39@stable", + "phpstan/phpstan": "^2.3@stable", "tracy/tracy": "^2.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-master": "1.4-dev" } }, "autoload": { @@ -3480,40 +3558,40 @@ { "name": "David Grudl", "homepage": "https://davidgrudl.com" - }, - { - "name": "Nette Community", - "homepage": "https://nette.org/contributors" } ], - "description": "📐 Nette Schema: validating data structures against a given Schema.", - "homepage": "https://nette.org", + "description": "📐 Nette Schema: validates and normalizes data such as configuration or API input against a schema described in a few readable lines of PHP, with precise error messages.", + "homepage": "https://doc.nette.org/schema", "keywords": [ - "config", - "nette" + "configuration", + "data validation", + "nette", + "normalization", + "schema", + "validation" ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.3.5" + "source": "https://github.com/nette/schema/tree/v1.4.0" }, - "time": "2026-02-23T03:47:12+00:00" + "time": "2026-10-22T02:12:50+00:00" }, { "name": "nette/utils", - "version": "v4.1.3", + "version": "v4.1.6", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe" + "reference": "6bf24518accf32cf845bca305e0b92078700fd01" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/bb3ea637e3d131d72acc033cfc2746ee893349fe", - "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe", + "url": "https://api.github.com/repos/nette/utils/zipball/6bf24518accf32cf845bca305e0b92078700fd01", + "reference": "6bf24518accf32cf845bca305e0b92078700fd01", "shasum": "" }, "require": { - "php": "8.2 - 8.5" + "php": "8.2 - 8.6" }, "conflict": { "nette/finder": "<3", @@ -3529,7 +3607,7 @@ }, "suggest": { "ext-gd": "to use Image", - "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-iconv": "to use Strings::chr(), ord() and reverse()", "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", "ext-json": "to use Nette\\Utils\\Json", "ext-mbstring": "to use Strings::lower() etc...", @@ -3585,26 +3663,25 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.1.3" + "source": "https://github.com/nette/utils/tree/v4.1.6" }, - "time": "2026-02-13T03:05:33+00:00" + "time": "2026-10-20T17:07:56+00:00" }, { "name": "nikic/php-parser", - "version": "v5.7.0", + "version": "v5.9.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + "reference": "9e33da9553fe7786f0962b35f4e4ecf01be89def" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/9e33da9553fe7786f0962b35f4e4ecf01be89def", + "reference": "9e33da9553fe7786f0962b35f4e4ecf01be89def", "shasum": "" }, "require": { - "ext-ctype": "*", "ext-json": "*", "ext-tokenizer": "*", "php": ">=7.4" @@ -3643,37 +3720,37 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.9.0" }, - "time": "2025-12-06T11:56:16+00:00" + "time": "2026-09-13T18:51:52+00:00" }, { "name": "nunomaduro/termwind", - "version": "v2.4.0", + "version": "v2.4.1", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "712a31b768f5daea284c2169a7d227031001b9a8" + "reference": "9b85ae4d802403bfc004ea633ce542bc262ee5c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/712a31b768f5daea284c2169a7d227031001b9a8", - "reference": "712a31b768f5daea284c2169a7d227031001b9a8", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/9b85ae4d802403bfc004ea633ce542bc262ee5c3", + "reference": "9b85ae4d802403bfc004ea633ce542bc262ee5c3", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^8.2", - "symfony/console": "^7.4.4 || ^8.0.4" + "symfony/console": "^7.4.23 || ^8.1.11" }, "require-dev": { - "illuminate/console": "^11.47.0", - "laravel/pint": "^1.27.1", - "mockery/mockery": "^1.6.12", - "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.3.2", - "phpstan/phpstan": "^1.12.32", + "illuminate/console": "^11.51 || ^12.70 || ^13.40", + "laravel/pint": "^1.30.4", + "mockery/mockery": "^1.6.15", + "pestphp/pest": "^2.36.1 || ^3.8.7 || ^4.7.8 || ^5.3.5", + "phpstan/phpstan": "^1.12.34", "phpstan/phpstan-strict-rules": "^1.6.2", - "symfony/var-dumper": "^7.3.5 || ^8.0.4", + "symfony/var-dumper": "^7.4.22 || ^8.1.10", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -3716,7 +3793,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.4.0" + "source": "https://github.com/nunomaduro/termwind/tree/v2.4.1" }, "funding": [ { @@ -3732,7 +3809,7 @@ "type": "github" } ], - "time": "2026-02-16T23:10:27+00:00" + "time": "2026-11-07T03:57:16+00:00" }, { "name": "nyholm/psr7", @@ -3814,16 +3891,16 @@ }, { "name": "paragonie/constant_time_encoding", - "version": "v3.1.3", + "version": "v3.1.4", "source": { "type": "git", "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77" + "reference": "5efef6e6cb43e088ccab7d0c1e2f2ab25b9e8b54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", - "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/5efef6e6cb43e088ccab7d0c1e2f2ab25b9e8b54", + "reference": "5efef6e6cb43e088ccab7d0c1e2f2ab25b9e8b54", "shasum": "" }, "require": { @@ -3879,69 +3956,39 @@ "issues": "https://github.com/paragonie/constant_time_encoding/issues", "source": "https://github.com/paragonie/constant_time_encoding" }, - "time": "2025-09-24T15:06:41+00:00" + "time": "2026-11-06T20:31:19+00:00" }, { - "name": "phpoffice/phpspreadsheet", - "version": "1.30.5", + "name": "phpdocumentor/reflection-common", + "version": "2.2.1", "source": { "type": "git", - "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", - "reference": "97bcabd32a64924688487dcd64aceaf158affb5c" + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "ee15a5a2022c8383906b225055e9496a6a5e6e3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/97bcabd32a64924688487dcd64aceaf158affb5c", - "reference": "97bcabd32a64924688487dcd64aceaf158affb5c", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/ee15a5a2022c8383906b225055e9496a6a5e6e3b", + "reference": "ee15a5a2022c8383906b225055e9496a6a5e6e3b", "shasum": "" }, "require": { - "composer/pcre": "^1||^2||^3", - "ext-ctype": "*", - "ext-dom": "*", - "ext-fileinfo": "*", - "ext-gd": "*", - "ext-iconv": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-simplexml": "*", - "ext-xml": "*", - "ext-xmlreader": "*", - "ext-xmlwriter": "*", - "ext-zip": "*", - "ext-zlib": "*", - "ezyang/htmlpurifier": "^4.15", - "maennchen/zipstream-php": "^2.1 || ^3.0", - "markbaker/complex": "^3.0", - "markbaker/matrix": "^3.0", - "php": ">=7.4.0 <8.5.0", - "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + "php": "^7.4 || ^8.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "dev-main", - "doctrine/instantiator": "^1.5", - "dompdf/dompdf": "^1.0 || ^2.0 || ^3.0", - "friendsofphp/php-cs-fixer": "^3.2", - "mitoteam/jpgraph": "^10.3", - "mpdf/mpdf": "^8.1.1", - "phpcompatibility/php-compatibility": "^9.3", - "phpstan/phpstan": "^1.1", - "phpstan/phpstan-phpunit": "^1.0", - "phpunit/phpunit": "^8.5 || ^9.0", - "squizlabs/php_codesniffer": "^3.7", - "tecnickcom/tcpdf": "^6.5" - }, - "suggest": { - "dompdf/dompdf": "Option for rendering PDF with PDF Writer", - "ext-intl": "PHP Internationalization Functions", - "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", - "mpdf/mpdf": "Option for rendering PDF with PDF Writer", - "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" + "phpstan/phpstan": "^1.8", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^4.25" }, "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, "autoload": { "psr-4": { - "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + "phpDocumentor\\Reflection\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3950,93 +3997,361 @@ ], "authors": [ { - "name": "Maarten Balliauw", - "homepage": "https://blog.maartenballiauw.be" - }, - { - "name": "Mark Baker", - "homepage": "https://markbakeruk.net" - }, - { - "name": "Franck Lefevre", - "homepage": "https://rootslabs.net" - }, - { - "name": "Erik Tilt" - }, - { - "name": "Adrien Crivelli" - }, - { - "name": "Owen Leibman" + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" } ], - "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", - "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "https://www.phpdoc.org", "keywords": [ - "OpenXML", - "excel", - "gnumeric", - "ods", - "php", - "spreadsheet", - "xls", - "xlsx" + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" ], "support": { - "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", - "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.5" + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.2.1" }, - "time": "2026-05-31T05:13:11+00:00" + "time": "2025-11-10T21:21:55+00:00" }, { - "name": "phpoption/phpoption", - "version": "1.9.5", + "name": "phpdocumentor/reflection-docblock", + "version": "6.0.3", "source": { "type": "git", - "url": "https://github.com/schmittjoh/php-option.git", - "reference": "75365b91986c2405cf5e1e012c5595cd487a98be" + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "7bae67520aa9f5ecc506d646810bd40d9da54582" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be", - "reference": "75365b91986c2405cf5e1e012c5595cd487a98be", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/7bae67520aa9f5ecc506d646810bd40d9da54582", + "reference": "7bae67520aa9f5ecc506d646810bd40d9da54582", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0" + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^2.0", + "phpstan/phpdoc-parser": "^2.0", + "webmozart/assert": "^1.9.1 || ^2" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26", + "shipmonk/dead-code-detector": "^0.5.1" }, "type": "library", "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - }, "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "5.x-dev" } }, "autoload": { "psr-4": { - "PhpOption\\": "src/PhpOption/" + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" + "MIT" ], "authors": [ { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com", - "homepage": "https://github.com/schmittjoh" + "name": "Mike van Riel", + "email": "me@mikevanriel.com" }, { - "name": "Graham Campbell", + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/6.0.3" + }, + "time": "2026-03-18T20:49:53+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "dbe2133bb23e2e33a6834f4ed417f24a1b167769" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/dbe2133bb23e2e33a6834f4ed417f24a1b167769", + "reference": "dbe2133bb23e2e33a6834f4ed417f24a1b167769", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev", + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/2.1.0" + }, + "time": "2026-09-27T19:32:54+00:00" + }, + { + "name": "phpoffice/phpspreadsheet", + "version": "5.11.0", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "3aa1f0d0f3c658b803c18050ac49244d07d96907" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/3aa1f0d0f3c658b803c18050ac49244d07d96907", + "reference": "3aa1f0d0f3c658b803c18050ac49244d07d96907", + "shasum": "" + }, + "require": { + "composer/pcre": "^1||^2||^3", + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-filter": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "maennchen/zipstream-php": "^2.1 || ^3.0", + "markbaker/complex": "^3.0", + "markbaker/matrix": "^3.0", + "php": "^8.2", + "phpoffice/wmf": "^0.2.1", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "cyclonedx/cyclonedx-php-composer": "^6.2", + "dealerdirect/phpcodesniffer-composer-installer": "dev-main", + "dompdf/dompdf": "^2.0 || ^3.0", + "ext-intl": "*", + "ext-openssl": "*", + "friendsofphp/php-cs-fixer": "^3.2", + "mitoteam/jpgraph": "^10.5", + "mpdf/mpdf": "^8.1.1", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.1 || ^2.0", + "phpstan/phpstan-deprecation-rules": "^1.0 || ^2.0", + "phpstan/phpstan-phpunit": "^1.0 || ^2.0", + "phpunit/phpunit": "^10.5 || ^11.0", + "squizlabs/php_codesniffer": "^3.7", + "tecnickcom/tcpdf": ">=6.8.0 <7.0.0" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "ext-intl": "PHP Internationalization Functions, required for NumberFormat Wizard and StringHelper::setLocale()", + "ext-openssl": "Handline Agile-encrypted Xlsx spreadsheets", + "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + }, + { + "name": "Owen Leibman" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/5.11.0" + }, + "time": "2026-11-08T06:29:49+00:00" + }, + { + "name": "phpoffice/wmf", + "version": "0.2.1", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/WMF.git", + "reference": "d1eafbab74509ee2adfcdda3888737cd8d349820" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/WMF/zipball/d1eafbab74509ee2adfcdda3888737cd8d349820", + "reference": "d1eafbab74509ee2adfcdda3888737cd8d349820", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "php": "^7.1|^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.88 || ^1.0.0", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\WMF\\": "src/WMF/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Progi1984", + "homepage": "https://lefevre.dev" + } + ], + "description": "WMF - Manipulate WMF Images", + "homepage": "https://phpoffice.github.io/WMF/", + "keywords": [ + "emf", + "image", + "php", + "wmf" + ], + "support": { + "issues": "https://github.com/PHPOffice/WMF/issues", + "source": "https://github.com/PHPOffice/WMF/tree/0.2.1" + }, + "time": "2026-10-08T19:34:28+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "67b192b6a42ec03944b972d6e633ddec78ad2c6d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/67b192b6a42ec03944b972d6e633ddec78ad2c6d", + "reference": "67b192b6a42ec03944b972d6e633ddec78ad2c6d", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.54 || ^9.6.36 || ^10.5.64 || ^11.5.56 || ^12.5.33" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", "email": "hello@gjcampbell.co.uk", "homepage": "https://github.com/GrahamCampbell" } @@ -4050,7 +4365,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.5" + "source": "https://github.com/schmittjoh/php-option/tree/1.10.0" }, "funding": [ { @@ -4062,20 +4377,20 @@ "type": "tidelift" } ], - "time": "2025-12-27T19:41:33+00:00" + "time": "2026-08-24T00:54:40+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "2.3.2", + "version": "2.4.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" + "reference": "af8e840551031d254cfdb3a18a62c3470a309c10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", - "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/af8e840551031d254cfdb3a18a62c3470a309c10", + "reference": "af8e840551031d254cfdb3a18a62c3470a309c10", "shasum": "" }, "require": { @@ -4107,22 +4422,22 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.4.1" }, - "time": "2026-01-25T14:56:51+00:00" + "time": "2026-10-30T19:36:28+00:00" }, { "name": "pragmarx/google2fa", - "version": "v9.0.0", + "version": "v9.1.0", "source": { "type": "git", "url": "https://github.com/antonioribeiro/google2fa.git", - "reference": "e6bc62dd6ae83acc475f57912e27466019a1f2cf" + "reference": "f00bc788c555adfb6765c437ff3538e59cd88af1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/e6bc62dd6ae83acc475f57912e27466019a1f2cf", - "reference": "e6bc62dd6ae83acc475f57912e27466019a1f2cf", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/f00bc788c555adfb6765c437ff3538e59cd88af1", + "reference": "f00bc788c555adfb6765c437ff3538e59cd88af1", "shasum": "" }, "require": { @@ -4130,8 +4445,11 @@ "php": "^7.1|^8.0" }, "require-dev": { - "phpstan/phpstan": "^1.9", - "phpunit/phpunit": "^7.5.15|^8.5|^9.0" + "phpstan/phpstan": "^1.0|^2.0", + "phpstan/phpstan-phpunit": "^1.0|^2.0", + "phpunit/phpunit": "~9|~10|~11|~12|~13", + "psalm/plugin-phpunit": "^0.19|^0.20", + "vimeo/psalm": "^5.26|^6.13" }, "type": "library", "autoload": { @@ -4154,14 +4472,23 @@ "keywords": [ "2fa", "Authentication", + "MFA", "Two Factor Authentication", - "google2fa" + "google-authenticator", + "google2fa", + "hotp", + "otp", + "rfc4226", + "rfc6238", + "totp" ], "support": { + "docs": "https://github.com/antonioribeiro/google2fa#readme", "issues": "https://github.com/antonioribeiro/google2fa/issues", - "source": "https://github.com/antonioribeiro/google2fa/tree/v9.0.0" + "security": "https://github.com/antonioribeiro/google2fa/security/policy", + "source": "https://github.com/antonioribeiro/google2fa" }, - "time": "2025-09-19T22:51:08+00:00" + "time": "2026-08-15T13:22:01+00:00" }, { "name": "psr/clock", @@ -4577,16 +4904,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.21", + "version": "v0.12.24", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "4821fab5b7cd8c49a673a9fd5754dc9162bb9e97" + "reference": "ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/4821fab5b7cd8c49a673a9fd5754dc9162bb9e97", - "reference": "4821fab5b7cd8c49a673a9fd5754dc9162bb9e97", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1", + "reference": "ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1", "shasum": "" }, "require": { @@ -4650,9 +4977,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.21" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.24" }, - "time": "2026-03-06T21:21:28+00:00" + "time": "2026-06-29T15:41:09+00:00" }, { "name": "ralouphie/getallheaders", @@ -4776,20 +5103,20 @@ }, { "name": "ramsey/uuid", - "version": "4.9.2", + "version": "4.9.4", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "8429c78ca35a09f27565311b98101e2826affde0" + "reference": "75d73f48d02797c2c285a7e9f348fadc0102ffe2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0", - "reference": "8429c78ca35a09f27565311b98101e2826affde0", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/75d73f48d02797c2c285a7e9f348fadc0102ffe2", + "reference": "75d73f48d02797c2c285a7e9f348fadc0102ffe2", "shasum": "" }, "require": { - "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", + "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14 || ^0.15 || ^0.16 || ^0.17 || ^0.18 || ^0.19 || ^0.20 || ^1.0", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" }, @@ -4848,41 +5175,39 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.9.2" + "source": "https://github.com/ramsey/uuid/tree/4.9.4" }, - "time": "2025-12-14T04:43:48+00:00" + "time": "2026-09-16T11:39:30+00:00" }, { "name": "sabberworm/php-css-parser", - "version": "v9.3.0", + "version": "v9.5.0", "source": { "type": "git", "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", - "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949" + "reference": "f284e63b6e891e0c28631e54ba06c3ed102a9ef3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949", - "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/f284e63b6e891e0c28631e54ba06c3ed102a9ef3", + "reference": "f284e63b6e891e0c28631e54ba06c3ed102a9ef3", "shasum": "" }, "require": { "ext-iconv": "*", - "php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", - "thecodingmachine/safe": "^1.3 || ^2.5 || ^3.4" + "php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0 || ~8.6.0" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "1.4.0", "phpstan/extension-installer": "1.4.3", - "phpstan/phpstan": "1.12.32 || 2.1.32", - "phpstan/phpstan-phpunit": "1.4.2 || 2.0.8", - "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.7", - "phpunit/phpunit": "8.5.52", + "phpstan/phpstan": "1.12.33 || 2.2.9", + "phpstan/phpstan-phpunit": "1.4.2 || 2.0.18", + "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.12", + "phpunit/phpunit": "8.5.54", "rawr/phpunit-data-provider": "3.3.1", - "rector/rector": "1.2.10 || 2.2.8", - "rector/type-perfect": "1.0.0 || 2.1.0", - "squizlabs/php_codesniffer": "4.0.1", - "thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.1" + "rector/rector": "1.2.10 || 2.6.2", + "rector/type-perfect": "1.0.0 || 2.1.4", + "squizlabs/php_codesniffer": "4.0.4" }, "suggest": { "ext-mbstring": "for parsing UTF-8 CSS" @@ -4890,7 +5215,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "9.4.x-dev" + "dev-main": "9.6.x-dev" } }, "autoload": { @@ -4928,29 +5253,29 @@ ], "support": { "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", - "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.3.0" + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.5.0" }, - "time": "2026-03-03T17:31:43+00:00" + "time": "2026-09-20T15:02:00+00:00" }, { "name": "sentry/sentry", - "version": "4.29.0", + "version": "4.37.0", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-php.git", - "reference": "d732a4da195f231cedb2a2a78ae16dd73082afa3" + "reference": "635a94908528ca5c4f0804640a0ec578e51639b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/d732a4da195f231cedb2a2a78ae16dd73082afa3", - "reference": "d732a4da195f231cedb2a2a78ae16dd73082afa3", + "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/635a94908528ca5c4f0804640a0ec578e51639b3", + "reference": "635a94908528ca5c4f0804640a0ec578e51639b3", "shasum": "" }, "require": { "ext-curl": "*", "ext-json": "*", "ext-mbstring": "*", - "guzzlehttp/psr7": "^1.8.4|^2.1.1", + "guzzlehttp/psr7": "^1.8.4|^2.1.1|^3.0.0", "jean85/pretty-package-versions": "^1.5|^2.0.4", "php": "^7.2|^8.0", "psr/log": "^1.0|^2.0|^3.0", @@ -4962,14 +5287,17 @@ "require-dev": { "carthage-software/mago": "1.30.0", "friendsofphp/php-cs-fixer": "^3.4", - "guzzlehttp/promises": "^2.0.3", + "guzzlehttp/guzzle": "^7.0|^8.0", + "guzzlehttp/promises": "^2.0.3|^3.0.0", "monolog/monolog": "^1.6|^2.0|^3.0", "nyholm/psr7": "^1.8", - "open-telemetry/api": "^1.0", - "open-telemetry/exporter-otlp": "^1.0", - "open-telemetry/sdk": "^1.0", + "open-telemetry/api": "^1.1", + "open-telemetry/context": "^1.1", + "open-telemetry/exporter-otlp": "^1.1", + "open-telemetry/gen-otlp-protobuf": "^1.2.1", + "open-telemetry/sdk": "^1.1", "open-telemetry/sem-conv": "^1.27", - "phpstan/phpstan": "^1.3", + "phpstan/phpstan": "^2.2", "phpunit/phpunit": "^8.5.52|^9.6.34", "spiral/roadrunner-http": "^3.6", "spiral/roadrunner-worker": "^3.6" @@ -5012,7 +5340,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-php/issues", - "source": "https://github.com/getsentry/sentry-php/tree/4.29.0" + "source": "https://github.com/getsentry/sentry-php/tree/4.37.0" }, "funding": [ { @@ -5024,32 +5352,33 @@ "type": "custom" } ], - "time": "2026-06-29T14:47:44+00:00" + "time": "2026-11-11T15:29:22+00:00" }, { "name": "sentry/sentry-laravel", - "version": "4.26.0", + "version": "4.30.0", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-laravel.git", - "reference": "8f2cc669ee99d3cda31a89f0a18197fd2efbd1b6" + "reference": "001f8c7eeeb0d512491c4a22c5a4880c833bd036" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/8f2cc669ee99d3cda31a89f0a18197fd2efbd1b6", - "reference": "8f2cc669ee99d3cda31a89f0a18197fd2efbd1b6", + "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/001f8c7eeeb0d512491c4a22c5a4880c833bd036", + "reference": "001f8c7eeeb0d512491c4a22c5a4880c833bd036", "shasum": "" }, "require": { "illuminate/support": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 | ^12.0 | ^13.0", "nyholm/psr7": "^1.0", "php": "^7.2 | ^8.0", - "sentry/sentry": "^4.28.0", + "sentry/sentry": "^4.34.0", "symfony/psr-http-message-bridge": "^1.0 | ^2.0 | ^6.0 | ^7.0 | ^8.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.11", "guzzlehttp/guzzle": "^7.2", + "laravel/ai": "^0.1.5 | ^1.0", "laravel/folio": "^1.1", "laravel/framework": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0 | ^12.0 | ^13.0", "laravel/octane": "^2.15", @@ -5057,7 +5386,7 @@ "livewire/livewire": "^2.0 | ^3.0 | ^4.0", "mockery/mockery": "^1.3", "orchestra/testbench": "^4.7 | ^5.1 | ^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0", - "phpstan/phpstan": "^1.10", + "phpstan/phpstan": "^1.10 | ^2.2", "phpunit/phpunit": "^8.5 | ^9.6 | ^10.4 | ^11.5" }, "type": "library", @@ -5103,7 +5432,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-laravel/issues", - "source": "https://github.com/getsentry/sentry-laravel/tree/4.26.0" + "source": "https://github.com/getsentry/sentry-laravel/tree/4.30.0" }, "funding": [ { @@ -5115,20 +5444,20 @@ "type": "custom" } ], - "time": "2026-06-11T13:22:14+00:00" + "time": "2026-11-11T19:17:46+00:00" }, { "name": "spatie/laravel-package-tools", - "version": "1.93.0", + "version": "1.93.3", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "0d097bce95b2bf6802fb1d83e1e753b0f5a948e7" + "reference": "7aeef26bd8d4909bec744dbf47aee7d1395588c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/0d097bce95b2bf6802fb1d83e1e753b0f5a948e7", - "reference": "0d097bce95b2bf6802fb1d83e1e753b0f5a948e7", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/7aeef26bd8d4909bec744dbf47aee7d1395588c1", + "reference": "7aeef26bd8d4909bec744dbf47aee7d1395588c1", "shasum": "" }, "require": { @@ -5168,7 +5497,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.93.0" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.93.3" }, "funding": [ { @@ -5176,20 +5505,20 @@ "type": "github" } ], - "time": "2026-02-21T12:49:54+00:00" + "time": "2026-09-28T07:16:50+00:00" }, { "name": "spatie/laravel-permission", - "version": "7.2.2", + "version": "8.5.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-permission.git", - "reference": "4b2572eb5ef2c8cb719df09e0e6feb7431fda93f" + "reference": "31c6a4f649c6bc4703ae5546cebb7ce1a20a15e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/4b2572eb5ef2c8cb719df09e0e6feb7431fda93f", - "reference": "4b2572eb5ef2c8cb719df09e0e6feb7431fda93f", + "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/31c6a4f649c6bc4703ae5546cebb7ce1a20a15e0", + "reference": "31c6a4f649c6bc4703ae5546cebb7ce1a20a15e0", "shasum": "" }, "require": { @@ -5202,11 +5531,12 @@ }, "require-dev": { "larastan/larastan": "^3.9", + "laravel/octane": "^2.0", "laravel/passport": "^13.0", "laravel/pint": "^1.0", "orchestra/testbench": "^10.0|^11.0", - "pestphp/pest": "^3.0|^4.0", - "pestphp/pest-plugin-laravel": "^3.0|^4.1", + "pestphp/pest": "^4.7.8|^5.3.3", + "pestphp/pest-plugin-laravel": "^4.1|^5.0.1", "phpstan/phpstan": "^2.1" }, "type": "library", @@ -5217,8 +5547,8 @@ ] }, "branch-alias": { - "dev-main": "7.x-dev", - "dev-master": "7.x-dev" + "dev-main": "8.x-dev", + "dev-master": "8.x-dev" } }, "autoload": { @@ -5255,7 +5585,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-permission/issues", - "source": "https://github.com/spatie/laravel-permission/tree/7.2.2" + "source": "https://github.com/spatie/laravel-permission/tree/8.5.0" }, "funding": [ { @@ -5263,41 +5593,43 @@ "type": "github" } ], - "time": "2026-02-22T08:20:44+00:00" + "time": "2026-11-06T11:43:27+00:00" }, { - "name": "symfony/clock", - "version": "v7.4.0", + "name": "spomky-labs/cbor-php", + "version": "3.4.2", "source": { "type": "git", - "url": "https://github.com/symfony/clock.git", - "reference": "9169f24776edde469914c1e7a1442a50f7a4e110" + "url": "https://github.com/Spomky-Labs/cbor-php.git", + "reference": "8f5ea00a07ad529d20886505cdbeb2b9ac7bb2d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/clock/zipball/9169f24776edde469914c1e7a1442a50f7a4e110", - "reference": "9169f24776edde469914c1e7a1442a50f7a4e110", + "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/8f5ea00a07ad529d20886505cdbeb2b9ac7bb2d6", + "reference": "8f5ea00a07ad529d20886505cdbeb2b9ac7bb2d6", "shasum": "" }, "require": { - "php": ">=8.2", - "psr/clock": "^1.0", - "symfony/polyfill-php83": "^1.28" + "brick/math": "^0.9|^0.10|^0.11|^0.12|^0.13|^0.14|^0.15|^0.16|^0.17|^0.18|^0.19|^0.20|^1.0", + "ext-mbstring": "*", + "php": ">=8.0", + "symfony/polyfill-php81": "^1.32" }, - "provide": { - "psr/clock-implementation": "1.0" + "require-dev": { + "ext-json": "*", + "roave/security-advisories": "dev-latest", + "symfony/error-handler": "^6.4|^7.1|^8.0", + "symfony/var-dumper": "^6.4|^7.1|^8.0" + }, + "suggest": { + "ext-bcmath": "Improves the library performance when ext-gmp is missing, and is required to handle the Big Float and Decimal Fraction Tags (4 and 5)", + "ext-gmp": "Strongly recommended when decoding untrusted input: without it, converting the byte string of a Big Number Tag (2 and 3) is quadratic in its length. Also improves the library performance overall" }, "type": "library", "autoload": { - "files": [ - "Resources/now.php" - ], "psr-4": { - "Symfony\\Component\\Clock\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "CBOR\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5305,35 +5637,215 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" }, { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "All contributors", + "homepage": "https://github.com/Spomky-Labs/cbor-php/contributors" } ], - "description": "Decouples applications from the system clock", - "homepage": "https://symfony.com", + "description": "CBOR Encoder/Decoder for PHP", "keywords": [ - "clock", - "psr20", - "time" + "Concise Binary Object Representation", + "RFC7049", + "cbor" ], "support": { - "source": "https://github.com/symfony/clock/tree/v7.4.0" + "issues": "https://github.com/Spomky-Labs/cbor-php/issues", + "source": "https://github.com/Spomky-Labs/cbor-php/tree/3.4.2" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/Spomky", "type": "github" }, { - "url": "https://github.com/nicolas-grekas", + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2026-09-15T06:55:29+00:00" + }, + { + "name": "spomky-labs/pki-framework", + "version": "1.6.3", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/pki-framework.git", + "reference": "792e909d4e387adffe3c4f404451c7d57a3d2022" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/792e909d4e387adffe3c4f404451c7d57a3d2022", + "reference": "792e909d4e387adffe3c4f404451c7d57a3d2022", + "shasum": "" + }, + "require": { + "brick/math": "^0.10|^0.11|^0.12|^0.13|^0.14|^0.15|^0.16|^0.17|^0.18|^0.19|^0.20|^1.0", + "ext-mbstring": "*", + "php": ">=8.1" + }, + "require-dev": { + "ekino/phpstan-banned-code": "^1.0|^2.0|^3.0", + "ext-gmp": "*", + "ext-openssl": "*", + "infection/infection": "^0.28|^0.29|^0.31", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpstan/extension-installer": "^1.3|^2.0", + "phpstan/phpstan": "^1.8|^2.0", + "phpstan/phpstan-deprecation-rules": "^1.0|^2.0", + "phpstan/phpstan-phpunit": "^1.1|^2.0", + "phpstan/phpstan-strict-rules": "^1.3|^2.0", + "phpunit/phpunit": "^10.1|^11.0|^12.0", + "rector/rector": "^1.0|^2.0", + "roave/security-advisories": "dev-latest", + "symfony/string": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symplify/easy-coding-standard": "^12.0 || ^13.0" + }, + "suggest": { + "ext-bcmath": "For better performance (or GMP)", + "ext-gmp": "For better performance (or BCMath)", + "ext-openssl": "For OpenSSL based cyphering", + "ext-sodium": "To verify Ed25519 signatures where the OpenSSL extension has no EdDSA" + }, + "type": "library", + "autoload": { + "psr-4": { + "SpomkyLabs\\Pki\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Joni Eskelinen", + "email": "jonieske@gmail.com", + "role": "Original developer" + }, + { + "name": "Florent Morselli", + "email": "florent.morselli@spomky-labs.com", + "role": "Spomky-Labs PKI Framework developer" + } + ], + "description": "A PHP framework for managing Public Key Infrastructures. It comprises X.509 public key certificates, attribute certificates, certification requests and certification path validation.", + "homepage": "https://github.com/spomky-labs/pki-framework", + "keywords": [ + "DER", + "Private Key", + "ac", + "algorithm identifier", + "asn.1", + "asn1", + "attribute certificate", + "certificate", + "certification request", + "cryptography", + "csr", + "decrypt", + "ec", + "encrypt", + "pem", + "pkcs", + "public key", + "rsa", + "sign", + "signature", + "verify", + "x.509", + "x.690", + "x509", + "x690" + ], + "support": { + "issues": "https://github.com/Spomky-Labs/pki-framework/issues", + "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.6.3" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2026-09-12T19:02:49+00:00" + }, + { + "name": "symfony/clock", + "version": "v7.4.21", + "source": { + "type": "git", + "url": "https://github.com/symfony/clock.git", + "reference": "07437f612dca89c1894c1db04491002b1e798f03" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/clock/zipball/07437f612dca89c1894c1db04491002b1e798f03", + "reference": "07437f612dca89c1894c1db04491002b1e798f03", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/clock": "^1.0", + "symfony/polyfill-php83": "^1.28" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Decouples applications from the system clock", + "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/v7.4.21" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", "type": "github" }, { @@ -5341,20 +5853,20 @@ "type": "tidelift" } ], - "time": "2025-11-12T15:39:26+00:00" + "time": "2026-10-12T14:31:36+00:00" }, { "name": "symfony/console", - "version": "v7.4.7", + "version": "v7.4.23", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d" + "reference": "81957c2440c05c962fe8291f318de6e85f8db330" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/e1e6770440fb9c9b0cf725f81d1361ad1835329d", - "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d", + "url": "https://api.github.com/repos/symfony/console/zipball/81957c2440c05c962fe8291f318de6e85f8db330", + "reference": "81957c2440c05c962fe8291f318de6e85f8db330", "shasum": "" }, "require": { @@ -5419,7 +5931,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.7" + "source": "https://github.com/symfony/console/tree/v7.4.23" }, "funding": [ { @@ -5439,20 +5951,20 @@ "type": "tidelift" } ], - "time": "2026-03-06T14:06:20+00:00" + "time": "2026-11-03T21:47:07+00:00" }, { "name": "symfony/css-selector", - "version": "v7.4.6", + "version": "v7.4.22", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "2e7c52c647b406e2107dd867db424a4dbac91864" + "reference": "4cdbb4a575111d771791339a9d5231155918f699" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/2e7c52c647b406e2107dd867db424a4dbac91864", - "reference": "2e7c52c647b406e2107dd867db424a4dbac91864", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/4cdbb4a575111d771791339a9d5231155918f699", + "reference": "4cdbb4a575111d771791339a9d5231155918f699", "shasum": "" }, "require": { @@ -5488,7 +6000,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.4.6" + "source": "https://github.com/symfony/css-selector/tree/v7.4.22" }, "funding": [ { @@ -5508,20 +6020,20 @@ "type": "tidelift" } ], - "time": "2026-02-17T07:53:42+00:00" + "time": "2026-10-18T14:39:27+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.6.0", + "version": "v3.8.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + "reference": "feb1edf4e546af3b85c5caebe8359a20db230ca3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/feb1edf4e546af3b85c5caebe8359a20db230ca3", + "reference": "feb1edf4e546af3b85c5caebe8359a20db230ca3", "shasum": "" }, "require": { @@ -5534,7 +6046,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.8-dev" } }, "autoload": { @@ -5559,7 +6071,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.8.1" }, "funding": [ { @@ -5570,25 +6082,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-09-16T14:15:10+00:00" }, { "name": "symfony/error-handler", - "version": "v7.4.4", + "version": "v7.4.22", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8" + "reference": "545852cfe9c4979c82389155b4d8d05f8864e4f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/8da531f364ddfee53e36092a7eebbbd0b775f6b8", - "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/545852cfe9c4979c82389155b4d8d05f8864e4f1", + "reference": "545852cfe9c4979c82389155b4d8d05f8864e4f1", "shasum": "" }, "require": { @@ -5637,7 +6153,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.4.4" + "source": "https://github.com/symfony/error-handler/tree/v7.4.22" }, "funding": [ { @@ -5657,20 +6173,20 @@ "type": "tidelift" } ], - "time": "2026-01-20T16:42:42+00:00" + "time": "2026-10-25T14:40:16+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.4.4", + "version": "v7.4.17", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "dc2c0eba1af673e736bb851d747d266108aea746" + "reference": "d269974ee93c61d03620ffee358355bfdb471d66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/dc2c0eba1af673e736bb851d747d266108aea746", - "reference": "dc2c0eba1af673e736bb851d747d266108aea746", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/d269974ee93c61d03620ffee358355bfdb471d66", + "reference": "d269974ee93c61d03620ffee358355bfdb471d66", "shasum": "" }, "require": { @@ -5722,7 +6238,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.4" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.4.17" }, "funding": [ { @@ -5742,20 +6258,20 @@ "type": "tidelift" } ], - "time": "2026-01-05T11:45:34+00:00" + "time": "2026-08-21T17:40:08+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.6.0", + "version": "v3.8.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + "reference": "658e29751424da3ef4758c60f769e59d1b2981eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", - "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/658e29751424da3ef4758c60f769e59d1b2981eb", + "reference": "658e29751424da3ef4758c60f769e59d1b2981eb", "shasum": "" }, "require": { @@ -5769,7 +6285,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.8-dev" } }, "autoload": { @@ -5802,7 +6318,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.8.1" }, "funding": [ { @@ -5813,25 +6329,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-09-16T14:15:10+00:00" }, { "name": "symfony/finder", - "version": "v7.4.6", + "version": "v7.4.23", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf" + "reference": "c723f990aefe8c1053b18374faf4231eba84f518" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/8655bf1076b7a3a346cb11413ffdabff50c7ffcf", - "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf", + "url": "https://api.github.com/repos/symfony/finder/zipball/c723f990aefe8c1053b18374faf4231eba84f518", + "reference": "c723f990aefe8c1053b18374faf4231eba84f518", "shasum": "" }, "require": { @@ -5866,7 +6386,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.6" + "source": "https://github.com/symfony/finder/tree/v7.4.23" }, "funding": [ { @@ -5886,20 +6406,20 @@ "type": "tidelift" } ], - "time": "2026-01-29T09:40:50+00:00" + "time": "2026-11-02T10:56:49+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.4.7", + "version": "v7.4.23", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "f94b3e7b7dafd40e666f0c9ff2084133bae41e81" + "reference": "7fb73cffa06e2bcc2cb523fcccb1e8174106b206" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f94b3e7b7dafd40e666f0c9ff2084133bae41e81", - "reference": "f94b3e7b7dafd40e666f0c9ff2084133bae41e81", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/7fb73cffa06e2bcc2cb523fcccb1e8174106b206", + "reference": "7fb73cffa06e2bcc2cb523fcccb1e8174106b206", "shasum": "" }, "require": { @@ -5948,7 +6468,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.4.7" + "source": "https://github.com/symfony/http-foundation/tree/v7.4.23" }, "funding": [ { @@ -5968,20 +6488,20 @@ "type": "tidelift" } ], - "time": "2026-03-06T13:15:18+00:00" + "time": "2026-11-04T08:27:22+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.4.7", + "version": "v7.4.23", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "3b3fcf386c809be990c922e10e4c620d6367cab1" + "reference": "94c57f4c5c45a311d244a0aeadb72b14ee41d25d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/3b3fcf386c809be990c922e10e4c620d6367cab1", - "reference": "3b3fcf386c809be990c922e10e4c620d6367cab1", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/94c57f4c5c45a311d244a0aeadb72b14ee41d25d", + "reference": "94c57f4c5c45a311d244a0aeadb72b14ee41d25d", "shasum": "" }, "require": { @@ -6039,7 +6559,7 @@ "symfony/validator": "^6.4|^7.0|^8.0", "symfony/var-dumper": "^6.4|^7.0|^8.0", "symfony/var-exporter": "^6.4|^7.0|^8.0", - "twig/twig": "^3.12" + "twig/twig": "^3.12|^4.0" }, "type": "library", "autoload": { @@ -6067,7 +6587,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.4.7" + "source": "https://github.com/symfony/http-kernel/tree/v7.4.23" }, "funding": [ { @@ -6087,20 +6607,20 @@ "type": "tidelift" } ], - "time": "2026-03-06T16:33:18+00:00" + "time": "2026-11-04T11:26:18+00:00" }, { "name": "symfony/mailer", - "version": "v7.4.6", + "version": "v7.4.22", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9" + "reference": "1aa1e2ee7515826bb17b0ad0d2f16962fcaa7859" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/b02726f39a20bc65e30364f5c750c4ddbf1f58e9", - "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9", + "url": "https://api.github.com/repos/symfony/mailer/zipball/1aa1e2ee7515826bb17b0ad0d2f16962fcaa7859", + "reference": "1aa1e2ee7515826bb17b0ad0d2f16962fcaa7859", "shasum": "" }, "require": { @@ -6151,7 +6671,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.4.6" + "source": "https://github.com/symfony/mailer/tree/v7.4.22" }, "funding": [ { @@ -6171,20 +6691,20 @@ "type": "tidelift" } ], - "time": "2026-02-25T16:50:00+00:00" + "time": "2026-10-23T16:51:52+00:00" }, { "name": "symfony/mime", - "version": "v7.4.7", + "version": "v7.4.22", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1" + "reference": "94a4c017435bc515f5ac80421a2c555fb9ca3676" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/da5ab4fde3f6c88ab06e96185b9922f48b677cd1", - "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1", + "url": "https://api.github.com/repos/symfony/mime/zipball/94a4c017435bc515f5ac80421a2c555fb9ca3676", + "reference": "94a4c017435bc515f5ac80421a2c555fb9ca3676", "shasum": "" }, "require": { @@ -6208,7 +6728,7 @@ "symfony/process": "^6.4|^7.0|^8.0", "symfony/property-access": "^6.4|^7.0|^8.0", "symfony/property-info": "^6.4|^7.0|^8.0", - "symfony/serializer": "^6.4.3|^7.0.3|^8.0" + "symfony/serializer": "^6.4.44|^7.4.17|^8.1.5" }, "type": "library", "autoload": { @@ -6240,7 +6760,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.4.7" + "source": "https://github.com/symfony/mime/tree/v7.4.22" }, "funding": [ { @@ -6260,20 +6780,20 @@ "type": "tidelift" } ], - "time": "2026-03-05T15:24:09+00:00" + "time": "2026-10-28T17:57:58+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.4.8", + "version": "v7.4.22", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "2888fcdc4dc2fd5f7c7397be78631e8af12e02b4" + "reference": "c95dd81729944936b48f9fecb38ff524eeaf97c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/2888fcdc4dc2fd5f7c7397be78631e8af12e02b4", - "reference": "2888fcdc4dc2fd5f7c7397be78631e8af12e02b4", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/c95dd81729944936b48f9fecb38ff524eeaf97c0", + "reference": "c95dd81729944936b48f9fecb38ff524eeaf97c0", "shasum": "" }, "require": { @@ -6311,7 +6831,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.4.8" + "source": "https://github.com/symfony/options-resolver/tree/v7.4.22" }, "funding": [ { @@ -6331,20 +6851,20 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-10-18T14:39:27+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.33.0", + "version": "v1.37.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", - "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", "shasum": "" }, "require": { @@ -6394,7 +6914,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" }, "funding": [ { @@ -6414,20 +6934,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-04-10T16:19:22+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.33.0", + "version": "v1.43.3", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + "reference": "5a8f33c87aeecffce24e721b5064fab05f4becf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5a8f33c87aeecffce24e721b5064fab05f4becf8", + "reference": "5a8f33c87aeecffce24e721b5064fab05f4becf8", "shasum": "" }, "require": { @@ -6476,7 +6996,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.43.3" }, "funding": [ { @@ -6496,20 +7016,20 @@ "type": "tidelift" } ], - "time": "2025-06-27T09:58:17+00:00" + "time": "2026-10-15T09:18:53+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.33.0", + "version": "v1.43.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" + "reference": "533d68cf99c8f8ee2e8893c59ed59192e92cfd74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", - "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/533d68cf99c8f8ee2e8893c59ed59192e92cfd74", + "reference": "533d68cf99c8f8ee2e8893c59ed59192e92cfd74", "shasum": "" }, "require": { @@ -6563,7 +7083,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.43.0" }, "funding": [ { @@ -6583,20 +7103,20 @@ "type": "tidelift" } ], - "time": "2024-09-10T14:38:51+00:00" + "time": "2026-09-23T14:40:04+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.33.0", + "version": "v1.43.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" + "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/ebd57ccc7edb93d632e184efbe7693d2517bab3b", + "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b", "shasum": "" }, "require": { @@ -6648,7 +7168,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.43.0" }, "funding": [ { @@ -6668,20 +7188,20 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-09-23T22:02:09+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.33.0", + "version": "v1.43.3", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + "reference": "122889ed24dc000165bcbfd77ec412f15cbdfc6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", - "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/122889ed24dc000165bcbfd77ec412f15cbdfc6a", + "reference": "122889ed24dc000165bcbfd77ec412f15cbdfc6a", "shasum": "" }, "require": { @@ -6733,7 +7253,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.43.3" }, "funding": [ { @@ -6753,20 +7273,20 @@ "type": "tidelift" } ], - "time": "2024-12-23T08:48:59+00:00" + "time": "2026-10-28T09:35:49+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.33.0", + "version": "v1.43.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + "reference": "9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", - "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74", + "reference": "9c6a5d6b01ca51d486c813e9a9c0ed55f208bb74", "shasum": "" }, "require": { @@ -6817,7 +7337,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.43.0" }, "funding": [ { @@ -6837,20 +7357,20 @@ "type": "tidelift" } ], - "time": "2025-01-02T08:10:11+00:00" + "time": "2026-09-25T15:50:05+00:00" }, { - "name": "symfony/polyfill-php83", - "version": "v1.33.0", + "name": "symfony/polyfill-php81", + "version": "v1.38.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", - "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", + "reference": "6bfb9c766cacffbc8e118cb87217d08ed84e5cd7", "shasum": "" }, "require": { @@ -6868,7 +7388,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php83\\": "" + "Symfony\\Polyfill\\Php81\\": "" }, "classmap": [ "Resources/stubs" @@ -6888,7 +7408,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -6897,7 +7417,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.38.1" }, "funding": [ { @@ -6917,20 +7437,20 @@ "type": "tidelift" } ], - "time": "2025-07-08T02:45:35+00:00" + "time": "2026-05-26T12:45:58+00:00" }, { - "name": "symfony/polyfill-php84", - "version": "v1.33.0", + "name": "symfony/polyfill-php83", + "version": "v1.43.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "80ccff923a8d61f73ebe9da0aa94e25232f004ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", - "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/80ccff923a8d61f73ebe9da0aa94e25232f004ce", + "reference": "80ccff923a8d61f73ebe9da0aa94e25232f004ce", "shasum": "" }, "require": { @@ -6948,7 +7468,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php84\\": "" + "Symfony\\Polyfill\\Php83\\": "" }, "classmap": [ "Resources/stubs" @@ -6968,7 +7488,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -6977,7 +7497,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.43.0" }, "funding": [ { @@ -6997,20 +7517,20 @@ "type": "tidelift" } ], - "time": "2025-06-24T13:30:11+00:00" + "time": "2026-09-25T15:50:05+00:00" }, { - "name": "symfony/polyfill-php85", - "version": "v1.33.0", + "name": "symfony/polyfill-php84", + "version": "v1.43.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php85.git", - "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91" + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "22c6548499cbe5c5bb072fc0c42312dff062a015" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", - "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/22c6548499cbe5c5bb072fc0c42312dff062a015", + "reference": "22c6548499cbe5c5bb072fc0c42312dff062a015", "shasum": "" }, "require": { @@ -7028,7 +7548,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php85\\": "" + "Symfony\\Polyfill\\Php84\\": "" }, "classmap": [ "Resources/stubs" @@ -7048,7 +7568,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -7057,7 +7577,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.43.0" }, "funding": [ { @@ -7077,31 +7597,25 @@ "type": "tidelift" } ], - "time": "2025-06-23T16:12:55+00:00" + "time": "2026-09-23T22:01:09+00:00" }, { - "name": "symfony/polyfill-uuid", - "version": "v1.33.0", + "name": "symfony/polyfill-php85", + "version": "v1.43.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-uuid.git", - "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2" + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "86a42cbcc07adc331481a5ab310a4024bfc227f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2", - "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/86a42cbcc07adc331481a5ab310a4024bfc227f6", + "reference": "86a42cbcc07adc331481a5ab310a4024bfc227f6", "shasum": "" }, "require": { "php": ">=7.2" }, - "provide": { - "ext-uuid": "*" - }, - "suggest": { - "ext-uuid": "For best performance" - }, "type": "library", "extra": { "thanks": { @@ -7114,8 +7628,11 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Uuid\\": "" - } + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -7123,24 +7640,24 @@ ], "authors": [ { - "name": "Grégoire Pineau", - "email": "lyrixx@lyrixx.info" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for uuid functions", + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", "polyfill", "portable", - "uuid" + "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.33.0" + "source": "https://github.com/symfony/polyfill-php85/tree/v1.43.0" }, "funding": [ { @@ -7160,32 +7677,38 @@ "type": "tidelift" } ], - "time": "2024-09-09T11:45:10+00:00" + "time": "2026-09-25T15:46:47+00:00" }, { - "name": "symfony/process", - "version": "v7.4.5", + "name": "symfony/polyfill-time", + "version": "v1.42.0", "source": { "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "608476f4604102976d687c483ac63a79ba18cc97" + "url": "https://github.com/symfony/polyfill-time.git", + "reference": "ac9f1ed802dd58f37e720388130c088434aa09fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/608476f4604102976d687c483ac63a79ba18cc97", - "reference": "608476f4604102976d687c483ac63a79ba18cc97", + "url": "https://api.github.com/repos/symfony/polyfill-time/zipball/ac9f1ed802dd58f37e720388130c088434aa09fc", + "reference": "ac9f1ed802dd58f37e720388130c088434aa09fc", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.1" }, "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, "autoload": { "psr-4": { - "Symfony\\Component\\Process\\": "" + "Symfony\\Polyfill\\Time\\": "" }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -7194,18 +7717,30 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Ignace Philippe Nyamagana Butera", + "email": "nyamsprod@gmail.com" + }, + { + "name": "Tim Düsterhus", + "email": "tim@tideways-gmbh.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Executes commands in sub-processes", + "description": "Symfony polyfill for the Time API added to PHP 8.6", "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "duration", + "polyfill", + "portable", + "shim", + "time" + ], "support": { - "source": "https://github.com/symfony/process/tree/v7.4.5" + "source": "https://github.com/symfony/polyfill-time/tree/v1.42.0" }, "funding": [ { @@ -7225,50 +7760,45 @@ "type": "tidelift" } ], - "time": "2026-01-26T15:07:59+00:00" + "time": "2026-08-07T13:08:13+00:00" }, { - "name": "symfony/psr-http-message-bridge", - "version": "v7.4.8", + "name": "symfony/polyfill-uuid", + "version": "v1.43.0", "source": { "type": "git", - "url": "https://github.com/symfony/psr-http-message-bridge.git", - "reference": "76f1a57719a4a04c0ea18678a6c9305b5dcb9da8" + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "b23ff8245cc5c4479ea2be3d0132d2e6dd433993" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/76f1a57719a4a04c0ea18678a6c9305b5dcb9da8", - "reference": "76f1a57719a4a04c0ea18678a6c9305b5dcb9da8", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/b23ff8245cc5c4479ea2be3d0132d2e6dd433993", + "reference": "b23ff8245cc5c4479ea2be3d0132d2e6dd433993", "shasum": "" }, "require": { - "php": ">=8.2", - "psr/http-message": "^1.0|^2.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0" + "php": ">=7.2" }, - "conflict": { - "php-http/discovery": "<1.15", - "symfony/http-kernel": "<6.4" + "provide": { + "ext-uuid": "*" }, - "require-dev": { - "nyholm/psr7": "^1.1", - "php-http/discovery": "^1.15", - "psr/log": "^1.1.4|^2|^3", - "symfony/browser-kit": "^6.4|^7.0|^8.0", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/event-dispatcher": "^6.4|^7.0|^8.0", - "symfony/framework-bundle": "^6.4.13|^7.1.6|^8.0", - "symfony/http-kernel": "^6.4.13|^7.1.6|^8.0", - "symfony/runtime": "^6.4.13|^7.1.6|^8.0" + "suggest": { + "ext-uuid": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } }, - "type": "symfony-bridge", "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Bridge\\PsrHttpMessage\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Polyfill\\Uuid\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -7276,24 +7806,24 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "PSR HTTP message bridge", + "description": "Symfony polyfill for uuid functions", "homepage": "https://symfony.com", "keywords": [ - "http", - "http-message", - "psr-17", - "psr-7" + "compatibility", + "polyfill", + "portable", + "uuid" ], "support": { - "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.4.8" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.43.0" }, "funding": [ { @@ -7313,43 +7843,29 @@ "type": "tidelift" } ], - "time": "2026-03-24T13:12:05+00:00" + "time": "2026-09-24T05:20:52+00:00" }, { - "name": "symfony/routing", - "version": "v7.4.6", + "name": "symfony/process", + "version": "v7.4.22", "source": { "type": "git", - "url": "https://github.com/symfony/routing.git", - "reference": "238d749c56b804b31a9bf3e26519d93b65a60938" + "url": "https://github.com/symfony/process.git", + "reference": "a5a6912edbbfc4c98cd080bc4bbe37c8e7eeb20a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/238d749c56b804b31a9bf3e26519d93b65a60938", - "reference": "238d749c56b804b31a9bf3e26519d93b65a60938", + "url": "https://api.github.com/repos/symfony/process/zipball/a5a6912edbbfc4c98cd080bc4bbe37c8e7eeb20a", + "reference": "a5a6912edbbfc4c98cd080bc4bbe37c8e7eeb20a", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3" - }, - "conflict": { - "symfony/config": "<6.4", - "symfony/dependency-injection": "<6.4", - "symfony/yaml": "<6.4" - }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/expression-language": "^6.4|^7.0|^8.0", - "symfony/http-foundation": "^6.4|^7.0|^8.0", - "symfony/yaml": "^6.4|^7.0|^8.0" + "php": ">=8.2" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Routing\\": "" + "Symfony\\Component\\Process\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -7369,16 +7885,10 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Maps an HTTP request to a set of configuration variables", + "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", - "keywords": [ - "router", - "routing", - "uri", - "url" - ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.4.6" + "source": "https://github.com/symfony/process/tree/v7.4.22" }, "funding": [ { @@ -7398,46 +7908,37 @@ "type": "tidelift" } ], - "time": "2026-02-25T16:50:00+00:00" + "time": "2026-10-25T14:40:16+00:00" }, { - "name": "symfony/service-contracts", - "version": "v3.6.1", + "name": "symfony/property-access", + "version": "v7.4.23", "source": { "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + "url": "https://github.com/symfony/property-access.git", + "reference": "4dce45112d7a31d530d2e0e69143fc88eaa1521f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "url": "https://api.github.com/repos/symfony/property-access/zipball/4dce45112d7a31d530d2e0e69143fc88eaa1521f", + "reference": "4dce45112d7a31d530d2e0e69143fc88eaa1521f", "shasum": "" }, "require": { - "php": ">=8.1", - "psr/container": "^1.1|^2.0", - "symfony/deprecation-contracts": "^2.5|^3" + "php": ">=8.2", + "symfony/property-info": "^6.4.32|~7.3.10|^7.4.4|^8.0.4" }, - "conflict": { - "ext-psr": "<1.1|>=2" + "require-dev": { + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/var-exporter": "^6.4.1|^7.0.1|^8.0" }, "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.6-dev" - } - }, "autoload": { "psr-4": { - "Symfony\\Contracts\\Service\\": "" + "Symfony\\Component\\PropertyAccess\\": "" }, "exclude-from-classmap": [ - "/Test/" + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -7446,26 +7947,29 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to writing services", + "description": "Provides functions to read and write from/to an object or array using a simple string notation", "homepage": "https://symfony.com", "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "access", + "array", + "extraction", + "index", + "injection", + "object", + "property", + "property-path", + "reflection" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + "source": "https://github.com/symfony/property-access/tree/v7.4.23" }, "funding": [ { @@ -7485,47 +7989,46 @@ "type": "tidelift" } ], - "time": "2025-07-15T11:30:57+00:00" + "time": "2026-11-01T19:17:22+00:00" }, { - "name": "symfony/string", - "version": "v7.4.6", + "name": "symfony/property-info", + "version": "v7.4.23", "source": { "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "9f209231affa85aa930a5e46e6eb03381424b30b" + "url": "https://github.com/symfony/property-info.git", + "reference": "1eeaa666bcd1864b883b98cad9e5188064ddac26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/9f209231affa85aa930a5e46e6eb03381424b30b", - "reference": "9f209231affa85aa930a5e46e6eb03381424b30b", + "url": "https://api.github.com/repos/symfony/property-info/zipball/1eeaa666bcd1864b883b98cad9e5188064ddac26", + "reference": "1eeaa666bcd1864b883b98cad9e5188064ddac26", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.33", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0" + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0|^8.0", + "symfony/type-info": "^7.4.7|^8.0.7" }, "conflict": { - "symfony/translation-contracts": "<2.5" + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/cache": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/serializer": "<6.4" }, "require-dev": { - "symfony/emoji": "^7.1|^8.0", - "symfony/http-client": "^6.4|^7.0|^8.0", - "symfony/intl": "^6.4|^7.0|^8.0", - "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0|^8.0" + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { - "files": [ - "Resources/functions.php" - ], "psr-4": { - "Symfony\\Component\\String\\": "" + "Symfony\\Component\\PropertyInfo\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -7537,26 +8040,26 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Kévin Dunglas", + "email": "dunglas@gmail.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "description": "Extracts information about PHP class' properties using metadata of popular sources", "homepage": "https://symfony.com", "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" + "doctrine", + "phpdoc", + "property", + "symfony", + "type", + "validator" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.4.6" + "source": "https://github.com/symfony/property-info/tree/v7.4.23" }, "funding": [ { @@ -7576,64 +8079,46 @@ "type": "tidelift" } ], - "time": "2026-02-09T09:33:46+00:00" + "time": "2026-11-02T10:56:49+00:00" }, { - "name": "symfony/translation", - "version": "v7.4.6", + "name": "symfony/psr-http-message-bridge", + "version": "v7.4.21", "source": { "type": "git", - "url": "https://github.com/symfony/translation.git", - "reference": "1888cf064399868af3784b9e043240f1d89d25ce" + "url": "https://github.com/symfony/psr-http-message-bridge.git", + "reference": "b05964aa9d788bc8ffcc31c59d5c4573e3d4d1fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/1888cf064399868af3784b9e043240f1d89d25ce", - "reference": "1888cf064399868af3784b9e043240f1d89d25ce", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/b05964aa9d788bc8ffcc31c59d5c4573e3d4d1fc", + "reference": "b05964aa9d788bc8ffcc31c59d5c4573e3d4d1fc", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/translation-contracts": "^2.5.3|^3.3" + "psr/http-message": "^1.0|^2.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0" }, "conflict": { - "nikic/php-parser": "<5.0", - "symfony/config": "<6.4", - "symfony/console": "<6.4", - "symfony/dependency-injection": "<6.4", - "symfony/http-client-contracts": "<2.5", - "symfony/http-kernel": "<6.4", - "symfony/service-contracts": "<2.5", - "symfony/twig-bundle": "<6.4", - "symfony/yaml": "<6.4" - }, - "provide": { - "symfony/translation-implementation": "2.3|3.0" + "php-http/discovery": "<1.15", + "symfony/http-kernel": "<6.4" }, "require-dev": { - "nikic/php-parser": "^5.0", - "psr/log": "^1|^2|^3", + "nyholm/psr7": "^1.1", + "php-http/discovery": "^1.15", + "psr/log": "^1.1.4|^2|^3", + "symfony/browser-kit": "^6.4|^7.0|^8.0", "symfony/config": "^6.4|^7.0|^8.0", - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/dependency-injection": "^6.4|^7.0|^8.0", - "symfony/finder": "^6.4|^7.0|^8.0", - "symfony/http-client-contracts": "^2.5|^3.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/intl": "^6.4|^7.0|^8.0", - "symfony/polyfill-intl-icu": "^1.21", - "symfony/routing": "^6.4|^7.0|^8.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^6.4|^7.0|^8.0" + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/framework-bundle": "^6.4.13|^7.1.6|^8.0", + "symfony/http-kernel": "^6.4.13|^7.1.6|^8.0", + "symfony/runtime": "^6.4.13|^7.1.6|^8.0" }, - "type": "library", + "type": "symfony-bridge", "autoload": { - "files": [ - "Resources/functions.php" - ], "psr-4": { - "Symfony\\Component\\Translation\\": "" + "Symfony\\Bridge\\PsrHttpMessage\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -7653,10 +8138,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides tools to internationalize your application", + "description": "PSR HTTP message bridge", "homepage": "https://symfony.com", + "keywords": [ + "http", + "http-message", + "psr-17", + "psr-7" + ], "support": { - "source": "https://github.com/symfony/translation/tree/v7.4.6" + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.4.21" }, "funding": [ { @@ -7676,41 +8167,46 @@ "type": "tidelift" } ], - "time": "2026-02-17T07:53:42+00:00" + "time": "2026-10-07T08:20:48+00:00" }, { - "name": "symfony/translation-contracts", - "version": "v3.6.1", + "name": "symfony/routing", + "version": "v7.4.23", "source": { "type": "git", - "url": "https://github.com/symfony/translation-contracts.git", - "reference": "65a8bc82080447fae78373aa10f8d13b38338977" + "url": "https://github.com/symfony/routing.git", + "reference": "0000ade604b7bc798cb1e75edf6f17df48185f73" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", - "reference": "65a8bc82080447fae78373aa10f8d13b38338977", + "url": "https://api.github.com/repos/symfony/routing/zipball/0000ade604b7bc798cb1e75edf6f17df48185f73", + "reference": "0000ade604b7bc798cb1e75edf6f17df48185f73", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.6-dev" - } + "conflict": { + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0" }, + "type": "library", "autoload": { "psr-4": { - "Symfony\\Contracts\\Translation\\": "" + "Symfony\\Component\\Routing\\": "" }, "exclude-from-classmap": [ - "/Test/" + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -7719,26 +8215,24 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to translation", + "description": "Maps an HTTP request to a set of configuration variables", "homepage": "https://symfony.com", "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "router", + "routing", + "uri", + "url" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1" + "source": "https://github.com/symfony/routing/tree/v7.4.23" }, "funding": [ { @@ -7758,33 +8252,68 @@ "type": "tidelift" } ], - "time": "2025-07-15T13:41:35+00:00" + "time": "2026-11-04T08:27:22+00:00" }, { - "name": "symfony/uid", - "version": "v7.4.4", + "name": "symfony/serializer", + "version": "v7.4.23", "source": { "type": "git", - "url": "https://github.com/symfony/uid.git", - "reference": "7719ce8aba76be93dfe249192f1fbfa52c588e36" + "url": "https://github.com/symfony/serializer.git", + "reference": "9df8eb88c4d067d7aef1f3940075521769f34086" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/7719ce8aba76be93dfe249192f1fbfa52c588e36", - "reference": "7719ce8aba76be93dfe249192f1fbfa52c588e36", + "url": "https://api.github.com/repos/symfony/serializer/zipball/9df8eb88c4d067d7aef1f3940075521769f34086", + "reference": "9df8eb88c4d067d7aef1f3940075521769f34086", "shasum": "" }, "require": { "php": ">=8.2", - "symfony/polyfill-uuid": "^1.15" + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php84": "^1.30" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/dependency-injection": "<6.4", + "symfony/property-access": "<6.4.31|>=7.0,<7.4.2|>=8.0,<8.0.2", + "symfony/property-info": "<6.4.43", + "symfony/type-info": "<7.2.5", + "symfony/uid": "<6.4", + "symfony/validator": "<6.4", + "symfony/yaml": "<6.4" }, "require-dev": { - "symfony/console": "^6.4|^7.0|^8.0" + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "phpstan/phpdoc-parser": "^1.0|^2.0", + "seld/jsonlint": "^1.10", + "symfony/cache": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^7.2|^8.0", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/filesystem": "^6.4|^7.0|^8.0", + "symfony/form": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4.31|^7.4.2|^8.0.2", + "symfony/property-info": "^6.4.43|^7.4.15|^8.0.15", + "symfony/translation-contracts": "^2.5|^3", + "symfony/type-info": "^7.2.5|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/validator": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Uid\\": "" + "Symfony\\Component\\Serializer\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -7796,27 +8325,18 @@ ], "authors": [ { - "name": "Grégoire Pineau", - "email": "lyrixx@lyrixx.info" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Provides an object-oriented API to generate and represent UIDs", + "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", - "keywords": [ - "UID", - "ulid", - "uuid" - ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.4.4" + "source": "https://github.com/symfony/serializer/tree/v7.4.23" }, "funding": [ { @@ -7836,47 +8356,476 @@ "type": "tidelift" } ], - "time": "2026-01-03T23:30:35+00:00" + "time": "2026-11-04T08:27:22+00:00" }, { - "name": "symfony/var-dumper", - "version": "v7.4.6", + "name": "symfony/service-contracts", + "version": "v3.8.1", "source": { "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "052852a96fd5e5ebdb617dba22031e41a3300fb5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/045321c440ac18347b136c63d2e9bf28a2dc0291", - "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/052852a96fd5e5ebdb617dba22031e41a3300fb5", + "reference": "052852a96fd5e5ebdb617dba22031e41a3300fb5", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { - "symfony/console": "<6.4" + "ext-psr": "<1.1|>=2" }, - "require-dev": { - "symfony/console": "^6.4|^7.0|^8.0", - "symfony/http-kernel": "^6.4|^7.0|^8.0", - "symfony/process": "^6.4|^7.0|^8.0", - "symfony/uid": "^6.4|^7.0|^8.0", - "twig/twig": "^3.12" + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.8-dev" + } }, - "bin": [ - "Resources/bin/var-dump-server" + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.8.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } ], + "time": "2026-10-20T17:38:25+00:00" + }, + { + "name": "symfony/string", + "version": "v7.4.22", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "8dff7da3a56638f1d189c5368183294102de4ced" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/8dff7da3a56638f1d189c5368183294102de4ced", + "reference": "8dff7da3a56638f1d189c5368183294102de4ced", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.33", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0" + }, "type": "library", "autoload": { "files": [ - "Resources/functions/dump.php" + "Resources/functions.php" ], "psr-4": { - "Symfony\\Component\\VarDumper\\": "" + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.4.22" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-10-23T16:51:52+00:00" + }, + { + "name": "symfony/translation", + "version": "v7.4.23", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "00dc42a2c904e95433825c7b58fe24c08550d1c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/00dc42a2c904e95433825c7b58fe24c08550d1c8", + "reference": "00dc42a2c904e95433825c7b58fe24c08550d1c8", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.5.3|^3.3" + }, + "conflict": { + "nikic/php-parser": "<5.0", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<6.4", + "symfony/yaml": "<6.4" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/intl": "^6.4|^7.0|^8.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v7.4.23" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-11-01T22:26:51+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.8.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "0a3ae917402632fcba2aeddae9c98332f3d7dd05" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/0a3ae917402632fcba2aeddae9c98332f3d7dd05", + "reference": "0a3ae917402632fcba2aeddae9c98332f3d7dd05", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.8.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-10-03T08:17:19+00:00" + }, + { + "name": "symfony/type-info", + "version": "v7.4.23", + "source": { + "type": "git", + "url": "https://github.com/symfony/type-info.git", + "reference": "eca5fdb294539268ad3245b2214f32f24b337e09" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/type-info/zipball/eca5fdb294539268ad3245b2214f32f24b337e09", + "reference": "eca5fdb294539268ad3245b2214f32f24b337e09", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.30" + }, + "require-dev": { + "phpstan/phpdoc-parser": "^1.30|^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\TypeInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mathias Arlaud", + "email": "mathias.arlaud@gmail.com" + }, + { + "name": "Baptiste LEDUC", + "email": "baptiste.leduc@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts PHP types information.", + "homepage": "https://symfony.com", + "keywords": [ + "PHPStan", + "phpdoc", + "symfony", + "type" + ], + "support": { + "source": "https://github.com/symfony/type-info/tree/v7.4.23" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-11-02T09:31:16+00:00" + }, + { + "name": "symfony/uid", + "version": "v7.4.21", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "3e706a245b5343ea93be9cafe84525bfb54e60fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/3e706a245b5343ea93be9cafe84525bfb54e60fc", + "reference": "3e706a245b5343ea93be9cafe84525bfb54e60fc", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Uid\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -7887,6 +8836,10 @@ "MIT" ], "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -7896,14 +8849,15 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "description": "Provides an object-oriented API to generate and represent UIDs", "homepage": "https://symfony.com", "keywords": [ - "debug", - "dump" + "UID", + "ulid", + "uuid" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.4.6" + "source": "https://github.com/symfony/uid/tree/v7.4.21" }, "funding": [ { @@ -7923,150 +8877,94 @@ "type": "tidelift" } ], - "time": "2026-02-15T10:53:20+00:00" + "time": "2026-10-12T14:31:36+00:00" }, { - "name": "thecodingmachine/safe", - "version": "v3.4.0", + "name": "symfony/var-dumper", + "version": "v7.4.22", "source": { "type": "git", - "url": "https://github.com/thecodingmachine/safe.git", - "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19" + "url": "https://github.com/symfony/var-dumper.git", + "reference": "6e6ff33ea8cf5df27f00a5561ebd5ee9bd7f1618" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19", - "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6e6ff33ea8cf5df27f00a5561ebd5ee9bd7f1618", + "reference": "6e6ff33ea8cf5df27f00a5561ebd5ee9bd7f1618", "shasum": "" }, "require": { - "php": "^8.1" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<6.4" }, "require-dev": { - "php-parallel-lint/php-parallel-lint": "^1.4", - "phpstan/phpstan": "^2", - "phpunit/phpunit": "^10", - "squizlabs/php_codesniffer": "^3.2" + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", + "twig/twig": "^3.12|^4.0" }, + "bin": [ + "Resources/bin/var-dump-server" + ], "type": "library", "autoload": { "files": [ - "lib/special_cases.php", - "generated/apache.php", - "generated/apcu.php", - "generated/array.php", - "generated/bzip2.php", - "generated/calendar.php", - "generated/classobj.php", - "generated/com.php", - "generated/cubrid.php", - "generated/curl.php", - "generated/datetime.php", - "generated/dir.php", - "generated/eio.php", - "generated/errorfunc.php", - "generated/exec.php", - "generated/fileinfo.php", - "generated/filesystem.php", - "generated/filter.php", - "generated/fpm.php", - "generated/ftp.php", - "generated/funchand.php", - "generated/gettext.php", - "generated/gmp.php", - "generated/gnupg.php", - "generated/hash.php", - "generated/ibase.php", - "generated/ibmDb2.php", - "generated/iconv.php", - "generated/image.php", - "generated/imap.php", - "generated/info.php", - "generated/inotify.php", - "generated/json.php", - "generated/ldap.php", - "generated/libxml.php", - "generated/lzf.php", - "generated/mailparse.php", - "generated/mbstring.php", - "generated/misc.php", - "generated/mysql.php", - "generated/mysqli.php", - "generated/network.php", - "generated/oci8.php", - "generated/opcache.php", - "generated/openssl.php", - "generated/outcontrol.php", - "generated/pcntl.php", - "generated/pcre.php", - "generated/pgsql.php", - "generated/posix.php", - "generated/ps.php", - "generated/pspell.php", - "generated/readline.php", - "generated/rnp.php", - "generated/rpminfo.php", - "generated/rrd.php", - "generated/sem.php", - "generated/session.php", - "generated/shmop.php", - "generated/sockets.php", - "generated/sodium.php", - "generated/solr.php", - "generated/spl.php", - "generated/sqlsrv.php", - "generated/ssdeep.php", - "generated/ssh2.php", - "generated/stream.php", - "generated/strings.php", - "generated/swoole.php", - "generated/uodbc.php", - "generated/uopz.php", - "generated/url.php", - "generated/var.php", - "generated/xdiff.php", - "generated/xml.php", - "generated/xmlrpc.php", - "generated/yaml.php", - "generated/yaz.php", - "generated/zip.php", - "generated/zlib.php" + "Resources/functions/dump.php" ], - "classmap": [ - "lib/DateTime.php", - "lib/DateTimeImmutable.php", - "lib/Exceptions/", - "generated/Exceptions/" + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "PHP core functions that throw exceptions instead of returning FALSE on error", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], "support": { - "issues": "https://github.com/thecodingmachine/safe/issues", - "source": "https://github.com/thecodingmachine/safe/tree/v3.4.0" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.22" }, "funding": [ { - "url": "https://github.com/OskarStark", - "type": "github" + "url": "https://symfony.com/sponsor", + "type": "custom" }, { - "url": "https://github.com/shish", + "url": "https://github.com/fabpot", "type": "github" }, { - "url": "https://github.com/silasjoisten", + "url": "https://github.com/nicolas-grekas", "type": "github" }, { - "url": "https://github.com/staabm", - "type": "github" + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } ], - "time": "2026-02-04T18:08:13+00:00" + "time": "2026-10-25T21:04:04+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -8115,125 +9013,371 @@ "role": "Developer" } ], - "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.", - "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", - "support": { - "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", - "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.4.0" - }, - "time": "2025-12-02T11:56:42+00:00" + "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.", + "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", + "support": { + "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.4.0" + }, + "time": "2025-12-02T11:56:42+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.8.0", + "source": { + "type": "git", + "url": "https://github.com/phpdotenv/phpdotenv.git", + "reference": "96149103e62ac67e915f6d2ef59b72dee2e011e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpdotenv/phpdotenv/zipball/96149103e62ac67e915f6d2ef59b72dee2e011e0", + "reference": "96149103e62ac67e915f6d2ef59b72dee2e011e0", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.2", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.10", + "symfony/polyfill-ctype": "^1.26", + "symfony/polyfill-mbstring": "^1.26", + "symfony/polyfill-php80": "^1.26" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "5.8-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `$_ENV` and `$_SERVER` automagically, and optionally to `getenv()`.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/phpdotenv/phpdotenv/issues", + "source": "https://github.com/phpdotenv/phpdotenv/tree/v5.8.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2026-10-25T16:26:19+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "8e1051fe39379367aecf014f41744ce7539a856f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/8e1051fe39379367aecf014f41744ce7539a856f", + "reference": "8e1051fe39379367aecf014f41744ce7539a856f", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpunit/phpunit": "~8.5 || ~9.6 || ~10.5 || ~11.5" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "https://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "support": { + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/2.1.1" + }, + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2026-04-26T05:33:54+00:00" + }, + { + "name": "web-auth/cose-lib", + "version": "4.8.2", + "source": { + "type": "git", + "url": "https://github.com/web-auth/cose-lib.git", + "reference": "8849e8bf043a2d42d0bec5bda5db2469ad376148" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/web-auth/cose-lib/zipball/8849e8bf043a2d42d0bec5bda5db2469ad376148", + "reference": "8849e8bf043a2d42d0bec5bda5db2469ad376148", + "shasum": "" + }, + "require": { + "brick/math": "^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14 || ^0.15 || ^0.16 || ^0.17 || ^0.18 || ^0.19 || ^0.20 || ^1.0", + "ext-json": "*", + "ext-openssl": "*", + "php": ">=8.1", + "spomky-labs/pki-framework": "^1.0" + }, + "conflict": { + "spomky-labs/cbor-php": "<3.4.0" + }, + "require-dev": { + "spomky-labs/cbor-php": "^3.4" + }, + "suggest": { + "ext-bcmath": "Recommended: without GMP or BCMath, signing with RSASSA-PSS (PS256/PS384/PS512) blinds its private exponentiation in pure PHP", + "ext-gmp": "Recommended: without GMP or BCMath, signing with RSASSA-PSS (PS256/PS384/PS512) blinds its private exponentiation in pure PHP", + "ext-sodium": "Required by the EdDSA/Ed25519 signature algorithms (-8, -19, -260, -261) and to recompute an OKP public key from its private key", + "spomky-labs/cbor-php": "Required by the RFC 9052 header reader and cryptographic structures. 3.4.0 or later: it ships the six COSE message classes (CBOR\\Tag\\CoseSign1Tag and its siblings) that replace the deprecated Cose\\...Tag classes, and its decoder is what enforces the RFC 9052 label uniqueness and nesting bounds this library relies on" + }, + "type": "library", + "autoload": { + "psr-4": { + "Cose\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" + }, + { + "name": "All contributors", + "homepage": "https://github.com/web-auth/cose/contributors" + } + ], + "description": "CBOR Object Signing and Encryption (COSE) For PHP", + "homepage": "https://github.com/web-auth", + "keywords": [ + "COSE", + "RFC8152" + ], + "support": { + "issues": "https://github.com/web-auth/cose-lib/issues", + "source": "https://github.com/web-auth/cose-lib/tree/4.8.2" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2026-09-15T17:30:33+00:00" }, { - "name": "vlucas/phpdotenv", - "version": "v5.6.3", + "name": "web-auth/webauthn-lib", + "version": "5.3.12", "source": { "type": "git", - "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "955e7815d677a3eaa7075231212f2110983adecc" + "url": "https://github.com/web-auth/webauthn-lib.git", + "reference": "c7aeeb1d35e39b7d32ab5146cce4b668f271a68e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/955e7815d677a3eaa7075231212f2110983adecc", - "reference": "955e7815d677a3eaa7075231212f2110983adecc", + "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/c7aeeb1d35e39b7d32ab5146cce4b668f271a68e", + "reference": "c7aeeb1d35e39b7d32ab5146cce4b668f271a68e", "shasum": "" }, "require": { - "ext-pcre": "*", - "graham-campbell/result-type": "^1.1.4", - "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9.5", - "symfony/polyfill-ctype": "^1.26", - "symfony/polyfill-mbstring": "^1.26", - "symfony/polyfill-php80": "^1.26" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.2", - "ext-filter": "*", - "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + "ext-json": "*", + "ext-openssl": "*", + "paragonie/constant_time_encoding": "^2.6|^3.0", + "php": ">=8.2", + "phpdocumentor/reflection-docblock": "^5.3|^6.0", + "psr/clock": "^1.0", + "psr/event-dispatcher": "^1.0", + "psr/log": "^1.0|^2.0|^3.0", + "spomky-labs/cbor-php": "^3.4", + "spomky-labs/pki-framework": "^1.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/deprecation-contracts": "^3.2", + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", + "web-auth/cose-lib": "^4.8" }, "suggest": { - "ext-filter": "Required to use the boolean validator." + "psr/log-implementation": "Recommended to receive logs from the library", + "symfony/event-dispatcher": "Recommended to use dispatched events", + "web-token/jwt-library": "Mandatory for fetching Metadata Statement from distant sources" }, "type": "library", "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - }, - "branch-alias": { - "dev-master": "5.6-dev" + "thanks": { + "url": "https://github.com/web-auth/webauthn-framework", + "name": "web-auth/webauthn-framework" } }, "autoload": { "psr-4": { - "Dotenv\\": "src/" + "Webauthn\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" + "name": "Florent Morselli", + "homepage": "https://github.com/Spomky" }, { - "name": "Vance Lucas", - "email": "vance@vancelucas.com", - "homepage": "https://github.com/vlucas" + "name": "All contributors", + "homepage": "https://github.com/web-auth/webauthn-library/contributors" } ], - "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "description": "FIDO2/Webauthn Support For PHP", + "homepage": "https://github.com/web-auth", "keywords": [ - "dotenv", - "env", - "environment" + "FIDO2", + "fido", + "webauthn" ], "support": { - "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.3" + "source": "https://github.com/web-auth/webauthn-lib/tree/5.3.12" }, "funding": [ { - "url": "https://github.com/GrahamCampbell", + "url": "https://github.com/Spomky", "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", - "type": "tidelift" + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" } ], - "time": "2025-12-27T19:49:13+00:00" + "time": "2026-11-03T09:21:08+00:00" }, { - "name": "voku/portable-ascii", - "version": "2.0.3", + "name": "webmozart/assert", + "version": "2.5.0", "source": { "type": "git", - "url": "https://github.com/voku/portable-ascii.git", - "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d" + "url": "https://github.com/webmozarts/assert.git", + "reference": "8a163706bbcac613733dba03f51e13b5eb1bb819" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", - "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/8a163706bbcac613733dba03f51e13b5eb1bb819", + "reference": "8a163706bbcac613733dba03f51e13b5eb1bb819", "shasum": "" }, "require": { - "php": ">=7.0.0" - }, - "require-dev": { - "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", + "php": "^8.2" }, "suggest": { - "ext-intl": "Use Intl for transliterator_transliterate() support" + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" }, "type": "library", + "extra": { + "psalm": { + "pluginClass": "Webmozart\\Assert\\PsalmPlugin" + }, + "branch-alias": { + "dev-master": "2.0-dev", + "dev-feature/2-0": "2.0-dev" + } + }, "autoload": { "psr-4": { - "voku\\": "src/voku/" + "Webmozart\\Assert\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -8242,44 +9386,25 @@ ], "authors": [ { - "name": "Lars Moelleken", - "homepage": "https://www.moelleken.org/" + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com" } ], - "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", - "homepage": "https://github.com/voku/portable-ascii", + "description": "Assertions to validate method input/output with nice error messages.", "keywords": [ - "ascii", - "clean", - "php" + "assert", + "check", + "validate" ], "support": { - "issues": "https://github.com/voku/portable-ascii/issues", - "source": "https://github.com/voku/portable-ascii/tree/2.0.3" + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/2.5.0" }, - "funding": [ - { - "url": "https://www.paypal.me/moelleken", - "type": "custom" - }, - { - "url": "https://github.com/voku", - "type": "github" - }, - { - "url": "https://opencollective.com/portable-ascii", - "type": "open_collective" - }, - { - "url": "https://www.patreon.com/voku", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", - "type": "tidelift" - } - ], - "time": "2024-11-21T01:49:47+00:00" + "time": "2026-10-05T23:14:19+00:00" } ], "packages-dev": [ @@ -8348,16 +9473,16 @@ }, { "name": "filp/whoops", - "version": "2.18.4", + "version": "2.19.3", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d" + "reference": "d5480d53f10069dc4620a39f87e38e8945d47322" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/d2102955e48b9fd9ab24280a7ad12ed552752c4d", - "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d", + "url": "https://api.github.com/repos/filp/whoops/zipball/d5480d53f10069dc4620a39f87e38e8945d47322", + "reference": "d5480d53f10069dc4620a39f87e38e8945d47322", "shasum": "" }, "require": { @@ -8366,8 +9491,8 @@ }, "require-dev": { "mockery/mockery": "^1.0", - "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^4.0 || ^5.0" + "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3 || ^10.5.58", + "symfony/var-dumper": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0" }, "suggest": { "symfony/var-dumper": "Pretty print complex values better with var-dumper available", @@ -8407,7 +9532,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.18.4" + "source": "https://github.com/filp/whoops/tree/2.19.3" }, "funding": [ { @@ -8415,23 +9540,25 @@ "type": "github" } ], - "time": "2025-08-08T12:00:00+00:00" + "time": "2026-11-09T23:30:17+00:00" }, { "name": "hamcrest/hamcrest-php", - "version": "v2.1.1", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/hamcrest/hamcrest-php.git", - "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" + "reference": "b61cd040da1a4925bc90a51c074f5297e7c0fa52" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", - "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/b61cd040da1a4925bc90a51c074f5297e7c0fa52", + "reference": "b61cd040da1a4925bc90a51c074f5297e7c0fa52", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-dom": "*", "php": "^7.4|^8.0" }, "replace": { @@ -8440,13 +9567,15 @@ "kodova/hamcrest-php": "*" }, "require-dev": { + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -8464,9 +9593,139 @@ ], "support": { "issues": "https://github.com/hamcrest/hamcrest-php/issues", - "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" + "source": "https://github.com/hamcrest/hamcrest-php/tree/v3.0.0" + }, + "time": "2026-03-17T11:56:53+00:00" + }, + { + "name": "iamcal/sql-parser", + "version": "v0.7", + "source": { + "type": "git", + "url": "https://github.com/iamcal/SQLParser.git", + "reference": "610392f38de49a44dab08dc1659960a29874c4b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/iamcal/SQLParser/zipball/610392f38de49a44dab08dc1659960a29874c4b8", + "reference": "610392f38de49a44dab08dc1659960a29874c4b8", + "shasum": "" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^1.0", + "phpunit/phpunit": "^5|^6|^7|^8|^9" + }, + "type": "library", + "autoload": { + "psr-4": { + "iamcal\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Cal Henderson", + "email": "cal@iamcal.com" + } + ], + "description": "MySQL schema parser", + "support": { + "issues": "https://github.com/iamcal/SQLParser/issues", + "source": "https://github.com/iamcal/SQLParser/tree/v0.7" + }, + "time": "2026-01-28T22:20:33+00:00" + }, + { + "name": "larastan/larastan", + "version": "v3.15.0", + "source": { + "type": "git", + "url": "https://github.com/larastan/larastan.git", + "reference": "ffb29680700b7e56000183ddacd68fab991f690b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/larastan/larastan/zipball/ffb29680700b7e56000183ddacd68fab991f690b", + "reference": "ffb29680700b7e56000183ddacd68fab991f690b", + "shasum": "" + }, + "require": { + "iamcal/sql-parser": "^0.7.0", + "illuminate/console": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/container": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/contracts": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/database": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/http": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/pipeline": "^11.44.2 || ^12.4.1 || ^13", + "illuminate/support": "^11.44.2 || ^12.4.1 || ^13", + "php": "^8.2", + "phpstan/phpstan": "^2.3.5" + }, + "require-dev": { + "doctrine/coding-standard": "^14", + "laravel/framework": "^11.44.2 || ^12.7.2 || ^13", + "mockery/mockery": "^1.6.12", + "nikic/php-parser": "^5.6", + "orchestra/canvas": "^v9.2.2 || ^10.0.1 || ^11", + "orchestra/testbench-core": "^9.12.0 || ^10.1 || ^11", + "phpstan/phpstan-deprecation-rules": "^2.0.1", + "phpunit/phpunit": "^10.5.35 || ^11.5.15 || ^12.5.8 || ^13.3.0" + }, + "suggest": { + "orchestra/testbench": "Using Larastan for analysing a package needs Testbench", + "phpmyadmin/sql-parser": "Install to enable Larastan's optional phpMyAdmin-based SQL parser automatically" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-master": "3.0-dev" + } }, - "time": "2025-04-30T06:54:44+00:00" + "autoload": { + "psr-4": { + "Larastan\\Larastan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Can Vural", + "email": "can9119@gmail.com" + } + ], + "description": "Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel", + "keywords": [ + "PHPStan", + "code analyse", + "code analysis", + "larastan", + "laravel", + "package", + "php", + "static analysis" + ], + "support": { + "issues": "https://github.com/larastan/larastan/issues", + "source": "https://github.com/larastan/larastan/tree/v3.15.0" + }, + "funding": [ + { + "url": "https://github.com/canvural", + "type": "github" + } + ], + "time": "2026-11-11T15:06:29+00:00" }, { "name": "laravel/boost", @@ -8609,38 +9868,38 @@ }, { "name": "laravel/pail", - "version": "v1.2.6", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/laravel/pail.git", - "reference": "aa71a01c309e7f66bc2ec4fb1a59291b82eb4abf" + "reference": "72d615ff1104717c1317c6d5d7eab679e43a0408" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pail/zipball/aa71a01c309e7f66bc2ec4fb1a59291b82eb4abf", - "reference": "aa71a01c309e7f66bc2ec4fb1a59291b82eb4abf", + "url": "https://api.github.com/repos/laravel/pail/zipball/72d615ff1104717c1317c6d5d7eab679e43a0408", + "reference": "72d615ff1104717c1317c6d5d7eab679e43a0408", "shasum": "" }, "require": { "ext-mbstring": "*", - "illuminate/console": "^10.24|^11.0|^12.0|^13.0", - "illuminate/contracts": "^10.24|^11.0|^12.0|^13.0", - "illuminate/log": "^10.24|^11.0|^12.0|^13.0", - "illuminate/process": "^10.24|^11.0|^12.0|^13.0", - "illuminate/support": "^10.24|^11.0|^12.0|^13.0", - "nunomaduro/termwind": "^1.15|^2.0", + "illuminate/console": "^12.0|^13.0", + "illuminate/contracts": "^12.0|^13.0", + "illuminate/log": "^12.0|^13.0", + "illuminate/process": "^12.0|^13.0", + "illuminate/support": "^12.0|^13.0", + "nunomaduro/termwind": "^2.0", "php": "^8.2", - "symfony/console": "^6.0|^7.0|^8.0" + "symfony/console": "^7.0|^8.0" }, "require-dev": { - "laravel/framework": "^10.24|^11.0|^12.0|^13.0", + "laravel/framework": "^12.69|^13.30", "laravel/pint": "^1.13", - "orchestra/testbench-core": "^8.13|^9.17|^10.8|^11.0", - "pestphp/pest": "^2.20|^3.0|^4.0", - "pestphp/pest-plugin-type-coverage": "^2.3|^3.0|^4.0", + "orchestra/testbench-core": "^10.8|^11.0", + "pestphp/pest": "^3.0|^4.0", + "pestphp/pest-plugin-type-coverage": "^3.0|^4.0", "phpstan/phpstan": "^1.12.27", - "symfony/var-dumper": "^6.3|^7.0|^8.0", - "symfony/yaml": "^6.3|^7.0|^8.0" + "symfony/var-dumper": "^7.0|^8.0", + "symfony/yaml": "^7.0|^8.0" }, "type": "library", "extra": { @@ -8685,20 +9944,20 @@ "issues": "https://github.com/laravel/pail/issues", "source": "https://github.com/laravel/pail" }, - "time": "2026-02-09T13:44:54+00:00" + "time": "2026-11-09T14:56:19+00:00" }, { "name": "laravel/pint", - "version": "v1.29.0", + "version": "v1.32.3", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "bdec963f53172c5e36330f3a400604c69bf02d39" + "reference": "1cf8e0320bed4b3c6e51df92039a5636c3887728" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/bdec963f53172c5e36330f3a400604c69bf02d39", - "reference": "bdec963f53172c5e36330f3a400604c69bf02d39", + "url": "https://api.github.com/repos/laravel/pint/zipball/1cf8e0320bed4b3c6e51df92039a5636c3887728", + "reference": "1cf8e0320bed4b3c6e51df92039a5636c3887728", "shasum": "" }, "require": { @@ -8706,17 +9965,19 @@ "ext-mbstring": "*", "ext-tokenizer": "*", "ext-xml": "*", - "php": "^8.2.0" + "php": "^8.3.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.94.2", - "illuminate/view": "^12.54.1", - "larastan/larastan": "^3.9.3", - "laravel-zero/framework": "^12.0.5", - "mockery/mockery": "^1.6.12", - "nunomaduro/termwind": "^2.4.0", - "pestphp/pest": "^3.8.6", - "shipfastlabs/agent-detector": "^1.1.0" + "composer/semver": "^3.6.0", + "friendsofphp/php-cs-fixer": "^3.95.30", + "illuminate/view": "^13.42.0", + "larastan/larastan": "^3.15.0", + "laravel-zero/framework": "^13.0.0", + "laravel/agent-detector": "^2.0.5", + "laravel/prompts": "^0.3.27", + "mockery/mockery": "^1.6.15", + "nunomaduro/termwind": "^2.4.1", + "pestphp/pest": "^4.7.8" }, "bin": [ "builds/pint" @@ -8753,7 +10014,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2026-03-12T15:51:39+00:00" + "time": "2026-11-11T16:45:13+00:00" }, { "name": "laravel/roster", @@ -8818,16 +10079,16 @@ }, { "name": "laravel/sail", - "version": "v1.53.0", + "version": "v1.70.0", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "e340eaa2bea9b99192570c48ed837155dbf24fbb" + "reference": "56c1c94117ef12ee707d50433273228ebee7138e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/e340eaa2bea9b99192570c48ed837155dbf24fbb", - "reference": "e340eaa2bea9b99192570c48ed837155dbf24fbb", + "url": "https://api.github.com/repos/laravel/sail/zipball/56c1c94117ef12ee707d50433273228ebee7138e", + "reference": "56c1c94117ef12ee707d50433273228ebee7138e", "shasum": "" }, "require": { @@ -8877,33 +10138,32 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2026-02-06T12:16:02+00:00" + "time": "2026-10-15T08:53:46+00:00" }, { "name": "mockery/mockery", - "version": "1.6.12", + "version": "1.6.15", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" + "reference": "967a801bd188989a5669bd280f252d51c0fdc9ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", - "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "url": "https://api.github.com/repos/mockery/mockery/zipball/967a801bd188989a5669bd280f252d51c0fdc9ee", + "reference": "967a801bd188989a5669bd280f252d51c0fdc9ee", "shasum": "" }, "require": { - "hamcrest/hamcrest-php": "^2.0.1", - "lib-pcre": ">=7.0", + "hamcrest/hamcrest-php": "^2.0 || ^3.0", "php": ">=7.3" }, "conflict": { "phpunit/phpunit": "<8.0" }, "require-dev": { - "phpunit/phpunit": "^8.5 || ^9.6.17", - "symplify/easy-coding-standard": "^12.1.14" + "phpunit/phpunit": "^9.6.36", + "symplify/easy-coding-standard": "^13.2.17" }, "type": "library", "autoload": { @@ -8960,24 +10220,24 @@ "security": "https://github.com/mockery/mockery/security/advisories", "source": "https://github.com/mockery/mockery" }, - "time": "2024-05-16T03:13:13+00:00" + "time": "2026-08-19T19:37:52+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.13.4", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", - "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", + "reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.0" }, "conflict": { "doctrine/collections": "<1.6.8", @@ -9012,48 +10272,48 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + "source": "https://github.com/myclabs/DeepCopy/tree/1.14.0" }, "funding": [ { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" + "url": "https://github.com/mnapoli", + "type": "github" } ], - "time": "2025-08-01T08:46:24+00:00" + "time": "2026-08-11T10:17:44+00:00" }, { "name": "nunomaduro/collision", - "version": "v8.9.1", + "version": "v8.9.6", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "a1ed3fa530fd60bc515f9303e8520fcb7d4bd935" + "reference": "68806f9257968f23775bee62970cb90925b79d51" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/a1ed3fa530fd60bc515f9303e8520fcb7d4bd935", - "reference": "a1ed3fa530fd60bc515f9303e8520fcb7d4bd935", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/68806f9257968f23775bee62970cb90925b79d51", + "reference": "68806f9257968f23775bee62970cb90925b79d51", "shasum": "" }, "require": { - "filp/whoops": "^2.18.4", - "nunomaduro/termwind": "^2.4.0", + "filp/whoops": "^2.19.3", + "nunomaduro/termwind": "^2.4.1", "php": "^8.2.0", - "symfony/console": "^7.4.4 || ^8.0.4" + "symfony/console": "^7.4.23 || ^8.1.11" }, "conflict": { "laravel/framework": "<11.48.0 || >=14.0.0", "phpunit/phpunit": "<11.5.50 || >=14.0.0" }, "require-dev": { - "brianium/paratest": "^7.8.5", - "larastan/larastan": "^3.9.2", - "laravel/framework": "^11.48.0 || ^12.52.0", - "laravel/pint": "^1.27.1", - "orchestra/testbench-core": "^9.12.0 || ^10.9.0", - "pestphp/pest": "^3.8.5 || ^4.4.1 || ^5.0.0", - "sebastian/environment": "^7.2.1 || ^8.0.3 || ^9.0.0" + "brianium/paratest": "^7.8.6", + "larastan/larastan": "^3.14.0", + "laravel/framework": "^11.57.1 || ^12.72.0 || ^13.40.0", + "laravel/pint": "^1.30.4", + "orchestra/testbench-core": "^9.24.0 || ^10.16.0 || ^11.6.0", + "pestphp/pest": "^3.8.7 || ^4.7.8 || ^5.3.5", + "sebastian/environment": "^7.2.1 || ^8.1.2 || ^9.3.3" }, "type": "library", "extra": { @@ -9116,7 +10376,7 @@ "type": "patreon" } ], - "time": "2026-02-17T17:33:08+00:00" + "time": "2026-11-10T17:06:52+00:00" }, { "name": "phar-io/manifest", @@ -9236,6 +10496,70 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "phpstan/phpstan", + "version": "2.3.5", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/645a1bd6bead15fc1b8474137467961d0fa30a79", + "reference": "645a1bd6bead15fc1b8474137467961d0fa30a79", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ondřej Mirtes" + }, + { + "name": "Markus Staab" + }, + { + "name": "Vincent Langlet" + } + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2026-11-03T12:14:22+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "11.0.12", @@ -9585,26 +10909,26 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.55", + "version": "11.5.58", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00" + "reference": "c14b3145a2fcfa5dd9b25ee999ce2ba5933adf6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/adc7262fccc12de2b30f12a8aa0b33775d814f00", - "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c14b3145a2fcfa5dd9b25ee999ce2ba5933adf6f", + "reference": "c14b3145a2fcfa5dd9b25ee999ce2ba5933adf6f", "shasum": "" }, "require": { "ext-dom": "*", + "ext-filter": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", - "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.4", + "myclabs/deep-copy": "^1.14.0", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", @@ -9667,31 +10991,15 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.55" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.58" }, "funding": [ { - "url": "https://phpunit.de/sponsors.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", - "type": "tidelift" + "url": "https://phpunit.de/sponsoring.html", + "type": "other" } ], - "time": "2026-02-18T12:37:06+00:00" + "time": "2026-11-13T11:08:08+00:00" }, { "name": "sebastian/cli-parser", @@ -10733,16 +12041,16 @@ }, { "name": "symfony/yaml", - "version": "v7.4.6", + "version": "v7.4.23", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "58751048de17bae71c5aa0d13cb19d79bca26391" + "reference": "1e601207ffe627a2e621faa9d661f1d32dcf1cec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/58751048de17bae71c5aa0d13cb19d79bca26391", - "reference": "58751048de17bae71c5aa0d13cb19d79bca26391", + "url": "https://api.github.com/repos/symfony/yaml/zipball/1e601207ffe627a2e621faa9d661f1d32dcf1cec", + "reference": "1e601207ffe627a2e621faa9d661f1d32dcf1cec", "shasum": "" }, "require": { @@ -10785,7 +12093,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.4.6" + "source": "https://github.com/symfony/yaml/tree/v7.4.23" }, "funding": [ { @@ -10805,7 +12113,7 @@ "type": "tidelift" } ], - "time": "2026-02-09T09:33:46+00:00" + "time": "2026-11-04T08:27:22+00:00" }, { "name": "theseer/tokenizer", diff --git a/config/app.php b/config/app.php index ccc7d996..9c8abddc 100644 --- a/config/app.php +++ b/config/app.php @@ -81,9 +81,9 @@ | */ - 'locale' => env('APP_LOCALE', 'en'), + 'locale' => env('APP_LOCALE', 'fr'), - 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), + 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'fr'), 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'), diff --git a/database/migrations/2026_02_16_150000_add_unique_indexes_to_matricules.php b/database/migrations/2026_02_16_150000_add_unique_indexes_to_matricules.php index 60ef1dc7..53d5760b 100644 --- a/database/migrations/2026_02_16_150000_add_unique_indexes_to_matricules.php +++ b/database/migrations/2026_02_16_150000_add_unique_indexes_to_matricules.php @@ -16,7 +16,7 @@ public function up(): void Schema::table('users', function (Blueprint $table) { // Check if index doesn't already exist if (! Schema::hasIndex('users', 'users_natricule_unique')) { - $table->unique('natricule')->index(); + $table->unique('natricule'); } }); } @@ -25,7 +25,7 @@ public function up(): void if (Schema::hasTable('students') && Schema::hasColumn('students', 'registration_number')) { Schema::table('students', function (Blueprint $table) { if (! Schema::hasIndex('students', 'students_registration_number_unique')) { - $table->unique('registration_number')->index(); + $table->unique('registration_number'); } }); } diff --git a/database/migrations/2026_06_14_000011_add_locked_at_to_evaluations_table.php b/database/migrations/2026_06_14_000011_add_locked_at_to_evaluations_table.php index d8bbe6ec..b1000e37 100644 --- a/database/migrations/2026_06_14_000011_add_locked_at_to_evaluations_table.php +++ b/database/migrations/2026_06_14_000011_add_locked_at_to_evaluations_table.php @@ -10,7 +10,7 @@ public function up(): void { Schema::table('evaluations', function (Blueprint $table) { $table->timestamp('locked_at')->nullable()->after('status'); - $table->foreignUuid('locked_by')->nullable()->constrained('users')->nullOnDelete()->after('locked_at'); + $table->foreignUuid('locked_by')->nullable()->after('locked_at')->constrained('users')->nullOnDelete(); }); } diff --git a/database/migrations/2026_10_21_000001_add_locale_to_users_table.php b/database/migrations/2026_10_21_000001_add_locale_to_users_table.php new file mode 100644 index 00000000..7700f758 --- /dev/null +++ b/database/migrations/2026_10_21_000001_add_locale_to_users_table.php @@ -0,0 +1,23 @@ +string('locale', 5)->nullable()->after('email'); + }); + } + + public function down(): void + { + Schema::table('users', function (Blueprint $table): void { + $table->dropColumn('locale'); + }); + } +}; diff --git a/database/seeders/DemoSeeder.php b/database/seeders/DemoSeeder.php index f4d851ca..8dca323c 100644 --- a/database/seeders/DemoSeeder.php +++ b/database/seeders/DemoSeeder.php @@ -251,7 +251,7 @@ private function resolveContext(): bool /** Cycle d'une classe, tel que référencé par self::CURRICULUM. */ private function cycleOf(Classroom $class): string { - $type = Str::lower($class->type?->name ?? ''); + $type = Str::lower($class->type->name ?? ''); return match (true) { str_contains($type, 'primaire'), str_contains($type, 'maternelle') => 'primaire', diff --git a/database/seeders/StudentTestSeeder.php b/database/seeders/StudentTestSeeder.php index b367ac1f..a21b9a50 100644 --- a/database/seeders/StudentTestSeeder.php +++ b/database/seeders/StudentTestSeeder.php @@ -54,10 +54,12 @@ public function run(): void $classroomIds = Classroom::query()->pluck('id')->all(); $enrolledBy = User::query()->value('id'); - $canEnroll = $school && $academicYear && $classroomIds !== []; + // $academicYear est toujours résolu (créé au besoin) ; seules l'école et + // les classes peuvent manquer sur une base vide. + $canEnroll = $school && $classroomIds !== []; if (! $canEnroll) { $this->command?->warn( - 'Élèves créés sans inscription : école, année académique active ou classes manquantes.' + 'Élèves créés sans inscription : école ou classes manquantes.' ); } @@ -162,7 +164,7 @@ public function run(): void * Année académique active : on réutilise l'existante, sinon on crée * l'année scolaire courante (développement uniquement). */ - private function resolveAcademicYear(): ?AcademicYear + private function resolveAcademicYear(): AcademicYear { $active = AcademicYear::query()->where('active', true)->first() ?? AcademicYear::query()->latest('start_date')->first(); diff --git a/lang/en/administration.php b/lang/en/administration.php new file mode 100644 index 00000000..c137b6cc --- /dev/null +++ b/lang/en/administration.php @@ -0,0 +1,570 @@ + [ + 'head' => 'Audit log', + 'subtitle' => 'History of creations, modifications and deletions on sensitive data.', + 'search_ph' => 'Search (entity label)…', + 'action' => 'Action', + 'all_actions' => 'All actions', + 'entity' => 'Entity', + 'all_entities' => 'All entities', + 'event_created' => 'Creation', + 'event_updated' => 'Modification', + 'event_deleted' => 'Deletion', + 'event_restored' => 'Restoration', + 'col_date' => 'Date', + 'col_author' => 'Author', + 'col_action' => 'Action', + 'col_entity' => 'Entity', + 'col_changes' => 'Changes', + 'empty' => 'No entry.', + 'system' => 'System', + 'more_fields' => '+:count field(s)…', + 'range' => 'Showing :from to :to of :total', + ], + + 'guardians' => [ + 'index' => [ + 'head' => 'Portal access', + 'subtitle' => 'Guardian accounts and student linking for the parent portal.', + 'new' => 'New guardian', + 'search_ph' => 'Search for a guardian (name, email, phone) or a child (ID number, name)…', + 'clear_search' => 'Clear search', + 'col_guardian' => 'Guardian', + 'col_email' => 'Email', + 'col_children' => 'Children', + 'col_status' => 'Status', + 'col_actions' => 'Actions', + 'empty' => 'No guardian account.', + 'activated' => 'Activated', + 'pending' => 'Pending', + 'resend_invite' => 'Resend the invitation', + 'edit' => 'Edit', + 'delete' => 'Delete', + 'range' => 'Showing :from to :to of :total', + 'delete_title' => 'Delete this guardian account?', + 'delete_desc' => 'Portal access will be revoked. Students are not affected.', + 'cancel' => 'Cancel', + ], + 'create' => [ + 'head' => 'New guardian', + 'title' => 'New guardian', + 'subtitle' => 'Create the account and link it to one or more students (by ID number).', + ], + 'edit' => [ + 'head' => 'Edit guardian', + 'title' => 'Edit guardian', + ], + 'form' => [ + 'first_name' => 'First name *', + 'last_name' => 'Last name *', + 'email' => 'Email *', + 'phone' => 'Phone', + 'linked_students' => 'Linked students', + 'search_student_ph' => 'Search for a student by name or ID number…', + 'searching' => 'Searching…', + 'no_student' => 'No student found.', + 'remove_student' => 'Remove :name', + 'none_linked' => 'No linked student yet.', + 'matricule_not_found' => 'An ID number cannot be found.', + 'send_invitation' => 'Send the invitation by email', + 'save' => 'Save', + 'create' => 'Create the guardian', + 'cancel' => 'Cancel', + ], + ], + + 'permissions' => [ + 'index' => [ + 'head' => 'Permissions', + 'subtitle' => 'Manage the application permissions', + 'new' => 'New permission', + 'stat_total' => 'Total permissions', + 'stat_avg_per_role' => 'Average per role', + 'stat_page' => 'Page', + 'search_ph' => 'Search for a permission by name or description...', + 'search' => 'Search', + 'results_for' => 'Results for:', + 'col_name' => 'Name', + 'col_description' => 'Description', + 'col_roles' => 'Associated roles', + 'col_actions' => 'Actions', + 'empty' => 'No permission found', + 'view' => 'View', + 'edit' => 'Edit', + 'delete' => 'Delete', + 'range' => 'Showing :from to :to of :total permissions', + 'previous' => 'Previous', + 'next' => 'Next', + 'page_of' => 'Page :current of :last', + 'delete_title' => 'Delete the permission', + 'delete_desc' => 'Are you sure you want to delete this permission? This action is irreversible.', + 'cancel' => 'Cancel', + 'deleting' => 'Deleting...', + ], + 'create' => [ + 'head' => 'Create a permission', + 'title' => 'Create a new permission', + 'name' => 'Permission name *', + 'name_ph' => 'E.g. view_students', + 'name_hint' => 'Use lowercase characters and underscores', + 'description' => 'Description', + 'description_ph' => 'Describe the permission...', + 'creating' => 'Creating...', + 'create' => 'Create', + 'cancel' => 'Cancel', + ], + 'edit' => [ + 'head' => 'Edit :name', + 'title' => 'Edit the permission', + 'name' => 'Permission name *', + 'name_ph' => 'E.g. view_students', + 'name_hint' => 'Use lowercase characters and underscores', + 'description' => 'Description', + 'description_ph' => 'Describe the permission...', + 'updating' => 'Updating...', + 'update' => 'Update', + 'cancel' => 'Cancel', + ], + 'show' => [ + 'edit' => 'Edit', + 'info' => 'Information', + 'name' => 'Name', + 'description' => 'Description', + 'assigned_roles' => 'Assigned roles', + 'events' => 'Events', + 'created_on' => 'Created on', + 'updated_on' => 'Modified on', + 'stats' => 'Statistics', + 'associated_roles' => 'Associated roles', + 'no_role' => 'No role uses this permission.', + ], + ], + + 'roles' => [ + 'index' => [ + 'head' => 'Roles', + 'title' => 'Roles and Permissions', + 'subtitle' => 'Manage roles and their permissions', + 'new' => 'New role', + 'stat_total' => 'Total roles', + 'stat_avg_permissions' => 'Average permissions', + 'stat_page' => 'Page', + 'search_ph' => 'Search for a role by name or description...', + 'search' => 'Search', + 'results_for' => 'Results for:', + 'col_name' => 'Name', + 'col_description' => 'Description', + 'col_permissions' => 'Permissions', + 'col_actions' => 'Actions', + 'empty' => 'No role found', + 'view' => 'View', + 'edit' => 'Edit', + 'delete' => 'Delete', + 'range' => 'Showing :from to :to of :total roles', + 'previous' => 'Previous', + 'next' => 'Next', + 'page_of' => 'Page :current of :last', + 'delete_title' => 'Delete the role', + 'delete_desc' => 'Are you sure you want to delete this role? This action is irreversible.', + 'cancel' => 'Cancel', + 'deleting' => 'Deleting...', + ], + 'create' => [ + 'head' => 'Create a role', + 'title' => 'Create a new role', + 'name' => 'Role name *', + 'name_ph' => 'E.g. Administrator', + 'description' => 'Description', + 'description_ph' => 'Describe the role...', + 'creating' => 'Creating...', + 'create' => 'Create', + 'cancel' => 'Cancel', + 'permissions' => 'Permissions (:count)', + ], + 'edit' => [ + 'head' => 'Edit :name', + 'title' => 'Edit the role', + 'name' => 'Role name *', + 'name_ph' => 'E.g. Administrator', + 'description' => 'Description', + 'description_ph' => 'Describe the role...', + 'updating' => 'Updating...', + 'update' => 'Update', + 'cancel' => 'Cancel', + 'permissions' => 'Permissions (:count)', + ], + 'show' => [ + 'edit' => 'Edit', + 'info' => 'Information', + 'name' => 'Name', + 'description' => 'Description', + 'assigned_permissions' => 'Assigned permissions', + 'events' => 'Events', + 'created_on' => 'Created on', + 'updated_on' => 'Modified on', + 'stats' => 'Statistics', + 'role_permissions' => 'Role permissions', + 'no_permission' => 'No permission assigned to this role.', + ], + ], + + 'subject_assignments' => [ + 'active' => 'Active', + 'inactive' => 'Inactive', + 'index' => [ + 'head' => 'Subject assignments', + 'title' => 'Subject assignments', + 'subtitle' => 'Manage teacher assignments to subjects and classes', + 'statistics' => 'Assignment statistics', + 'new' => 'New assignment', + 'stat_total' => 'Total assignments', + 'stat_active' => 'Active assignments', + 'stat_page' => 'Page', + 'all_years' => 'All years', + 'all_classes' => 'All classes', + 'filter_year' => 'Filter by year', + 'filter_class' => 'Filter by class', + 'search_ph' => 'Search by subject, teacher or class...', + 'col_subject' => 'Subject', + 'col_teacher' => 'Teacher', + 'col_class' => 'Class', + 'col_year' => 'Year', + 'col_status' => 'Status', + 'col_actions' => 'Actions', + 'empty' => 'No assignment found', + 'view' => 'View', + 'edit' => 'Edit', + 'delete' => 'Delete', + 'range' => 'Showing :from to :to of :total results', + 'previous' => 'Previous', + 'next' => 'Next', + 'delete_title' => 'Confirm deletion', + 'delete_desc' => 'Are you sure you want to delete this assignment? This action is irreversible.', + 'cancel' => 'Cancel', + 'deleting' => 'Deleting...', + ], + 'form' => [ + 'subject' => 'Subject', + 'subject_required' => 'Subject', + 'select_subject' => 'Select a subject', + 'teacher' => 'Teacher', + 'search_teacher_ph' => 'Search for a teacher...', + 'no_user' => 'No user found.', + 'class' => 'Class', + 'select_class' => 'Select a class', + 'year' => 'Academic year', + 'select_year' => 'Select a year', + 'extra_info' => 'Additional information', + 'notes' => 'Notes / comments', + 'notes_ph' => 'Notes or comments...', + 'active' => 'Active assignment', + 'cancel' => 'Cancel', + ], + 'create' => [ + 'head' => 'Create an assignment', + 'title' => 'Create a new assignment', + 'subtitle' => 'Associate a subject, a teacher, a class and an academic year.', + 'creating' => 'Creating...', + 'submit' => 'Create the assignment', + ], + 'edit' => [ + 'head' => 'Edit assignment - :subject', + 'title' => 'Edit the assignment', + 'saving' => 'Saving...', + 'submit' => 'Save changes', + ], + 'show' => [ + 'head' => 'Assignment - :subject', + 'title' => 'Assignment details', + 'edit' => 'Edit', + 'info' => 'Assignment information', + 'subject' => 'Subject', + 'teacher' => 'Teacher', + 'class' => 'Class', + 'class_type' => 'Class type', + 'year' => 'Academic year', + 'status' => 'Status', + 'notes' => 'Notes', + 'system_info' => 'System information', + 'created_at' => 'Creation date', + 'updated_at' => 'Last modification', + 'id' => 'Identifier', + 'about' => 'About', + 'about_desc' => 'Assignments link a teacher to a specific subject for a given class and academic year.', + ], + 'statistics' => [ + 'head' => 'Assignment statistics', + 'title' => 'Assignment statistics', + 'subtitle' => "A teacher's assignments for an academic year", + 'export' => 'Export (PDF)', + 'year' => 'Academic year', + 'teacher' => 'Teacher', + 'select_teacher' => '— Select a teacher —', + 'select_prompt' => 'Select a teacher to see their assignments.', + 'kpi_assignments' => 'Assignments', + 'kpi_active' => 'Active', + 'kpi_subjects' => 'Subjects', + 'kpi_classes' => 'Classes', + 'col_subject' => 'Subject', + 'col_class' => 'Class', + 'col_status' => 'Status', + 'empty' => 'No assignment for this teacher in this year.', + ], + ], + + 'users' => [ + 'gender' => [ + 'male' => 'Male', + 'female' => 'Female', + 'other' => 'Other', + ], + 'pay_status' => [ + 'draft' => 'Draft', + 'validated' => 'Validated', + 'paid' => 'Paid', + 'cancelled' => 'Cancelled', + ], + 'days' => [ + 1 => 'Monday', + 2 => 'Tuesday', + 3 => 'Wednesday', + 4 => 'Thursday', + 5 => 'Friday', + 6 => 'Saturday', + 7 => 'Sunday', + ], + 'months_short' => [ + 1 => 'Jan', + 2 => 'Feb', + 3 => 'Mar', + 4 => 'Apr', + 5 => 'May', + 6 => 'Jun', + 7 => 'Jul', + 8 => 'Aug', + 9 => 'Sep', + 10 => 'Oct', + 11 => 'Nov', + 12 => 'Dec', + ], + + 'index' => [ + 'head' => 'Users', + 'subtitle' => 'Manage the application users', + 'new' => 'New user', + 'stat_total' => 'Total users', + 'stat_active' => 'Active users', + 'stat_page' => 'Page', + 'search_ph' => 'Search for a user by name, email or ID number...', + 'all_roles' => 'All roles', + 'all_genders' => 'All genders', + 'filter' => 'Filter', + 'reset' => 'Reset', + 'active_filters' => 'Active filters', + 'filter_search' => ' • Search: ":search"', + 'filter_role' => ' • Role selected', + 'filter_gender' => ' • Gender: :gender', + 'count' => ':count user(s)', + 'per_page' => 'Rows per page:', + 'col_user' => 'User', + 'col_email' => 'Email', + 'col_matricule' => 'ID number', + 'col_phone' => 'Phone', + 'col_roles' => 'Roles', + 'col_actions' => 'Actions', + 'empty' => 'No user found', + 'no_role' => 'No role', + 'view' => 'View', + 'edit' => 'Edit', + 'delete' => 'Delete', + 'range' => ':from–:to of :total users', + 'delete_title' => 'Delete the user', + 'delete_desc' => 'Are you sure you want to delete this user? This action is irreversible.', + 'cancel' => 'Cancel', + 'deleting' => 'Deleting...', + ], + + 'form' => [ + 'personal_info' => 'Personal information', + 'firstname' => 'First name *', + 'firstname_ph' => 'E.g. John', + 'lastname' => 'Last name *', + 'lastname_ph' => 'E.g. Doe', + 'gender' => 'Gender *', + 'birth_date' => 'Date of birth', + 'contact' => 'Contact details', + 'email' => 'Email *', + 'email_ph' => 'example@email.com', + 'phone' => 'Phone', + 'phone_ph' => '+228 XX XX XX XX', + 'address' => 'Address', + 'address_ph' => '123 Peace Street', + 'security' => 'Security', + 'security_hint' => 'Leave these fields empty to keep the current password.', + 'password' => 'Password *', + 'new_password' => 'New password', + 'password_confirmation' => 'Confirm password *', + 'password_confirmation_opt' => 'Confirm password', + 'profile' => 'Profile', + 'profile_desc' => 'Profile description', + 'profile_ph' => "Describe the user's profile...", + 'selected_roles' => 'Selected roles', + 'roles_hint' => 'Assign one or more roles according to responsibilities.', + 'roles' => 'Roles', + 'cancel' => 'Cancel', + ], + + 'create' => [ + 'head' => 'Create a user', + 'title' => 'Create a new user', + 'creating' => 'Creating...', + 'submit' => 'Create the user', + ], + + 'edit' => [ + 'head' => 'Edit :name', + 'title' => 'Edit :name', + 'updating' => 'Updating...', + 'submit' => 'Update', + ], + + 'show' => [ + 'edit' => 'Edit', + 'personal_info' => 'Personal information', + 'firstname' => 'First name', + 'lastname' => 'Last name', + 'gender' => 'Gender', + 'birth_date' => 'Date of birth', + 'contact' => 'Contact details', + 'email' => 'Email', + 'phone' => 'Phone', + 'address' => 'Address', + 'not_provided' => 'Not provided', + 'not_provided_f' => 'Not provided', + 'assignments' => 'Subject assignments (:count)', + 'no_assignment' => 'No subject assignment.', + 'col_subject' => 'Subject', + 'col_class' => 'Class', + 'col_year' => 'Year', + 'col_status' => 'Status', + 'active' => 'Active', + 'inactive' => 'Inactive', + 'timetable' => 'Timetable (:count)', + 'no_slot' => 'No slot assigned.', + 'col_day' => 'Day', + 'col_slot' => 'Slot', + 'col_room' => 'Room', + 'payslips' => 'Recent payslips (:count)', + 'no_payslip' => 'No payslip generated.', + 'download' => 'Download', + 'profile' => 'Profile', + 'history' => 'History', + 'created_on' => 'Created on', + 'updated_on' => 'Last modification', + 'roles' => 'Roles (:count)', + 'no_role' => 'No role assigned', + 'permissions' => 'Permissions', + 'no_permission' => 'No permission', + ], + + 'payroll' => [ + 'title' => 'Payroll / HR profile', + 'edit' => 'Edit', + 'remove' => 'Remove', + 'no_profile' => 'This user does not have a payroll profile yet.', + 'add' => 'Add to payroll', + 'employee_number' => 'Employee number', + 'job' => 'Job', + 'contract' => 'Contract', + 'status' => 'Status', + 'grade_base' => 'Grade / base salary', + 'off_grade' => 'Off grade (entered manually)', + 'payment' => 'Payment', + 'cnss' => 'CNSS number', + 'allowances' => "Employee's allowances & deductions", + 'no_allowance' => 'No recurring allowance or deduction.', + 'added_by' => 'added by :name', + 'percent_base' => ':amount% of base', + 'delete' => 'Delete', + 'label' => 'Label', + 'label_ph' => 'E.g. Responsibility bonus', + 'type' => 'Type', + 'type_earning' => 'Earning', + 'type_deduction' => 'Deduction', + 'mode' => 'Mode', + 'mode_fixed' => 'Fixed amount', + 'mode_percent' => '% of base', + 'percentage' => 'Percentage', + 'amount' => 'Amount', + 'reason' => 'Reason (traceability)', + 'reason_ph' => 'E.g. Department head since September', + 'add_btn' => 'Add', + 'remove_title' => 'Remove from payroll?', + 'remove_desc' => 'The payroll profile will be deleted. Not possible if the employee already has payslips.', + 'cancel' => 'Cancel', + 'delete_allowance_title' => 'Delete this allowance / deduction?', + 'delete_allowance_desc' => 'It will no longer apply to future payslips. Already generated payslips are not affected.', + 'contract_cdi' => 'Permanent', + 'contract_cdd' => 'Fixed-term', + 'contract_vacataire' => 'Contractor', + 'contract_stagiaire' => 'Intern', + 'status_active' => 'Active', + 'status_suspended' => 'Suspended', + 'status_terminated' => 'Off payroll', + 'method_cash' => 'Cash', + 'method_mobile_money' => 'Mobile Money', + 'method_bank_transfer' => 'Transfer', + 'method_cheque' => 'Cheque', + ], + + 'employee_form' => [ + 'identity' => 'Identity & position', + 'user' => 'Associated user *', + 'select_user' => 'Select a user', + 'employee' => 'Employee', + 'employee_number' => 'Employee number', + 'employee_number_ph' => 'Auto if empty', + 'job' => 'Job *', + 'job_ph' => 'E.g. Teacher, Supervisor…', + 'department' => 'Department / service', + 'department_ph' => 'E.g. Primary, Administration…', + 'contract' => 'Contract type *', + 'select' => 'Select', + 'status' => 'Status *', + 'remuneration' => 'Remuneration & payment', + 'grade' => 'Salary grade', + 'no_grade_ph' => 'None (salary entered manually)', + 'no_grade' => 'None (off grade)', + 'grade_base' => 'Base salary: :amount (from the grade)', + 'base_ignored' => 'Base salary (ignored, grade active)', + 'base' => 'Monthly base salary (F) *', + 'base_ph' => 'E.g. 120000', + 'payment_method' => 'Payment method *', + 'momo_number' => 'Mobile Money number', + 'momo_number_ph' => 'E.g. +228 90 00 00 00', + 'bank_name' => 'Bank', + 'bank_name_ph' => 'E.g. Ecobank', + 'bank_account' => 'Account number', + 'cnss_number' => 'CNSS number', + 'cnss_number_ph' => 'Social security', + 'contract_notes' => 'Contract & notes', + 'hire_date' => 'Hire date', + 'end_date' => 'End date (if applicable)', + 'notes' => 'Notes', + 'update' => 'Update', + 'saving' => 'Saving...', + 'updating' => 'Updating...', + 'create' => 'Save the employee', + 'cancel' => 'Cancel', + 'status_active' => 'Active', + 'status_suspended' => 'Suspended', + 'status_terminated' => 'Off payroll', + 'method_cash' => 'Cash', + 'method_mobile_money' => 'Mobile Money', + 'method_bank_transfer' => 'Bank transfer', + 'method_cheque' => 'Cheque', + ], + ], +]; diff --git a/lang/en/archives.php b/lang/en/archives.php new file mode 100644 index 00000000..fb95de47 --- /dev/null +++ b/lang/en/archives.php @@ -0,0 +1,89 @@ + 'Document archives', + 'title' => 'Document archives', + 'subtitle_docs' => ':count archived document(s)', + 'trashed_suffix' => ' · :count in trash', + 'view_active' => 'View active', + 'trash' => 'Trash', + 'archive_doc' => 'Archive a document', + 'search' => 'Search', + 'search_ph' => 'Title, reference…', + 'category' => 'Category', + 'all_categories' => 'All', + 'from' => 'From', + 'to' => 'To', + 'reset' => 'Reset', + 'th_reference' => 'Reference', + 'th_document' => 'Document', + 'th_tags' => 'Tags', + 'th_linked' => 'Linked to', + 'th_size' => 'Size', + 'th_archived' => 'Archived', + 'th_actions' => 'Actions', + 'empty_trash' => 'Trash is empty.', + 'empty' => 'No archived document.', + 'restore' => 'Restore', + 'delete_forever' => 'Delete permanently', + 'download' => 'Download', + 'edit' => 'Edit', + 'delete' => 'Delete', + 'prev' => 'Previous', + 'next' => 'Next', + 'page_of' => 'Page :current / :last', + 'edit_title' => 'Edit document', + 'create_title' => 'Archive a document', + 'edit_desc' => 'Update the metadata and tags.', + 'create_desc' => 'Add a file to the document vault with its metadata and tags.', + 'f_title' => 'Title', + 'f_category' => 'Category', + 'retention' => 'Retain until', + 'description' => 'Description', + 'file' => 'File', + 'tags' => 'Tags', + 'add_tag_ph' => 'Add a tag then Enter…', + 'linked_to' => 'Linked to', + 'link_none' => 'None', + 'link_classroom' => 'Class', + 'link_student' => 'Student (ID number)', + 'classroom' => 'Class', + 'choose' => 'Choose', + 'student_matricule' => 'Student ID number', + 'cancel' => 'Cancel', + 'save' => 'Save', + 'archive' => 'Archive', + 'delete_forever_q' => 'Delete permanently?', + 'delete_forever_desc' => 'The file ":title" will be removed from storage. This action is irreversible.', + + // Tags + 'tags_page' => [ + 'head' => 'Archive tags', + 'title' => 'Archive tags', + 'subtitle' => 'Organize archived documents by reusable keywords.', + 'new_tag' => 'New tag', + 'total' => 'Total tags', + 'search_ph' => 'Search a tag by name...', + 'th_tag' => 'Tag', + 'th_documents' => 'Documents', + 'th_color' => 'Color', + 'th_actions' => 'Actions', + 'documents_count' => ':count document(s)', + 'change_color' => 'Change color', + 'color_aria' => 'Color :color', + 'delete' => 'Delete', + 'empty' => 'No tag found', + 'showing' => 'Showing :from to :to of :total', + 'new_tag_desc' => 'A reusable keyword to classify archived documents.', + 'name' => 'Tag name', + 'name_ph' => 'e.g. Contract, Inspection…', + 'color' => 'Color', + 'preview' => 'Preview:', + 'default_name' => 'Tag name', + 'cancel' => 'Cancel', + 'create' => 'Create', + 'delete_q' => 'Delete this tag?', + 'delete_desc' => 'The tag will be removed from all associated documents. The documents are not deleted.', + ], +]; diff --git a/lang/en/auth.php b/lang/en/auth.php new file mode 100644 index 00000000..126adc7f --- /dev/null +++ b/lang/en/auth.php @@ -0,0 +1,64 @@ + [ + 'title' => 'Sign in', + 'description' => 'Enter your credentials to access your account', + 'email' => 'Email address', + 'email_placeholder' => 'you@example.com', + 'password' => 'Password', + 'password_placeholder' => 'Password', + 'forgot' => 'Forgot your password?', + 'remember' => 'Remember me', + 'submit' => 'Sign in', + ], + 'forgot_password' => [ + 'title' => 'Forgot password', + 'description' => 'Enter your email to receive a password reset link', + 'email' => 'Email address', + 'email_placeholder' => 'email@example.com', + 'submit' => 'Email password reset link', + 'return_prefix' => 'Or, return to', + 'login_link' => 'log in', + ], + 'two_factor' => [ + 'head' => 'Two-Factor Authentication', + 'auth_title' => 'Authentication Code', + 'auth_description' => 'Enter the authentication code provided by your authenticator application.', + 'auth_toggle' => 'login using a recovery code', + 'recovery_title' => 'Recovery Code', + 'recovery_description' => 'Please confirm access to your account by entering one of your emergency recovery codes.', + 'recovery_toggle' => 'login using an authentication code', + 'recovery_placeholder' => 'Enter recovery code', + 'continue' => 'Continue', + 'or_you_can' => 'or you can', + ], + 'verify_email' => [ + 'title' => 'Verify email', + 'description' => 'Please verify your email address by clicking on the link we just emailed to you.', + 'head' => 'Email verification', + 'link_sent' => 'A new verification link has been sent to the email address you provided during registration.', + 'resend' => 'Resend verification email', + 'logout' => 'Log out', + ], + 'confirm_password' => [ + 'title' => 'Confirm your password', + 'description' => 'This is a secure area of the application. Please confirm your password before continuing.', + 'head' => 'Confirm password', + 'password' => 'Password', + 'password_placeholder' => 'Password', + 'submit' => 'Confirm password', + ], + 'reset_password' => [ + 'title' => 'Reset password', + 'description' => 'Please enter your new password below', + 'head' => 'Reset password', + 'email' => 'Email', + 'password' => 'Password', + 'password_placeholder' => 'Password', + 'confirm' => 'Confirm password', + 'confirm_placeholder' => 'Confirm password', + 'submit' => 'Reset password', + ], + 'logout' => 'Log out', +]; diff --git a/lang/en/calendar.php b/lang/en/calendar.php new file mode 100644 index 00000000..9ec89c43 --- /dev/null +++ b/lang/en/calendar.php @@ -0,0 +1,42 @@ + 'Calendar', + 'header_title' => 'Academic calendar', + 'subtitle' => 'Holidays, exams, meetings and events', + 'new_event' => 'New event', + 'today' => 'Today', + 'prev' => 'Previous', + 'next' => 'Next', + 'year' => 'Year', + 'type' => 'Type', + 'all_types' => 'All types', + 'drag_hint' => 'Tip: drag an event to move it, pull its edge to change its end.', + 'empty' => 'No event.', + 'all_day_label' => 'All day', + 'resize_hint' => 'Stretch the end', + + 'view' => [ + 'month' => 'Month', + 'week' => 'Week', + 'day' => 'Day', + 'agenda' => 'List', + ], + + 'dialog' => [ + 'edit_title' => 'Edit event', + 'new_title' => 'New event', + 'desc' => 'Fill in the event information.', + 'field_title' => 'Title *', + 'field_type' => 'Type', + 'start' => 'Start *', + 'end' => 'End', + 'all_day' => 'All day', + 'start_time' => 'Start time', + 'end_time' => 'End time', + 'description' => 'Description', + ], + + 'delete_title' => 'Delete this event?', + 'delete_desc' => 'This action is irreversible.', +]; diff --git a/lang/en/common.php b/lang/en/common.php new file mode 100644 index 00000000..50847749 --- /dev/null +++ b/lang/en/common.php @@ -0,0 +1,60 @@ + [ + 'save' => 'Save', + 'cancel' => 'Cancel', + 'create' => 'Create', + 'add' => 'Add', + 'edit' => 'Edit', + 'update' => 'Update', + 'delete' => 'Delete', + 'remove' => 'Remove', + 'search' => 'Search', + 'filter' => 'Filter', + 'reset' => 'Reset', + 'confirm' => 'Confirm', + 'back' => 'Back', + 'close' => 'Close', + 'print' => 'Print', + 'export' => 'Export', + 'download' => 'Download', + 'view' => 'View', + 'details' => 'Details', + 'validate' => 'Validate', + 'next' => 'Next', + 'previous' => 'Previous', + 'yes' => 'Yes', + 'no' => 'No', + ], + 'status' => [ + 'active' => 'Active', + 'inactive' => 'Inactive', + 'pending' => 'Pending', + 'loading' => 'Loading…', + ], + 'messages' => [ + 'none' => 'No results', + 'confirm_delete' => 'Confirm deletion', + 'saved' => 'Saved', + 'required' => 'This field is required', + ], + 'pagination' => [ + 'showing' => 'Showing :from to :to of :total', + 'per_page' => 'Rows per page', + 'page' => 'Page', + ], + 'editor' => [ + 'bold' => 'Bold', + 'italic' => 'Italic', + 'heading1' => 'Heading 1', + 'heading2' => 'Heading 2', + 'bullet_list' => 'Bulleted list', + 'numbered_list' => 'Numbered list', + 'align_left' => 'Align left', + 'align_center' => 'Center', + 'align_right' => 'Align right', + 'undo' => 'Undo', + 'redo' => 'Redo', + ], +]; diff --git a/lang/en/comptabilite.php b/lang/en/comptabilite.php new file mode 100644 index 00000000..e3c4a986 --- /dev/null +++ b/lang/en/comptabilite.php @@ -0,0 +1,260 @@ + [ + 'head' => 'Verify a receipt', + 'title' => 'Verify a receipt', + 'subtitle' => 'Scan the barcode or enter the receipt verification code.', + 'placeholder' => 'Verification code (e.g. DAL-XXXXXXXXXXXX)', + 'verify' => 'Verify', + 'authentic' => 'Authentic receipt', + 'student' => 'Student', + 'matricule' => 'ID number', + 'class' => 'Class', + 'date' => 'Date', + 'amount' => 'Amount collected', + 'not_found' => 'Receipt not found', + 'not_found_hint' => 'No receipt matches this code. This document may be forged.', + ], + + 'dashboard' => [ + 'head' => 'Accounting', + 'title' => 'Accounting dashboard', + 'year_prefix' => 'School year :year', + 'all_years' => 'All years', + 'filter' => 'Filter:', + 'all_classes' => 'All classes', + 'reset' => 'Reset', + 'alert_unpaid' => ':count student(s) have made no payment', + 'alert_unpaid_hint' => 'These students may be expelled if the situation does not change.', + 'total_billed' => 'Total billed', + 'invoices' => ':count invoices', + 'collected' => 'Collected', + 'of_total' => ':pct% of total', + 'recovered' => ':pct% recovered', + 'remaining' => 'Outstanding', + 'open_files' => ':count open files', + 'paid_late' => 'Paid / Overdue', + 'partial_payments' => ':count partial payments', + 'recovery_global' => 'Overall recovery rate', + 'collected_of' => ':paid collected of :total', + 'monthly' => 'Monthly collection trend', + 'month_collected' => 'Monthly collection', + 'cumul_collected' => 'Cumulative collection', + 'expected' => 'Total expected', + 'expected_short' => 'Expected', + 'by_class' => 'Situation by class', + 'col_class' => 'Class', + 'col_students' => 'Students', + 'col_billed' => 'Billed', + 'col_collected' => 'Collected', + 'col_remaining' => 'Remaining', + 'col_rate' => 'Rate', + 'col_statuses' => 'Statuses', + 'unpaid_title' => 'Students with unpaid balance', + 'view_all' => 'View all', + 'top_n' => 'Top :shown largest balances of :total · ', + 'sorted' => 'Sorted by remaining amount descending · ', + 'no_payment_count' => ':count no payment', + 'all_uptodate' => 'All students are up to date', + 'all_uptodate_hint' => 'No unpaid balance for the selected filters.', + 'col_student' => 'Student', + 'col_total' => 'Total', + 'col_paid' => 'Paid', + 'col_status' => 'Status', + 'col_risk' => 'Risk', + 'to_expel' => 'To expel', + 'partial' => 'Partial', + 'follow' => 'Tracking', + 'status' => [ + 'ISSUED' => 'Unpaid', + 'PARTIALLY_PAID' => 'Partial', + 'PAID' => 'Paid', + 'CANCELLED' => 'Cancelled', + ], + ], + + 'tx' => [ + 'head' => 'Transactions journal', + 'title' => 'Transactions journal', + 'subtitle' => 'History of all financial movements', + 'new_expense' => 'New expense', + 'total_collected' => 'Total collected', + 'transactions_count' => ':count transaction(s)', + 'total_out' => 'Total outflows', + 'net_balance' => 'Net balance', + 'positive' => 'Positive', + 'negative' => 'Negative', + 'transactions' => 'Transactions', + 'of_total' => 'in total', + 'cash_accounts' => 'Cash accounts:', + 'filters' => 'Filters', + 'clear_all' => 'Clear all', + 'type' => 'Type', + 'all_types' => 'All types', + 'income' => 'Income', + 'outflows' => 'Outflows', + 'income_arrow' => '↑ Income', + 'expense_arrow' => '↓ Outflows', + 'reference' => 'Reference', + 'all_references' => 'All references', + 'ref_payment' => 'Tuition payment', + 'ref_scholarship' => 'Scholarship granted', + 'ref_cancellation' => 'Cancellation', + 'ref_expense' => 'Manual expense', + 'category' => 'Category (expense)', + 'all_categories' => 'All categories', + 'cash' => 'Cash account', + 'all_cash' => 'All cash accounts', + 'period' => 'Period', + 'quick' => 'Quick:', + 'today' => 'Today', + 'this_month' => 'This month', + 'this_year' => 'This year', + 'active' => 'Active:', + 'filtered' => '— filtered', + 'per_page' => 'Rows per page:', + 'empty' => 'No transaction found', + 'clear_filters' => 'Clear filters', + 'col_type' => 'Type', + 'col_description' => 'Description', + 'col_reference' => 'Reference', + 'col_cash' => 'Cash account', + 'col_amount' => 'Amount', + 'col_date' => 'Date', + 'by_user' => 'by :name', + 'range' => ':from–:to of :total', + 'first_page' => 'First page', + 'last_page' => 'Last page', + 'delete_title' => 'Delete this expense?', + 'delete_desc' => 'The amount will be credited back to the relevant cash account. This action is irreversible.', + 'delete_expense_aria' => 'Delete this expense', + 'txtype' => [ + 'INCOME' => 'Income', + 'EXPENSE' => 'Outflow', + ], + 'ref' => [ + 'PAYMENT' => 'Payment', + 'SCHOLARSHIP' => 'Scholarship', + 'EXPENSE' => 'Expense', + 'CANCELLATION' => 'Cancellation', + ], + ], + + 'situation' => [ + 'head' => 'Situation by class', + 'title' => 'Situation by class', + 'year' => 'Year :year', + 'class_prefix' => 'Class :name', + 'all_classes_suffix' => 'All classes', + 'students_concerned' => 'Students concerned', + 'classes_count' => ':count class(es)', + 'total_collected' => 'Total collected', + 'of_billed' => ':pct% of billed', + 'remaining' => 'Outstanding', + 'pending_files' => ':count pending file(s)', + 'paid_unpaid' => 'Paid / Unpaid', + 'partial_count' => ':count partial payment(s)', + 'collected_of_short' => ':paid of :total', + 'summary_by_class' => 'Summary by class', + 'no_data' => 'No data for the selected filters', + 'col_export' => 'Export', + 'selected' => 'selected', + 'export_all' => 'All', + 'export_unpaid' => 'Unpaid', + 'export_all_title' => 'Export all', + 'export_unpaid_title' => 'Export unpaid', + 'students_of' => 'Students — :name', + 'students_sub' => ':count student(s) · :risk with no payment', + 'col_invoice' => 'Invoice no.', + 'col_progress' => 'Progress', + 'settled' => 'Settled', + 'total_students_foot' => ':count student(s)', + 'empty_class' => 'No student in this class', + ], + + 'cash' => [ + 'head' => 'Cash accounts', + 'title' => 'Cash accounts', + 'subtitle' => 'Manage your cash accounts: cash, mobile money, bank.', + 'new' => 'New cash account', + 'global_balance' => 'Overall balance (all accounts)', + 'active_count' => ':count active account(s)', + 'create_submit' => 'Create the cash account', + 'empty' => 'No cash account configured.', + 'empty_hint' => 'Create your first cash account to get started.', + 'name' => 'Name *', + 'name_ph' => 'E.g. Main cash', + 'type_label' => 'Type *', + 'opt_cash' => '💵 Cash', + 'opt_mobile' => '📱 Mobile Money (Flooz/TMoney)', + 'opt_bank' => '🏦 Bank', + 'description' => 'Description', + 'description_ph' => 'Optional', + 'current_balance' => 'Current balance', + 'payments_count' => ':count payment(s)', + 'delete_title' => 'Delete this cash account?', + 'delete_desc' => 'This action is irreversible. Payments linked to this account will not be deleted.', + 'type' => [ + 'CASH' => 'Cash', + 'MOBILE_MONEY' => 'Mobile Money', + 'BANK' => 'Bank', + ], + ], + + 'receipt_doc' => [ + 'head' => 'Receipt :number', + 'print' => 'Print the receipt', + 'copy_school' => 'School copy', + 'copy_payer' => 'Payer copy', + 'title' => 'Payment receipt', + 'receipt_no' => 'Receipt no.', + 'date' => 'Date:', + 'student' => 'Student', + 'mat' => 'ID', + 'class_year' => 'Class · Year', + 'enrollment_no' => 'Enrollment no.', + 'invoice' => 'Invoice', + 'designation' => 'Description', + 'amount' => 'Amount', + 'invoice_total' => 'Invoice total', + 'amount_collected' => 'Amount collected', + 'ref' => 'Ref:', + 'in_words' => 'This receipt is set at the sum of:', + 'total_paid' => 'Total paid', + 'remaining' => 'Amount due', + 'stamp' => 'School stamp', + 'accounting' => 'Accounting', + 'paid_by' => 'Paid by', + 'established_on' => 'Issued on', + 'verify_p1' => 'Receipt verifiable at ', + 'verify_p2' => ' — code: ', + 'method' => [ + 'CASH' => 'Cash', + 'MOBILE_MONEY' => 'Mobile Money', + 'BANK_TRANSFER' => 'Bank transfer', + 'CHEQUE' => 'Cheque', + ], + ], + + 'expense' => [ + 'head' => 'New expense', + 'title' => 'Record an expense', + 'subtitle' => 'Cash outflow: rent, salaries, supplies…', + 'details' => 'Expense details', + 'category' => 'Category *', + 'select_category' => 'Select a category', + 'amount' => 'Amount (F) *', + 'amount_ph' => 'E.g. 25000', + 'description' => 'Description *', + 'description_ph' => 'E.g. Electricity bill — October 2026', + 'date' => 'Date *', + 'cash_account' => 'Cash account *', + 'select_cash' => 'Select a cash account', + 'balance_opt' => ':name — balance :balance', + 'after_expense' => 'Balance after expense:', + 'recent' => 'Recent expenses', + 'saving' => 'Saving...', + 'submit' => 'Record the expense', + ], +]; diff --git a/lang/en/dashboard.php b/lang/en/dashboard.php new file mode 100644 index 00000000..a233e1a9 --- /dev/null +++ b/lang/en/dashboard.php @@ -0,0 +1,143 @@ + 'Dashboard', + + 'header' => [ + 'school_year' => 'School year :year', + 'no_active_year' => 'No active year', + 'academic_year' => 'Academic year', + 'active_suffix' => 'active', + ], + + 'alert' => [ + 'no_payment' => ':count student(s) have made no payment', + 'no_payment_hint' => 'These students are at risk of exclusion for non-payment.', + ], + + 'kpi' => [ + 'students_enrolled' => 'Enrolled students', + 'this_week' => 'This week', + 'this_week_sub' => 'New enrollments', + 'active_students' => 'Active students', + 'active_students_sub' => 'of :total total', + 'recovery_rate' => 'Recovery rate', + 'boys' => 'Boys', + 'girls' => 'Girls', + 'gender_sub' => ':pct% of enrolled', + 'active_classes' => 'Active classes', + 'active_classes_sub' => 'Open classes', + 'users' => 'Users', + 'users_sub' => 'Accounts in total', + 'teachers' => 'Teachers', + 'teachers_sub' => 'Teaching staff', + 'rem' => 'Student/teacher ratio', + 'rem_sub' => 'STR · :count teacher(s)', + 'present_today' => "Today's attendance", + 'present_today_sub' => ':count absence(s) today', + 'presence_rate' => 'Attendance rate (today)', + 'presence_rate_sub' => ':present/:total recorded', + 'presence_rate_none' => 'No records', + 'pending_permissions' => 'Pending requests', + 'pending_permissions_sub' => 'To review', + 'documents' => 'Documents issued', + 'this_month' => 'This month', + 'official_exams' => 'Official exams', + 'official_exams_sub' => ':count registration(s)', + 'validated_bulletins' => 'Validated report cards', + 'period' => 'Period: :period', + 'all_periods' => 'All periods', + 'current_period' => 'Current period', + 'general_average' => 'Overall average', + 'total_billed' => 'Total billed', + 'total_billed_sub' => ':count invoices', + 'collected' => 'Collected', + 'collected_sub' => 'Rate :pct%', + 'remaining' => 'Outstanding', + 'remaining_sub' => ':count open files', + 'paid_unpaid' => 'Paid / Unpaid', + 'paid_unpaid_sub' => ':count partial payments', + 'collected_month' => 'Collected (month)', + 'expenses_month' => 'Expenses (month)', + 'net_month' => 'Net balance (month)', + 'surplus' => 'Surplus', + 'deficit' => 'Deficit', + ], + + 'section' => [ + 'pending_permissions' => 'Pending requests', + 'pending_permissions_action' => 'Requests', + 'pending_permissions_empty' => 'No pending request', + 'upcoming_exams' => 'Upcoming official exams', + 'upcoming_exams_action' => 'Exams', + 'upcoming_exams_empty' => 'No scheduled exam', + 'registered' => ':count registered', + 'monthly' => 'Collections — last 6 months', + 'monthly_empty' => 'No payment in the period', + 'collected_tooltip' => 'Collected', + 'cash_balances' => 'Cash balances', + 'cash_balances_empty' => 'No active cash account', + 'total' => 'Total', + 'payment_methods' => 'Payment methods', + 'payment_methods_empty' => 'No collection in the period', + 'invoice_breakdown' => 'Invoice breakdown', + 'students_per_class' => 'Students per class', + 'students_tooltip' => 'Students', + 'recent_payments' => 'Recent payments', + 'recent_payments_action' => 'Accounting', + 'recent_payments_empty' => 'No payment recorded', + 'recent_enrollments' => 'Recent enrollments', + 'recent_enrollments_action' => 'Enrollments', + 'recent_enrollments_empty' => 'No enrollment for this year', + 'payment_methods_year' => 'Payment methods — :year', + 'students_no_pay' => 'At-risk students — no payment', + 'students_no_pay_action' => 'View all', + 'col_student' => 'Student', + 'col_class' => 'Class', + 'col_amount_due' => 'Amount due', + 'invoice_action' => 'Invoice', + ], + + 'teacher' => [ + 'today_timetable' => 'My timetable — today', + 'timetable_action' => 'Timetable', + 'today_empty' => 'No class scheduled today.', + 'room' => 'room :room', + 'pending_marks' => 'Marks to enter', + 'evaluations_action' => 'Evaluations', + 'pending_marks_empty' => 'All your evaluations are entered. 🎉', + 'my_classes' => 'My classes & subjects', + 'assignments_action' => 'Assignments', + 'assignments_empty' => 'No assignment for year :year', + 'take_attendance' => 'Take attendance', + ], + + 'empty' => [ + 'title' => 'Dashboard not available', + 'subtitle' => 'No data to display for your profile.', + ], + + 'method' => [ + 'cash' => 'Cash', + 'mobile_money' => 'Mobile Money', + 'bank_transfer' => 'Bank transfer', + 'bank_transfer_short' => 'Transfer', + 'cheque' => 'Cheque', + 'bank' => 'Bank', + 'other' => 'Other', + ], + + 'status' => [ + 'issued' => 'Unpaid', + 'partially_paid' => 'Partial', + 'paid' => 'Paid', + 'active' => 'Active', + 'pending' => 'Pending', + ], + + 'reason' => [ + 'medical' => 'Medical', + 'familial' => 'Family', + 'autre' => 'Other', + ], +]; diff --git a/lang/en/enrollments.php b/lang/en/enrollments.php new file mode 100644 index 00000000..e22c92d2 --- /dev/null +++ b/lang/en/enrollments.php @@ -0,0 +1,272 @@ + [ + 'title' => 'Enrollments & Payments', + 'subtitle' => 'Manage student enrollments by academic year', + 'new' => 'New enrollment', + 'billed' => 'Total billed', + 'collected' => 'Collected', + 'recovered' => ':rate% recovered', + 'remaining' => 'Outstanding', + 'unpaid' => 'Unpaid', + 'no_payment' => 'with no payment', + 'search_ph' => 'Code, student, class, year...', + 'all_statuses' => 'All statuses', + 'all_years' => 'All years', + 'all_classes' => 'All classes', + 'reset' => 'Reset', + 'unit' => 'enrollment(s)', + 'per_page' => 'Rows per page:', + 'empty' => 'No enrollment found', + 'range' => ':from–:to of :total enrollments', + 'delete_title' => 'Delete this enrollment?', + 'delete_desc' => 'This action is irreversible.', + + 'status' => [ + 'pending' => 'Pending', + 'active' => 'Active', + 'cancelled' => 'Cancelled', + ], + + 'col' => [ + 'code' => 'Code', + 'student' => 'Student', + 'class' => 'Class', + 'year' => 'Year', + 'date' => 'Date', + 'status' => 'Status', + 'payment' => 'Payment', + 'actions' => 'Actions', + ], + + 'pay' => [ + 'no_invoice' => 'No invoice', + 'paid' => 'Paid', + 'remaining' => ':amount left', + 'unpaid' => 'Unpaid', + ], + ], + + 'form' => [ + 'school' => 'School *', + 'select_school' => 'Select a school', + 'student' => 'Student *', + 'select_student' => 'Select a student', + 'class' => 'Class *', + 'select_class' => 'Select a class', + 'academic_year' => 'Academic year *', + 'select_year' => 'Select a year', + 'active_suffix' => 'active', + 'code' => 'Enrollment code', + 'date' => 'Enrollment date *', + 'status' => 'Status *', + 'saving' => 'Saving...', + ], + + 'edit' => [ + 'head' => 'Edit enrollment', + 'title' => 'Edit the enrollment', + ], + + 'create' => [ + 'head' => 'New enrollment', + 'subtitle' => 'Enroll a student for the school year', + 'step' => 'Step :n', + 'fields_filled' => ':n / 4 required fields filled', + 'search_student_ph' => 'Search for a student...', + 'search_input_ph' => 'Name, first name or ID...', + 'no_student' => 'No student found', + 'more_students' => ':count students — showing the first 50 matches', + 'creating' => 'Creating...', + 'submit_with_payment' => 'Enroll & record payment', + 'submit' => 'Create the enrollment', + + 'steps' => [ + 'enrollment' => 'Enrollment', + 'summary' => 'Summary', + 'payment' => 'Payment', + ], + + 's1' => [ + 'title' => 'Enrollment information', + 'subtitle' => 'Enter the school, the student and the class', + 'establishment' => 'School', + 'school' => 'School', + 'academic_year' => 'Academic year', + 'student' => 'Student', + 'class_details' => 'Class & details', + 'class' => 'Class', + 'date' => 'Enrollment date', + 'code' => 'Enrollment code', + 'code_ph' => 'Auto-generated if empty', + 'code_hint' => 'Leave empty for automatic generation', + 'status' => 'Status', + ], + + 's2' => [ + 'title' => 'Fee summary', + 'subtitle' => 'Fees generated automatically from the fee schedule', + 'total' => 'Total due', + 'no_fees' => 'No fee configured', + 'no_fees_hint' => 'The invoice will be created empty — you can add fees manually.', + ], + + 's3' => [ + 'title' => 'First payment', + 'subtitle' => 'Optional — can be recorded now or later', + 'now' => 'Payment to record now', + 'later' => 'Pay later', + 'now_hint' => 'A receipt will be generated automatically', + 'later_hint' => 'The enrollment will be created with an invoice and no payment', + 'method' => 'Payment method', + 'amount' => 'Amount (F)', + 'fill_total' => 'Fill the total (:amount)', + 'payment_date' => 'Payment date', + 'paid_by' => 'Paid by', + 'paid_by_ph' => "Payer's name", + 'reference' => 'Reference / Transaction no.', + 'reference_ph' => 'E.g. TXN-123456', + ], + + 'method' => [ + 'cash' => 'Cash', + 'cash_desc' => 'Cash payment', + 'mobile' => 'Mobile', + 'mobile_desc' => 'Flooz, T-Money...', + 'transfer' => 'Transfer', + 'transfer_desc' => 'Bank transfer', + 'cheque' => 'Cheque', + 'cheque_desc' => 'Bank cheque', + ], + ], + + 'invoice' => [ + 'paid_progress' => ':amount paid', + 'total_due' => 'Total due', + 'detail_title' => 'Invoice details', + 'no_fees' => 'No fee configured for this class / year.', + 'payments_count' => 'Payments (:count)', + 'new_payment' => 'New payment', + 'record_payment' => 'Record a payment', + 'amount' => 'Amount (F) *', + 'amount_max' => 'Max: :amount', + 'method' => 'Payment method *', + 'cash_account' => 'Destination cash account', + 'no_cash_account' => '— No cash account —', + 'payment_date' => 'Payment date *', + 'paid_by' => 'Paid by', + 'paid_by_ph' => "Payer's name", + 'reference' => 'Reference / Transaction no.', + 'reference_ph' => 'E.g. TXN-123456', + 'notes' => 'Notes (optional)', + 'notes_ph' => 'Remarks...', + 'empty_payments' => 'No payment recorded', + 'receipt' => 'Receipt :number', + 'opt' => [ + 'cash' => 'Cash', + 'mobile' => 'Mobile Money', + 'transfer' => 'Bank transfer', + 'cheque' => 'Cheque', + ], + ], + + 'invoice_doc' => [ + 'head' => 'Invoice :number', + 'print' => 'Print the invoice', + 'title' => 'Invoice', + 'number' => 'No.', + 'issued_on' => 'Issued on :date', + 'student' => 'Student', + 'mat' => 'ID', + 'class_year' => 'Class · Year', + 'enrollment_ref' => 'Enrollment :code', + 'col_designation' => 'Description', + 'col_amount' => 'Amount', + 'no_fees' => 'No fee configured.', + 'subtotal' => 'Subtotal', + 'discount' => 'Discount', + 'total_due' => 'Total due', + 'in_words' => 'This invoice is set at the sum of:', + 'status' => 'Status', + 'total_paid' => 'Total paid', + 'remaining' => 'Amount due', + 'payments_received' => 'Payments received', + 'col_date' => 'Date', + 'col_method' => 'Method', + 'col_reference' => 'Reference', + 'stamp' => 'School stamp', + 'accounting' => 'Accounting', + 'status_opt' => [ + 'ISSUED' => 'Unpaid', + 'PARTIALLY_PAID' => 'Partially paid', + 'PAID' => 'Settled', + 'CANCELLED' => 'Cancelled', + ], + 'method' => [ + 'CASH' => 'Cash', + 'MOBILE_MONEY' => 'Mobile Money', + 'BANK_TRANSFER' => 'Transfer', + 'CHEQUE' => 'Cheque', + ], + ], + + 'receipt' => [ + 'head' => 'Confirmation - :code', + 'title' => 'Enrollment confirmation', + 'number' => 'No.', + 'issued_on' => 'On :date', + 'student' => 'Student', + 'matricule' => 'ID number', + 'class' => 'Class', + 'academic_year' => 'Academic year', + 'status' => 'Status', + 'settlement' => 'Settlement', + 'registered_by' => 'Registered by', + 'stamp' => 'School stamp', + 'management' => 'Management', + 'established_on' => 'Document issued on :date. Keep for your records.', + 'reserve_note' => 'Document issued subject to settlement of the remaining balance (:amount).', + 'mention_p1' => 'The management confirms the enrollment of student ', + 'mention_p2' => ' in class ', + 'mention_p3' => ' for the academic year ', + 'status_opt' => [ + 'PENDING' => 'Pending', + 'ACTIVE' => 'Active', + 'CANCELLED' => 'Cancelled', + ], + 'settle' => [ + 'no_fees' => 'No fees billed', + 'settled' => 'Enrollment settled', + 'partial' => 'Partially paid — :amount left', + 'unpaid' => 'Unpaid — :amount left', + ], + ], + + 'show' => [ + 'head' => 'Enrollment details', + 'title' => 'Enrollment details', + 'confirmation' => 'Confirmation', + 'financial' => 'Financial tracking', + 'code' => 'Enrollment code', + 'status' => 'Status', + 'school' => 'School', + 'student' => 'Student', + 'class' => 'Class', + 'academic_year' => 'Academic year', + 'date' => 'Enrollment date', + 'enrolled_by' => 'Enrolled by', + 'created_at' => 'Created on', + 'updated_at' => 'Modified on', + 'invoice' => 'Invoice :number', + 'total' => 'Total', + 'paid' => 'Paid', + 'remaining' => 'Remaining', + 'inv' => [ + 'ISSUED' => 'Unpaid', + 'PARTIALLY_PAID' => 'Partially paid', + 'PAID' => 'Settled', + 'CANCELLED' => 'Cancelled', + ], + ], +]; diff --git a/lang/en/errors.php b/lang/en/errors.php new file mode 100644 index 00000000..c592218e --- /dev/null +++ b/lang/en/errors.php @@ -0,0 +1,36 @@ + 'Dashboard', + 'home' => 'Back to home', + 'previous' => 'Previous page', + + 'fallback' => [ + 'title' => 'An error occurred', + 'message' => 'Something went wrong. Please try again.', + ], + '403' => [ + 'title' => 'Access denied', + 'message' => 'You do not have permission to access this page.', + ], + '404' => [ + 'title' => 'Page not found', + 'message' => 'The page you are looking for does not exist or has been moved.', + ], + '419' => [ + 'title' => 'Session expired', + 'message' => 'Your session has expired. Please sign in again, then retry.', + ], + '429' => [ + 'title' => 'Too many requests', + 'message' => 'You have made too many requests. Please wait a moment before trying again.', + ], + '500' => [ + 'title' => 'Server error', + 'message' => 'An unexpected error occurred. Try again in a moment; if the problem persists, contact your school administrator.', + ], + '503' => [ + 'title' => 'Under maintenance', + 'message' => 'The service is temporarily unavailable. Please come back in a few moments.', + ], +]; diff --git a/lang/en/examens.php b/lang/en/examens.php new file mode 100644 index 00000000..ad07acc5 --- /dev/null +++ b/lang/en/examens.php @@ -0,0 +1,302 @@ + [ + 'index' => [ + 'head' => 'Evaluation templates', + 'title' => 'Evaluation templates', + 'year' => 'Year: :year', + 'subtitle_default' => 'Define global exams before deploying them per class', + 'new' => 'New template', + 'search_ph' => 'Search a template...', + 'period' => 'Period', + 'all_periods' => 'All periods', + 'filter' => 'Filter', + 'col_name' => 'Template name', + 'col_type' => 'Type', + 'col_period' => 'Period', + 'col_coeff' => 'Coeff.', + 'col_scale' => 'Scale', + 'col_date' => 'Indicative date', + 'col_deployed' => 'Deployed', + 'col_actions' => 'Actions', + 'empty' => 'No evaluation template. Create one to get started.', + 'classes_count' => ':count class(es)', + 'total' => ':count template(s) in total', + 'delete_title' => 'Delete this template?', + 'delete_desc' => 'All associated evaluations and marks will be deleted. Irreversible action.', + ], + + 'form' => [ + 'name' => 'Name *', + 'name_ph' => 'E.g. Assignment 1, Term exam...', + 'period' => 'Period *', + 'current' => 'current', + 'eval_type' => 'Evaluation type *', + 'eval_type_short' => 'Type *', + 'class_type' => 'Class type', + 'all_types' => 'All types', + 'coefficient' => 'Coefficient *', + 'scale' => 'Scale (max score) *', + 'scale_short' => 'Scale *', + 'date' => 'Indicative date', + 'description' => 'Description', + 'description_ph' => 'Instructions, objectives...', + 'saving' => 'Saving...', + ], + + 'create' => [ + 'head' => 'New evaluation template', + 'title' => 'New evaluation template', + 'subtitle' => '— This template will then be deployed per class and subject.', + 'submit' => 'Create the template', + ], + + 'edit' => [ + 'head' => 'Edit: :name', + 'title' => 'Edit the template', + 'submit' => 'Save', + ], + + 'show' => [ + 'head' => 'Template: :name', + 'deploy' => 'Deploy to classes', + 'no_active_year' => 'No active academic year', + 'no_active_year_hint' => 'Activate an academic year to deploy this template.', + 'manage_years' => 'Manage academic years', + 'no_classes' => 'No class/subject available', + 'no_classes_hint' => 'No class/subject assignment exists for the active academic year.', + 'configure_classes' => 'Configure classes', + 'all_deployed' => 'All classes/subjects of the active year have already been deployed for this template.', + 'select_title' => 'Select the classes/subjects to deploy', + 'select_all' => 'Select all', + 'deselect_all' => 'Deselect all', + 'col_class' => 'Class', + 'col_subject_coeff' => 'Subject · coeff.', + 'col_planned_date' => 'Planned date', + 'coeff' => 'coeff. :n', + 'selected_summary' => ':count class(es) selected', + 'with_date' => ':count with planned date', + 'generating' => 'Generating...', + 'generate' => 'Generate :count evaluation(s)', + 'generated' => 'Generated evaluations', + 'empty' => 'No evaluation deployed. Use the button above to deploy this template.', + 'col_subject' => 'Subject', + 'col_subject_coeff2' => 'Subject coeff.', + 'col_date' => 'Date', + 'col_status' => 'Status', + 'col_progress' => 'Progress', + 'col_actions' => 'Actions', + 'status_completed' => 'Completed', + 'status_scheduled' => 'Scheduled', + 'marks' => 'Marks', + 'delete_eval_title' => 'Delete this evaluation?', + 'delete_eval_desc' => 'All marks entered for this evaluation will be lost.', + ], + ], + + 'official' => [ + 'index' => [ + 'head' => 'Official exams', + 'subtitle' => 'CEPD, BEPC, Baccalaureate — year :year.', + 'new' => 'New exam', + 'year' => 'Academic year', + 'active_suffix' => 'active', + 'search' => 'Search', + 'search_ph' => 'Exam name...', + 'type' => 'Type', + 'all' => 'All', + 'session' => 'Session', + 'all_sessions' => 'All', + 'status' => 'Status', + 'all_statuses' => 'All', + 'reset' => 'Reset filters (:count)', + 'col_type' => 'Type', + 'col_exam' => 'Exam', + 'col_class' => 'Class', + 'col_session' => 'Session', + 'col_date' => 'Date', + 'col_center' => 'Center', + 'col_registered' => 'Registered', + 'col_admitted' => 'Admitted', + 'col_status' => 'Status', + 'col_actions' => 'Actions', + 'empty' => 'No exam for these criteria.', + 'manage' => 'Manage', + 'total' => ':count exam(s)', + 'create_title' => 'New official exam', + 'attached' => 'Attached to the active academic year:suffix.', + 'form_type' => 'Type *', + 'form_year' => 'Year (calendar) *', + 'form_name' => 'Name / Label *', + 'name_ph' => 'E.g. BEPC 2026 - Normal session', + 'form_session' => 'Session', + 'form_date' => 'Exam date', + 'form_class' => 'Class *', + 'choose_class' => 'Choose a class', + 'form_center' => 'Exam center', + 'center_ph' => 'E.g. Tokoin High School', + 'delete_title' => 'Delete this exam?', + 'delete_desc' => 'All associated registrations and results will be deleted.', + ], + + 'show' => [ + 'class_prefix' => 'Class: :name', + 'register_students' => 'Register students', + 'save_results' => 'Save results', + 'registered' => 'Registered', + 'admitted' => 'Admitted', + 'failed' => 'Failed', + 'success_rate' => 'Success rate', + 'col_student' => 'Student', + 'col_table_no' => 'Table no.', + 'col_series' => 'Series', + 'col_status' => 'Status', + 'col_average' => 'Average', + 'col_mention' => 'Mention', + 'empty' => 'No student registered. Click "Register students".', + 'all_registered' => 'All students are already registered.', + 'selected' => ':count selected', + 'register' => 'Register', + ], + ], + + 'planning' => [ + 'head' => 'Exam schedule', + 'title' => 'Exam schedule', + 'subtitle' => 'Plan and view exam dates by class', + 'export' => 'Export / Print', + 'class' => 'Class', + 'select_class' => 'Select a class', + 'all_classes' => '— All classes —', + 'period' => 'Period', + 'all_periods' => 'All periods', + 'no_date_alert_1' => ':count evaluation(s) without a planned date. Click', + 'no_date_alert_2' => 'to schedule them.', + 'select_prompt' => 'Select a class to view its schedule', + 'no_classes' => 'No class with deployed evaluations for the active year.', + 'count' => ':count evaluation(s)', + 'empty_base' => 'No evaluation for this class', + 'and_period' => ' and this period', + 'col_subject' => 'Subject', + 'col_template' => 'Template', + 'col_period' => 'Period', + 'col_type' => 'Type', + 'col_coeff' => 'Coeff.', + 'col_datetime' => 'Date & time', + 'col_status' => 'Status', + 'col_progress' => 'Progress', + 'col_actions' => 'Actions', + 'time' => 'Time', + 'date_first' => 'Enter the date first', + 'validate' => 'Validate', + 'not_scheduled' => 'Not scheduled', + 'status_completed' => 'Completed', + 'status_scheduled' => 'Scheduled', + 'edit_reschedule' => 'Edit / reschedule', + 'delete_title' => 'Delete this schedule entry?', + 'delete_desc_1' => 'The evaluation ":template" in :subject will be removed from the schedule.', + 'delete_desc_warn' => ':count mark(s) entered will also be deleted. This action is irreversible.', + ], + + 'marks' => [ + 'head' => 'Marks — :name', + 'coeff_exam' => 'Exam coeff.', + 'coeff_subject' => 'Subject coeff.', + 'on' => 'Out of', + 'saving' => 'Saving...', + 'saved' => 'Saved!', + 'save_marks' => 'Save marks', + 'locked_title' => 'Evaluation closed', + 'locked_hint' => 'Mark entry is disabled since :date. Use the "Appeal" button to request a mark correction.', + 'students' => 'Students', + 'graded' => 'Graded', + 'absent' => 'Absent', + 'avg_saved' => 'Saved avg.', + 'grid_locked' => 'Saved marks (read-only)', + 'grid_entry' => 'Mark entry', + 'status_completed' => 'Completed', + 'status_scheduled' => 'Scheduled', + 'no_students' => 'No student enrolled in this class.', + 'col_matricule' => 'ID number', + 'col_student' => 'Student', + 'col_mark' => 'Mark /:max', + 'col_absent' => 'Absent', + 'col_comment' => 'Comment', + 'col_appeal' => 'Appeal', + 'absent_mark' => 'Absent', + 'comment_ph' => 'Comment...', + 'appeal' => 'Appeal', + 'locked_footer' => 'Evaluation closed — entry is disabled. Click "Appeal" to request a correction.', + 'entered_count' => ':count mark(s) entered', + 'absent_count' => ':count absent', + 'of_students' => '/ :total students', + ], + + 'evaluations' => [ + 'index' => [ + 'head' => 'Evaluations by class', + 'title' => 'Evaluations by class', + 'subtitle' => 'Evaluations generated from templates, by class and subject', + 'manage_templates' => 'Manage templates', + 'search_ph' => 'Class, subject, template...', + 'class' => 'Class', + 'all_classes' => 'All classes', + 'subject' => 'Subject', + 'all_subjects' => 'All subjects', + 'type' => 'Type', + 'all_types' => 'All types', + 'period' => 'Period', + 'all_periods' => 'All periods', + 'status' => 'Status', + 'all_statuses' => 'All statuses', + 'scheduled' => 'Scheduled', + 'completed' => 'Completed', + 'scheduling' => 'Scheduling', + 'all' => 'All', + 'with_date' => 'With date', + 'without_date' => 'Without date', + 'filter' => 'Filter', + 'active_filters' => ':count active filter(s)', + 'reset' => 'Reset', + 'col_template' => 'Template', + 'col_class' => 'Class', + 'col_subject' => 'Subject', + 'col_period' => 'Period', + 'col_coeff' => 'Coeff.', + 'col_date' => 'Date', + 'col_status' => 'Status', + 'col_progress' => 'Progress', + 'col_actions' => 'Actions', + 'empty' => 'No evaluation. Create templates and deploy them per class.', + 'locked' => 'Closed', + 'marks' => 'Marks', + 'lock' => 'Close', + 'unlock' => 'Reopen', + 'total' => ':count evaluation(s) in total', + 'delete_title' => 'Delete this evaluation?', + 'delete_desc' => 'All associated marks will be lost.', + ], + + 'show' => [ + 'head' => ':template — :class', + 'students' => 'Students', + 'graded' => 'Graded', + 'absent' => 'Absent', + 'avg' => 'Average /:max', + 'status_completed' => 'Completed', + 'status_scheduled' => 'Scheduled', + 'enter_marks' => 'Enter marks', + 'min_label' => 'Min:', + 'max_label' => 'Max:', + 'avg_label' => 'Average:', + 'marks_entered' => 'Marks entered', + 'no_marks' => 'No mark entered yet.', + 'col_matricule' => 'ID number', + 'col_student' => 'Student', + 'col_mark' => 'Mark', + 'col_comment' => 'Comment', + 'absent_mark' => 'Absent', + ], + ], +]; diff --git a/lang/en/menu.php b/lang/en/menu.php new file mode 100644 index 00000000..5c7fb621 --- /dev/null +++ b/lang/en/menu.php @@ -0,0 +1,82 @@ + 'Dashboard', + 'calendar' => 'Calendar', + + 'students' => 'Students', + 'students_list' => 'Students', + 'enrollments' => 'Enrollments & Payments', + 'roster' => 'Rosters / Lists', + 'promotion' => 'Class promotion', + 'student_stats' => 'Student statistics', + 'student_scholarships' => 'Student scholarships', + 'timetable' => 'Timetable', + + 'attendance' => 'Attendance', + 'attendance_entry' => 'Take attendance', + 'attendance_stats' => 'Statistics', + 'absence_permissions' => 'Absence requests', + + 'exams' => 'Exams', + 'evaluation_templates' => 'Evaluation templates', + 'evaluations' => 'Class evaluations', + 'exam_planning' => 'Exam planning', + 'official_exams' => 'Official exams', + + 'grades' => 'Grades', + 'grades_entry' => 'Grade entry', + 'bulletins' => 'Report cards', + 'bulletin_template' => 'Report card template', + 'note_reclamations' => 'Grade claims', + + 'accounting' => 'Accounting', + 'accounting_overview' => 'Overview', + 'transactions' => 'Transaction journal', + 'new_expense' => 'New expense', + 'situation' => 'Status by class', + 'cash_accounts' => 'Cash accounts', + 'verify_receipt' => 'Verify a receipt', + + 'payroll' => 'Staff & Payroll', + 'payroll_overview' => 'Overview', + 'personnel' => 'Staff', + 'pay_runs' => 'Pay runs', + 'salary_grades' => 'Salary grades', + 'salary_components' => 'Pay components', + 'payroll_settings' => 'Settings (CNSS, ITS)', + + 'archives' => 'Archives', + 'archived_documents' => 'Archived documents', + 'tags' => 'Tags', + + 'administration' => 'Administration', + 'users' => 'Users', + 'roles' => 'Roles', + 'permissions' => 'Permissions', + 'subject_assignments' => 'Assignments', + 'audit_logs' => 'Audit log', + 'portal_access' => 'Portal access', + + 'statistics' => 'Statistics', + + 'settings' => 'Settings', + 'school' => 'School', + 'classrooms' => 'Classes', + 'classroom_types' => 'Class types', + 'subjects' => 'Subjects', + 'countries' => 'Countries', + 'evaluation_types' => 'Evaluation types', + 'academic_years' => 'Academic years', + 'academic_periods' => 'Academic periods', + 'fee_categories' => 'Fee categories', + 'fee_structures' => 'Fee structures', + 'scholarships' => 'Scholarships', + 'grading_configs' => 'Grade calculation', + 'file_storage' => 'Files & Storage', + 'document_templates' => 'Document templates', + 'backups' => 'Backups', + 'about' => 'About', + + 'my_account' => 'My account', +]; diff --git a/lang/en/notes.php b/lang/en/notes.php new file mode 100644 index 00000000..822e23ff --- /dev/null +++ b/lang/en/notes.php @@ -0,0 +1,193 @@ + [ + 'index' => [ + 'head' => 'Mark entry', + 'year' => 'School year: :year', + 'no_year' => 'No active school year', + 'saving' => 'Saving...', + 'saved' => 'Saved!', + 'save' => 'Save marks', + 'select_class' => 'Select a class', + 'select_subject' => 'Select a subject', + 'select_period' => 'Select a period', + 'current' => 'current', + 'coef' => 'coef. :n', + 'no_subjects' => 'No subject assigned to this class for the current year.', + 'stat_students' => 'Enrolled students', + 'stat_graded' => 'Marks entered', + 'stat_average' => 'Class average', + 'stat_rate' => 'Entry rate', + 'subheader' => ':period — Coefficient :coef — Marks out of 20', + 'col_matricule' => 'ID number', + 'col_name' => 'Last & First name', + 'col_mark' => 'Mark /20', + 'col_comment' => 'Comment', + 'col_bulletin' => 'Report card', + 'comment_ph' => 'Comment...', + 'bulletin' => 'Report card', + 'empty_class' => 'No student enrolled in this class', + 'empty_class_hint' => 'Enroll students to enter their marks.', + 'initial' => 'Select a class to begin', + 'initial_hint' => 'Then choose the subject and term.', + ], + + 'student' => [ + 'head' => 'Report card — :name', + 'matricule_label' => 'ID number:', + 'class_label' => 'Class:', + 'general_average' => 'Overall average', + 'rank' => 'Rank', + 'mention' => 'Mention', + 'graded_subjects' => 'Graded subjects', + 'transcript' => 'Transcript — :period', + 'total_coef' => 'Total coefficients: :total', + 'col_subject' => 'Subject', + 'col_coef' => 'Coef.', + 'col_mark' => 'Mark /20', + 'col_weighted' => 'Weighted', + 'col_rank' => 'Rank', + 'col_comment' => 'Comment', + 'no_subjects' => 'No subject assigned to this class.', + 'not_enrolled' => 'Student not enrolled this year', + 'not_enrolled_hint' => 'This student has no active enrollment for the current school year.', + 'ord_first' => '1st', + 'ord_suffix' => 'th', + ], + ], + + 'bulletins' => [ + 'index' => [ + 'head' => 'Report cards', + 'subtitle' => 'Generate and freeze report cards by class and period', + 'download_class' => 'Download whole class (:count)', + 'choose_class' => 'Choose a class', + 'choose_period' => 'Choose a period', + 'current' => 'current', + 'validate_hint' => 'Validating freezes the report cards (marks, ranks, mentions) and enables PDF download.', + 'validated_count' => ':count/:total validated', + 'observations_ph' => 'Head of school observations (applied to the whole class, optional)', + 'revalidate' => 'Re-validate the class report cards', + 'validate' => 'Validate the class report cards', + 'revalidate_note' => 'Re-validating recalculates marks and ranks but keeps your manual edits (comments, observations, decision, discipline).', + 'confirm_title' => 'Re-validate :count already frozen report card(s)?', + 'confirm_desc' => 'Marks, averages and ranks will be recalculated. By default, your manual edits (comments, observations, decision, discipline) are kept.', + 'regen_all' => 'Regenerate all', + 'regen_all_desc' => '— start from scratch and erase manual edits.', + 'regen_action' => 'Regenerate (erases edits)', + 'revalidate_action' => 'Re-validate (keeps edits)', + 'devalidate_title' => "Invalidate :name's report card?", + 'devalidate_desc' => 'The frozen report card will be deleted, including its manual edits. The student will return to "draft" and can be re-validated. This action is irreversible.', + 'devalidate' => 'Invalidate', + 'col_student' => 'Student', + 'col_average' => 'Average', + 'col_rank' => 'Rank', + 'col_mention' => 'Mention', + 'col_status' => 'Status', + 'col_bulletin' => 'Report card', + 'empty' => 'No active student in this class.', + 'status_validated' => 'Validated', + 'status_draft' => 'Draft', + 'edit' => 'Edit', + 'devalidate_label' => 'Invalidate this report card', + 'ord_first' => '1st', + 'ord_suffix' => 'th', + ], + + 'edit' => [ + 'head' => 'Edit report card', + 'matricule_label' => 'ID number', + 'saving' => 'Saving…', + 'appreciations' => 'Comments by subject', + 'col_subject' => 'Subject', + 'col_average' => 'Average', + 'col_appreciation' => 'Comment', + 'appreciation_ph' => "Teacher's comment", + 'discipline' => 'Discipline & decision', + 'lateness' => 'Lateness (auto)', + 'absences' => 'Absences (auto)', + 'punishments' => 'Punishments', + 'exclusions' => 'Exclusions', + 'decision' => 'Council decision', + 'decision_ph' => 'E.g. Honor roll, Congratulations, Warning…', + 'observations' => 'Head of school observations', + 'observations_ph' => 'Observations', + ], + ], + + 'reclamations' => [ + 'status' => [ + 'pending' => 'Pending', + 'approved' => 'Approved', + 'rejected' => 'Rejected', + ], + + 'index' => [ + 'head' => 'Mark appeals', + 'subtitle' => 'Correction requests on closed evaluations', + 'pending_badge' => ':count awaiting processing', + 'all_statuses' => 'All statuses', + 'status_pending' => 'Pending', + 'status_approved' => 'Approved', + 'status_rejected' => 'Rejected', + 'col_date' => 'Date', + 'col_student' => 'Student', + 'col_evaluation' => 'Evaluation', + 'col_requested_by' => 'Requested by', + 'col_original' => 'Orig. score', + 'col_requested' => 'Req. score', + 'col_status' => 'Status', + 'col_action' => 'Action', + 'empty' => 'No appeal.', + 'process' => 'Process', + 'view' => 'View', + 'total' => ':count appeal(s)', + ], + + 'create' => [ + 'head' => 'File an appeal', + 'subtitle' => 'Correction of a mark on a closed evaluation', + 'locked' => 'Closed evaluation', + 'on_max' => 'Out of :max', + 'student_concerned' => 'Student concerned', + 'pending_exists' => 'Appeal already in progress', + 'pending_exists_hint' => 'An appeal is already awaiting processing for this student.', + 'current_score' => 'Current mark (recorded)', + 'requested_score' => 'Requested mark', + 'reason' => 'Reason for the appeal *', + 'reason_ph' => 'Describe the reason for the appeal (entry error, justified absence, etc.)', + 'submitting' => 'Sending...', + 'submit' => 'Submit appeal', + 'view_reclamations' => 'View appeals', + ], + + 'show' => [ + 'head' => 'Appeal — :name', + 'title' => 'Appeal details', + 'filed_on' => 'Filed on :date', + 'student' => 'Student', + 'evaluation' => 'Evaluation', + 'score_registered' => 'Recorded mark', + 'score_requested' => 'Requested mark', + 'score_corrected' => 'Corrected mark', + 'reason_label' => 'Reason for the appeal', + 'requested_by' => 'Requested by :name', + 'decision' => 'Decision — :status', + 'reviewed_by' => 'By :name', + 'process_title' => 'Process the appeal', + 'approve' => 'Approve', + 'reject' => 'Reject', + 'approving' => 'Approving the appeal', + 'rejecting' => 'Rejecting the appeal', + 'corrected_score_label' => 'Corrected mark *', + 'internal_note' => 'Internal note (optional)', + 'reject_reason' => 'Rejection reason', + 'internal_note_ph' => 'Possible remarks...', + 'reject_reason_ph' => 'Explain why the appeal is rejected...', + 'saving' => 'Saving...', + 'confirm_approve' => 'Confirm approval', + 'confirm_reject' => 'Confirm rejection', + ], + ], +]; diff --git a/lang/en/paie.php b/lang/en/paie.php new file mode 100644 index 00000000..c5ed24be --- /dev/null +++ b/lang/en/paie.php @@ -0,0 +1,130 @@ + [ + 1 => 'January', + 2 => 'February', + 3 => 'March', + 4 => 'April', + 5 => 'May', + 6 => 'June', + 7 => 'July', + 8 => 'August', + 9 => 'September', + 10 => 'October', + 11 => 'November', + 12 => 'December', + ], + + 'status' => [ + 'draft' => 'Draft', + 'validated' => 'Validated', + 'paid' => 'Paid', + 'cancelled' => 'Cancelled', + ], + + 'line_type' => [ + 'earning' => 'Earning', + 'deduction' => 'Deduction', + ], + + 'index' => [ + 'head' => 'Payroll runs', + 'subtitle' => 'Generate, validate and disburse monthly payroll.', + 'new' => 'New run', + 'search_ph' => 'Reference, label...', + 'all_statuses' => 'All statuses', + 'all_years' => 'All years', + 'all_months' => 'All months', + 'search' => 'Search', + 'reset' => 'Reset', + 'count' => ':count run(s)', + 'filtered' => '— filtered', + 'per_page' => 'Rows per page:', + 'col_period' => 'Period', + 'col_reference' => 'Reference', + 'col_payslips' => 'Payslips', + 'col_net' => 'Total net', + 'col_status' => 'Status', + 'col_actions' => 'Actions', + 'empty' => 'No payroll run', + 'view' => 'View details', + 'delete' => 'Delete the run', + 'range' => ':from–:to of :total', + 'delete_title' => 'Delete this payroll run?', + 'delete_desc_prefix' => '":period" and its :count payslip(s) will be permanently deleted. ', + 'delete_desc_paid' => 'As the run is paid, the cash accounts will be credited back and the accounting entries deleted.', + 'delete_desc_irreversible' => 'This action is irreversible.', + 'cancel' => 'Cancel', + 'delete_btn' => 'Delete', + ], + + 'create' => [ + 'head' => 'New payroll run', + 'title' => 'New payroll run', + 'subtitle' => 'A payslip will be generated for each active employee.', + 'period' => 'Period', + 'month' => 'Month *', + 'year' => 'Year *', + 'label' => 'Label (optional)', + 'label_ph' => 'E.g. Year-end payroll', + 'employees_info' => ':count active employee(s) — one payslip per employee.', + 'components_info' => ':count automatic item(s) applied.', + 'draft_note' => 'You can adjust each payslip while the run is a draft.', + 'generating' => 'Generating...', + 'generate' => 'Generate the run', + 'cancel' => 'Cancel', + 'no_employees' => 'Add active employees first.', + ], + + 'show' => [ + 'head' => 'Payroll :period', + 'title' => 'Payroll :period', + 'disbursed_on' => ' — disbursed from :account', + 'validate' => 'Validate', + 'cancel' => 'Cancel', + 'pay' => 'Pay', + 'cancel_pay' => 'Cancel the payroll', + 'delete' => 'Delete', + 'total_earnings' => 'Total earnings', + 'total_deductions' => 'Total deductions', + 'net_mass' => 'Net payroll mass', + 'payslips_count' => ':count payslip(s)', + 'search_ph' => 'Search for an employee…', + 'col_employee' => 'Employee', + 'col_earnings' => 'Earnings', + 'col_deductions' => 'Deductions', + 'col_net' => 'Net to pay', + 'empty' => 'No payslip found.', + 'download_payslip' => 'Download the payslip', + 'edit' => 'Edit', + 'range' => ':from–:to of :total', + 'editor_title' => 'Payslip — :name', + 'line_label_ph' => 'Label', + 'remove' => 'Remove', + 'add_line' => 'Add a line', + 'net' => 'Net: :amount', + 'cancel_btn' => 'Cancel', + 'save' => 'Save', + 'pay_title' => 'Disburse the payroll', + 'pay_amount' => 'Net mass to disburse: :amount.', + 'cash_account' => 'Cash account *', + 'select_cash' => 'Select a cash account', + 'cash_balance' => ':name — balance :balance', + 'over_balance' => 'The cash account balance is insufficient: it will go negative.', + 'confirm_payment' => 'Confirm payment', + 'confirm_validate_title' => 'Validate this run?', + 'confirm_cancel_title' => 'Cancel this run?', + 'confirm_validate_desc' => 'The payslips will no longer be editable. You can then disburse the payroll.', + 'confirm_cancel_paid_desc' => 'The payroll will be cancelled: the cash accounts will be credited back and the accounting entries deleted.', + 'confirm_cancel_desc' => 'The run will be cancelled.', + 'back' => 'Back', + 'validate_action' => 'Validate', + 'cancel_action' => 'Cancel the run', + 'delete_title' => 'Delete this payroll run?', + 'delete_desc_prefix' => '":period" and its :count payslip(s) will be permanently deleted.', + 'delete_desc_paid' => 'As the run is paid, the cash accounts will be credited back and the accounting entries deleted.', + 'delete_desc_irreversible' => 'This action is irreversible.', + 'delete_btn' => 'Delete', + ], +]; diff --git a/lang/en/parametres.php b/lang/en/parametres.php new file mode 100644 index 00000000..672e0ad8 --- /dev/null +++ b/lang/en/parametres.php @@ -0,0 +1,1092 @@ + [ + 'view' => 'View', + 'edit' => 'Edit', + 'delete' => 'Delete', + 'cancel' => 'Cancel', + 'save' => 'Save', + 'search' => 'Search', + 'clear' => 'Clear', + 'prev' => 'Previous', + 'next' => 'Next', + 'showing' => 'Showing :from to :to of :total', + 'actions' => 'Actions', + 'irreversible' => 'This action is irreversible.', + 'creating' => 'Creating...', + 'updating' => 'Updating...', + 'update' => 'Update', + 'created_at' => 'Created at', + 'created_on' => 'Created on', + 'updated_on' => 'Last updated', + ], + + 'countries' => [ + 'head' => 'Countries', + 'title' => 'Countries', + 'subtitle' => 'Manage the list of countries (nationalities, origins).', + 'new' => 'New country', + 'search_ph' => 'Search a country (name, code)…', + 'th_name' => 'Name', + 'th_code' => 'Code', + 'empty' => 'No country found', + 'delete_q' => 'Delete this country?', + 'create_head' => 'Create a country', + 'create_title' => 'Create a country', + 'create_subtitle' => 'Enter the country name and code.', + 'edit_head' => 'Edit :name', + 'edit_title' => 'Edit a country', + 'edit_subtitle' => 'Update the information for :name.', + 'form_section' => 'Country information', + 'name' => 'Name', + 'name_ph' => 'e.g. Togo', + 'code' => 'Code', + 'code_ph' => 'e.g. TG', + 'code_hint' => 'Unique code (e.g. ISO: TG, BJ, CI…).', + 'submit_create' => 'Create country', + ], + + 'schools' => [ + 'active' => 'Active', + 'inactive' => 'Inactive', + 'section_general' => 'General information', + 'terme_entity' => 'Term / Entity', + 'devise' => 'Motto', + 'email' => 'Email', + 'phone' => 'Phone', + 'section_location' => 'Location', + 'region' => 'Region', + 'city' => 'City', + 'po_box' => 'PO Box', + 'address' => 'Address', + 'section_events' => 'Events', + 'created_on' => 'Created on', + 'updated_on' => 'Updated on', + + 'create_head' => 'New school', + 'create_title' => 'Configure the school', + 'create_subtitle' => 'Enter the school information.', + 'edit_head' => 'Edit :name', + 'edit_title' => 'School settings', + 'edit_subtitle' => 'Information, logo, header and motto for :name.', + + 'section_identity' => 'School identity', + 'name' => 'Name', + 'name_ph' => 'e.g. Jean Jaurès High School', + 'code' => 'Code', + 'code_ph' => 'e.g. LJJ001', + 'terme' => 'Term / Parent entity', + 'terme_ph' => 'e.g. Togolese Republic', + 'ministry' => 'Supervising ministry', + 'ministry_ph' => 'e.g. Ministry of Primary, Secondary and Technical Education', + 'ministry_hint' => 'Shown at the top left of the official document header.', + 'devise_label' => 'School motto (slogan)', + 'devise_ph' => 'e.g. Work, Freedom, Homeland', + 'currency' => 'Currency (accounting)', + 'section_logo' => 'Logo and contact', + 'logo_label' => 'School logo', + 'change_logo' => 'Change logo', + 'choose_image' => 'Choose an image', + 'logo_hint' => 'JPG, PNG, WebP — max 2 MB', + 'email_label' => 'Email', + 'email_ph' => 'contact@school.com', + 'phone_label' => 'Phone', + 'phone_ph' => '+228 XX XX XX XX', + 'region_label' => 'Region', + 'city_label' => 'City', + 'po_box_label' => 'PO Box', + 'po_box_ph' => 'PO Box 123', + 'address_label' => 'Full address', + 'section_class_types' => 'Offered class types', + 'no_class_types' => 'No class type available. Create one first in Settings → Class types.', + 'semester' => 'Semester (2 terms)', + 'trimester' => 'Trimester (3 terms)', + 'section_status' => 'Status', + 'active_label' => 'Active school', + 'portal_label' => 'Parent / student portal access', + 'portal_hint' => 'When disabled, login and all parent/student area features are blocked.', + 'submit_create' => 'Create school', + ], + + 'about' => [ + 'head' => 'About', + 'intro' => 'School management system — from student enrollment to report cards, including tuition and payroll. Designed for Togolese schools 🇹🇬.', + 'section_covers' => 'What the application covers', + 'section_license' => 'Publisher & license', + 'edited_by' => 'Published by', + 'license' => 'License', + 'license_value' => 'GNU GPL v3 — free software', + 'rights' => 'Rights', + 'license_note' => 'You may use, study, modify and redistribute this software under the terms of the GPL v3.', + 'section_tech' => 'Technical information', + 'app_version' => 'Application version', + 'environment' => 'Environment', + 'section_support' => 'Need help?', + 'support_before' => 'First check the', + 'link_docs' => 'documentation', + 'support_middle' => '. To report a problem or suggest an improvement, open a ticket on the', + 'link_repo' => 'GitHub repository', + 'modules' => [ + 'students' => ['title' => 'Students & enrollment', 'desc' => 'Full records, enrollment, class promotion, scholarships.'], + 'grades' => ['title' => 'Grades & reports', 'desc' => 'Entry, configurable averages, PDF reports, appeals.'], + 'attendance' => ['title' => 'Attendance', 'desc' => 'Roll call per session, attendance statistics, absence permissions.'], + 'exams' => ['title' => 'Official exams', 'desc' => 'CEPD, BEPC, BAC: registrations, scheduling, results.'], + 'finance' => ['title' => 'Finances & recovery', 'desc' => 'Fees, invoices, payments (incl. Mobile Money), receipts.'], + 'payroll' => ['title' => 'Staff & payroll', 'desc' => 'Employee records, pay runs, payslips (CNSS, ITS).'], + 'timetable' => ['title' => 'Timetable & calendar', 'desc' => 'Grids per class, school calendar.'], + 'backups' => ['title' => 'Backups & statistics', 'desc' => 'Scheduled backups, dashboards and exports.'], + ], + 'links' => [ + 'docs' => ['title' => 'Documentation', 'sub' => 'A to Z guide'], + 'website' => ['title' => 'Website', 'sub' => 'Overview & features'], + 'source' => ['title' => 'Source code', 'sub' => 'GitHub repository'], + 'changelog' => ['title' => 'Changelog', 'sub' => 'History of changes'], + ], + ], + + 'evaluation_types' => [ + 'head' => 'Evaluation types', + 'title' => 'Evaluation types', + 'subtitle' => 'Manage evaluation types', + 'new' => 'New type', + 'stat_total' => 'Total types', + 'stat_shown' => 'Results shown', + 'stat_page' => 'Page', + 'search_ph' => 'Search a type...', + 'th_name' => 'Name', + 'th_description' => 'Description', + 'th_created' => 'Created on', + 'empty' => 'No type found', + 'delete_q' => 'Delete this type?', + + 'create_head' => 'Create an evaluation type', + 'create_title' => 'Create an evaluation type', + 'create_subtitle' => 'Enter the evaluation type information.', + 'edit_head' => 'Edit evaluation type', + 'edit_title' => 'Edit an evaluation type', + 'edit_subtitle' => 'Update the information for :name.', + + 'show_subtitle' => 'Evaluation type details.', + 'main_info' => 'Main information', + 'name' => 'Name', + 'description' => 'Description', + 'no_description' => 'No description provided for this evaluation type.', + 'history' => 'History', + 'created_on' => 'Created on', + 'updated_on' => 'Updated on', + + 'form_section' => 'Evaluation type information', + 'name_label' => 'Name *', + 'name_ph' => 'e.g. Supervised test', + 'category_label' => 'Category *', + 'category_ph' => 'Choose a category', + 'category_hint' => 'Determines whether grades feed the report card\'s "class grade" or "composition".', + 'cat_continu' => 'Continuous assessment (class grade)', + 'cat_composition' => 'Composition (exam)', + 'description_label' => 'Description', + 'description_ph' => 'Specify the educational context of this evaluation type...', + 'preview' => 'Preview', + 'preview_name' => 'Type name', + 'preview_no_description' => 'No description provided.', + 'submit_create' => 'Create type', + ], + + 'grading_configs' => [ + 'head' => 'Average calculation settings', + 'title' => 'Average calculation', + 'subtitle' => 'Customize the calculation formula per school', + 'new' => 'New configuration', + 'filter_all' => 'All schools', + 'th_name' => 'Name', + 'th_school' => 'School', + 'th_class_type' => 'Class type', + 'th_threshold' => 'Threshold', + 'th_max' => 'Max score', + 'th_round' => 'Rounding', + 'th_status' => 'Status', + 'all_default' => 'All (default)', + 'decimals' => ':n dec.', + 'active' => 'Active', + 'inactive' => 'Inactive', + 'activate' => 'Activate', + 'empty' => 'No configuration. Create one to customize average calculation.', + 'count' => ':n configuration(s)', + 'delete_q' => 'Delete this configuration?', + + 'create_head' => 'New calculation configuration', + 'create_title' => 'New configuration', + 'create_subtitle' => 'Define the average calculation formula (per class type)', + 'submit_create' => 'Create configuration', + 'edit_head' => 'Edit — :name', + 'edit_title' => 'Edit configuration', + 'saving' => 'Saving...', + + 'school_label' => 'School *', + 'school_ph' => 'Select a school', + 'class_type_label' => 'Class type', + 'all_classes' => 'All classes (school default)', + 'class_type_hint' => 'Leave "All classes" for a school default formula.', + 'name_label' => 'Name *', + 'name_ph' => 'e.g. Standard formula 2025', + 'passing_label' => 'Passing threshold', + 'max_label' => 'Default maximum score', + 'weight_label' => 'Class grade / Composition weighting', + 'class_weight_label' => 'Class grade (continuous assessment)', + 'comp_weight_label' => 'Composition', + 'pct_class' => 'Class', + 'pct_comp' => 'Composition', + 'formula' => 'Subject average = (Class × :cw + Composition × :pw) / :total.', + 'precision_label' => 'Rounding precision', + 'precision_0' => 'Integer (e.g. 12)', + 'precision_1' => '1 decimal (e.g. 12.5)', + 'precision_2' => '2 decimals (e.g. 12.50)', + 'precision_3' => '3 decimals (e.g. 12.500)', + 'mentions_label' => 'Honors (thresholds)', + 'add' => 'Add', + 'no_mentions' => 'No honors configured.', + 'mention_label_ph' => 'Label (e.g. Congratulations)', + 'mentions_hint' => 'The honor applied is the one with the highest threshold less than or equal to the average.', + ], + + 'subjects' => [ + 'head' => 'Subjects', + 'title' => 'Subjects', + 'subtitle' => 'Manage the subjects taught', + 'new' => 'New subject', + 'stat_total' => 'Total subjects', + 'stat_shown' => 'Results shown', + 'stat_page' => 'Page', + 'search_ph' => 'Search a subject by name or code...', + 'search_results' => 'Results for:', + 'th_code' => 'Code', + 'th_name' => 'Name', + 'th_description' => 'Description', + 'no_description' => 'No description', + 'empty' => 'No subject found', + 'page_showing' => 'Showing', + 'page_to' => 'to', + 'page_of' => 'of', + 'page_unit' => 'subjects', + 'page_label' => 'Page', + 'delete_title' => 'Delete subject', + 'delete_desc' => 'Are you sure you want to delete this subject? This action is irreversible.', + 'deleting' => 'Deleting...', + + 'create_head' => 'Create a subject', + 'create_title' => 'Create a subject', + 'create_subtitle' => 'Enter the subject information.', + 'edit_head' => 'Edit :name', + 'edit_title' => 'Edit a subject', + 'edit_subtitle' => 'Update the information for :name.', + + 'show_subtitle' => 'Subject detail and tracking.', + 'info_section' => 'Subject information', + 'description' => 'Description', + 'show_no_description' => 'No description provided for this subject.', + 'history' => 'History', + 'created_on' => 'Created on', + 'updated_on' => 'Last updated', + 'summary' => 'Summary', + 'summary_before' => 'The subject', + 'summary_middle' => 'is identified by the code', + 'summary_stats' => 'Usage statistics will be shown here as soon as they are available.', + 'edit_this' => 'Edit this subject', + + 'form_section' => 'Subject information', + 'code_label' => 'Code *', + 'code_ph' => 'e.g. MATH101', + 'code_hint' => 'Unique code used to identify the subject.', + 'name_label' => 'Name *', + 'name_ph' => 'e.g. Mathematics', + 'parent_label' => 'Parent subject (sub-subject)', + 'no_parent' => 'None (main subject)', + 'parent_hint' => 'Attach this subject to a main subject to make it a sub-subject (e.g. "Dictation" under "French").', + 'description_label' => 'Description', + 'description_ph' => 'Briefly describe the content and objectives of the subject...', + 'description_hint' => 'Optional, maximum 1000 characters.', + 'preview' => 'Preview', + 'preview_code' => 'CODE', + 'preview_name' => 'Subject name', + 'submit_create' => 'Create subject', + ], + + 'fee_categories' => [ + 'head' => 'Fee categories', + 'title' => 'Fee categories', + 'subtitle' => 'Manage school fee categories', + 'new' => 'New category', + 'stat_total' => 'Total categories', + 'search_ph' => 'Search by name or description...', + 'th_name' => 'Name', + 'th_description' => 'Description', + 'th_created' => 'Created date', + 'empty' => 'No category found', + 'delete_title' => 'Delete category', + 'delete_desc' => 'This action cannot be undone. Are you sure you want to delete this category?', + 'deleting' => 'Deleting...', + + 'create_head' => 'New fee category', + 'create_title' => 'Create a fee category', + 'edit_head' => 'Edit fee category', + 'edit_title' => 'Edit fee category', + 'identity' => 'Category identity', + 'name_label' => 'Name *', + 'name_ph' => 'e.g. Tuition fees, Canteen fees...', + 'description' => 'Description', + 'description_ph' => 'Describe this fee category...', + 'create' => 'Create', + + 'show_head' => 'Category: :name', + 'show_title' => 'Category detail', + 'name' => 'Name', + 'created_on' => 'Created on', + 'show_no_description' => 'No description provided', + 'updated_on' => 'Last updated', + 'id' => 'ID', + 'delete_desc_show' => 'This action cannot be undone. Are you sure you want to delete this fee category?', + ], + + 'classroom_types' => [ + 'head' => 'Class types', + 'title' => 'Class types', + 'subtitle' => 'Manage the available class types', + 'new' => 'New type', + 'stat_total' => 'Total types', + 'stat_active' => 'Active types', + 'stat_page' => 'Page', + 'search_ph' => 'Search a type...', + 'reset' => 'Reset', + 'th_name' => 'Name', + 'th_description' => 'Description', + 'th_status' => 'Status', + 'empty' => 'No type found', + 'active' => '✓ Active', + 'inactive' => 'Inactive', + 'page_showing_before' => 'Showing', + 'page_showing_to' => 'to', + 'page_showing_on' => 'of', + 'page_unit' => 'types', + 'page_label' => 'Page', + 'page_of' => 'of', + 'delete_title' => 'Delete type', + 'delete_desc' => 'Are you sure you want to delete this class type? This action is irreversible.', + 'deleting' => 'Deleting...', + + 'create_head' => 'New class type', + 'create_title' => 'Create a class type', + 'create_subtitle' => 'Enter the type information.', + 'edit_head' => 'Edit :name', + 'edit_title' => 'Edit a class type', + 'edit_subtitle' => 'Update the information for :name.', + + 'show_status' => 'Status', + 'show_active' => '✓ Active', + 'show_inactive' => 'Inactive', + 'linked_classrooms' => 'Linked classrooms', + 'created_on' => 'Created on', + 'updated_on' => 'Last updated', + 'classes_using' => 'Classrooms using this type', + 'classes_using_sub' => 'Showing all classrooms assigned to this type', + 'no_classroom' => 'No classroom uses this type yet', + 'create_classroom' => 'Create a classroom', + 'cr_name' => 'Name', + 'cr_code' => 'Code', + 'cr_capacity' => 'Capacity', + 'cr_status' => 'Status', + 'cr_action' => 'Action', + 'cr_places' => ':n seats', + 'cr_active' => '✓ Active', + 'cr_inactive' => 'Inactive', + 'cr_view' => 'View', + + 'form_section' => 'Type information', + 'name_label' => 'Name *', + 'name_ph' => 'e.g. General class', + 'description_label' => 'Description', + 'description_ph' => 'Class type description...', + 'period_label' => 'Period system *', + 'period_trimestre' => 'Trimester (3 periods)', + 'period_semestre' => 'Semester (2 periods)', + 'period_hint' => 'Determines how the year is split (report cards, assessments) for this class type.', + 'status_section' => 'Status', + 'active_label' => 'Active type', + 'submit_create' => 'Create type', + ], + + 'scholarships' => [ + 'head' => 'Scholarships', + 'title' => 'Scholarships', + 'subtitle' => 'Manage tuition fee reductions', + 'new' => 'New scholarship', + 'stat_total' => 'Total scholarships', + 'stat_percentage' => 'Percentage scholarships', + 'stat_fixed' => 'Fixed amounts', + 'search_ph' => 'Search a scholarship...', + 'search_results' => 'Results for:', + 'th_name' => 'Name', + 'th_type' => 'Type', + 'th_value' => 'Value', + 'th_created' => 'Created date', + 'empty' => 'No scholarship found', + 'type_percentage' => 'Percentage', + 'type_fixed' => 'Fixed amount', + 'page_showing' => 'Showing', + 'page_to' => 'to', + 'page_of' => 'of', + 'page_unit' => 'scholarships', + 'page_label' => 'Page', + 'delete_title' => 'Delete scholarship', + 'delete_desc' => 'Are you sure you want to delete this scholarship? This action is irreversible.', + 'deleting' => 'Deleting...', + + 'create_head' => 'Create a scholarship', + 'create_title' => 'Create a new scholarship', + 'edit_head' => 'Edit :name', + 'edit_title' => 'Edit :name', + 'form_section' => 'Scholarship information', + 'name_label' => 'Name *', + 'name_ph' => 'e.g. Excellence scholarship', + 'type_label' => 'Type *', + 'type_ph' => 'Select a type', + 'value_label_pct' => 'Value (%) *', + 'value_label_fixed' => 'Value (amount) *', + 'value_ph_pct' => 'e.g. 15', + 'value_ph_fixed' => 'e.g. 100.00', + 'description' => 'Description', + 'description_ph' => 'Describe the scholarship...', + 'submit_create' => 'Create scholarship', + + 'show_value' => 'Value', + 'show_description' => 'Description', + 'show_no_description' => 'No description available', + 'history' => 'History', + 'created_on' => 'Created on', + 'updated_on' => 'Last updated', + 'summary' => 'Summary', + 'current_value' => 'Current value', + ], + + 'bulletin_templates' => [ + 'head' => 'Report card template', + 'title' => 'Report card template', + 'subtitle_before' => 'Build the report card columns for :name. The', + 'subtitle_after' => 'token becomes "Term." or "Sem." depending on the class type.', + 'load_preset' => 'Load a preset…', + 'saving' => 'Saving…', + 'columns' => 'Columns (:n)', + 'total_width' => 'Total width: :w%', + 'default_column' => 'Column', + 'label_ph' => 'Label', + 'type_prefix' => 'Type: :name', + 'add_column' => 'Add a column', + 'footer' => 'Report card footer', + 'opt_class_stats' => 'Show class statistics (highest / lowest / overall average)', + 'opt_period_recap' => 'Show inter-period recap + annual average', + 'opt_discipline' => 'Show the discipline block (lateness, absences, board decision)', + 'signatory_left' => 'Signatory (left)', + 'signatory_right' => 'Signatory (right)', + 'legal_note' => 'Legal notice (N.B.)', + ], + + 'file_storage' => [ + 'head' => 'Files & Storage', + 'title' => 'Files & Storage', + 'subtitle' => 'Configure where uploaded files (logos, documents) will be stored.', + 'toast_saved_title' => 'Configuration saved', + 'toast_saved_desc' => 'Storage settings have been updated.', + 'toast_error_title' => 'Error', + 'toast_error_desc' => 'Check the fields and try again.', + 'toast_test_ok_title' => 'Connection successful', + 'toast_test_ok_desc' => 'The ":driver" disk is accessible and working.', + 'toast_test_fail_title' => 'Connection failed', + 'toast_unknown_error' => 'Unknown error.', + 'toast_unreachable' => 'Unable to reach the storage.', + 'driver_local' => 'Local storage', + 'driver_local_desc' => 'Files on the Laravel server (public/storage)', + 'driver_s3' => 'S3 cloud storage', + 'driver_s3_desc' => 'AWS S3, Cloudflare R2, MinIO or S3-compatible', + 's3_section' => 'S3 / Cloudflare R2 configuration', + 'access_key' => 'Access Key', + 'secret_key' => 'Secret Key', + 'keep_hint' => '(leave empty to keep)', + 'bucket' => 'Bucket *', + 'region' => 'Region *', + 'region_ph' => 'auto (R2) or eu-west-1 (AWS)', + 'endpoint' => 'Endpoint', + 'endpoint_hint' => '(R2/MinIO — leave empty for AWS)', + 'public_url' => 'Public URL', + 'public_url_hint' => '(CDN or custom domain)', + 'r2_title' => 'Cloudflare R2 (recommended)', + 'r2_region' => '• Region:', + 'r2_endpoint' => '• Endpoint:', + 'r2_bandwidth' => '• No egress bandwidth fees (ideal for Africa)', + 'local_active' => 'Local storage active', + 'local_active_before' => 'Files are stored in', + 'local_active_middle' => 'and served via', + 'test_connection' => 'Test connection', + 'save' => 'Save', + ], + + 'backups' => [ + 'head' => 'Backups', + 'title' => 'Backups', + 'subtitle' => 'Back up the database (JSON / SQL) and schedule automatic backups.', + 'size_mb' => ':n MB', + 'size_kb' => ':n KB', + 'day_1' => 'Monday', + 'day_2' => 'Tuesday', + 'day_3' => 'Wednesday', + 'day_4' => 'Thursday', + 'day_5' => 'Friday', + 'day_6' => 'Saturday', + 'day_7' => 'Sunday', + 'dest_label' => 'Destination:', + 'dest_remote' => 'Remote repository (S3 / R2)', + 'dest_local' => 'Local server storage', + 'dest_remote_desc' => 'Backups are sent to your configured cloud storage.', + 'dest_local_desc' => 'Configure S3/R2 storage in "Files & Storage" to externalize backups.', + 'manual_title' => 'Back up now', + 'include_media' => 'Include media (documents, photos)', + 'running' => 'Backing up…', + 'run_backup' => 'Run backup', + 'manual_hint_before' => 'With media, the backup is produced in', + 'manual_hint_after' => 'format (database + uploaded files). Otherwise, only the database is backed up.', + 'archive_title' => 'Archive a school year', + 'archive_desc_before' => 'Generates a complete', + 'archive_desc_locked' => 'locked', + 'archive_desc_after' => 'snapshot kept long-term (excluded from automatic retention). The archive is also created automatically when a year is closed.', + 'academic_year' => 'School year', + 'choose_year' => 'Choose a year…', + 'already_archived' => ' — already archived', + 'archiving' => 'Archiving…', + 'archive_year' => 'Archive the year', + 'schedule_title' => 'Automatic scheduling', + 'frequency' => 'Frequency', + 'freq_none' => 'Disabled', + 'freq_daily' => 'Daily', + 'freq_weekly' => 'Weekly', + 'time' => 'Time', + 'day' => 'Day', + 'retention' => 'Retention (number kept)', + 'formats_generated' => 'Generated formats', + 'schedule_hint_before' => 'Scheduling requires the Laravel scheduler to run on the server', + 'schedule_hint_after' => 'via cron, every minute).', + 'save_schedule' => 'Save schedule', + 'restore_title' => 'Restore the database', + 'restore_warn_before' => 'Restoring', + 'restore_warn_bold' => 'overwrites current data', + 'restore_warn_after' => 'with the file content. A safety backup (JSON) is created automatically before the operation.', + 'restoring' => 'Restoring…', + 'restore' => 'Restore', + 'restore_one_table' => 'Restore a single table (optional)', + 'all_tables' => 'Whole database', + 'restore_table_hint_before' => 'Table-targeted restore is only available with', + 'restore_table_hint_after' => '(or .json.gz) files and only overwrites the chosen table.', + 'accepted_formats' => 'Accepted formats: .json, .sql, .gz (compressed), .dump (PostgreSQL) or .zip (database + media) from this application — max 200 MB.', + 'history_title' => 'Backup history', + 'th_file' => 'File', + 'th_format' => 'Format', + 'th_size' => 'Size', + 'th_origin' => 'Origin', + 'th_status' => 'Status', + 'th_date' => 'Date', + 'empty' => 'No backup yet.', + 'badge_archive' => 'Archive', + 'badge_media' => '+ media', + 'origin_scheduled' => 'Scheduled', + 'origin_manual' => 'Manual', + 'status_ok' => 'OK', + 'status_fail' => 'Failed', + 'verify_integrity' => 'Verify integrity', + 'restore_confirm_title' => 'Confirm restore?', + 'restore_confirm_desc' => 'All current data will be replaced by the content of ":name". A safety backup is created first. This action is irreversible.', + 'restore_now' => 'Restore now', + 'delete_title' => 'Delete this backup?', + 'delete_desc' => 'The file will be permanently deleted from storage.', + ], + + 'academic_years' => [ + 'head' => 'Academic years', + 'title' => 'Academic years', + 'subtitle' => 'Manage academic years', + 'new' => 'New year', + 'stat_total' => 'Total years', + 'stat_active' => 'Active years', + 'stat_page' => 'Page', + 'search_ph' => 'Search for a year...', + 'th_year' => 'Year', + 'th_start' => 'Start date', + 'th_end' => 'End date', + 'th_status' => 'Status', + 'status_active' => 'Active', + 'status_inactive' => 'Inactive', + 'empty' => 'No academic year found', + 'delete_title' => 'Confirm deletion', + 'delete_desc' => 'Are you sure you want to delete this academic year? This action is irreversible.', + 'deleting' => 'Deleting...', + 'create_head' => 'Create an academic year', + 'create_title' => 'Create a new academic year', + 'edit_head' => 'Edit :year', + 'edit_title' => 'Edit :year', + 'section_period' => 'Academic period', + 'label_year' => 'Year', + 'year_ph' => 'e.g. 2025-2026', + 'label_start' => 'Start date', + 'label_end' => 'End date', + 'section_activation' => 'Activation', + 'active_label' => 'Active year', + 'active_hint' => 'Check this box if it is the current academic year', + 'submit_create' => 'Create academic year', + 'info_title' => 'Academic year information', + 'field_status' => 'Status', + 'field_status_active' => 'Active', + 'field_status_inactive' => 'Inactive', + 'period_title' => 'Period', + 'period_from' => 'From', + 'period_to' => 'To', + 'history_title' => 'History', + 'stats_title' => 'Statistics', + 'stats_classes' => 'Classes', + 'stats_students' => 'Enrolled students', + 'stats_teachers' => 'Teachers', + 'state_title' => 'State', + 'state_active_title' => 'Current year', + 'state_active_desc' => 'This year is currently active', + 'state_inactive_title' => 'Inactive year', + 'state_inactive_desc' => 'This year is not active', + ], + + 'academic_periods' => [ + 'head' => 'Academic periods', + 'title' => 'Academic periods', + 'subtitle' => 'Manage academic periods (terms and semesters)', + 'new' => 'New period', + 'stat_total' => 'Total periods', + 'stat_active' => 'Active periods', + 'stat_page' => 'Page', + 'search_ph' => 'Search for a period...', + 'th_name' => 'Name', + 'th_year' => 'Year', + 'th_type' => 'Type', + 'th_order' => 'Order', + 'th_period' => 'Period', + 'th_status' => 'Status', + 'type_trimester' => 'Term', + 'type_semester' => 'Semester', + 'status_active' => 'Active', + 'status_inactive' => 'Inactive', + 'empty' => 'No academic period found', + 'delete_title' => 'Confirm deletion', + 'delete_desc' => 'Are you sure you want to delete this academic period? This action is irreversible.', + 'deleting' => 'Deleting...', + 'create_head' => 'Create an academic period', + 'create_title' => 'Create an academic period', + 'create_subtitle' => 'Set the information for the new period.', + 'edit_head' => 'Edit :name', + 'edit_title' => 'Edit period', + 'section_identification' => 'Identification', + 'label_name' => 'Name', + 'name_ph' => 'e.g. Term 1, Semester 1...', + 'label_year' => 'Academic year', + 'year_ph' => 'Select a year', + 'section_classification' => 'Classification', + 'label_type' => 'Type', + 'label_order' => 'Order', + 'order_ph' => '1, 2, 3...', + 'label_class_type' => 'Class type', + 'class_type_all' => 'All classes (global)', + 'class_type_hint' => 'Leave "global" to apply to all class types.', + 'label_weight' => 'Weight (annual average)', + 'section_calendar' => 'Calendar', + 'label_start' => 'Start date', + 'label_end' => 'End date', + 'section_details' => 'Details & Status', + 'label_description' => 'Description', + 'description_ph' => 'Period description...', + 'active_label' => 'Active period (current)', + 'submit_create' => 'Create period', + 'saving' => 'Saving...', + 'submit_update' => 'Save changes', + 'info_title' => 'Period information', + 'system_title' => 'System information', + 'label_id' => 'Identifier', + 'about_title' => 'About', + 'about_desc' => 'Academic periods divide the school year into terms or semesters to organize assessment and student tracking.', + ], + + 'classrooms' => [ + 'head' => 'Classes', + 'title' => 'Classes', + 'subtitle' => 'Manage your school\'s classes', + 'new' => 'New class', + 'stat_total' => 'Total classes', + 'stat_active' => 'Active classes', + 'stat_page' => 'Page', + 'search_ph' => 'Search for a class...', + 'reset' => 'Reset', + 'th_name' => 'Name', + 'th_code' => 'Code', + 'th_type' => 'Type', + 'th_capacity' => 'Capacity', + 'empty' => 'No class found', + 'assign_subjects' => 'Assign subjects', + 'showing_sep' => 'of', + 'showing_unit' => 'classes', + 'delete_title' => 'Delete class', + 'delete_desc' => 'Are you sure you want to delete this class? This action is irreversible.', + 'deleting' => 'Deleting...', + 'create_head' => 'New class', + 'create_title' => 'Create a class', + 'create_subtitle' => 'Enter the class information.', + 'edit_head' => 'Edit :name', + 'edit_title' => 'Edit a class', + 'edit_subtitle' => 'Update the information for :name.', + 'form_info' => 'Class information', + 'label_name' => 'Name', + 'name_ph' => 'e.g. 6th A', + 'label_code' => 'Code', + 'code_ph' => 'e.g. 6A-2026', + 'label_capacity' => 'Capacity', + 'label_expected_age' => 'Expected age', + 'expected_age_ph' => 'e.g. 12', + 'expected_age_hint' => 'Official expected age (basis for over-age calculation).', + 'label_type' => 'Class type', + 'type_none' => 'No type', + 'preview' => 'Preview', + 'preview_text_before' => 'This class will accommodate up to', + 'preview_text_after' => 'students.', + 'submit_create' => 'Create class', + 'creating' => 'Creating...', + 'show_code' => 'Code', + 'show_capacity' => 'Capacity', + 'show_capacity_hint' => 'available seats', + 'show_type' => 'Class type', + 'show_created_on' => 'Created on', + 'show_updated_on' => 'Updated on', + 'subjects_title' => 'Subjects and Coefficients', + 'subjects_summary' => ':n subject(s) • Total coefficients: :total', + 'select_year_ph' => 'Select a year', + 'coef' => 'Coef.', + 'no_assignments' => 'No subject assigned for this academic year', + // Assignments + 'assign_head' => 'Assignments - :name', + 'assign_title' => 'Assign subjects', + 'assign_class_before' => 'Class:', + 'assign_year_section' => 'Academic year', + 'assign_year_ph' => 'Select an academic year', + 'assign_subjects_section' => 'Subjects and coefficients', + 'assign_search_ph' => 'Search for a subject...', + 'assign_coef_ph' => 'Coef', + 'assign_selected_count' => ':n subject(s) selected', + 'assign_submit' => 'Save assignments', + 'assign_saving' => 'Saving...', + 'err_year_required' => 'The academic year is required.', + 'err_subjects_required' => 'Please select at least one subject.', + 'err_coef_invalid' => 'Invalid coefficient (must be > 0).', + ], + + 'documents' => [ + // Index + 'head' => 'Document templates', + 'title' => 'Document templates', + 'subtitle' => 'Manage certificate, attestation and report card templates exportable to PDF.', + 'btn_header' => 'Header', + 'btn_registry' => 'Registry', + 'btn_new' => 'New template', + 'cat_empty' => 'No template in this category.', + 'badge_default' => 'Default', + 'status_active' => '● Active', + 'status_inactive' => '● Inactive', + 'delete_title' => 'Delete this template?', + 'delete_desc' => 'This action is irreversible. Documents already issued are not affected.', + // Registry + 'registry_head' => 'Document registry', + 'registry_title' => 'Document registry', + 'registry_subtitle' => 'Traceability of all issued documents (:n).', + 'registry_search_ph' => 'Reference no., student name or ID...', + 'registry_th_ref' => 'Reference', + 'registry_th_doc' => 'Document', + 'registry_th_student' => 'Student', + 'registry_th_issuer' => 'Issued by', + 'registry_th_date' => 'Date', + 'registry_empty' => 'No document issued.', + // Show + 'cat_certificat' => 'Certificate', + 'cat_attestation' => 'Attestation', + 'cat_bulletin' => 'Report card', + 'show_features' => 'Characteristics', + 'show_type' => 'Type', + 'show_orientation' => 'Orientation', + 'show_signatory' => 'Signatory', + 'orientation_portrait' => 'Portrait', + 'orientation_landscape' => 'Landscape', + 'flag_header' => 'Official header', + 'flag_signature' => 'Signature block', + 'show_default' => 'Default template', + 'show_active' => 'Active', + 'show_inactive' => 'Inactive', + 'show_sample_note' => 'The preview uses sample data. Variables will be replaced with the student\'s real information at generation.', + 'show_preview' => 'Document preview', + // Edit + 'edit_head' => 'Edit template', + 'create_head' => 'New template', + 'edit_title' => 'Edit template', + 'create_title' => 'New document template', + 'edit_subtitle' => 'Configure the document and its content with dynamic variables.', + 'toast_saved_title' => 'Saved', + 'toast_saved_desc' => 'Template saved.', + 'toast_error_title' => 'Error', + 'toast_error_desc' => 'Check the fields.', + 'toast_preview_error' => 'Preview failed.', + 'label_category' => 'Category', + 'label_type' => 'Type', + 'type_ph' => 'Choose a type', + 'label_name' => 'Template name', + 'name_ph' => 'e.g. Standard enrollment certificate', + 'label_description' => 'Description', + 'description_ph' => 'Template usage (optional)', + 'label_source' => 'Body source', + 'label_layout' => 'Predefined layout', + 'layout_ph' => 'Choose a layout', + 'layout_hint' => 'Layout provided by the application (logic, tables). The school header, signature and watermark still apply.', + 'label_content' => 'Document content', + 'content_tip' => 'Tip: place the cursor then click a variable on the right to insert it.', + 'variables_title' => 'Available variables', + 'insert_var' => 'Insert :var', + 'options_title' => 'Layout', + 'label_orientation' => 'Orientation', + 'label_signatory' => 'Signatory title', + 'signatory_ph' => 'The Principal', + 'opt_header' => 'Official header (logo, term, motto)', + 'opt_signature' => 'Signature block', + 'opt_default' => 'Default template for this type', + 'opt_active' => 'Active', + 'btn_preview' => 'Preview', + 'preview_sample_title' => 'Preview (sample data)', + // HeaderDesigner + 'hd_head' => 'Document header', + 'hd_title' => 'Document header', + 'hd_subtitle' => 'Compose the header used for certificates, attestations and report cards. Drag the blocks to position them.', + 'hd_reset' => 'Reset', + 'hd_saving' => 'Saving…', + 'hd_preset_label' => 'Header template', + 'hd_watermark_both' => 'The watermark applies to both templates.', + 'hd_ministerial_title' => 'Ministerial header (official)', + 'hd_ministerial_p1' => 'The three-column header (Ministry & school · logo · Republic & motto) is generated automatically from the school information. Drag-and-drop is disabled for this template.', + 'hd_ministerial_p2' => 'To change the ministry, name, logo or motto, go to the school settings.', + 'hd_add_block' => 'Add a block', + 'hd_block_logo' => 'Logo', + 'hd_block_name' => 'Name', + 'hd_block_motto' => 'Motto', + 'hd_block_term' => 'Term', + 'hd_block_contact' => 'Contact details', + 'hd_block_text' => 'Text', + 'hd_default_text' => 'Free text', + 'hd_canvas_title' => 'Preview (A4 width)', + 'hd_height' => 'Height', + 'hd_canvas_hint' => 'Click a block to edit it, drag it to move it.', + 'hd_block_props' => 'Block properties', + 'hd_select_block' => 'Select a block in the preview.', + 'hd_size_px' => 'Size (px)', + 'hd_delete' => 'Delete', + 'hd_content' => 'Content', + 'hd_insert_var_ph' => 'Insert a variable…', + 'hd_size' => 'Size', + 'hd_width' => 'Width', + 'hd_delete_block' => 'Delete block', + 'hd_color' => 'Color', + 'hd_watermark' => 'Watermark', + 'hd_enable' => 'Enable', + 'hd_wm_text' => 'Text', + 'hd_wm_image' => 'Image / logo', + 'hd_wm_text_ph' => 'Watermark text', + 'hd_opacity' => 'Opacity: :n%', + 'hd_wm_size' => 'Size: :n', + 'hd_rotation' => 'Rotation: :n°', + 'hd_var_note_before' => 'Variables like', + 'hd_var_note_after' => 'are replaced when each document is generated.', + ], + + 'class_subjects' => [ + // Index + 'head' => 'Subject assignment to classes', + 'title' => 'Subject assignment to classes', + 'subtitle' => 'Manage the assignment of subjects to each class', + 'new' => 'Add an assignment', + 'stat_assignments' => 'Assignments', + 'stat_classes' => 'Classes covered', + 'search_ph' => 'Search for a class or subject...', + 'search_results' => 'Results for:', + 'th_class' => 'Class', + 'th_subjects' => 'Assigned subjects', + 'th_year' => 'Academic year', + 'coef' => 'Coef', + 'empty' => 'No assignment found', + 'showing' => 'Showing :from to :to of :total assignments', + 'page_of' => 'Page :current of :last', + 'delete_title' => 'Confirm deletion', + 'delete_desc' => 'Are you sure you want to delete this assignment? This action is irreversible.', + 'deleting' => 'Deleting...', + // Create / Edit shared + 'create_head' => 'Assign subjects to a class', + 'create_title' => 'Assign subjects to a class', + 'create_subtitle' => 'Select a class, an academic year and the subjects to teach', + 'edit_head' => 'Edit assigned subjects', + 'edit_title' => 'Edit assigned subjects', + 'edit_subtitle' => 'Update the subjects assigned to this class', + 'section_base' => 'Basic selection', + 'label_class' => 'Class', + 'class_ph' => 'Select a class', + 'label_year' => 'Academic year', + 'year_ph' => 'Select an academic year', + 'section_subjects' => 'Subject and coefficient selection', + 'search_subjects' => 'Search subjects', + 'search_subjects_ph' => 'Search by name or code...', + 'code' => 'Code', + 'coef_ph' => 'Coef', + 'group_required' => 'Required', + 'group_optional' => 'Optional', + 'no_subjects' => 'No subject found', + 'selected_count' => ':n subject(s) selected with coefficient', + 'submit_create' => 'Assign subjects', + 'creating' => 'Assigning...', + 'submit_update' => 'Update', + 'updating' => 'Updating...', + 'err_class_required' => 'The class is required.', + 'err_year_required' => 'The academic year is required.', + 'err_subjects_required' => 'Please select at least one subject.', + 'err_coef_invalid' => 'Invalid coefficient (must be > 0).', + // Show + 'show_head' => 'Assignment details', + 'show_title' => 'Assignment details', + 'show_subtitle' => 'Information about the subject assignment to the class', + 'show_class' => 'Class', + 'show_subject' => 'Subject', + 'show_name' => 'Name', + 'show_coef' => 'Coefficient', + 'show_year' => 'Academic year', + 'show_year_label' => 'Year', + 'show_start' => 'Start date', + 'show_end' => 'End date', + 'show_tracking' => 'Tracking information', + 'show_created_on' => 'Created on', + 'show_updated_on' => 'Updated on', + ], + + 'fee_structures' => [ + // Index + 'head' => 'Fee structures', + 'title' => 'Fee structures', + 'subtitle' => 'Manage fee amounts by category, class and academic year', + 'replicate_year' => 'Replicate a year', + 'new' => 'New structure', + 'stat_total' => 'Total amount', + 'stat_average' => 'Average amount', + 'stat_classes' => 'Classes covered', + 'stat_categories' => 'Categories covered', + 'stat_gap' => 'Min/max gap', + 'stat_gap_detail' => 'Min :min | Max :max', + 'search_ph' => 'Search by category, class or year...', + 'filter_year' => 'Academic year', + 'filter_all_years' => 'All years', + 'active_suffix' => ' (active)', + 'filter_category' => 'Category', + 'filter_all_categories' => 'All categories', + 'filter_class' => 'Class', + 'filter_all_classes' => 'All classes', + 'filter_amount_min' => 'Min amount', + 'filter_amount_max' => 'Max amount', + 'search' => 'Search', + 'reset' => 'Reset', + 'filtered_count' => ':count filtered structure(s) | Max: :max', + 'col_year' => 'Academic year', + 'col_category' => 'Category', + 'col_class' => 'Class', + 'col_amount' => 'Amount', + 'col_actions' => 'Actions', + 'empty' => 'No fee structure found', + 'pagination' => 'Showing :from to :to of :total results', + // Delete dialog + 'delete_title' => 'Confirm deletion', + 'delete_desc' => 'Are you sure you want to delete this fee structure? This action is irreversible.', + 'cancel' => 'Cancel', + 'deleting' => 'Deleting...', + 'delete' => 'Delete', + // Replicate dialog + 'replicate_title' => 'Replicate fee structures', + 'replicate_desc' => 'Copies all structures (and their installments) from one year to the active year. Category / class combinations already present are ignored.', + 'replicate_target' => 'Target year (active):', + 'replicate_source' => 'Source year *', + 'replicate_source_ph' => 'Choose the year to copy', + 'replicating' => 'Replicating...', + 'replicate' => 'Replicate', + 'replicate_no_active' => 'No active academic year. Activate a year before replicating.', + // Create / Edit + 'create_head' => 'New fee structure', + 'create_title' => 'New fee structure', + 'create_subtitle' => 'Create a new fee structure for an academic year, category and class', + 'edit_head' => 'Edit fee structure', + 'edit_title' => 'Edit fee structure', + 'edit_subtitle' => 'Edit the information for this fee structure', + 'back' => 'Back', + 'section_academic' => 'Academic context', + 'active_year' => 'Active academic year', + 'active_badge' => 'Active', + 'no_active_year' => 'No active academic year. Activate one first.', + 'auto_attach' => 'The structure is automatically attached to the active year.', + 'field_year' => 'Academic year', + 'field_year_ph' => 'Select an academic year', + 'field_category' => 'Fee category', + 'field_category_ph' => 'Select a category', + 'field_class_section' => 'Class concerned', + 'field_class' => 'Class', + 'field_class_ph' => 'Select a class', + 'field_amount' => 'Amount', + 'field_amount_currency' => 'Amount (:currency)', + 'field_amount_ph' => 'Ex: 50000', + 'saving' => 'Saving...', + 'save' => 'Save', + 'updating' => 'Updating...', + 'update' => 'Update', + // Show + 'show_head' => 'Fee structure details', + 'manage_installments' => 'Manage installments', + 'edit' => 'Edit', + 'show_heading' => 'Fee structure', + 'show_amount_total' => 'Total amount', + 'installments_count' => ':count installment(s)', + 'show_general' => 'General information', + 'show_year' => 'Academic year', + 'show_category' => 'Fee category', + 'show_class' => 'Class concerned', + 'show_code' => 'Code', + 'show_amount_section' => 'Amount and installments', + 'installments' => 'Installments', + 'manage' => 'Manage', + 'installment_num' => 'Installment :num', + 'no_installments' => 'No installment defined', + 'show_system' => 'System information', + 'show_created_on' => 'Created on', + 'show_updated_on' => 'Last modified', + 'show_uid' => 'Unique identifier', + // Installments modal + 'modal_title' => 'Manage installments', + 'modal_desc' => 'Add or edit the installments for this fee structure.', + 'current_structure' => 'Current structure', + 'current_total' => 'Total amount:', + 'current_installments' => 'Current installments:', + 'configured_total' => 'Total of configured installments', + 'installments_total_label' => 'Total installment amount:', + 'installment_name' => 'Installment name', + 'installment_name_ph' => 'Ex: First installment', + 'installment_number_label' => 'No.', + 'installment_amount' => 'Amount', + 'add_installment' => 'Add an installment', + 'save_installments' => 'Save installments', + 'total_exceeds' => 'The installment total (:total) exceeds the structure amount (:amount)', + 'err_valid_installment' => 'Please add at least one valid installment.', + 'err_total_exceeds' => 'The installment total cannot exceed the fee structure amount.', + 'err_saving' => 'Error while saving the installments.', + ], +]; diff --git a/lang/en/presences.php b/lang/en/presences.php new file mode 100644 index 00000000..a857eee1 --- /dev/null +++ b/lang/en/presences.php @@ -0,0 +1,146 @@ + [ + 'present' => 'Present', + 'absent' => 'Absent', + 'late' => 'Late', + 'excused' => 'Excused', + ], + + 'reason' => [ + 'medical' => 'Medical', + 'familial' => 'Family', + 'autre' => 'Other', + ], + + 'session' => [ + 'journee' => 'Full day', + 'journee_short' => 'Day', + 'matin' => 'Morning', + 'apres_midi' => 'Afternoon', + ], + + 'attendances' => [ + 'index' => [ + 'head' => 'Attendance entry', + 'subtitle' => 'Record the daily roll call by class', + 'stats' => 'Statistics', + 'class' => 'Class', + 'period' => 'Period', + 'date' => 'Date', + 'session_label' => 'Session', + 'select' => 'Select', + 'class_dash' => '— Class —', + 'period_dash' => '— Period —', + 'need_filters' => 'Select a class and a period to start the roll call.', + 'existing' => 'Roll call already recorded for this slot — you can edit it.', + 'all_present' => 'All present', + 'all_absent' => 'All absent', + 'empty' => 'No student enrolled in this class for the active year.', + 'col_matricule' => 'ID number', + 'col_student' => 'Student', + 'col_status' => 'Status', + 'col_late' => 'Lateness (min)', + 'col_comment' => 'Comment', + 'permission' => 'Permission :dates', + 'comment_ph' => 'Comment...', + 'notes_ph' => 'General remarks on the roll call...', + 'saving' => 'Saving...', + 'saved' => 'Saved!', + 'save' => 'Save roll call', + ], + + 'stats' => [ + 'head' => 'Attendance statistics', + 'subtitle' => 'Absence rate and tracking per student', + 'back_to_entry' => 'Roll call entry', + 'class' => 'Class', + 'period' => 'Period', + 'select' => 'Select', + 'all_classes' => '— All —', + 'all_periods' => 'All periods', + 'empty' => 'Select a class to display the statistics.', + 'recorded_calls' => 'Recorded roll calls', + 'col_date' => 'Date', + 'per_student' => 'Tracking per student', + 'students_count' => ':count students', + 'col_student' => 'Student', + 'col_present' => 'Present', + 'col_absent' => 'Absent', + 'col_late' => 'Late', + 'col_excused' => 'Excused', + 'col_absence_rate' => 'Absence rate', + ], + ], + + 'permissions' => [ + 'status' => [ + 'pending' => 'Pending', + 'approved' => 'Approved', + 'rejected' => 'Rejected', + ], + + 'index' => [ + 'head' => 'Permission requests', + 'subtitle' => 'Manage justified absence requests', + 'new' => 'New request', + 'total' => 'Total', + 'pending' => 'Pending', + 'approved' => 'Approved', + 'rejected' => 'Rejected', + 'search_ph' => "Student name or ID number...", + 'all_statuses' => 'All statuses', + 'filter' => 'Filter', + 'col_student' => 'Student', + 'col_reason' => 'Reason', + 'col_period' => 'Period', + 'col_duration' => 'Duration', + 'col_requested_by' => 'Requested by', + 'col_status' => 'Status', + 'col_actions' => 'Actions', + 'empty' => 'No permission request.', + 'day_one' => ':count day', + 'day_other' => ':count days', + 'review' => 'Review', + 'view' => 'View', + 'total_count' => ':count request(s) in total', + 'delete_title' => 'Delete this request?', + 'delete_desc' => 'This action is irreversible.', + ], + + 'create' => [ + 'head' => 'New permission request', + 'subtitle' => 'Justified absence request for a student', + 'student' => 'Student *', + 'select_student' => 'Select a student', + 'start_date' => 'Start date *', + 'end_date' => 'End date *', + 'reason' => 'Reason *', + 'description' => 'Description / Justification *', + 'description_ph' => 'Explain the reason for the absence...', + 'submitting' => 'Sending...', + 'submit' => 'Submit request', + ], + + 'show' => [ + 'head' => 'Permission — :name', + 'details' => 'Request details', + 'reason' => 'Reason', + 'absence_period' => 'Absence period', + 'day_one' => ':count day', + 'day_other' => ':count days', + 'requested_by' => 'Requested by', + 'reviewed_by' => 'Reviewed by', + 'reviewed_on' => 'on :date', + 'description' => 'Description', + 'review_comment' => 'Review comment', + 'linked_absences' => 'Linked excused absences', + 'decision' => 'Decision', + 'comment_optional' => 'Comment (optional)', + 'comment_ph' => 'Reason for approval or rejection...', + 'approve' => 'Approve', + 'reject' => 'Reject', + ], + ], +]; diff --git a/lang/en/promotion.php b/lang/en/promotion.php new file mode 100644 index 00000000..5b73fe52 --- /dev/null +++ b/lang/en/promotion.php @@ -0,0 +1,32 @@ + 'Class promotion', + 'subtitle' => 'Bulk re-enroll validated students to the next class and year.', + 'source_year' => 'Source year', + 'source_class' => 'Source class', + 'target_year' => 'Target year', + 'target_class' => 'Target class', + 'choose' => 'Choose', + 'active' => 'active', + 'select_prompt' => 'Choose a source year and class', + + 'stat' => [ + 'students' => 'Students', + 'valide' => 'Validated', + 'non_valide' => 'Not validated', + 'reenrolled' => 'Already re-enrolled', + ], + + 'selected' => ':count student(s) selected', + 'choose_target' => 'Choose the target year and class', + 'reenroll_selection' => 'Re-enroll the selection', + + 'col_student' => 'Student', + 'col_matricule' => 'ID number', + 'col_status_source' => 'Status (source year)', + 'col_target' => 'Target', + 'empty' => 'No student in this class.', + 'already_reenrolled' => 'Already re-enrolled', + 'to_reenroll' => 'To re-enroll', +]; diff --git a/lang/en/rh.php b/lang/en/rh.php new file mode 100644 index 00000000..bce3353c --- /dev/null +++ b/lang/en/rh.php @@ -0,0 +1,253 @@ + [ + 'active' => 'Active', + 'suspended' => 'Suspended', + 'terminated' => 'Terminated', + ], + + 'pay_status' => [ + 'draft' => 'Draft', + 'validated' => 'Validated', + 'paid' => 'Paid', + 'cancelled' => 'Cancelled', + ], + + 'personnel' => [ + 'index' => [ + 'head' => 'Staff', + 'subtitle' => 'Assign grades and manage your staff payroll.', + 'add' => 'Add to staff', + 'ungraded_banner' => ':count employee(s) without a grade — click to filter them.', + 'search_ph' => 'Name, job, ID number…', + 'all_statuses' => 'All statuses', + 'status_active' => 'Active', + 'status_suspended' => 'Suspended', + 'status_terminated' => 'Terminated', + 'all_grades' => 'All grades', + 'ungraded' => 'Without grade', + 'search' => 'Search', + 'reset' => 'Reset', + 'count' => ':count employee(s)', + 'per_page' => 'Rows per page:', + 'empty' => 'No employee', + 'col_employee' => 'Employee', + 'col_job' => 'Job', + 'col_grade' => 'Grade', + 'col_base' => 'Base salary', + 'col_status' => 'Status', + 'choose_grade' => 'Choose a grade', + 'off_grade_dash' => '— Off grade —', + 'off_grade' => 'Off grade', + 'open_sheet' => 'Open the file', + 'open_sheet_title' => 'Full file (bonuses, payslips…)', + 'range' => ':from–:to of :total', + 'modal_title' => 'Add to staff', + 'user' => 'User *', + 'search_user_ph' => 'Search for a user…', + 'no_result' => 'No result', + 'no_assignable' => 'All users already have a payroll profile.', + 'job' => 'Job *', + 'job_ph' => 'E.g. Teacher', + 'contract' => 'Contract *', + 'grade' => 'Grade', + 'base' => 'Base salary', + 'base_ignored' => '(ignored)', + 'base_ph' => 'E.g. 120000', + 'cancel' => 'Cancel', + 'add_btn' => 'Add', + ], + + 'overview' => [ + 'head' => 'Staff & Payroll — Overview', + 'title' => 'Staff & Payroll', + 'subtitle' => 'Overview of staff and payroll.', + 'new_run' => 'New payroll run', + 'kpi_active' => 'Active employees', + 'kpi_ungraded' => 'To classify (no grade)', + 'kpi_ungraded_sub_some' => 'Click to display them', + 'kpi_ungraded_sub_none' => 'Everything is classified', + 'kpi_grades' => 'Active grades', + 'kpi_mass' => 'Payroll mass (base/month)', + 'kpi_mass_sub' => 'Sum of base salaries', + 'ungraded_banner' => ':count active employee(s) are not attached to any salary grade.', + 'last_run' => 'Last payroll run', + 'all_runs' => 'All runs', + 'no_run' => 'No payroll run generated yet.', + 'net_mass' => 'Net mass', + 'shortcut_personnel' => 'Staff', + 'shortcut_personnel_desc' => 'List & grade assignment', + 'shortcut_grades' => 'Salary grades', + 'shortcut_grades_desc' => 'Categories & amounts', + 'shortcut_runs' => 'Payroll runs', + 'shortcut_runs_desc' => 'Generate & disburse', + ], + ], + + 'salary_components' => [ + 'head' => 'Payroll items', + 'subtitle' => 'Reusable earnings and deductions. "Auto" items are applied when generating a run.', + 'new' => 'New item', + 'search_ph' => 'Name, code...', + 'all_types' => 'All types', + 'earnings' => 'Earnings', + 'deductions' => 'Deductions', + 'search' => 'Search', + 'reset' => 'Reset', + 'count' => ':count item(s)', + 'filtered' => '— filtered', + 'per_page' => 'Rows per page:', + 'col_component' => 'Item', + 'col_code' => 'Code', + 'col_type' => 'Type', + 'col_default_amount' => 'Default amount', + 'col_application' => 'Application', + 'col_status' => 'Status', + 'col_actions' => 'Actions', + 'empty' => 'No item', + 'type_earning' => 'Earning', + 'type_deduction' => 'Deduction', + 'auto' => 'Automatic', + 'manual' => 'Manual', + 'active' => 'Active', + 'inactive' => 'Inactive', + 'edit' => 'Edit', + 'delete' => 'Delete', + 'range' => ':from–:to of :total', + 'modal_edit' => 'Edit the item', + 'modal_new' => 'New item', + 'name' => 'Name *', + 'name_ph' => 'E.g. Transport allowance', + 'type' => 'Type *', + 'code' => 'Code', + 'code_ph' => 'E.g. TRANSPORT', + 'default_amount' => 'Default amount', + 'default_amount_ph' => 'Optional', + 'apply_auto' => 'Apply automatically to every payslip', + 'active_label' => 'Active', + 'cancel' => 'Cancel', + 'saving' => 'Saving...', + 'save' => 'Save', + 'add' => 'Add', + 'delete_title' => 'Delete this item?', + 'delete_desc' => 'Already generated payslips are not affected (their lines are frozen).', + ], + + 'salary_grades' => [ + 'index' => [ + 'head' => 'Salary grades', + 'subtitle' => 'Each category/step carries a base salary; the employee is attached to it from their file.', + 'settings' => 'Payroll settings', + 'new' => 'New grade', + 'seniority_active' => 'Seniority bonus', + 'seniority_active_state' => 'active', + 'seniority_active_desc' => '+:rate% of base per year', + 'seniority_cap' => ', capped at :cap%', + 'seniority_disabled_state' => 'disabled', + 'search_ph' => 'Name, category...', + 'all' => 'All', + 'actives' => 'Active', + 'inactives' => 'Inactive', + 'search' => 'Search', + 'reset' => 'Reset', + 'count' => ':count grade(s)', + 'per_page' => 'Rows per page:', + 'empty' => 'No salary grade', + 'create_first' => 'Create the first grade', + 'col_grade' => 'Grade', + 'col_category' => 'Category / step', + 'col_employees' => 'Employees', + 'col_base' => 'Base salary', + 'col_status' => 'Status', + 'cat' => 'Cat. :category', + 'echelon' => 'step :echelon', + 'active' => 'Active', + 'inactive' => 'Inactive', + 'assign_employees' => 'Assign employees', + 'edit' => 'Edit', + 'delete' => 'Delete', + 'range' => ':from–:to of :total', + 'delete_title' => 'Delete this grade?', + 'delete_desc' => 'Not possible if employees are attached. Already generated payslips are not affected.', + 'cancel' => 'Cancel', + ], + + 'form' => [ + 'section' => 'Grade definition', + 'name' => 'Name *', + 'name_ph' => 'E.g. Teacher — Category B, step 2', + 'category' => 'Category', + 'category_ph' => 'E.g. B', + 'echelon' => 'Step', + 'echelon_ph' => 'E.g. 2', + 'base' => 'Base salary (F) *', + 'base_ph' => 'E.g. 145000', + 'sort_order' => 'Display order', + 'active' => 'Active grade', + 'update' => 'Update', + 'creating' => 'Creating...', + 'updating' => 'Updating...', + 'create' => 'Create the grade', + 'cancel' => 'Cancel', + ], + + 'create' => [ + 'head' => 'New salary grade', + 'title' => 'New salary grade', + 'subtitle' => 'A category/step with its base salary.', + ], + + 'edit' => [ + 'head' => 'Edit :name', + 'title' => 'Edit the grade', + ], + + 'employees' => [ + 'head' => 'Assign — :name', + 'title' => 'Assign employees', + 'subtitle' => 'Grade :name — base salary :base.', + 'search_ph' => 'Search for an employee…', + 'selected' => ':count selected', + 'empty' => 'No active employee', + 'select_all' => 'Select all', + 'select_all_filtered' => ' (filtered results)', + 'no_result' => 'No result.', + 'current' => 'Current: :name', + 'on_this_grade' => 'On this grade', + 'moving' => ':count employee(s) will be moved from another grade.', + 'detach_note' => 'Unchecked employees who were on this grade will be detached (fallback to their manual salary).', + 'saving' => 'Saving...', + 'save' => 'Save assignments', + 'cancel' => 'Cancel', + ], + ], + + 'payroll_settings' => [ + 'head' => 'Payroll settings', + 'title' => 'Payroll settings', + 'subtitle' => 'Seniority, social security (CNSS) and tax (ITS) applied automatically when generating payslips.', + 'disclaimer' => 'These contributions and taxes are disabled by default. The provided rates/scales are indicative — verify them against the current regulations before enabling.', + 'seniority' => 'Seniority bonus', + 'seniority_enable' => 'Enable the seniority bonus', + 'seniority_rate' => 'Rate per year (% of base)', + 'seniority_cap' => 'Cap (% of base, 0 = none)', + 'cnss' => 'CNSS (social security)', + 'cnss_enable' => 'Enable the CNSS deduction', + 'cnss_employee' => 'Employee share (%)', + 'cnss_employer' => 'Employer share (%)', + 'cnss_ceiling' => 'Monthly ceiling (F, 0 = none)', + 'cnss_note' => 'The employee share is deducted on the payslip; the employer share is recorded as an employer charge (informational, not deducted from net).', + 'its' => 'ITS — Tax on wages and salaries', + 'its_enable' => 'Enable ITS (progressive scale)', + 'its_desc' => 'Scale by brackets on taxable net (gross − employee CNSS). Leave "Up to" empty for the last bracket (beyond).', + 'its_up_to' => 'Up to (F)', + 'its_rate' => 'Rate (%)', + 'its_empty' => 'No bracket. Add some to define the scale.', + 'its_beyond' => 'beyond', + 'its_add' => 'Add a bracket', + 'saving' => 'Saving...', + 'save' => 'Save the settings', + 'back' => 'Back', + ], +]; diff --git a/lang/en/roster.php b/lang/en/roster.php new file mode 100644 index 00000000..5aa1f811 --- /dev/null +++ b/lang/en/roster.php @@ -0,0 +1,52 @@ + 'Headcount / Class lists', + 'subtitle' => 'Students by year and class, schooling tracking (validation, dropout, transfer).', + 'export_pdf' => 'Export to PDF', + 'year' => 'School year', + 'choose' => 'Choose', + 'class' => 'Class', + 'choose_class' => 'Choose a class', + 'active' => 'active', + 'status' => 'Status', + 'all' => 'All', + 'search' => 'Search', + 'search_ph' => 'Name or ID...', + 'select_prompt' => 'Select a year and a class', + + 'stat' => [ + 'enrolled' => 'Enrolled', + 'en_cours' => 'In progress', + 'valide' => 'Validated', + 'non_valide' => 'Not validated', + 'abandon' => 'Dropouts', + ], + + 'selected' => ':count selected', + 'validate' => 'Validate', + 'invalidate' => 'Invalidate', + + 'col_student' => 'Student', + 'col_matricule' => 'ID number', + 'col_payment' => 'Payment', + 'col_schooling' => 'Schooling status', + 'col_action' => 'Action', + 'empty' => 'No student for this filter.', + 'status_btn' => 'Status', + + 'dialog' => [ + 'title' => 'Schooling status', + 'status' => 'Status', + 'reason' => 'Reason', + 'reason_ph' => 'Reason for dropout / transfer...', + 'abandon_warning' => 'The student will automatically be removed from attendance lists.', + ], + + 'pay' => [ + 'PAID' => 'Paid', + 'PARTIALLY_PAID' => 'Partial', + 'ISSUED' => 'Unpaid', + 'NONE' => 'No invoice', + ], +]; diff --git a/lang/en/scholarships.php b/lang/en/scholarships.php new file mode 100644 index 00000000..462ecfb8 --- /dev/null +++ b/lang/en/scholarships.php @@ -0,0 +1,86 @@ + [ + 'title' => 'Student scholarships', + 'subtitle' => 'Manage scholarship awards to students', + 'new' => 'New award', + 'total' => 'Total awards', + 'search' => 'Search', + 'search_ph' => 'Search by student, scholarship...', + 'scholarship' => 'Scholarship', + 'all_scholarships' => 'All scholarships', + 'academic_year' => 'Academic year', + 'all_years' => 'All years', + 'clear_filters' => 'Clear filters', + 'per_page' => 'Items per page', + 'col_student' => 'Student', + 'col_scholarship' => 'Scholarship', + 'col_year' => 'Academic year', + 'col_date' => 'Award date', + 'col_actions' => 'Actions', + 'empty' => 'No scholarship award found', + 'showing' => 'Showing :from to :to of :total results', + 'delete_title' => 'Confirm deletion', + 'delete_confirm' => 'Are you sure you want to delete this scholarship award? This action is irreversible.', + 'deleting' => 'Deleting...', + ], + + 'form' => [ + 'create_head' => 'New scholarship award', + 'create_title' => 'New scholarship award', + 'create_subtitle' => 'Award a scholarship to a student', + 'edit_head' => 'Edit scholarship award', + 'edit_title' => 'Edit scholarship award', + 'edit_subtitle' => 'Edit the details of the scholarship award', + 'section_award' => 'Scholarship award', + 'student' => 'Student *', + 'student_search_ph' => 'Search and select a student...', + 'select_student' => 'Select a student', + 'no_student' => 'No student found', + 'more_students' => 'And :count more students...', + 'scholarship' => 'Scholarship *', + 'select_scholarship' => 'Select a scholarship', + 'academic_year' => 'Academic year *', + 'select_year' => 'Select a year', + 'number_of_year' => 'Number of years', + 'number_of_year_ph' => 'E.g. 1, 2, 3...', + 'start_date' => 'Start date', + 'end_date' => 'End date', + 'extra_info' => 'Additional information', + 'notes' => 'Notes (optional)', + 'notes_ph' => 'Add notes about this award...', + 'saving' => 'Saving...', + ], + + 'show' => [ + 'head' => 'Scholarship - :name', + 'title' => 'Scholarship award', + 'subtitle' => 'Details of the scholarship award to :name', + 'student_info' => 'Student information', + 'full_name' => 'Full name', + 'matricule' => 'ID number', + 'gender' => 'Gender', + 'male' => 'Male', + 'female' => 'Female', + 'other' => 'Other', + 'birth_date' => 'Date of birth', + 'email' => 'Email', + 'phone' => 'Phone', + 'scholarship_info' => 'Scholarship information', + 'scholarship_name' => 'Scholarship name', + 'type' => 'Type', + 'max_value' => 'Maximum value', + 'description' => 'Description', + 'notes' => 'Notes', + 'academic_year' => 'Academic year', + 'year' => 'Year', + 'period' => 'Period', + 'details' => 'Award details', + 'award_date' => 'Award date', + 'last_modified' => 'Last modified', + 'number_of_year' => 'Number of years', + 'start_date' => 'Start date', + 'end_date' => 'End date', + ], +]; diff --git a/lang/en/settings.php b/lang/en/settings.php new file mode 100644 index 00000000..d6efdff3 --- /dev/null +++ b/lang/en/settings.php @@ -0,0 +1,93 @@ + 'Settings', + 'account' => 'My account', + 'account_desc' => 'Manage your profile and account settings', + + 'profile' => [ + 'title' => 'Profile', + 'desc' => 'Your personal information', + 'page' => [ + 'breadcrumb' => 'Account settings', + 'head' => 'My profile', + 'heading' => 'My profile', + 'default_name' => 'My profile', + 'section_title' => 'Personal information', + 'section_desc' => 'Manage your profile information', + 'firstname' => 'First name', + 'firstname_ph' => 'Your first name', + 'lastname' => 'Last name', + 'lastname_ph' => 'Your last name', + 'matricule' => 'ID number', + 'matricule_ph' => 'Not assigned', + 'gender' => 'Gender', + 'gender_ph' => 'Select', + 'gender_male' => 'Male', + 'gender_female' => 'Female', + 'gender_other' => 'Other', + 'birth_date' => 'Date of birth', + 'email' => 'Email address', + 'email_ph' => 'example@email.com', + 'telephone' => 'Phone', + 'telephone_ph' => '+243 XXX XXX XXX', + 'address' => 'Address', + 'address_ph' => 'Your full address', + 'email_unverified' => 'Your email address is not verified.', + 'email_resend' => 'Click here to resend the verification email.', + 'email_link_sent' => 'A new verification link has been sent to your email address.', + 'saving' => 'Saving...', + 'save' => 'Save', + 'saved' => '✓ Changes saved', + ], + ], + 'password' => [ + 'title' => 'Password', + 'desc' => 'Change your password', + 'page' => [ + 'breadcrumb' => 'Password', + 'head' => 'Password', + 'heading' => 'Password', + 'card_desc' => 'Make sure to use a long, secure password.', + 'current' => 'Current password', + 'current_ph' => 'Enter your current password', + 'new_section' => 'New password', + 'new' => 'New password', + 'new_ph' => 'Enter a new password', + 'confirm' => 'Confirm password', + 'confirm_ph' => 'Confirm the new password', + 'tip_label' => 'Tip:', + 'tip' => 'At least 12 characters mixing uppercase, lowercase, numbers and symbols. Avoid passwords already used elsewhere.', + 'saving' => 'Updating...', + 'submit' => 'Update password', + 'saved' => '✓ Password updated', + ], + ], + 'appearance' => [ + 'title' => 'Appearance', + 'desc' => 'Light, dark or system theme', + ], + 'two_factor' => [ + 'title' => 'Two-factor authentication', + 'desc' => 'Secure your account with a second factor', + 'page' => [ + 'breadcrumb' => 'Two-factor authentication', + 'head' => 'Two-factor authentication', + 'heading' => 'Two-factor authentication', + 'card_desc' => 'Strengthen your account security with a second factor.', + 'enabled_badge' => 'Enabled', + 'enabled_desc' => 'With two-factor authentication enabled, you will be prompted for a secure code during sign-in, which you can retrieve from the TOTP app on your phone.', + 'disable' => 'Disable 2FA', + 'disabled_badge' => 'Disabled', + 'disabled_desc' => 'When you enable two-factor authentication, you will be prompted for a secure code during sign-in. This code can be retrieved from a TOTP app on your phone.', + 'continue_setup' => 'Continue setup', + 'enable' => 'Enable 2FA', + ], + ], + 'language' => [ + 'title' => 'Language', + 'desc' => 'Interface language, saved to your profile', + 'label' => 'Interface language', + 'updated' => 'Language updated', + ], +]; diff --git a/lang/en/statistiques.php b/lang/en/statistiques.php new file mode 100644 index 00000000..4f3be7fc --- /dev/null +++ b/lang/en/statistiques.php @@ -0,0 +1,169 @@ + 'Statistics', + 'subtitle' => 'School indicators — enrollment, finances and success.', + 'year_ph' => 'Year', + 'class_ph' => 'Class', + 'gender_ph' => 'Gender', + 'all_classes' => 'All classes', + 'all' => 'All', + 'boys' => 'Boys', + 'girls' => 'Girls', + 'other' => 'Other', + 'active_suffix' => ' · active', + 'all_cycles' => 'All', + 'sort' => 'Sort', + 'sort_level' => 'By level', + 'sort_headcount' => 'By headcount', + 'sort_amount' => 'By amount', + 'sort_rate' => 'By rate', + 'years_old' => ':value yrs', + + // Tabs + 'tab_effectifs' => 'Enrollment & parity', + 'tab_finances' => 'Finances & recovery', + 'tab_reussite' => 'Success & exams', + 'tab_encadrement' => 'Supervision', + 'tab_assiduite' => 'Attendance', + 'tab_comparaisons' => 'Comparisons', + 'tab_geographie' => 'Geography', + + // Enrollment + 'kpi_total' => 'Total headcount', + 'kpi_total_sub' => ':boys B · :girls G', + 'kpi_ips' => 'Parity index (GPI)', + 'kpi_ips_sub' => ':pct% girls', + 'kpi_redoublement' => 'Repetition rate', + 'kpi_promotion_sub' => 'Promotion :pct%', + 'kpi_abandon' => 'Dropout rate', + 'kpi_age_sub' => 'Average age :age', + 'card_by_class' => 'Headcount by class (boys / girls)', + 'card_by_gender' => 'Breakdown by gender', + 'card_age_pyramid' => 'Age pyramid (boys / girls)', + 'overage_label' => 'Over-age rate (≥ +:threshold yrs)', + 'overage_hint' => 'Set the expected age on classes to enable this calculation.', + 'card_city' => 'Geographic origin (top cities, by gender)', + 'not_filled' => 'Not filled in', + + // Finances + 'kpi_billed' => 'Total billed', + 'kpi_billed_sub' => ':count invoices', + 'kpi_collected' => 'Collected', + 'kpi_recovery' => 'Recovery rate', + 'kpi_remaining_sub' => ':amount remaining', + 'kpi_unpaid' => 'Unpaid', + 'kpi_unpaid_sub' => ':count partial', + 'card_monthly' => 'Monthly collections', + 'card_methods' => 'Payment methods', + 'recovery_by_class' => 'Recovery by class', + 'card_billed_split' => 'Billed: collected / remaining', + 'series_collected' => 'Collected', + 'series_remaining' => 'Remaining', + 'card_recovery_rate' => 'Recovery rate by class', + 'rate_label' => 'Rate', + 'avg_prefix' => 'avg. :rate%', + 'legend_high' => '≥ 80 %', + 'legend_mid' => '60–79 %', + 'legend_low' => '< 60 %', + 'card_detail' => 'Detail by class', + 'th_class' => 'Class', + 'th_billed' => 'Billed', + 'th_collected' => 'Collected', + 'th_remaining' => 'Remaining', + 'th_rate' => 'Rate', + 'method_cash' => 'Cash', + 'method_mobile_money' => 'Mobile Money', + 'method_bank_transfer' => 'Transfer', + 'method_cheque' => 'Cheque', + + // Success + 'kpi_bulletins' => 'Validated reports', + 'kpi_average' => 'Overall average', + 'kpi_passrate' => 'Internal pass rate (≥10)', + 'kpi_admission' => 'Official exam admission', + 'card_mentions' => 'Distinction breakdown', + 'mention_passable' => 'Pass', + 'mention_assez_bien' => 'Fairly good', + 'mention_bien' => 'Good', + 'mention_tres_bien' => 'Very good', + 'card_exams' => 'Official exams (CEPD / BEPC / BAC)', + 'no_exams' => 'No official exam for this year', + 'th_exam' => 'Exam', + 'th_registered' => 'Registered', + 'th_admitted' => 'Admitted', + 'th_admission' => 'Admission', + + // Supervision + 'kpi_rem' => 'Student / teacher ratio', + 'kpi_rem_sub' => ':count teachers', + 'kpi_students' => 'Total headcount', + 'kpi_avg_class' => 'Average class size', + 'kpi_avg_class_sub' => ':count classes', + 'kpi_overcrowded' => 'Overcrowded classes', + 'kpi_overcrowded_sub' => '> :threshold students', + 'card_class_sizes' => 'Headcount by class (red = overcrowded)', + 'series_students' => 'Students', + 'card_overcrowded' => 'Overcrowded classes to watch', + 'students_count' => ':count students', + + // Attendance + 'no_attendance' => 'No attendance data for this period.', + 'kpi_presence' => 'Attendance rate', + 'kpi_presence_sub' => ':count present', + 'kpi_absence' => 'Absence rate', + 'kpi_absence_sub' => ':count absences', + 'kpi_late' => 'Lateness rate', + 'kpi_late_sub' => ':count late', + 'kpi_chronic' => 'Chronic absenteeism', + 'kpi_chronic_sub' => '> :threshold absences', + 'card_by_period' => 'Attendance by term', + 'series_present' => 'Present', + 'series_absent' => 'Absent', + 'series_late' => 'Late', + 'card_absence_by_class' => 'Absence rate by class', + + // Comparisons + 'need_two_years' => 'Trends become meaningful with at least two academic years filled in.', + 'kpi_effectif' => 'Headcount', + 'kpi_reussite' => 'Success', + 'kpi_recouvrement' => 'Recovery', + 'kpi_abandon_short' => 'Dropout', + 'card_effectif_evol' => 'Headcount trend', + 'series_effectif' => 'Headcount', + 'card_performance' => 'Performance (%)', + 'series_reussite' => 'Success', + 'series_recouvrement' => 'Recovery', + 'card_deperdition' => 'School wastage (%)', + 'series_redoublement' => 'Repetition', + 'series_abandon' => 'Dropout', + 'deperdition_hint' => 'The current year is not plotted: end-of-year decisions (repetition, dropout) are not yet finalized.', + 'card_comparison' => 'Comparison table', + 'th_year' => 'Year', + 'th_effectif' => 'Headcount', + 'th_girls_pct' => '% girls', + 'th_redoubl' => 'Repet.', + 'th_abandon' => 'Dropout', + 'th_recouvr' => 'Recov.', + 'th_reussite' => 'Success', + 'in_progress' => 'in progress', + + // Geography + 'coverage_note_1' => 'Origin filled in for', + 'coverage_note_2' => 'of students', + 'coverage_note_3' => 'Complete the region/prefecture on the student record to refine.', + 'no_geo' => 'No geographic origin filled in.', + 'kpi_coverage' => 'Coverage', + 'kpi_coverage_sub' => ':localized / :total students', + 'kpi_grand_lome' => 'Greater Lomé', + 'kpi_grand_lome_sub' => 'Golfe + Agoè-Nyivé', + 'kpi_regions' => 'Regions', + 'kpi_regions_sub' => 'represented', + 'kpi_prefectures' => 'Prefectures', + 'kpi_prefectures_sub' => 'of origin', + 'card_origin' => 'Student origin — regions and prefectures', + 'students_tooltip' => ':value students', + 'headcount_label' => 'Headcount', + 'shade_note' => '· shade = prefecture headcount', + 'card_by_region' => 'Headcount by region', +]; diff --git a/lang/en/students.php b/lang/en/students.php new file mode 100644 index 00000000..beb2dc7a --- /dev/null +++ b/lang/en/students.php @@ -0,0 +1,368 @@ + 'Students', + 'subtitle' => "Manage your school's student profiles", + 'import' => 'Import', + 'new' => 'New student', + + 'gender' => [ + 'male' => 'Male', + 'female' => 'Female', + 'unknown' => 'Not specified', + ], + + 'stats' => [ + 'total' => 'Total students', + 'active' => 'Active students', + 'male' => 'Male students', + 'female' => 'Female students', + ], + + 'bulk' => [ + 'selected' => ':count student(s) selected', + 'choose' => 'Choose an action', + 'activate' => 'Activate', + 'deactivate' => 'Deactivate', + 'apply' => 'Apply', + ], + + 'search' => [ + 'placeholder' => 'Search (name, ID, email)...', + 'nationality' => 'Nationality', + 'filters' => 'Filters', + 'reset_count' => 'Reset (:count)', + ], + + 'filter' => [ + 'all_classes' => 'All classes', + 'all_years' => 'All years', + 'schooling_all' => 'Schooling (all)', + 'gender' => 'Gender', + 'status' => 'Status', + 'region' => 'Region', + 'prefecture' => 'Prefecture', + 'active' => 'Active', + 'inactive' => 'Inactive', + ], + + 'sort' => [ + 'label' => 'Sort:', + 'created_at' => 'Date added', + 'lastname' => 'Last name', + 'firstname' => 'First name', + 'matricule' => 'ID number', + 'birth_date' => 'Date of birth', + 'asc' => 'Ascending', + 'desc' => 'Descending', + ], + + 'table' => [ + 'count' => ':count student(s)', + 'unit' => 'student(s)', + 'per_page' => 'Rows per page:', + 'student' => 'Student', + 'matricule' => 'ID number', + 'gender' => 'Gender', + 'nationality' => 'Nationality', + 'contact' => 'Contact', + 'status' => 'Status', + 'actions' => 'Actions', + 'empty' => 'No student found', + 'select_all' => 'Select all students', + 'select_one' => 'Select :name', + 'view' => 'View profile', + 'history' => 'History', + 'range' => ':from–:to of :total students', + ], + + 'delete' => [ + 'title' => 'Delete student', + 'confirm' => 'Are you sure you want to delete this student? This action is irreversible.', + 'deleting' => 'Deleting...', + ], + + 'admission' => [ + 'new' => 'New enrollment', + 'transfer' => 'Transfer', + 're_admission' => 'Re-admission', + ], + + 'stats_page' => [ + 'title' => 'Student statistics', + 'subtitle' => 'Demographic overview and enrolment figures for the selected year.', + 'year' => 'Year', + 'enrolled' => 'Enrolled :year', + 'classes' => ':count classes', + 'active' => 'Active', + 'inactive' => ':count inactive', + 'avg_age' => 'Average age', + 'years' => ':n yrs', + 'cohort' => 'of the cohort', + 'over_age' => 'Over-age', + 'over_age_sub' => ':count students behind', + 'boys' => 'Boys', + 'girls' => 'Girls', + 'no_data' => 'No data', + 'gender_dist' => 'Distribution by sex', + 'girls_sub' => 'girls', + 'ips' => 'GPI', + 'age_dist' => 'Distribution by age group', + 'headcount' => 'Headcount', + 'students_count' => ':v students', + 'by_class' => 'Headcount by class', + 'all' => 'All', + 'sort' => 'Sort', + 'by_level' => 'By level', + 'by_headcount' => 'By headcount', + 'no_class_filter' => 'No class for this filter', + 'total' => 'Total', + 'seats' => 'seats', + ], + + 'import_page' => [ + 'title' => 'Import students', + 'subtitle' => 'Bulk import from a CSV file (ideal at the start of the year).', + 'step1' => 'Download the template', + 'tmpl_intro' => 'Use the CSV template. Columns:', + 'tmpl_required_prefix' => 'The columns', + 'tmpl_required_cols' => 'first name, last name, sex (M/F), date of birth (YYYY-MM-DD)', + 'tmpl_required_suffix' => 'are required. The ID number is generated if left empty.', + 'download_template' => 'Download the CSV template', + 'step2' => 'Import the file', + 'importing' => 'Importing…', + 'choose_file' => 'Click to choose a CSV file', + 'rows_read' => 'Rows read', + 'imported' => 'Imported', + 'rows_error' => 'Rows with errors', + 'success' => ':count student(s) imported successfully.', + 'view_list' => 'View list', + 'not_imported' => 'Rows not imported — fix then re-import', + 'no_name' => '(no name)', + ], + + 'history' => [ + 'title' => 'Enrollment history', + 'view_profile' => 'View student profile', + 'journey' => 'Academic journey', + 'count' => ':count enrollment(s)', + 'total' => 'Total enrollments', + 'first_class' => 'First class', + 'current_class' => 'Current class', + 'by_class_year' => 'Journey by class and school year', + 'col_class' => 'Class', + 'col_year' => 'School year', + 'col_date' => 'Enrollment date', + 'col_code' => 'Enrollment code', + 'col_payment' => 'Payment', + 'col_schooling' => 'Schooling', + 'empty' => 'No enrollment found for this student.', + 'pay' => [ + 'PAID' => 'Paid', + 'PARTIALLY_PAID' => 'Partial', + 'ISSUED' => 'Unpaid', + 'NONE' => 'No invoice', + ], + ], + + 'field' => [ + 'full_name' => 'Full name', + 'matricule' => 'ID number', + 'gender' => 'Gender', + 'status' => 'Status', + 'birth_date' => 'Date of birth', + 'place_of_birth' => 'Place of birth', + 'user_account' => 'User account', + 'account_email' => 'Account email', + 'phone' => 'Phone', + 'student_email' => 'Student email', + 'city' => 'City', + 'nationality' => 'Nationality', + 'address' => 'Address', + 'admission_type' => 'Admission type', + 'birth_cert_number' => 'Birth certificate no.', + 'issue_date' => 'Issue date', + 'issue_place' => 'Issue place', + 'father' => 'Father', + 'mother' => 'Mother', + 'father_phone' => "Father's phone", + 'mother_phone' => "Mother's phone", + 'parent_email' => 'Parent email', + 'blood_group' => 'Blood group', + 'emergency_contact' => 'Emergency contact', + 'emergency_phone' => 'Emergency phone', + 'allergies' => 'Allergies', + ], + + 'show' => [ + 'title' => 'Student details', + 'subtitle' => "View all the student's information", + 'deliver_document' => 'Issue a document', + 'change_photo' => 'Change photo', + 'change' => 'Change', + 'add_photo' => 'Add a photo', + 'photo_hint' => 'JPG, PNG or WebP — max 2 MB', + 'current_schooling' => 'Current schooling (:year)', + 'change_class' => 'Change class', + 'identity' => 'Identity', + 'status' => 'Status', + 'student_active' => 'Active student', + 'student_inactive' => 'Inactive student', + 'account' => 'Account: :name', + 'medical_emergency' => 'Medical emergency', + 'no_emergency' => 'No emergency contact provided', + 'phone_missing' => 'Phone not provided', + 'contact_fallback' => 'Contact', + 'portal' => 'Student portal', + 'portal_on' => 'Access enabled', + 'portal_off' => 'Access not enabled', + 'portal_login' => 'login with the ID number', + 'portal_password' => 'Password (min 8)', + 'portal_reset' => 'Reset', + 'portal_activate' => 'Activate', + 'portal_deactivate' => 'Deactivate', + 'contact_account' => 'Contact & account', + 'admin_file' => 'Administrative file', + 'parents_medical' => 'Parents & medical', + 'emergency_available' => 'Emergency contact available', + 'docs_title' => 'File documents', + 'doc_name_label' => 'Document name *', + 'doc_name_ph' => 'E.g. Birth certificate, Passport...', + 'doc_file_label' => 'File *', + 'adding' => 'Adding...', + 'docs_hint' => "Stored in the student's private file. PDF, image or Word — max 5 MB.", + 'docs_empty' => 'No document in the file.', + 'issued_docs' => 'Issued documents', + 'issued_empty' => 'No document issued for this student.', + 'issued_by' => 'by :name', + 'created_at' => 'Created on :date', + 'updated_at' => 'Updated on :date', + ], + + 'dialog' => [ + 'deliver_desc' => 'Generate a certificate or attestation for :name.', + 'doc_type' => 'Document type *', + 'choose_template' => 'Choose a template', + 'class' => 'Class', + 'class_ph' => 'E.g. 9th A', + 'school_year' => 'School year', + 'generate_pdf' => 'Generate PDF', + 'change_class_desc' => 'Reassign :name for the :year year.', + 'new_class' => 'New class', + 'choose_class' => 'Choose a class', + 'reassign_hint' => 'Fees already invoiced are not recalculated automatically.', + 'reassign' => 'Reassign', + ], + + 'form' => [ + 'create_title' => 'Add a student', + 'create_subtitle' => 'Follow the steps to add a new student.', + 'edit_title' => 'Edit a student', + 'edit_subtitle' => 'Update the information through the same step-by-step form.', + 'edit_head' => 'Edit student', + 'adding' => 'Adding...', + 'saving' => 'Saving...', + 'step' => 'Step :n', + 'select' => 'Select', + 'photo_selected' => 'Selected: :name', + 'photo_hint' => 'Image (JPG, PNG…), 4 MB max.', + 'photo_hint_keep' => ' Leave empty to keep the current photo.', + 'summary_hint' => 'Review the information before adding the student.', + 'photo_kept' => 'Current photo kept', + 'photo_none' => 'None', + + 'steps' => [ + 'student' => 'Student information', + 'admin' => 'Administrative file', + 'parents' => 'Parents', + 'health' => 'Health', + 'summary' => 'Summary', + ], + + 'sec' => [ + 'identity' => 'Student identity', + 'birth' => 'Birth', + 'contact' => 'Contact details', + 'photo_status' => 'Photo and status', + 'admission' => 'Admission type', + 'birth_cert' => 'Birth certificate', + 'father' => "Father's information", + 'mother' => "Mother's information", + 'parents_contact' => 'Parents contact', + 'medical' => 'Medical information', + 'emergency' => 'Emergency contact', + 'allergies' => 'Allergies and sensitivities', + 'summary' => 'Summary', + ], + + 'lbl' => [ + 'matricule' => 'ID number', + 'gender' => 'Gender *', + 'firstname' => 'First name *', + 'lastname' => 'Last name *', + 'birth_date' => 'Date of birth *', + 'place_of_birth' => 'Place of birth', + 'nationality' => 'Nationality', + 'phone' => 'Phone', + 'email' => 'Email', + 'city' => 'City', + 'region' => 'Region', + 'prefecture' => 'Prefecture', + 'address' => 'Address', + 'profile_photo' => 'Profile photo', + 'active' => 'Active student', + 'admission_type' => 'Admission type *', + 'birth_cert_number' => 'Birth certificate no.', + 'issue_date' => 'Issue date', + 'issue_place' => 'Issue place', + 'father_firstname' => "Father's first name *", + 'father_lastname' => "Father's last name *", + 'father_profession' => "Father's occupation", + 'father_phone' => "Father's phone", + 'mother_firstname' => "Mother's first name *", + 'mother_lastname' => "Mother's last name *", + 'mother_profession' => "Mother's occupation", + 'mother_phone' => "Mother's phone", + 'parent_email' => 'Parent/guardian email', + 'blood_group' => 'Blood group', + 'vaccinations' => 'Vaccinations', + 'emergency_contact' => 'Emergency contact', + 'emergency_phone' => 'Emergency phone', + 'allergies' => 'Allergies', + ], + + 'recap' => [ + 'identity' => 'Identity', + 'birth' => 'Birth', + 'origin_contact' => 'Origin & contact', + 'admin' => 'Administrative file', + 'parents' => 'Parents', + 'health' => 'Health', + 'photo_status' => 'Photo & status', + 'matricule' => 'ID number', + 'lastname' => 'Last name', + 'firstname' => 'First name', + 'gender' => 'Gender', + 'date' => 'Date', + 'place' => 'Place', + 'region' => 'Region', + 'prefecture' => 'Prefecture', + 'city' => 'City', + 'address' => 'Address', + 'nationality' => 'Nationality', + 'phone' => 'Phone', + 'email' => 'Email', + 'admission_type' => 'Admission type', + 'birth_cert' => 'Birth certificate no.', + 'father' => 'Father', + 'father_phone' => 'Father phone', + 'mother' => 'Mother', + 'mother_phone' => 'Mother phone', + 'blood_group' => 'Blood group', + 'emergency' => 'Emergency contact', + 'emergency_phone' => 'Emergency phone', + 'photo' => 'Photo', + 'status' => 'Status', + ], + ], +]; diff --git a/lang/en/timetable.php b/lang/en/timetable.php new file mode 100644 index 00000000..7b7a64c9 --- /dev/null +++ b/lang/en/timetable.php @@ -0,0 +1,36 @@ + 'Timetable', + 'title' => 'Timetable', + 'subtitle' => 'Organize class slots per class for the active year.', + 'teacher_view' => 'Teacher view', + 'choose_class' => 'Choose a class', + 'export_pdf' => 'Export to PDF', + 'select_class_prompt' => 'Select a class to display its timetable.', + 'add_slot' => 'Add a slot', + 'subject_fallback' => 'Subject', + 'edit_slot' => 'Edit slot', + 'new_slot' => 'New slot', + 'day' => 'Day', + 'start' => 'Start', + 'end' => 'End', + 'subject' => 'Subject', + 'choose' => 'Choose', + 'teacher' => 'Teacher', + 'room' => 'Room', + 'room_ph' => 'e.g. Room 12', + 'cancel' => 'Cancel', + 'update' => 'Update', + 'add' => 'Add', + + 'head_teacher' => 'Timetable — by teacher', + 'subtitle_teacher' => 'A teacher\'s schedule for the active year, across all classes.', + 'choose_teacher' => 'Choose a teacher', + 'select_teacher_prompt' => 'Select a teacher to display their timetable.', + 'no_slots_teacher' => 'No slots for this teacher in the active year.', + 'slot_one' => 'slot', + 'slot_other' => 'slots', + 'class_one' => 'class', + 'class_other' => 'classes', +]; diff --git a/lang/en/welcome.php b/lang/en/welcome.php new file mode 100644 index 00000000..221e8259 --- /dev/null +++ b/lang/en/welcome.php @@ -0,0 +1,34 @@ + 'Dalibi — School management', + 'dashboard' => 'Dashboard', + 'logout' => 'Log out', + 'login' => 'Sign in', + 'hello' => 'Hello', + 'welcome_to' => 'Welcome to', + 'subtitle_authed' => 'Choose a module to get started.', + 'subtitle_guest' => 'Sign in to access your school\'s modules.', + 'no_modules' => 'No module accessible with your profile.', + 'open' => 'Open', + 'footer' => 'Dalibi — Open source school management software for Africa.', + + 'modules' => [ + 'students' => ['title' => 'Students', 'desc' => 'Enrollments, records and headcounts'], + 'bulletins' => ['title' => 'Grades & Reports', 'desc' => 'Grade entry and report cards'], + 'exams' => ['title' => 'Exams', 'desc' => 'Assessments and official exams'], + 'attendances' => ['title' => 'Attendance', 'desc' => 'Roll call and attendance tracking'], + 'accounting' => ['title' => 'Accounting', 'desc' => 'Tuition, payments and cash'], + 'payroll' => ['title' => 'Staff & Payroll', 'desc' => 'Employees, pay runs and payslips'], + 'archives' => ['title' => 'Documents & Archives', 'desc' => 'Certificates and archiving'], + 'statistics' => ['title' => 'Statistics', 'desc' => 'Indicators and steering'], + 'calendar' => ['title' => 'Calendar', 'desc' => 'School year events'], + 'timetable' => ['title' => 'Timetable', 'desc' => 'Weekly grid per class'], + 'scholarships' => ['title' => 'Scholarships', 'desc' => 'Student scholarships and aid'], + 'note_reclamations' => ['title' => 'Grade appeals', 'desc' => 'Grade requests and reviews'], + 'absence_permissions' => ['title' => 'Absence permissions', 'desc' => 'Requests and supporting documents'], + 'portal' => ['title' => 'Portal access', 'desc' => 'Parent and student accounts'], + 'users' => ['title' => 'Users', 'desc' => 'Staff accounts and roles'], + 'audit' => ['title' => 'Audit log', 'desc' => 'Action traceability'], + ], +]; diff --git a/lang/fr/administration.php b/lang/fr/administration.php new file mode 100644 index 00000000..c76a0cc0 --- /dev/null +++ b/lang/fr/administration.php @@ -0,0 +1,570 @@ + [ + 'head' => "Journal d'audit", + 'subtitle' => 'Historique des créations, modifications et suppressions sur les données sensibles.', + 'search_ph' => "Rechercher (libellé de l'entité)…", + 'action' => 'Action', + 'all_actions' => 'Toutes les actions', + 'entity' => 'Entité', + 'all_entities' => 'Toutes les entités', + 'event_created' => 'Création', + 'event_updated' => 'Modification', + 'event_deleted' => 'Suppression', + 'event_restored' => 'Restauration', + 'col_date' => 'Date', + 'col_author' => 'Auteur', + 'col_action' => 'Action', + 'col_entity' => 'Entité', + 'col_changes' => 'Modifications', + 'empty' => 'Aucune entrée.', + 'system' => 'Système', + 'more_fields' => '+:count champ(s)…', + 'range' => 'Affichage :from à :to sur :total', + ], + + 'guardians' => [ + 'index' => [ + 'head' => 'Accès portail', + 'subtitle' => 'Comptes tuteurs et liaison aux élèves pour le portail parents.', + 'new' => 'Nouveau tuteur', + 'search_ph' => 'Rechercher un tuteur (nom, e-mail, téléphone) ou un enfant (matricule, nom)…', + 'clear_search' => 'Effacer la recherche', + 'col_guardian' => 'Tuteur', + 'col_email' => 'E-mail', + 'col_children' => 'Enfants', + 'col_status' => 'Statut', + 'col_actions' => 'Actions', + 'empty' => 'Aucun compte tuteur.', + 'activated' => 'Activé', + 'pending' => 'En attente', + 'resend_invite' => "Renvoyer l'invitation", + 'edit' => 'Modifier', + 'delete' => 'Supprimer', + 'range' => 'Affichage :from à :to sur :total', + 'delete_title' => 'Supprimer ce compte tuteur ?', + 'delete_desc' => "L'accès au portail sera révoqué. Les élèves ne sont pas affectés.", + 'cancel' => 'Annuler', + ], + 'create' => [ + 'head' => 'Nouveau tuteur', + 'title' => 'Nouveau tuteur', + 'subtitle' => 'Créez le compte et liez-le à un ou plusieurs élèves (par matricule).', + ], + 'edit' => [ + 'head' => 'Modifier le tuteur', + 'title' => 'Modifier le tuteur', + ], + 'form' => [ + 'first_name' => 'Prénom *', + 'last_name' => 'Nom *', + 'email' => 'E-mail *', + 'phone' => 'Téléphone', + 'linked_students' => 'Élèves liés', + 'search_student_ph' => 'Rechercher un élève par nom ou matricule…', + 'searching' => 'Recherche…', + 'no_student' => 'Aucun élève trouvé.', + 'remove_student' => 'Retirer :name', + 'none_linked' => "Aucun élève lié pour l'instant.", + 'matricule_not_found' => 'Un matricule est introuvable.', + 'send_invitation' => "Envoyer l'invitation par e-mail", + 'save' => 'Enregistrer', + 'create' => 'Créer le tuteur', + 'cancel' => 'Annuler', + ], + ], + + 'permissions' => [ + 'index' => [ + 'head' => 'Permissions', + 'subtitle' => "Gérez les permissions de l'application", + 'new' => 'Nouvelle permission', + 'stat_total' => 'Permissions totales', + 'stat_avg_per_role' => 'Moyenne par rôle', + 'stat_page' => 'Page', + 'search_ph' => 'Rechercher une permission par nom ou description...', + 'search' => 'Rechercher', + 'results_for' => 'Résultats pour :', + 'col_name' => 'Nom', + 'col_description' => 'Description', + 'col_roles' => 'Rôles associés', + 'col_actions' => 'Actions', + 'empty' => 'Aucune permission trouvée', + 'view' => 'Voir', + 'edit' => 'Modifier', + 'delete' => 'Supprimer', + 'range' => 'Affichage :from à :to sur :total permissions', + 'previous' => 'Précédent', + 'next' => 'Suivant', + 'page_of' => 'Page :current sur :last', + 'delete_title' => 'Supprimer la permission', + 'delete_desc' => 'Êtes-vous sûr de vouloir supprimer cette permission ? Cette action est irréversible.', + 'cancel' => 'Annuler', + 'deleting' => 'Suppression...', + ], + 'create' => [ + 'head' => 'Créer une permission', + 'title' => 'Créer une nouvelle permission', + 'name' => 'Nom de la permission *', + 'name_ph' => 'Ex: view_students', + 'name_hint' => 'Utilisez des caractères minuscules et des underscores', + 'description' => 'Description', + 'description_ph' => 'Décrivez la permission...', + 'creating' => 'Création...', + 'create' => 'Créer', + 'cancel' => 'Annuler', + ], + 'edit' => [ + 'head' => 'Éditer :name', + 'title' => 'Éditer la permission', + 'name' => 'Nom de la permission *', + 'name_ph' => 'Ex: view_students', + 'name_hint' => 'Utilisez des caractères minuscules et des underscores', + 'description' => 'Description', + 'description_ph' => 'Décrivez la permission...', + 'updating' => 'Mise à jour...', + 'update' => 'Mettre à jour', + 'cancel' => 'Annuler', + ], + 'show' => [ + 'edit' => 'Éditer', + 'info' => 'Informations', + 'name' => 'Nom', + 'description' => 'Description', + 'assigned_roles' => 'Rôles attribués', + 'events' => 'Événements', + 'created_on' => 'Créée le', + 'updated_on' => 'Modifiée le', + 'stats' => 'Statistiques', + 'associated_roles' => 'Rôles associés', + 'no_role' => "Aucun rôle n'utilise cette permission.", + ], + ], + + 'roles' => [ + 'index' => [ + 'head' => 'Rôles', + 'title' => 'Rôles et Permissions', + 'subtitle' => 'Gérez les rôles et leurs permissions', + 'new' => 'Nouveau rôle', + 'stat_total' => 'Rôles totaux', + 'stat_avg_permissions' => 'Moyenne de permissions', + 'stat_page' => 'Page', + 'search_ph' => 'Rechercher un rôle par nom ou description...', + 'search' => 'Rechercher', + 'results_for' => 'Résultats pour :', + 'col_name' => 'Nom', + 'col_description' => 'Description', + 'col_permissions' => 'Permissions', + 'col_actions' => 'Actions', + 'empty' => 'Aucun rôle trouvé', + 'view' => 'Voir', + 'edit' => 'Modifier', + 'delete' => 'Supprimer', + 'range' => 'Affichage :from à :to sur :total rôles', + 'previous' => 'Précédent', + 'next' => 'Suivant', + 'page_of' => 'Page :current sur :last', + 'delete_title' => 'Supprimer le rôle', + 'delete_desc' => 'Êtes-vous sûr de vouloir supprimer ce rôle ? Cette action est irréversible.', + 'cancel' => 'Annuler', + 'deleting' => 'Suppression...', + ], + 'create' => [ + 'head' => 'Créer un rôle', + 'title' => 'Créer un nouveau rôle', + 'name' => 'Nom du rôle *', + 'name_ph' => 'Ex: Administrateur', + 'description' => 'Description', + 'description_ph' => 'Décrivez le rôle...', + 'creating' => 'Création...', + 'create' => 'Créer', + 'cancel' => 'Annuler', + 'permissions' => 'Permissions (:count)', + ], + 'edit' => [ + 'head' => 'Éditer :name', + 'title' => 'Éditer le rôle', + 'name' => 'Nom du rôle *', + 'name_ph' => 'Ex: Administrateur', + 'description' => 'Description', + 'description_ph' => 'Décrivez le rôle...', + 'updating' => 'Mise à jour...', + 'update' => 'Mettre à jour', + 'cancel' => 'Annuler', + 'permissions' => 'Permissions (:count)', + ], + 'show' => [ + 'edit' => 'Éditer', + 'info' => 'Informations', + 'name' => 'Nom', + 'description' => 'Description', + 'assigned_permissions' => 'Permissions attribuées', + 'events' => 'Événements', + 'created_on' => 'Créé le', + 'updated_on' => 'Modifié le', + 'stats' => 'Statistiques', + 'role_permissions' => 'Permissions du rôle', + 'no_permission' => 'Aucune permission attribuée à ce rôle.', + ], + ], + + 'subject_assignments' => [ + 'active' => 'Active', + 'inactive' => 'Inactive', + 'index' => [ + 'head' => 'Affectations de matières', + 'title' => 'Affectations de matières', + 'subtitle' => 'Gérez les affectations des enseignants aux matières et classes', + 'statistics' => 'Statistiques des affectations', + 'new' => 'Nouvelle affectation', + 'stat_total' => 'Affectations totales', + 'stat_active' => 'Affectations actives', + 'stat_page' => 'Page', + 'all_years' => 'Toutes les années', + 'all_classes' => 'Toutes les classes', + 'filter_year' => 'Filtrer par année', + 'filter_class' => 'Filtrer par classe', + 'search_ph' => 'Rechercher par matière, enseignant ou classe...', + 'col_subject' => 'Matière', + 'col_teacher' => 'Enseignant', + 'col_class' => 'Classe', + 'col_year' => 'Année', + 'col_status' => 'Statut', + 'col_actions' => 'Actions', + 'empty' => 'Aucune affectation trouvée', + 'view' => 'Voir', + 'edit' => 'Modifier', + 'delete' => 'Supprimer', + 'range' => 'Affichage de :from à :to sur :total résultats', + 'previous' => 'Précédent', + 'next' => 'Suivant', + 'delete_title' => 'Confirmer la suppression', + 'delete_desc' => 'Êtes-vous sûr de vouloir supprimer cette affectation ? Cette action est irréversible.', + 'cancel' => 'Annuler', + 'deleting' => 'Suppression...', + ], + 'form' => [ + 'subject' => 'Matière', + 'subject_required' => 'Matière', + 'select_subject' => 'Sélectionner une matière', + 'teacher' => 'Enseignant', + 'search_teacher_ph' => 'Rechercher un enseignant...', + 'no_user' => 'Aucun utilisateur trouvé.', + 'class' => 'Classe', + 'select_class' => 'Sélectionner une classe', + 'year' => 'Année académique', + 'select_year' => 'Sélectionner une année', + 'extra_info' => 'Informations complémentaires', + 'notes' => 'Notes / commentaires', + 'notes_ph' => 'Notes ou commentaires...', + 'active' => 'Affectation active', + 'cancel' => 'Annuler', + ], + 'create' => [ + 'head' => 'Créer une affectation', + 'title' => 'Créer une nouvelle affectation', + 'subtitle' => 'Associez une matière, un enseignant, une classe et une année académique.', + 'creating' => 'Création...', + 'submit' => "Créer l'affectation", + ], + 'edit' => [ + 'head' => 'Modifier affectation - :subject', + 'title' => "Modifier l'affectation", + 'saving' => 'Enregistrement...', + 'submit' => 'Enregistrer les modifications', + ], + 'show' => [ + 'head' => 'Affectation - :subject', + 'title' => "Détails de l'affectation", + 'edit' => 'Modifier', + 'info' => "Informations de l'affectation", + 'subject' => 'Matière', + 'teacher' => 'Enseignant', + 'class' => 'Classe', + 'class_type' => 'Type de classe', + 'year' => 'Année académique', + 'status' => 'Statut', + 'notes' => 'Notes', + 'system_info' => 'Informations système', + 'created_at' => 'Date de création', + 'updated_at' => 'Dernière modification', + 'id' => 'Identifiant', + 'about' => 'À propos', + 'about_desc' => 'Les affectations permettent de lier un enseignant à une matière spécifique pour une classe et une année académique données.', + ], + 'statistics' => [ + 'head' => 'Statistiques des affectations', + 'title' => 'Statistiques des affectations', + 'subtitle' => "Affectations d'un enseignant pour une année académique", + 'export' => 'Exporter (PDF)', + 'year' => 'Année académique', + 'teacher' => 'Enseignant', + 'select_teacher' => '— Sélectionner un enseignant —', + 'select_prompt' => 'Sélectionnez un enseignant pour voir ses affectations.', + 'kpi_assignments' => 'Affectations', + 'kpi_active' => 'Actives', + 'kpi_subjects' => 'Matières', + 'kpi_classes' => 'Classes', + 'col_subject' => 'Matière', + 'col_class' => 'Classe', + 'col_status' => 'Statut', + 'empty' => 'Aucune affectation pour cet enseignant sur cette année.', + ], + ], + + 'users' => [ + 'gender' => [ + 'male' => 'Masculin', + 'female' => 'Féminin', + 'other' => 'Autre', + ], + 'pay_status' => [ + 'draft' => 'Brouillon', + 'validated' => 'Validé', + 'paid' => 'Payé', + 'cancelled' => 'Annulé', + ], + 'days' => [ + 1 => 'Lundi', + 2 => 'Mardi', + 3 => 'Mercredi', + 4 => 'Jeudi', + 5 => 'Vendredi', + 6 => 'Samedi', + 7 => 'Dimanche', + ], + 'months_short' => [ + 1 => 'janv.', + 2 => 'févr.', + 3 => 'mars', + 4 => 'avr.', + 5 => 'mai', + 6 => 'juin', + 7 => 'juil.', + 8 => 'août', + 9 => 'sept.', + 10 => 'oct.', + 11 => 'nov.', + 12 => 'déc.', + ], + + 'index' => [ + 'head' => 'Utilisateurs', + 'subtitle' => "Gérez les utilisateurs de l'application", + 'new' => 'Nouvel utilisateur', + 'stat_total' => 'Utilisateurs totaux', + 'stat_active' => 'Utilisateurs actifs', + 'stat_page' => 'Page', + 'search_ph' => 'Rechercher un utilisateur par nom, email ou matricule...', + 'all_roles' => 'Tous les rôles', + 'all_genders' => 'Tous les genres', + 'filter' => 'Filtrer', + 'reset' => 'Réinitialiser', + 'active_filters' => 'Filtres actifs', + 'filter_search' => ' • Recherche : « :search »', + 'filter_role' => ' • Rôle sélectionné', + 'filter_gender' => ' • Genre : :gender', + 'count' => ':count utilisateur(s)', + 'per_page' => 'Lignes par page :', + 'col_user' => 'Utilisateur', + 'col_email' => 'Email', + 'col_matricule' => 'Matricule', + 'col_phone' => 'Téléphone', + 'col_roles' => 'Rôles', + 'col_actions' => 'Actions', + 'empty' => 'Aucun utilisateur trouvé', + 'no_role' => 'Aucun rôle', + 'view' => 'Voir', + 'edit' => 'Modifier', + 'delete' => 'Supprimer', + 'range' => ':from–:to sur :total utilisateurs', + 'delete_title' => "Supprimer l'utilisateur", + 'delete_desc' => 'Êtes-vous sûr de vouloir supprimer cet utilisateur ? Cette action est irréversible.', + 'cancel' => 'Annuler', + 'deleting' => 'Suppression...', + ], + + 'form' => [ + 'personal_info' => 'Informations personnelles', + 'firstname' => 'Prénom *', + 'firstname_ph' => 'Ex: Jean', + 'lastname' => 'Nom *', + 'lastname_ph' => 'Ex: Dupont', + 'gender' => 'Genre *', + 'birth_date' => 'Date de naissance', + 'contact' => 'Coordonnées', + 'email' => 'Email *', + 'email_ph' => 'exemple@email.com', + 'phone' => 'Téléphone', + 'phone_ph' => '+228 XX XX XX XX', + 'address' => 'Adresse', + 'address_ph' => '123 Rue de la Paix', + 'security' => 'Sécurité', + 'security_hint' => 'Laissez ces champs vides pour conserver le mot de passe actuel.', + 'password' => 'Mot de passe *', + 'new_password' => 'Nouveau mot de passe', + 'password_confirmation' => 'Confirmer le mot de passe *', + 'password_confirmation_opt' => 'Confirmer le mot de passe', + 'profile' => 'Profil', + 'profile_desc' => 'Description du profil', + 'profile_ph' => "Décrivez le profil de l'utilisateur...", + 'selected_roles' => 'Rôles sélectionnés', + 'roles_hint' => 'Attribuez un ou plusieurs rôles selon les responsabilités.', + 'roles' => 'Rôles', + 'cancel' => 'Annuler', + ], + + 'create' => [ + 'head' => 'Créer un utilisateur', + 'title' => 'Créer un nouvel utilisateur', + 'creating' => 'Création...', + 'submit' => "Créer l'utilisateur", + ], + + 'edit' => [ + 'head' => 'Modifier :name', + 'title' => 'Modifier :name', + 'updating' => 'Mise à jour...', + 'submit' => 'Mettre à jour', + ], + + 'show' => [ + 'edit' => 'Modifier', + 'personal_info' => 'Informations personnelles', + 'firstname' => 'Prénom', + 'lastname' => 'Nom', + 'gender' => 'Genre', + 'birth_date' => 'Date de naissance', + 'contact' => 'Coordonnées', + 'email' => 'Email', + 'phone' => 'Téléphone', + 'address' => 'Adresse', + 'not_provided' => 'Non renseigné', + 'not_provided_f' => 'Non renseignée', + 'assignments' => 'Affectations matières (:count)', + 'no_assignment' => 'Aucune affectation de matière.', + 'col_subject' => 'Matière', + 'col_class' => 'Classe', + 'col_year' => 'Année', + 'col_status' => 'Statut', + 'active' => 'Active', + 'inactive' => 'Inactive', + 'timetable' => 'Emploi du temps (:count)', + 'no_slot' => 'Aucun créneau assigné.', + 'col_day' => 'Jour', + 'col_slot' => 'Créneau', + 'col_room' => 'Salle', + 'payslips' => 'Derniers bulletins de paie (:count)', + 'no_payslip' => 'Aucun bulletin généré.', + 'download' => 'Télécharger', + 'profile' => 'Profil', + 'history' => 'Historique', + 'created_on' => 'Créé le', + 'updated_on' => 'Dernière modification', + 'roles' => 'Rôles (:count)', + 'no_role' => 'Aucun rôle attribué', + 'permissions' => 'Permissions', + 'no_permission' => 'Aucune permission', + ], + + 'payroll' => [ + 'title' => 'Profil paie / RH', + 'edit' => 'Modifier', + 'remove' => 'Retirer', + 'no_profile' => "Cet utilisateur n'a pas encore de profil paie.", + 'add' => 'Ajouter à la paie', + 'employee_number' => 'Matricule employé', + 'job' => 'Poste', + 'contract' => 'Contrat', + 'status' => 'Statut', + 'grade_base' => 'Grille / salaire de base', + 'off_grade' => 'Hors grille (saisi manuellement)', + 'payment' => 'Paiement', + 'cnss' => 'N° CNSS', + 'allowances' => "Primes & retenues de l'employé", + 'no_allowance' => 'Aucune prime ou retenue récurrente.', + 'added_by' => 'ajouté par :name', + 'percent_base' => ':amount% du base', + 'delete' => 'Supprimer', + 'label' => 'Libellé', + 'label_ph' => 'Ex: Prime de responsabilité', + 'type' => 'Type', + 'type_earning' => 'Gain', + 'type_deduction' => 'Retenue', + 'mode' => 'Mode', + 'mode_fixed' => 'Montant fixe', + 'mode_percent' => '% du base', + 'percentage' => 'Pourcentage', + 'amount' => 'Montant', + 'reason' => 'Motif (traçabilité)', + 'reason_ph' => 'Ex: Chef de département depuis septembre', + 'add_btn' => 'Ajouter', + 'remove_title' => 'Retirer de la paie ?', + 'remove_desc' => "Le profil paie sera supprimé. Impossible si l'employé a déjà des bulletins.", + 'cancel' => 'Annuler', + 'delete_allowance_title' => 'Supprimer cette prime / retenue ?', + 'delete_allowance_desc' => "Elle ne s'appliquera plus aux prochains bulletins. Les bulletins déjà générés ne sont pas affectés.", + 'contract_cdi' => 'CDI', + 'contract_cdd' => 'CDD', + 'contract_vacataire' => 'Vacataire', + 'contract_stagiaire' => 'Stagiaire', + 'status_active' => 'Actif', + 'status_suspended' => 'Suspendu', + 'status_terminated' => 'Sorti des effectifs', + 'method_cash' => 'Espèces', + 'method_mobile_money' => 'Mobile Money', + 'method_bank_transfer' => 'Virement', + 'method_cheque' => 'Chèque', + ], + + 'employee_form' => [ + 'identity' => 'Identité & poste', + 'user' => 'Utilisateur associé *', + 'select_user' => 'Sélectionner un utilisateur', + 'employee' => 'Employé', + 'employee_number' => 'Matricule employé', + 'employee_number_ph' => 'Auto si vide', + 'job' => 'Poste *', + 'job_ph' => 'Ex: Enseignant, Surveillant…', + 'department' => 'Département / service', + 'department_ph' => 'Ex: Primaire, Administration…', + 'contract' => 'Type de contrat *', + 'select' => 'Sélectionner', + 'status' => 'Statut *', + 'remuneration' => 'Rémunération & paiement', + 'grade' => 'Grille salariale', + 'no_grade_ph' => 'Aucune (salaire saisi manuellement)', + 'no_grade' => 'Aucune (hors grille)', + 'grade_base' => 'Salaire de base : :amount (issu de la grille)', + 'base_ignored' => 'Salaire de base (ignoré, grille active)', + 'base' => 'Salaire de base mensuel (F) *', + 'base_ph' => 'Ex: 120000', + 'payment_method' => 'Méthode de paiement *', + 'momo_number' => 'Numéro Mobile Money', + 'momo_number_ph' => 'Ex: +228 90 00 00 00', + 'bank_name' => 'Banque', + 'bank_name_ph' => 'Ex: Ecobank', + 'bank_account' => 'N° de compte', + 'cnss_number' => 'N° CNSS', + 'cnss_number_ph' => 'Sécurité sociale', + 'contract_notes' => 'Contrat & notes', + 'hire_date' => "Date d'embauche", + 'end_date' => 'Date de fin (si applicable)', + 'notes' => 'Notes', + 'update' => 'Mettre à jour', + 'saving' => 'Enregistrement...', + 'updating' => 'Mise à jour...', + 'create' => "Enregistrer l'employé", + 'cancel' => 'Annuler', + 'status_active' => 'Actif', + 'status_suspended' => 'Suspendu', + 'status_terminated' => 'Sorti des effectifs', + 'method_cash' => 'Espèces', + 'method_mobile_money' => 'Mobile Money', + 'method_bank_transfer' => 'Virement bancaire', + 'method_cheque' => 'Chèque', + ], + ], +]; diff --git a/lang/fr/archives.php b/lang/fr/archives.php new file mode 100644 index 00000000..4c8c648a --- /dev/null +++ b/lang/fr/archives.php @@ -0,0 +1,89 @@ + 'Archives documentaires', + 'title' => 'Archives documentaires', + 'subtitle_docs' => ':count document(s) archivé(s)', + 'trashed_suffix' => ' · :count en corbeille', + 'view_active' => 'Voir actifs', + 'trash' => 'Corbeille', + 'archive_doc' => 'Archiver un document', + 'search' => 'Recherche', + 'search_ph' => 'Titre, référence…', + 'category' => 'Catégorie', + 'all_categories' => 'Toutes', + 'from' => 'Du', + 'to' => 'Au', + 'reset' => 'Réinitialiser', + 'th_reference' => 'Référence', + 'th_document' => 'Document', + 'th_tags' => 'Tags', + 'th_linked' => 'Lié à', + 'th_size' => 'Taille', + 'th_archived' => 'Archivé', + 'th_actions' => 'Actions', + 'empty_trash' => 'Corbeille vide.', + 'empty' => 'Aucun document archivé.', + 'restore' => 'Restaurer', + 'delete_forever' => 'Supprimer définitivement', + 'download' => 'Télécharger', + 'edit' => 'Modifier', + 'delete' => 'Supprimer', + 'prev' => 'Précédent', + 'next' => 'Suivant', + 'page_of' => 'Page :current / :last', + 'edit_title' => 'Modifier le document', + 'create_title' => 'Archiver un document', + 'edit_desc' => 'Mettez à jour les métadonnées et les tags.', + 'create_desc' => 'Ajoutez un fichier au coffre documentaire avec ses métadonnées et tags.', + 'f_title' => 'Titre', + 'f_category' => 'Catégorie', + 'retention' => 'Conservation jusqu\'au', + 'description' => 'Description', + 'file' => 'Fichier', + 'tags' => 'Tags', + 'add_tag_ph' => 'Ajouter un tag puis Entrée…', + 'linked_to' => 'Lié à', + 'link_none' => 'Aucun', + 'link_classroom' => 'Classe', + 'link_student' => 'Élève (matricule)', + 'classroom' => 'Classe', + 'choose' => 'Choisir', + 'student_matricule' => 'Matricule élève', + 'cancel' => 'Annuler', + 'save' => 'Enregistrer', + 'archive' => 'Archiver', + 'delete_forever_q' => 'Supprimer définitivement ?', + 'delete_forever_desc' => 'Le fichier « :title » sera supprimé du stockage. Cette action est irréversible.', + + // Tags + 'tags_page' => [ + 'head' => 'Tags d\'archivage', + 'title' => 'Tags d\'archivage', + 'subtitle' => 'Organisez les documents archivés par mots-clés réutilisables.', + 'new_tag' => 'Nouveau tag', + 'total' => 'Tags au total', + 'search_ph' => 'Rechercher un tag par nom...', + 'th_tag' => 'Tag', + 'th_documents' => 'Documents', + 'th_color' => 'Couleur', + 'th_actions' => 'Actions', + 'documents_count' => ':count document(s)', + 'change_color' => 'Changer la couleur', + 'color_aria' => 'Couleur :color', + 'delete' => 'Supprimer', + 'empty' => 'Aucun tag trouvé', + 'showing' => 'Affichage :from à :to sur :total', + 'new_tag_desc' => 'Un mot-clé réutilisable pour classer les documents archivés.', + 'name' => 'Nom du tag', + 'name_ph' => 'Ex : Contrat, Inspection…', + 'color' => 'Couleur', + 'preview' => 'Aperçu :', + 'default_name' => 'Nom du tag', + 'cancel' => 'Annuler', + 'create' => 'Créer', + 'delete_q' => 'Supprimer ce tag ?', + 'delete_desc' => 'Le tag sera retiré de tous les documents associés. Les documents ne sont pas supprimés.', + ], +]; diff --git a/lang/fr/auth.php b/lang/fr/auth.php new file mode 100644 index 00000000..d9e1c517 --- /dev/null +++ b/lang/fr/auth.php @@ -0,0 +1,64 @@ + [ + 'title' => 'Connexion', + 'description' => 'Entrez vos identifiants pour accéder à votre compte', + 'email' => 'Adresse e-mail', + 'email_placeholder' => 'vous@exemple.com', + 'password' => 'Mot de passe', + 'password_placeholder' => 'Mot de passe', + 'forgot' => 'Mot de passe oublié ?', + 'remember' => 'Se souvenir de moi', + 'submit' => 'Se connecter', + ], + 'forgot_password' => [ + 'title' => 'Mot de passe oublié', + 'description' => 'Saisissez votre e-mail pour recevoir un lien de réinitialisation', + 'email' => 'Adresse e-mail', + 'email_placeholder' => 'email@exemple.com', + 'submit' => 'Envoyer le lien de réinitialisation', + 'return_prefix' => 'Ou revenez à la', + 'login_link' => 'connexion', + ], + 'two_factor' => [ + 'head' => 'Authentification à deux facteurs', + 'auth_title' => 'Code d\'authentification', + 'auth_description' => 'Saisissez le code d\'authentification fourni par votre application d\'authentification.', + 'auth_toggle' => 'se connecter avec un code de récupération', + 'recovery_title' => 'Code de récupération', + 'recovery_description' => 'Confirmez l\'accès à votre compte en saisissant l\'un de vos codes de récupération d\'urgence.', + 'recovery_toggle' => 'se connecter avec un code d\'authentification', + 'recovery_placeholder' => 'Saisir le code de récupération', + 'continue' => 'Continuer', + 'or_you_can' => 'ou vous pouvez', + ], + 'verify_email' => [ + 'title' => 'Vérifier l\'e-mail', + 'description' => 'Veuillez vérifier votre adresse e-mail en cliquant sur le lien que nous venons de vous envoyer.', + 'head' => 'Vérification de l\'e-mail', + 'link_sent' => 'Un nouveau lien de vérification a été envoyé à l\'adresse e-mail que vous avez fournie lors de l\'inscription.', + 'resend' => 'Renvoyer l\'e-mail de vérification', + 'logout' => 'Déconnexion', + ], + 'confirm_password' => [ + 'title' => 'Confirmez votre mot de passe', + 'description' => 'Ceci est une zone sécurisée de l\'application. Veuillez confirmer votre mot de passe avant de continuer.', + 'head' => 'Confirmer le mot de passe', + 'password' => 'Mot de passe', + 'password_placeholder' => 'Mot de passe', + 'submit' => 'Confirmer le mot de passe', + ], + 'reset_password' => [ + 'title' => 'Réinitialiser le mot de passe', + 'description' => 'Veuillez saisir votre nouveau mot de passe ci-dessous', + 'head' => 'Réinitialiser le mot de passe', + 'email' => 'E-mail', + 'password' => 'Mot de passe', + 'password_placeholder' => 'Mot de passe', + 'confirm' => 'Confirmer le mot de passe', + 'confirm_placeholder' => 'Confirmer le mot de passe', + 'submit' => 'Réinitialiser le mot de passe', + ], + 'logout' => 'Déconnexion', +]; diff --git a/lang/fr/calendar.php b/lang/fr/calendar.php new file mode 100644 index 00000000..4dd8f64e --- /dev/null +++ b/lang/fr/calendar.php @@ -0,0 +1,42 @@ + 'Calendrier', + 'header_title' => 'Calendrier académique', + 'subtitle' => 'Congés, examens, réunions et événements', + 'new_event' => 'Nouvel événement', + 'today' => "Aujourd'hui", + 'prev' => 'Précédent', + 'next' => 'Suivant', + 'year' => 'Année', + 'type' => 'Type', + 'all_types' => 'Tous les types', + 'drag_hint' => 'Astuce : glissez un événement pour le déplacer, tirez son bord pour changer sa fin.', + 'empty' => 'Aucun événement.', + 'all_day_label' => 'Journée', + 'resize_hint' => 'Étirer la fin', + + 'view' => [ + 'month' => 'Mois', + 'week' => 'Semaine', + 'day' => 'Jour', + 'agenda' => 'Liste', + ], + + 'dialog' => [ + 'edit_title' => "Modifier l'événement", + 'new_title' => 'Nouvel événement', + 'desc' => "Renseignez les informations de l'événement.", + 'field_title' => 'Titre *', + 'field_type' => 'Type', + 'start' => 'Début *', + 'end' => 'Fin', + 'all_day' => 'Toute la journée', + 'start_time' => 'Heure début', + 'end_time' => 'Heure fin', + 'description' => 'Description', + ], + + 'delete_title' => 'Supprimer cet événement ?', + 'delete_desc' => 'Cette action est irréversible.', +]; diff --git a/lang/fr/common.php b/lang/fr/common.php new file mode 100644 index 00000000..7e230654 --- /dev/null +++ b/lang/fr/common.php @@ -0,0 +1,60 @@ + [ + 'save' => 'Enregistrer', + 'cancel' => 'Annuler', + 'create' => 'Créer', + 'add' => 'Ajouter', + 'edit' => 'Modifier', + 'update' => 'Mettre à jour', + 'delete' => 'Supprimer', + 'remove' => 'Retirer', + 'search' => 'Rechercher', + 'filter' => 'Filtrer', + 'reset' => 'Réinitialiser', + 'confirm' => 'Confirmer', + 'back' => 'Retour', + 'close' => 'Fermer', + 'print' => 'Imprimer', + 'export' => 'Exporter', + 'download' => 'Télécharger', + 'view' => 'Voir', + 'details' => 'Détails', + 'validate' => 'Valider', + 'next' => 'Suivant', + 'previous' => 'Précédent', + 'yes' => 'Oui', + 'no' => 'Non', + ], + 'status' => [ + 'active' => 'Actif', + 'inactive' => 'Inactif', + 'pending' => 'En attente', + 'loading' => 'Chargement…', + ], + 'messages' => [ + 'none' => 'Aucun résultat', + 'confirm_delete' => 'Confirmer la suppression', + 'saved' => 'Enregistré', + 'required' => 'Ce champ est obligatoire', + ], + 'pagination' => [ + 'showing' => 'Affichage de :from à :to sur :total', + 'per_page' => 'Lignes par page', + 'page' => 'Page', + ], + 'editor' => [ + 'bold' => 'Gras', + 'italic' => 'Italique', + 'heading1' => 'Titre 1', + 'heading2' => 'Titre 2', + 'bullet_list' => 'Liste à puces', + 'numbered_list' => 'Liste numérotée', + 'align_left' => 'Aligner à gauche', + 'align_center' => 'Centrer', + 'align_right' => 'Aligner à droite', + 'undo' => 'Annuler', + 'redo' => 'Rétablir', + ], +]; diff --git a/lang/fr/comptabilite.php b/lang/fr/comptabilite.php new file mode 100644 index 00000000..066b36dc --- /dev/null +++ b/lang/fr/comptabilite.php @@ -0,0 +1,260 @@ + [ + 'head' => 'Vérifier un reçu', + 'title' => 'Vérifier un reçu', + 'subtitle' => 'Scannez le code-barres ou saisissez le code de vérification du reçu.', + 'placeholder' => 'Code de vérification (ex: DAL-XXXXXXXXXXXX)', + 'verify' => 'Vérifier', + 'authentic' => 'Reçu authentique', + 'student' => 'Élève', + 'matricule' => 'Matricule', + 'class' => 'Classe', + 'date' => 'Date', + 'amount' => 'Montant encaissé', + 'not_found' => 'Reçu introuvable', + 'not_found_hint' => 'Aucun reçu ne correspond à ce code. Ce document pourrait être falsifié.', + ], + + 'dashboard' => [ + 'head' => 'Comptabilité', + 'title' => 'Tableau de bord comptable', + 'year_prefix' => 'Année scolaire :year', + 'all_years' => 'Toutes les années', + 'filter' => 'Filtrer :', + 'all_classes' => 'Toutes les classes', + 'reset' => 'Réinitialiser', + 'alert_unpaid' => ":count élève(s) n'ont effectué aucun paiement", + 'alert_unpaid_hint' => "Ces élèves sont susceptibles d'être renvoyés si la situation n'évolue pas.", + 'total_billed' => 'Total facturé', + 'invoices' => ':count factures', + 'collected' => 'Encaissé', + 'of_total' => ':pct% du total', + 'recovered' => ':pct% recouvré', + 'remaining' => 'Reste à recouvrer', + 'open_files' => ':count dossiers ouverts', + 'paid_late' => 'Soldés / En retard', + 'partial_payments' => ':count paiements partiels', + 'recovery_global' => 'Taux de recouvrement global', + 'collected_of' => ':paid encaissés sur :total', + 'monthly' => 'Évolution mensuelle des encaissements', + 'month_collected' => 'Encaissé du mois', + 'cumul_collected' => 'Cumul encaissé', + 'expected' => 'Total attendu', + 'expected_short' => 'Attendu', + 'by_class' => 'Situation par classe', + 'col_class' => 'Classe', + 'col_students' => 'Élèves', + 'col_billed' => 'Facturé', + 'col_collected' => 'Encaissé', + 'col_remaining' => 'Reste', + 'col_rate' => 'Taux', + 'col_statuses' => 'Statuts', + 'unpaid_title' => 'Élèves avec solde impayé', + 'view_all' => 'Voir tout', + 'top_n' => 'Top :shown des plus gros soldes sur :total · ', + 'sorted' => 'Triés par montant restant décroissant · ', + 'no_payment_count' => ':count aucun paiement', + 'all_uptodate' => 'Tous les élèves sont à jour', + 'all_uptodate_hint' => 'Aucun solde impayé pour les filtres sélectionnés.', + 'col_student' => 'Élève', + 'col_total' => 'Total', + 'col_paid' => 'Payé', + 'col_status' => 'Statut', + 'col_risk' => 'Risque', + 'to_expel' => 'À renvoyer', + 'partial' => 'Partiel', + 'follow' => 'Suivi', + 'status' => [ + 'ISSUED' => 'Non payé', + 'PARTIALLY_PAID' => 'Partiel', + 'PAID' => 'Soldé', + 'CANCELLED' => 'Annulé', + ], + ], + + 'tx' => [ + 'head' => 'Journal des transactions', + 'title' => 'Journal des transactions', + 'subtitle' => 'Historique de tous les mouvements financiers', + 'new_expense' => 'Nouvelle dépense', + 'total_collected' => 'Total encaissé', + 'transactions_count' => ':count transaction(s)', + 'total_out' => 'Total sorties', + 'net_balance' => 'Solde net', + 'positive' => 'Positif', + 'negative' => 'Négatif', + 'transactions' => 'Transactions', + 'of_total' => 'au total', + 'cash_accounts' => 'Caisses :', + 'filters' => 'Filtres', + 'clear_all' => 'Tout effacer', + 'type' => 'Type', + 'all_types' => 'Tous les types', + 'income' => 'Entrées', + 'outflows' => 'Sorties', + 'income_arrow' => '↑ Entrées', + 'expense_arrow' => '↓ Sorties', + 'reference' => 'Référence', + 'all_references' => 'Toutes les références', + 'ref_payment' => 'Paiement écolage', + 'ref_scholarship' => 'Bourse accordée', + 'ref_cancellation' => 'Annulation', + 'ref_expense' => 'Dépense manuelle', + 'category' => 'Catégorie (dépense)', + 'all_categories' => 'Toutes les catégories', + 'cash' => 'Caisse', + 'all_cash' => 'Toutes les caisses', + 'period' => 'Période', + 'quick' => 'Rapide :', + 'today' => "Aujourd'hui", + 'this_month' => 'Ce mois', + 'this_year' => 'Cette année', + 'active' => 'Actifs :', + 'filtered' => '— filtrées', + 'per_page' => 'Lignes par page :', + 'empty' => 'Aucune transaction trouvée', + 'clear_filters' => 'Effacer les filtres', + 'col_type' => 'Type', + 'col_description' => 'Description', + 'col_reference' => 'Référence', + 'col_cash' => 'Caisse', + 'col_amount' => 'Montant', + 'col_date' => 'Date', + 'by_user' => 'par :name', + 'range' => ':from–:to sur :total', + 'first_page' => 'Première page', + 'last_page' => 'Dernière page', + 'delete_title' => 'Supprimer cette dépense ?', + 'delete_desc' => 'Le montant sera recrédité sur la caisse concernée. Cette action est irréversible.', + 'delete_expense_aria' => 'Supprimer cette dépense', + 'txtype' => [ + 'INCOME' => 'Entrée', + 'EXPENSE' => 'Sortie', + ], + 'ref' => [ + 'PAYMENT' => 'Paiement', + 'SCHOLARSHIP' => 'Bourse', + 'EXPENSE' => 'Dépense', + 'CANCELLATION' => 'Annulation', + ], + ], + + 'situation' => [ + 'head' => 'Situation par classe', + 'title' => 'Situation par classe', + 'year' => 'Année :year', + 'class_prefix' => 'Classe :name', + 'all_classes_suffix' => 'Toutes les classes', + 'students_concerned' => 'Élèves concernés', + 'classes_count' => ':count classe(s)', + 'total_collected' => 'Total encaissé', + 'of_billed' => ':pct% du facturé', + 'remaining' => 'Reste à recouvrer', + 'pending_files' => ':count dossier(s) en attente', + 'paid_unpaid' => 'Soldés / Non payés', + 'partial_count' => ':count paiement(s) partiel(s)', + 'collected_of_short' => ':paid sur :total', + 'summary_by_class' => 'Résumé par classe', + 'no_data' => 'Aucune donnée pour les filtres sélectionnés', + 'col_export' => 'Export', + 'selected' => 'sélectionnée', + 'export_all' => 'Tout', + 'export_unpaid' => 'Impayés', + 'export_all_title' => 'Exporter tout', + 'export_unpaid_title' => 'Exporter impayés', + 'students_of' => 'Élèves — :name', + 'students_sub' => ':count élève(s) · :risk sans aucun paiement', + 'col_invoice' => 'N° Facture', + 'col_progress' => 'Progression', + 'settled' => 'Soldé', + 'total_students_foot' => ':count élève(s)', + 'empty_class' => 'Aucun élève dans cette classe', + ], + + 'cash' => [ + 'head' => 'Gestion des caisses', + 'title' => 'Gestion des caisses', + 'subtitle' => 'Gérez vos caisses : espèces, mobile money, banque.', + 'new' => 'Nouvelle caisse', + 'global_balance' => 'Solde global toutes caisses', + 'active_count' => ':count caisse(s) active(s)', + 'create_submit' => 'Créer la caisse', + 'empty' => 'Aucune caisse configurée.', + 'empty_hint' => 'Créez votre première caisse pour commencer.', + 'name' => 'Nom *', + 'name_ph' => 'Ex: Caisse principale', + 'type_label' => 'Type *', + 'opt_cash' => '💵 Espèces', + 'opt_mobile' => '📱 Mobile Money (Flooz/TMoney)', + 'opt_bank' => '🏦 Banque', + 'description' => 'Description', + 'description_ph' => 'Optionnel', + 'current_balance' => 'Solde actuel', + 'payments_count' => ':count paiement(s)', + 'delete_title' => 'Supprimer cette caisse ?', + 'delete_desc' => 'Cette action est irréversible. Les paiements liés à cette caisse ne seront pas supprimés.', + 'type' => [ + 'CASH' => 'Espèces', + 'MOBILE_MONEY' => 'Mobile Money', + 'BANK' => 'Banque', + ], + ], + + 'receipt_doc' => [ + 'head' => 'Reçu :number', + 'print' => 'Imprimer le reçu', + 'copy_school' => 'Exemplaire établissement', + 'copy_payer' => 'Exemplaire payeur', + 'title' => 'Reçu de paiement', + 'receipt_no' => 'N° de reçu', + 'date' => 'Date :', + 'student' => 'Élève', + 'mat' => 'Mat.', + 'class_year' => 'Classe · Année', + 'enrollment_no' => 'N° inscription', + 'invoice' => 'Facture', + 'designation' => 'Désignation', + 'amount' => 'Montant', + 'invoice_total' => 'Total facture', + 'amount_collected' => 'Montant encaissé', + 'ref' => 'Réf :', + 'in_words' => 'Arrêté le présent reçu à la somme de :', + 'total_paid' => 'Total payé', + 'remaining' => 'Reste à payer', + 'stamp' => "Cachet de l'établissement", + 'accounting' => 'La Comptabilité', + 'paid_by' => 'Payé par', + 'established_on' => 'Établi le', + 'verify_p1' => 'Reçu authentifiable sur ', + 'verify_p2' => ' — code : ', + 'method' => [ + 'CASH' => 'Espèces', + 'MOBILE_MONEY' => 'Mobile Money', + 'BANK_TRANSFER' => 'Virement bancaire', + 'CHEQUE' => 'Chèque', + ], + ], + + 'expense' => [ + 'head' => 'Nouvelle dépense', + 'title' => 'Enregistrer une dépense', + 'subtitle' => 'Sortie de caisse : loyer, salaires, fournitures…', + 'details' => 'Détails de la dépense', + 'category' => 'Catégorie *', + 'select_category' => 'Sélectionner une catégorie', + 'amount' => 'Montant (F) *', + 'amount_ph' => 'Ex: 25000', + 'description' => 'Description *', + 'description_ph' => "Ex: Facture d'électricité — octobre 2026", + 'date' => 'Date *', + 'cash_account' => 'Caisse *', + 'select_cash' => 'Sélectionner une caisse', + 'balance_opt' => ':name — solde :balance', + 'after_expense' => 'Solde après dépense :', + 'recent' => 'Dernières dépenses', + 'saving' => 'Enregistrement...', + 'submit' => 'Enregistrer la dépense', + ], +]; diff --git a/lang/fr/dashboard.php b/lang/fr/dashboard.php new file mode 100644 index 00000000..c4156d41 --- /dev/null +++ b/lang/fr/dashboard.php @@ -0,0 +1,143 @@ + 'Tableau de bord', + + 'header' => [ + 'school_year' => 'Année scolaire :year', + 'no_active_year' => 'Aucune année active', + 'academic_year' => 'Année académique', + 'active_suffix' => 'active', + ], + + 'alert' => [ + 'no_payment' => ":count élève(s) n'ont effectué aucun paiement", + 'no_payment_hint' => "Ces élèves sont à risque d'exclusion faute de paiement.", + ], + + 'kpi' => [ + 'students_enrolled' => 'Élèves inscrits', + 'this_week' => 'Cette semaine', + 'this_week_sub' => 'Nouvelles inscriptions', + 'active_students' => 'Élèves actifs', + 'active_students_sub' => 'sur :total au total', + 'recovery_rate' => 'Taux de recouvrement', + 'boys' => 'Garçons', + 'girls' => 'Filles', + 'gender_sub' => ':pct% des inscrits', + 'active_classes' => 'Classes actives', + 'active_classes_sub' => 'Classes ouvertes', + 'users' => 'Utilisateurs', + 'users_sub' => 'Comptes au total', + 'teachers' => 'Enseignants', + 'teachers_sub' => 'Personnel pédagogique', + 'rem' => 'Ratio élèves/enseignant', + 'rem_sub' => 'REM · :count enseignant(s)', + 'present_today' => 'Présences du jour', + 'present_today_sub' => ":count absence(s) aujourd'hui", + 'presence_rate' => 'Taux de présence (jour)', + 'presence_rate_sub' => ':present/:total pointés', + 'presence_rate_none' => 'Aucun pointage', + 'pending_permissions' => 'Permissions en attente', + 'pending_permissions_sub' => 'À traiter', + 'documents' => 'Documents délivrés', + 'this_month' => 'Ce mois-ci', + 'official_exams' => 'Examens officiels', + 'official_exams_sub' => ':count inscription(s)', + 'validated_bulletins' => 'Bulletins validés', + 'period' => 'Période : :period', + 'all_periods' => 'Toutes périodes', + 'current_period' => 'Période courante', + 'general_average' => 'Moyenne générale', + 'total_billed' => 'Total facturé', + 'total_billed_sub' => ':count factures', + 'collected' => 'Encaissé', + 'collected_sub' => 'Taux :pct%', + 'remaining' => 'Reste à recouvrer', + 'remaining_sub' => ':count dossiers ouverts', + 'paid_unpaid' => 'Soldés / Impayés', + 'paid_unpaid_sub' => ':count paiements partiels', + 'collected_month' => 'Encaissé (mois)', + 'expenses_month' => 'Dépenses (mois)', + 'net_month' => 'Solde net (mois)', + 'surplus' => 'Excédent', + 'deficit' => 'Déficit', + ], + + 'section' => [ + 'pending_permissions' => 'Permissions en attente', + 'pending_permissions_action' => 'Permissions', + 'pending_permissions_empty' => 'Aucune demande en attente', + 'upcoming_exams' => 'Prochains examens officiels', + 'upcoming_exams_action' => 'Examens', + 'upcoming_exams_empty' => 'Aucun examen planifié', + 'registered' => ':count inscrit(s)', + 'monthly' => 'Encaissements — 6 derniers mois', + 'monthly_empty' => 'Aucun paiement sur la période', + 'collected_tooltip' => 'Encaissé', + 'cash_balances' => 'Soldes des caisses', + 'cash_balances_empty' => 'Aucune caisse active', + 'total' => 'Total', + 'payment_methods' => 'Moyens de paiement', + 'payment_methods_empty' => 'Aucun encaissement sur la période', + 'invoice_breakdown' => 'Répartition des factures', + 'students_per_class' => 'Élèves par classe', + 'students_tooltip' => 'Élèves', + 'recent_payments' => 'Derniers paiements', + 'recent_payments_action' => 'Comptabilité', + 'recent_payments_empty' => 'Aucun paiement enregistré', + 'recent_enrollments' => 'Dernières inscriptions', + 'recent_enrollments_action' => 'Inscriptions', + 'recent_enrollments_empty' => 'Aucune inscription pour cette année', + 'payment_methods_year' => 'Moyens de paiement — :year', + 'students_no_pay' => 'Élèves à risque — aucun paiement', + 'students_no_pay_action' => 'Voir tout', + 'col_student' => 'Élève', + 'col_class' => 'Classe', + 'col_amount_due' => 'Montant dû', + 'invoice_action' => 'Facture', + ], + + 'teacher' => [ + 'today_timetable' => "Mon emploi du temps — aujourd'hui", + 'timetable_action' => 'Emploi du temps', + 'today_empty' => "Aucun cours programmé aujourd'hui.", + 'room' => 'salle :room', + 'pending_marks' => 'Notes à saisir', + 'evaluations_action' => 'Évaluations', + 'pending_marks_empty' => 'Toutes vos évaluations sont saisies. 🎉', + 'my_classes' => 'Mes classes & matières', + 'assignments_action' => 'Affectations', + 'assignments_empty' => "Aucune affectation pour l'année :year", + 'take_attendance' => "Faire l'appel", + ], + + 'empty' => [ + 'title' => 'Tableau de bord non disponible', + 'subtitle' => 'Aucune donnée à afficher pour votre profil.', + ], + + 'method' => [ + 'cash' => 'Espèces', + 'mobile_money' => 'Mobile Money', + 'bank_transfer' => 'Virement bancaire', + 'bank_transfer_short' => 'Virement', + 'cheque' => 'Chèque', + 'bank' => 'Banque', + 'other' => 'Autre', + ], + + 'status' => [ + 'issued' => 'Non payé', + 'partially_paid' => 'Partiel', + 'paid' => 'Soldé', + 'active' => 'Actif', + 'pending' => 'En attente', + ], + + 'reason' => [ + 'medical' => 'Médical', + 'familial' => 'Familial', + 'autre' => 'Autre', + ], +]; diff --git a/lang/fr/enrollments.php b/lang/fr/enrollments.php new file mode 100644 index 00000000..8abe01c5 --- /dev/null +++ b/lang/fr/enrollments.php @@ -0,0 +1,272 @@ + [ + 'title' => 'Inscriptions & Paiements', + 'subtitle' => 'Gérez les inscriptions des élèves par année académique', + 'new' => 'Nouvelle inscription', + 'billed' => 'Total facturé', + 'collected' => 'Encaissé', + 'recovered' => ':rate% recouvré', + 'remaining' => 'Reste à recouvrer', + 'unpaid' => 'Impayées', + 'no_payment' => 'sans paiement', + 'search_ph' => 'Code, élève, classe, année...', + 'all_statuses' => 'Tous les statuts', + 'all_years' => 'Toutes les années', + 'all_classes' => 'Toutes les classes', + 'reset' => 'Réinit.', + 'unit' => 'inscription(s)', + 'per_page' => 'Lignes par page :', + 'empty' => 'Aucune inscription trouvée', + 'range' => ':from–:to sur :total inscriptions', + 'delete_title' => 'Supprimer cette inscription ?', + 'delete_desc' => 'Cette action est irréversible.', + + 'status' => [ + 'pending' => 'En attente', + 'active' => 'Actif', + 'cancelled' => 'Annulé', + ], + + 'col' => [ + 'code' => 'Code', + 'student' => 'Élève', + 'class' => 'Classe', + 'year' => 'Année', + 'date' => 'Date', + 'status' => 'Statut', + 'payment' => 'Paiement', + 'actions' => 'Actions', + ], + + 'pay' => [ + 'no_invoice' => 'Sans facture', + 'paid' => 'Payé', + 'remaining' => 'Reste :amount', + 'unpaid' => 'Impayé', + ], + ], + + 'form' => [ + 'school' => 'École *', + 'select_school' => 'Sélectionner une école', + 'student' => 'Élève *', + 'select_student' => 'Sélectionner un élève', + 'class' => 'Classe *', + 'select_class' => 'Sélectionner une classe', + 'academic_year' => 'Année académique *', + 'select_year' => 'Sélectionner une année', + 'active_suffix' => 'active', + 'code' => "Code d'inscription", + 'date' => "Date d'inscription *", + 'status' => 'Statut *', + 'saving' => 'Enregistrement...', + ], + + 'edit' => [ + 'head' => 'Modifier inscription', + 'title' => "Modifier l'inscription", + ], + + 'create' => [ + 'head' => 'Nouvelle inscription', + 'subtitle' => "Inscrire un élève pour l'année scolaire", + 'step' => 'Étape :n', + 'fields_filled' => ':n / 4 champs obligatoires remplis', + 'search_student_ph' => 'Rechercher un élève...', + 'search_input_ph' => 'Nom, prénom ou matricule...', + 'no_student' => 'Aucun élève trouvé', + 'more_students' => ':count élèves — affichage des 50 premiers correspondants', + 'creating' => 'Création en cours...', + 'submit_with_payment' => 'Inscrire & enregistrer le paiement', + 'submit' => "Créer l'inscription", + + 'steps' => [ + 'enrollment' => 'Inscription', + 'summary' => 'Récapitulatif', + 'payment' => 'Paiement', + ], + + 's1' => [ + 'title' => "Informations de l'inscription", + 'subtitle' => "Renseignez l'établissement, l'élève et la classe", + 'establishment' => 'Établissement', + 'school' => 'École', + 'academic_year' => 'Année académique', + 'student' => 'Élève', + 'class_details' => 'Classe & détails', + 'class' => 'Classe', + 'date' => "Date d'inscription", + 'code' => "Code d'inscription", + 'code_ph' => 'Auto-généré si vide', + 'code_hint' => 'Laisser vide pour génération automatique', + 'status' => 'Statut', + ], + + 's2' => [ + 'title' => 'Récapitulatif des frais', + 'subtitle' => 'Frais générés automatiquement depuis la grille tarifaire', + 'total' => 'Total à régler', + 'no_fees' => 'Aucun frais configuré', + 'no_fees_hint' => 'La facture sera créée vide — vous pourrez y ajouter des frais manuellement.', + ], + + 's3' => [ + 'title' => 'Premier paiement', + 'subtitle' => 'Optionnel — enregistrable maintenant ou plus tard', + 'now' => 'Paiement à enregistrer maintenant', + 'later' => 'Payer plus tard', + 'now_hint' => 'Un reçu sera généré automatiquement', + 'later_hint' => "L'inscription sera créée avec facture sans paiement", + 'method' => 'Mode de paiement', + 'amount' => 'Montant (F)', + 'fill_total' => 'Remplir le total (:amount)', + 'payment_date' => 'Date de paiement', + 'paid_by' => 'Payé par', + 'paid_by_ph' => 'Nom du payeur', + 'reference' => 'Référence / N° transaction', + 'reference_ph' => 'Ex: TXN-123456', + ], + + 'method' => [ + 'cash' => 'Espèces', + 'cash_desc' => 'Paiement en cash', + 'mobile' => 'Mobile', + 'mobile_desc' => 'Flooz, T-Money...', + 'transfer' => 'Virement', + 'transfer_desc' => 'Virement bancaire', + 'cheque' => 'Chèque', + 'cheque_desc' => 'Chèque bancaire', + ], + ], + + 'invoice' => [ + 'paid_progress' => ':amount payés', + 'total_due' => 'Total dû', + 'detail_title' => 'Détail de la facture', + 'no_fees' => 'Aucun frais configuré pour cette classe / année.', + 'payments_count' => 'Paiements (:count)', + 'new_payment' => 'Nouveau paiement', + 'record_payment' => 'Enregistrer un paiement', + 'amount' => 'Montant (F) *', + 'amount_max' => 'Max : :amount', + 'method' => 'Mode de paiement *', + 'cash_account' => 'Caisse de destination', + 'no_cash_account' => '— Sans caisse —', + 'payment_date' => 'Date de paiement *', + 'paid_by' => 'Payé par', + 'paid_by_ph' => 'Nom du payeur', + 'reference' => 'Référence / N° transaction', + 'reference_ph' => 'Ex: TXN-123456', + 'notes' => 'Notes (optionnel)', + 'notes_ph' => 'Remarques...', + 'empty_payments' => 'Aucun paiement enregistré', + 'receipt' => 'Reçu :number', + 'opt' => [ + 'cash' => 'Espèces', + 'mobile' => 'Mobile Money', + 'transfer' => 'Virement bancaire', + 'cheque' => 'Chèque', + ], + ], + + 'invoice_doc' => [ + 'head' => 'Facture :number', + 'print' => 'Imprimer la facture', + 'title' => 'Facture', + 'number' => 'N°', + 'issued_on' => 'Émise le :date', + 'student' => 'Élève', + 'mat' => 'Mat.', + 'class_year' => 'Classe · Année', + 'enrollment_ref' => 'Inscription :code', + 'col_designation' => 'Désignation', + 'col_amount' => 'Montant', + 'no_fees' => 'Aucun frais configuré.', + 'subtotal' => 'Sous-total', + 'discount' => 'Réduction', + 'total_due' => 'Total à payer', + 'in_words' => 'Arrêtée la présente facture à la somme de :', + 'status' => 'Statut', + 'total_paid' => 'Total payé', + 'remaining' => 'Reste à payer', + 'payments_received' => 'Paiements reçus', + 'col_date' => 'Date', + 'col_method' => 'Mode', + 'col_reference' => 'Référence', + 'stamp' => "Cachet de l'établissement", + 'accounting' => 'La Comptabilité', + 'status_opt' => [ + 'ISSUED' => 'Non payée', + 'PARTIALLY_PAID' => 'Partiellement payée', + 'PAID' => 'Soldée', + 'CANCELLED' => 'Annulée', + ], + 'method' => [ + 'CASH' => 'Espèces', + 'MOBILE_MONEY' => 'Mobile Money', + 'BANK_TRANSFER' => 'Virement', + 'CHEQUE' => 'Chèque', + ], + ], + + 'receipt' => [ + 'head' => 'Confirmation - :code', + 'title' => "Confirmation d'inscription", + 'number' => 'N°', + 'issued_on' => 'Le :date', + 'student' => 'Élève', + 'matricule' => 'Matricule', + 'class' => 'Classe', + 'academic_year' => 'Année académique', + 'status' => 'Statut', + 'settlement' => 'Règlement', + 'registered_by' => 'Enregistrée par', + 'stamp' => "Cachet de l'établissement", + 'management' => 'La Direction', + 'established_on' => 'Document établi le :date. À conserver.', + 'reserve_note' => 'Document délivré sous réserve du règlement du solde restant (:amount).', + 'mention_p1' => "La direction confirme l'inscription de l'élève ", + 'mention_p2' => ' en classe de ', + 'mention_p3' => " pour l'année académique ", + 'status_opt' => [ + 'PENDING' => 'En attente', + 'ACTIVE' => 'Active', + 'CANCELLED' => 'Annulée', + ], + 'settle' => [ + 'no_fees' => 'Aucun frais facturé', + 'settled' => 'Inscription soldée', + 'partial' => 'Partiellement réglée — reste :amount', + 'unpaid' => 'Non réglée — reste :amount', + ], + ], + + 'show' => [ + 'head' => 'Détail inscription', + 'title' => "Détail de l'inscription", + 'confirmation' => 'Confirmation', + 'financial' => 'Suivi financier', + 'code' => "Code d'inscription", + 'status' => 'Statut', + 'school' => 'École', + 'student' => 'Élève', + 'class' => 'Classe', + 'academic_year' => 'Année académique', + 'date' => "Date d'inscription", + 'enrolled_by' => 'Enregistré par', + 'created_at' => 'Créé le', + 'updated_at' => 'Modifié le', + 'invoice' => 'Facture :number', + 'total' => 'Total', + 'paid' => 'Payé', + 'remaining' => 'Reste', + 'inv' => [ + 'ISSUED' => 'Non payé', + 'PARTIALLY_PAID' => 'Partiellement payé', + 'PAID' => 'Soldé', + 'CANCELLED' => 'Annulé', + ], + ], +]; diff --git a/lang/fr/errors.php b/lang/fr/errors.php new file mode 100644 index 00000000..225b8cfc --- /dev/null +++ b/lang/fr/errors.php @@ -0,0 +1,36 @@ + 'Tableau de bord', + 'home' => 'Retour à l\'accueil', + 'previous' => 'Page précédente', + + 'fallback' => [ + 'title' => 'Une erreur est survenue', + 'message' => 'Quelque chose s\'est mal passé. Veuillez réessayer.', + ], + '403' => [ + 'title' => 'Accès refusé', + 'message' => 'Vous n\'avez pas l\'autorisation d\'accéder à cette page.', + ], + '404' => [ + 'title' => 'Page introuvable', + 'message' => 'La page que vous recherchez n\'existe pas ou a été déplacée.', + ], + '419' => [ + 'title' => 'Session expirée', + 'message' => 'Votre session a expiré. Veuillez vous reconnecter, puis réessayer.', + ], + '429' => [ + 'title' => 'Trop de requêtes', + 'message' => 'Vous avez effectué trop de requêtes. Patientez un instant avant de réessayer.', + ], + '500' => [ + 'title' => 'Erreur serveur', + 'message' => 'Une erreur inattendue s\'est produite. Réessayez dans un instant ; si le problème persiste, contactez l\'administrateur de votre établissement.', + ], + '503' => [ + 'title' => 'Maintenance en cours', + 'message' => 'Le service est momentanément indisponible. Merci de revenir dans quelques instants.', + ], +]; diff --git a/lang/fr/examens.php b/lang/fr/examens.php new file mode 100644 index 00000000..c9bd8520 --- /dev/null +++ b/lang/fr/examens.php @@ -0,0 +1,302 @@ + [ + 'index' => [ + 'head' => "Modèles d'évaluation", + 'title' => "Modèles d'évaluation", + 'year' => 'Année : :year', + 'subtitle_default' => 'Définissez les examens globaux avant de les déployer par classe', + 'new' => 'Nouveau modèle', + 'search_ph' => 'Rechercher un modèle...', + 'period' => 'Période', + 'all_periods' => 'Toutes les périodes', + 'filter' => 'Filtrer', + 'col_name' => 'Nom du modèle', + 'col_type' => 'Type', + 'col_period' => 'Période', + 'col_coeff' => 'Coeff.', + 'col_scale' => 'Barème', + 'col_date' => 'Date indicative', + 'col_deployed' => 'Déployé', + 'col_actions' => 'Actions', + 'empty' => "Aucun modèle d'évaluation. Créez-en un pour commencer.", + 'classes_count' => ':count classe(s)', + 'total' => ':count modèle(s) au total', + 'delete_title' => 'Supprimer ce modèle ?', + 'delete_desc' => 'Toutes les évaluations et notes associées seront supprimées. Action irréversible.', + ], + + 'form' => [ + 'name' => 'Nom *', + 'name_ph' => 'Ex: Devoir 1, Examen du trimestre...', + 'period' => 'Période *', + 'current' => 'en cours', + 'eval_type' => "Type d'évaluation *", + 'eval_type_short' => 'Type *', + 'class_type' => 'Type de classe', + 'all_types' => 'Tous les types', + 'coefficient' => 'Coefficient *', + 'scale' => 'Barème (note max) *', + 'scale_short' => 'Barème *', + 'date' => 'Date indicative', + 'description' => 'Description', + 'description_ph' => 'Instructions, objectifs...', + 'saving' => 'Enregistrement...', + ], + + 'create' => [ + 'head' => "Nouveau modèle d'évaluation", + 'title' => "Nouveau modèle d'évaluation", + 'subtitle' => '— Ce modèle sera ensuite déployé par classe et par matière.', + 'submit' => 'Créer le modèle', + ], + + 'edit' => [ + 'head' => 'Modifier : :name', + 'title' => 'Modifier le modèle', + 'submit' => 'Sauvegarder', + ], + + 'show' => [ + 'head' => 'Modèle : :name', + 'deploy' => 'Déployer vers des classes', + 'no_active_year' => 'Aucune année académique active', + 'no_active_year_hint' => 'Activez une année académique pour pouvoir déployer ce modèle.', + 'manage_years' => 'Gérer les années académiques', + 'no_classes' => 'Aucune classe/matière disponible', + 'no_classes_hint' => "Aucune affectation classe/matière n'existe pour l'année académique active.", + 'configure_classes' => 'Configurer les classes', + 'all_deployed' => "Toutes les classes/matières de l'année active ont déjà été déployées pour ce modèle.", + 'select_title' => 'Sélectionner les classes/matières à déployer', + 'select_all' => 'Tout sélectionner', + 'deselect_all' => 'Tout désélectionner', + 'col_class' => 'Classe', + 'col_subject_coeff' => 'Matière · coeff.', + 'col_planned_date' => 'Date planifiée', + 'coeff' => 'coeff. :n', + 'selected_summary' => ':count classe(s) sélectionnée(s)', + 'with_date' => ':count avec date planifiée', + 'generating' => 'Génération...', + 'generate' => 'Générer :count évaluation(s)', + 'generated' => 'Évaluations générées', + 'empty' => 'Aucune évaluation déployée. Utilisez le bouton ci-dessus pour déployer ce modèle.', + 'col_subject' => 'Matière', + 'col_subject_coeff2' => 'Coeff. matière', + 'col_date' => 'Date', + 'col_status' => 'Statut', + 'col_progress' => 'Progression', + 'col_actions' => 'Actions', + 'status_completed' => 'Terminée', + 'status_scheduled' => 'Planifiée', + 'marks' => 'Notes', + 'delete_eval_title' => 'Supprimer cette évaluation ?', + 'delete_eval_desc' => 'Toutes les notes saisies pour cette évaluation seront perdues.', + ], + ], + + 'official' => [ + 'index' => [ + 'head' => 'Examens officiels', + 'subtitle' => 'CEPD, BEPC, Baccalauréat — année :year.', + 'new' => 'Nouvel examen', + 'year' => 'Année académique', + 'active_suffix' => 'active', + 'search' => 'Recherche', + 'search_ph' => "Nom de l'examen...", + 'type' => 'Type', + 'all' => 'Tous', + 'session' => 'Session', + 'all_sessions' => 'Toutes', + 'status' => 'Statut', + 'all_statuses' => 'Tous', + 'reset' => 'Réinitialiser les filtres (:count)', + 'col_type' => 'Type', + 'col_exam' => 'Examen', + 'col_class' => 'Classe', + 'col_session' => 'Session', + 'col_date' => 'Date', + 'col_center' => 'Centre', + 'col_registered' => 'Inscrits', + 'col_admitted' => 'Admis', + 'col_status' => 'Statut', + 'col_actions' => 'Actions', + 'empty' => 'Aucun examen pour ces critères.', + 'manage' => 'Gérer', + 'total' => ':count examen(s)', + 'create_title' => 'Nouvel examen officiel', + 'attached' => "Rattaché à l'année académique active:suffix.", + 'form_type' => 'Type *', + 'form_year' => 'Année (calendaire) *', + 'form_name' => 'Nom / Libellé *', + 'name_ph' => 'Ex: BEPC 2026 - Session normale', + 'form_session' => 'Session', + 'form_date' => "Date d'examen", + 'form_class' => 'Classe *', + 'choose_class' => 'Choisir une classe', + 'form_center' => "Centre d'examen", + 'center_ph' => 'Ex: Lycée de Tokoin', + 'delete_title' => 'Supprimer cet examen ?', + 'delete_desc' => 'Toutes les inscriptions et résultats associés seront supprimés.', + ], + + 'show' => [ + 'class_prefix' => 'Classe : :name', + 'register_students' => 'Inscrire des élèves', + 'save_results' => 'Enregistrer les résultats', + 'registered' => 'Inscrits', + 'admitted' => 'Admis', + 'failed' => 'Échoués', + 'success_rate' => 'Taux de réussite', + 'col_student' => 'Élève', + 'col_table_no' => 'N° table', + 'col_series' => 'Série', + 'col_status' => 'Statut', + 'col_average' => 'Moyenne', + 'col_mention' => 'Mention', + 'empty' => 'Aucun élève inscrit. Cliquez sur « Inscrire des élèves ».', + 'all_registered' => 'Tous les élèves sont déjà inscrits.', + 'selected' => ':count sélectionné(s)', + 'register' => 'Inscrire', + ], + ], + + 'planning' => [ + 'head' => 'Planning des examens', + 'title' => 'Planning des examens', + 'subtitle' => "Planifiez et visualisez les dates d'examens par classe", + 'export' => 'Exporter / Imprimer', + 'class' => 'Classe', + 'select_class' => 'Sélectionner une classe', + 'all_classes' => '— Toutes les classes —', + 'period' => 'Période', + 'all_periods' => 'Toutes les périodes', + 'no_date_alert_1' => ':count évaluation(s) sans date planifiée. Cliquez sur', + 'no_date_alert_2' => 'pour les planifier.', + 'select_prompt' => 'Sélectionnez une classe pour afficher son planning', + 'no_classes' => "Aucune classe avec des évaluations déployées pour l'année active.", + 'count' => ':count évaluation(s)', + 'empty_base' => 'Aucune évaluation pour cette classe', + 'and_period' => ' et cette période', + 'col_subject' => 'Matière', + 'col_template' => 'Modèle', + 'col_period' => 'Période', + 'col_type' => 'Type', + 'col_coeff' => 'Coeff.', + 'col_datetime' => 'Date & heure', + 'col_status' => 'Statut', + 'col_progress' => 'Progression', + 'col_actions' => 'Actions', + 'time' => 'Heure', + 'date_first' => "Renseignez d'abord la date", + 'validate' => 'Valider', + 'not_scheduled' => 'Non planifiée', + 'status_completed' => 'Terminée', + 'status_scheduled' => 'Planifiée', + 'edit_reschedule' => 'Modifier / reporter', + 'delete_title' => 'Supprimer cette planification ?', + 'delete_desc_1' => "L'évaluation « :template » en :subject sera retirée du planning.", + 'delete_desc_warn' => ':count note(s) saisie(s) seront également supprimées. Cette action est irréversible.', + ], + + 'marks' => [ + 'head' => 'Notes — :name', + 'coeff_exam' => 'Coeff. examen', + 'coeff_subject' => 'Coeff. matière', + 'on' => 'Sur', + 'saving' => 'Enregistrement...', + 'saved' => 'Enregistré !', + 'save_marks' => 'Enregistrer les notes', + 'locked_title' => 'Évaluation clôturée', + 'locked_hint' => 'La saisie de notes est désactivée depuis le :date. Utilisez le bouton "Réclamation" pour demander une correction de note.', + 'students' => 'Élèves', + 'graded' => 'Notés', + 'absent' => 'Absents', + 'avg_saved' => 'Moy. sauvegardée', + 'grid_locked' => 'Notes enregistrées (lecture seule)', + 'grid_entry' => 'Saisie des notes', + 'status_completed' => 'Terminée', + 'status_scheduled' => 'Planifiée', + 'no_students' => 'Aucun élève inscrit dans cette classe.', + 'col_matricule' => 'Matricule', + 'col_student' => 'Élève', + 'col_mark' => 'Note /:max', + 'col_absent' => 'Absent', + 'col_comment' => 'Appréciation', + 'col_appeal' => 'Réclamation', + 'absent_mark' => 'Absent', + 'comment_ph' => 'Appréciation...', + 'appeal' => 'Réclamation', + 'locked_footer' => 'Évaluation clôturée — la saisie est désactivée. Cliquez sur "Réclamation" pour demander une correction.', + 'entered_count' => ':count note(s) saisie(s)', + 'absent_count' => ':count absent(s)', + 'of_students' => '/ :total élèves', + ], + + 'evaluations' => [ + 'index' => [ + 'head' => 'Évaluations par classe', + 'title' => 'Évaluations par classe', + 'subtitle' => 'Évaluations générées depuis les modèles, par classe et matière', + 'manage_templates' => 'Gérer les modèles', + 'search_ph' => 'Classe, matière, modèle...', + 'class' => 'Classe', + 'all_classes' => 'Toutes les classes', + 'subject' => 'Matière', + 'all_subjects' => 'Toutes les matières', + 'type' => 'Type', + 'all_types' => 'Tous les types', + 'period' => 'Période', + 'all_periods' => 'Toutes les périodes', + 'status' => 'Statut', + 'all_statuses' => 'Tous les statuts', + 'scheduled' => 'Planifiée', + 'completed' => 'Terminée', + 'scheduling' => 'Planification', + 'all' => 'Toutes', + 'with_date' => 'Avec date', + 'without_date' => 'Sans date', + 'filter' => 'Filtrer', + 'active_filters' => ':count filtre(s) actif(s)', + 'reset' => 'Réinitialiser', + 'col_template' => 'Modèle', + 'col_class' => 'Classe', + 'col_subject' => 'Matière', + 'col_period' => 'Période', + 'col_coeff' => 'Coeff.', + 'col_date' => 'Date', + 'col_status' => 'Statut', + 'col_progress' => 'Progression', + 'col_actions' => 'Actions', + 'empty' => 'Aucune évaluation. Créez des modèles et déployez-les par classe.', + 'locked' => 'Clôturée', + 'marks' => 'Notes', + 'lock' => 'Clôturer', + 'unlock' => 'Déclôturer', + 'total' => ':count évaluation(s) au total', + 'delete_title' => 'Supprimer cette évaluation ?', + 'delete_desc' => 'Toutes les notes associées seront perdues.', + ], + + 'show' => [ + 'head' => ':template — :class', + 'students' => 'Élèves', + 'graded' => 'Notés', + 'absent' => 'Absents', + 'avg' => 'Moyenne /:max', + 'status_completed' => 'Terminée', + 'status_scheduled' => 'Planifiée', + 'enter_marks' => 'Saisir les notes', + 'min_label' => 'Min :', + 'max_label' => 'Max :', + 'avg_label' => 'Moyenne :', + 'marks_entered' => 'Notes saisies', + 'no_marks' => 'Aucune note encore saisie.', + 'col_matricule' => 'Matricule', + 'col_student' => 'Élève', + 'col_mark' => 'Note', + 'col_comment' => 'Appréciation', + 'absent_mark' => 'Absent', + ], + ], +]; diff --git a/lang/fr/menu.php b/lang/fr/menu.php new file mode 100644 index 00000000..b8db70fb --- /dev/null +++ b/lang/fr/menu.php @@ -0,0 +1,82 @@ + 'Tableau de bord', + 'calendar' => 'Calendrier', + + 'students' => 'Élèves', + 'students_list' => 'Élèves', + 'enrollments' => 'Inscriptions & Paiements', + 'roster' => 'Effectifs / Listes', + 'promotion' => 'Passage de classe', + 'student_stats' => 'Statistiques élèves', + 'student_scholarships' => "Bourses d'étudiants", + 'timetable' => 'Emploi du temps', + + 'attendance' => 'Présences', + 'attendance_entry' => "Saisie de l'appel", + 'attendance_stats' => 'Statistiques', + 'absence_permissions' => 'Demandes de permission', + + 'exams' => 'Examens', + 'evaluation_templates' => "Modèles d'évaluation", + 'evaluations' => 'Évaluations par classe', + 'exam_planning' => 'Planning des examens', + 'official_exams' => 'Examens officiels', + + 'grades' => 'Notes', + 'grades_entry' => 'Saisie de notes', + 'bulletins' => 'Bulletins', + 'bulletin_template' => 'Modèle de bulletin', + 'note_reclamations' => 'Réclamations', + + 'accounting' => 'Comptabilité', + 'accounting_overview' => "Vue d'ensemble", + 'transactions' => 'Journal des transactions', + 'new_expense' => 'Nouvelle dépense', + 'situation' => 'Situation par classe', + 'cash_accounts' => 'Caisses', + 'verify_receipt' => 'Vérifier un reçu', + + 'payroll' => 'Personnel & Paie', + 'payroll_overview' => "Vue d'ensemble", + 'personnel' => 'Personnel', + 'pay_runs' => 'Cycles de paie', + 'salary_grades' => 'Grilles salariales', + 'salary_components' => 'Rubriques de paie', + 'payroll_settings' => 'Réglages (CNSS, ITS)', + + 'archives' => 'Archives', + 'archived_documents' => 'Documents archivés', + 'tags' => 'Tags', + + 'administration' => 'Administration', + 'users' => 'Utilisateurs', + 'roles' => 'Rôles', + 'permissions' => 'Permissions', + 'subject_assignments' => 'Affectations', + 'audit_logs' => "Journal d'audit", + 'portal_access' => 'Accès portail', + + 'statistics' => 'Statistiques', + + 'settings' => 'Paramètres', + 'school' => 'École', + 'classrooms' => 'Classes', + 'classroom_types' => 'Types de classes', + 'subjects' => 'Matières', + 'countries' => 'Pays', + 'evaluation_types' => "Types d'évaluation", + 'academic_years' => 'Années académiques', + 'academic_periods' => 'Périodes académiques', + 'fee_categories' => 'Catégories de frais', + 'fee_structures' => 'Structures de frais', + 'scholarships' => 'Bourses', + 'grading_configs' => 'Calcul des moyennes', + 'file_storage' => 'Fichiers & Stockage', + 'document_templates' => 'Modèles de documents', + 'backups' => 'Sauvegardes', + 'about' => 'À propos', + + 'my_account' => 'Mon compte', +]; diff --git a/lang/fr/notes.php b/lang/fr/notes.php new file mode 100644 index 00000000..ed0897cb --- /dev/null +++ b/lang/fr/notes.php @@ -0,0 +1,193 @@ + [ + 'index' => [ + 'head' => 'Saisie des Notes', + 'year' => 'Année scolaire : :year', + 'no_year' => 'Aucune année scolaire active', + 'saving' => 'Enregistrement...', + 'saved' => 'Enregistré !', + 'save' => 'Enregistrer les notes', + 'select_class' => 'Sélectionner une classe', + 'select_subject' => 'Sélectionner une matière', + 'select_period' => 'Sélectionner une période', + 'current' => 'en cours', + 'coef' => 'coef. :n', + 'no_subjects' => "Aucune matière assignée à cette classe pour l'année en cours.", + 'stat_students' => 'Élèves inscrits', + 'stat_graded' => 'Notes saisies', + 'stat_average' => 'Moyenne de classe', + 'stat_rate' => 'Taux de saisie', + 'subheader' => ':period — Coefficient :coef — Notes sur 20', + 'col_matricule' => 'Matricule', + 'col_name' => 'Nom & Prénom', + 'col_mark' => 'Note /20', + 'col_comment' => 'Appréciation', + 'col_bulletin' => 'Bulletin', + 'comment_ph' => 'Appréciation...', + 'bulletin' => 'Bulletin', + 'empty_class' => 'Aucun élève inscrit dans cette classe', + 'empty_class_hint' => 'Inscrivez des élèves pour saisir leurs notes.', + 'initial' => 'Sélectionnez une classe pour commencer', + 'initial_hint' => 'Choisissez ensuite la matière et le trimestre.', + ], + + 'student' => [ + 'head' => 'Bulletin — :name', + 'matricule_label' => 'Matricule :', + 'class_label' => 'Classe :', + 'general_average' => 'Moyenne générale', + 'rank' => 'Rang', + 'mention' => 'Mention', + 'graded_subjects' => 'Matières notées', + 'transcript' => 'Relevé de notes — :period', + 'total_coef' => 'Total des coefficients : :total', + 'col_subject' => 'Matière', + 'col_coef' => 'Coef.', + 'col_mark' => 'Note /20', + 'col_weighted' => 'Pondérée', + 'col_rank' => 'Rang', + 'col_comment' => 'Appréciation', + 'no_subjects' => 'Aucune matière assignée à cette classe.', + 'not_enrolled' => 'Élève non inscrit cette année', + 'not_enrolled_hint' => "Cet élève n'a pas d'inscription active pour l'année scolaire en cours.", + 'ord_first' => '1er', + 'ord_suffix' => 'e', + ], + ], + + 'bulletins' => [ + 'index' => [ + 'head' => 'Bulletins', + 'subtitle' => 'Générez et figez les bulletins par classe et période', + 'download_class' => 'Télécharger toute la classe (:count)', + 'choose_class' => 'Choisir une classe', + 'choose_period' => 'Choisir une période', + 'current' => 'en cours', + 'validate_hint' => 'Valider fige les bulletins (notes, rangs, mentions) et permet le téléchargement PDF.', + 'validated_count' => ':count/:total validé(s)', + 'observations_ph' => "Observations du Chef d'Établissement (appliquées à toute la classe, optionnel)", + 'revalidate' => 'Re-valider les bulletins de la classe', + 'validate' => 'Valider les bulletins de la classe', + 'revalidate_note' => 'Une re-validation recalcule notes et rangs mais conserve vos éditions manuelles (appréciations, observations, décision, discipline).', + 'confirm_title' => 'Re-valider :count bulletin(s) déjà figé(s) ?', + 'confirm_desc' => 'Les notes, moyennes et rangs seront recalculés. Par défaut, vos éditions manuelles (appréciations, observations, décision, discipline) sont conservées.', + 'regen_all' => 'Tout régénérer', + 'regen_all_desc' => '— repartir de zéro et effacer les éditions manuelles.', + 'regen_action' => 'Régénérer (efface les éditions)', + 'revalidate_action' => 'Re-valider (conserve les éditions)', + 'devalidate_title' => 'Dévalider le bulletin de :name ?', + 'devalidate_desc' => "Le bulletin figé sera supprimé, y compris ses éditions manuelles. L'élève repassera en « brouillon » et pourra être re-validé. Cette action est irréversible.", + 'devalidate' => 'Dévalider', + 'col_student' => 'Élève', + 'col_average' => 'Moyenne', + 'col_rank' => 'Rang', + 'col_mention' => 'Mention', + 'col_status' => 'Statut', + 'col_bulletin' => 'Bulletin', + 'empty' => 'Aucun élève actif dans cette classe.', + 'status_validated' => 'Validé', + 'status_draft' => 'Brouillon', + 'edit' => 'Éditer', + 'devalidate_label' => 'Dévalider ce bulletin', + 'ord_first' => '1er', + 'ord_suffix' => 'e', + ], + + 'edit' => [ + 'head' => 'Éditer le bulletin', + 'matricule_label' => 'Matricule', + 'saving' => 'Enregistrement…', + 'appreciations' => 'Appréciations par matière', + 'col_subject' => 'Matière', + 'col_average' => 'Moyenne', + 'col_appreciation' => 'Appréciation', + 'appreciation_ph' => 'Appréciation du professeur', + 'discipline' => 'Discipline & décision', + 'lateness' => 'Retards (auto)', + 'absences' => 'Absences (auto)', + 'punishments' => 'Punitions', + 'exclusions' => 'Exclusions', + 'decision' => 'Décision du conseil', + 'decision_ph' => "Ex : Tableau d'honneur, Félicitations, Avertissement…", + 'observations' => "Observations du Chef d'Établissement", + 'observations_ph' => 'Observations', + ], + ], + + 'reclamations' => [ + 'status' => [ + 'pending' => 'En attente', + 'approved' => 'Approuvée', + 'rejected' => 'Rejetée', + ], + + 'index' => [ + 'head' => 'Réclamations de notes', + 'subtitle' => 'Demandes de correction sur évaluations clôturées', + 'pending_badge' => ':count en attente de traitement', + 'all_statuses' => 'Tous les statuts', + 'status_pending' => 'En attente', + 'status_approved' => 'Approuvées', + 'status_rejected' => 'Rejetées', + 'col_date' => 'Date', + 'col_student' => 'Élève', + 'col_evaluation' => 'Évaluation', + 'col_requested_by' => 'Demandé par', + 'col_original' => 'Score orig.', + 'col_requested' => 'Score dem.', + 'col_status' => 'Statut', + 'col_action' => 'Action', + 'empty' => 'Aucune réclamation.', + 'process' => 'Traiter', + 'view' => 'Voir', + 'total' => ':count réclamation(s)', + ], + + 'create' => [ + 'head' => 'Déposer une réclamation', + 'subtitle' => "Correction d'une note sur évaluation clôturée", + 'locked' => 'Évaluation clôturée', + 'on_max' => 'Sur :max', + 'student_concerned' => 'Élève concerné', + 'pending_exists' => 'Réclamation déjà en cours', + 'pending_exists_hint' => 'Une réclamation est déjà en attente de traitement pour cet élève.', + 'current_score' => 'Note actuelle (enregistrée)', + 'requested_score' => 'Note souhaitée', + 'reason' => 'Raison de la réclamation *', + 'reason_ph' => 'Décrivez le motif de la réclamation (erreur de saisie, absent justifié, etc.)', + 'submitting' => 'Envoi...', + 'submit' => 'Déposer la réclamation', + 'view_reclamations' => 'Voir les réclamations', + ], + + 'show' => [ + 'head' => 'Réclamation — :name', + 'title' => 'Détail de la réclamation', + 'filed_on' => 'Déposée le :date', + 'student' => 'Élève', + 'evaluation' => 'Évaluation', + 'score_registered' => 'Note enregistrée', + 'score_requested' => 'Note souhaitée', + 'score_corrected' => 'Note corrigée', + 'reason_label' => 'Motif de la réclamation', + 'requested_by' => 'Demandé par :name', + 'decision' => 'Décision — :status', + 'reviewed_by' => 'Par :name', + 'process_title' => 'Traiter la réclamation', + 'approve' => 'Approuver', + 'reject' => 'Rejeter', + 'approving' => 'Approbation de la réclamation', + 'rejecting' => 'Rejet de la réclamation', + 'corrected_score_label' => 'Note corrigée *', + 'internal_note' => 'Note interne (optionnel)', + 'reject_reason' => 'Motif du rejet', + 'internal_note_ph' => 'Remarques éventuelles...', + 'reject_reason_ph' => 'Expliquez pourquoi la réclamation est rejetée...', + 'saving' => 'Enregistrement...', + 'confirm_approve' => "Confirmer l'approbation", + 'confirm_reject' => 'Confirmer le rejet', + ], + ], +]; diff --git a/lang/fr/paie.php b/lang/fr/paie.php new file mode 100644 index 00000000..9c65d05a --- /dev/null +++ b/lang/fr/paie.php @@ -0,0 +1,130 @@ + [ + 1 => 'Janvier', + 2 => 'Février', + 3 => 'Mars', + 4 => 'Avril', + 5 => 'Mai', + 6 => 'Juin', + 7 => 'Juillet', + 8 => 'Août', + 9 => 'Septembre', + 10 => 'Octobre', + 11 => 'Novembre', + 12 => 'Décembre', + ], + + 'status' => [ + 'draft' => 'Brouillon', + 'validated' => 'Validé', + 'paid' => 'Payé', + 'cancelled' => 'Annulé', + ], + + 'line_type' => [ + 'earning' => 'Gain', + 'deduction' => 'Retenue', + ], + + 'index' => [ + 'head' => 'Cycles de paie', + 'subtitle' => 'Générez, validez et décaissez la paie mensuelle.', + 'new' => 'Nouveau cycle', + 'search_ph' => 'Référence, libellé...', + 'all_statuses' => 'Tous les statuts', + 'all_years' => 'Toutes les années', + 'all_months' => 'Tous les mois', + 'search' => 'Rechercher', + 'reset' => 'Réinit.', + 'count' => ':count cycle(s)', + 'filtered' => '— filtrés', + 'per_page' => 'Lignes par page :', + 'col_period' => 'Période', + 'col_reference' => 'Référence', + 'col_payslips' => 'Bulletins', + 'col_net' => 'Total net', + 'col_status' => 'Statut', + 'col_actions' => 'Actions', + 'empty' => 'Aucun cycle de paie', + 'view' => 'Voir le détail', + 'delete' => 'Supprimer le cycle', + 'range' => ':from–:to sur :total', + 'delete_title' => 'Supprimer ce cycle de paie ?', + 'delete_desc_prefix' => '« :period » et ses :count bulletin(s) seront supprimés définitivement. ', + 'delete_desc_paid' => 'Le cycle étant payé, les caisses seront recréditées et les écritures comptables supprimées.', + 'delete_desc_irreversible' => 'Cette action est irréversible.', + 'cancel' => 'Annuler', + 'delete_btn' => 'Supprimer', + ], + + 'create' => [ + 'head' => 'Nouveau cycle de paie', + 'title' => 'Nouveau cycle de paie', + 'subtitle' => 'Un bulletin sera généré pour chaque employé actif.', + 'period' => 'Période', + 'month' => 'Mois *', + 'year' => 'Année *', + 'label' => 'Libellé (optionnel)', + 'label_ph' => "Ex: Paie de fin d'année", + 'employees_info' => ':count employé(s) actif(s) — un bulletin par employé.', + 'components_info' => ':count rubrique(s) automatique(s) appliquée(s).', + 'draft_note' => 'Vous pourrez ajuster chaque bulletin tant que le cycle est en brouillon.', + 'generating' => 'Génération...', + 'generate' => 'Générer le cycle', + 'cancel' => 'Annuler', + 'no_employees' => "Ajoutez d'abord des employés actifs.", + ], + + 'show' => [ + 'head' => 'Paie :period', + 'title' => 'Paie :period', + 'disbursed_on' => ' — décaissé sur :account', + 'validate' => 'Valider', + 'cancel' => 'Annuler', + 'pay' => 'Payer', + 'cancel_pay' => 'Annuler la paie', + 'delete' => 'Supprimer', + 'total_earnings' => 'Total des gains', + 'total_deductions' => 'Total des retenues', + 'net_mass' => 'Masse salariale nette', + 'payslips_count' => ':count bulletin(s)', + 'search_ph' => 'Rechercher un employé…', + 'col_employee' => 'Employé', + 'col_earnings' => 'Gains', + 'col_deductions' => 'Retenues', + 'col_net' => 'Net à payer', + 'empty' => 'Aucun bulletin trouvé.', + 'download_payslip' => 'Télécharger le bulletin', + 'edit' => 'Éditer', + 'range' => ':from–:to sur :total', + 'editor_title' => 'Bulletin — :name', + 'line_label_ph' => 'Libellé', + 'remove' => 'Retirer', + 'add_line' => 'Ajouter une ligne', + 'net' => 'Net : :amount', + 'cancel_btn' => 'Annuler', + 'save' => 'Enregistrer', + 'pay_title' => 'Décaisser la paie', + 'pay_amount' => 'Masse nette à décaisser : :amount.', + 'cash_account' => 'Caisse *', + 'select_cash' => 'Sélectionner une caisse', + 'cash_balance' => ':name — solde :balance', + 'over_balance' => 'Le solde de la caisse est insuffisant : elle deviendra négative.', + 'confirm_payment' => 'Confirmer le paiement', + 'confirm_validate_title' => 'Valider ce cycle ?', + 'confirm_cancel_title' => 'Annuler ce cycle ?', + 'confirm_validate_desc' => 'Les bulletins ne seront plus modifiables. Vous pourrez ensuite décaisser la paie.', + 'confirm_cancel_paid_desc' => 'La paie sera annulée : les caisses seront recréditées et les écritures comptables supprimées.', + 'confirm_cancel_desc' => 'Le cycle sera annulé.', + 'back' => 'Retour', + 'validate_action' => 'Valider', + 'cancel_action' => 'Annuler le cycle', + 'delete_title' => 'Supprimer ce cycle de paie ?', + 'delete_desc_prefix' => '« :period » et ses :count bulletin(s) seront supprimés définitivement.', + 'delete_desc_paid' => 'Le cycle étant payé, les caisses seront recréditées et les écritures comptables supprimées.', + 'delete_desc_irreversible' => 'Cette action est irréversible.', + 'delete_btn' => 'Supprimer', + ], +]; diff --git a/lang/fr/parametres.php b/lang/fr/parametres.php new file mode 100644 index 00000000..3d865e95 --- /dev/null +++ b/lang/fr/parametres.php @@ -0,0 +1,1092 @@ + [ + 'view' => 'Voir', + 'edit' => 'Modifier', + 'delete' => 'Supprimer', + 'cancel' => 'Annuler', + 'save' => 'Enregistrer', + 'search' => 'Rechercher', + 'clear' => 'Effacer', + 'prev' => 'Précédent', + 'next' => 'Suivant', + 'showing' => 'Affichage :from à :to sur :total', + 'actions' => 'Actions', + 'irreversible' => 'Cette action est irréversible.', + 'creating' => 'Création...', + 'updating' => 'Mise à jour...', + 'update' => 'Mettre à jour', + 'created_at' => 'Date de création', + 'created_on' => 'Créé le', + 'updated_on' => 'Dernière modification', + ], + + 'countries' => [ + 'head' => 'Pays', + 'title' => 'Pays', + 'subtitle' => 'Gérez la liste des pays (nationalités, origines).', + 'new' => 'Nouveau pays', + 'search_ph' => 'Rechercher un pays (nom, code)…', + 'th_name' => 'Nom', + 'th_code' => 'Code', + 'empty' => 'Aucun pays trouvé', + 'delete_q' => 'Supprimer ce pays ?', + 'create_head' => 'Créer un pays', + 'create_title' => 'Créer un pays', + 'create_subtitle' => 'Renseignez le nom et le code du pays.', + 'edit_head' => 'Éditer :name', + 'edit_title' => 'Éditer un pays', + 'edit_subtitle' => 'Mettez à jour les informations de :name.', + 'form_section' => 'Informations du pays', + 'name' => 'Nom', + 'name_ph' => 'Ex : Togo', + 'code' => 'Code', + 'code_ph' => 'Ex : TG', + 'code_hint' => 'Code unique (ex. ISO : TG, BJ, CI…).', + 'submit_create' => 'Créer le pays', + ], + + 'schools' => [ + 'active' => 'Active', + 'inactive' => 'Inactive', + 'section_general' => 'Informations générales', + 'terme_entity' => 'Terme / Entité', + 'devise' => 'Devise', + 'email' => 'E-mail', + 'phone' => 'Téléphone', + 'section_location' => 'Localisation', + 'region' => 'Région', + 'city' => 'Ville', + 'po_box' => 'Boîte postale', + 'address' => 'Adresse', + 'section_events' => 'Événements', + 'created_on' => 'Créée le', + 'updated_on' => 'Modifiée le', + + 'create_head' => 'Nouvelle école', + 'create_title' => 'Configurer l\'école', + 'create_subtitle' => 'Renseignez les informations de l\'établissement.', + 'edit_head' => 'Éditer :name', + 'edit_title' => 'Paramètres de l\'école', + 'edit_subtitle' => 'Informations, logo, en-tête et devise de :name.', + + 'section_identity' => 'Identité de l\'école', + 'name' => 'Nom', + 'name_ph' => 'Ex : Lycée Jean Jaurès', + 'code' => 'Code', + 'code_ph' => 'Ex : LJJ001', + 'terme' => 'Terme / Entité supérieure', + 'terme_ph' => 'Ex : République Togolaise', + 'ministry' => 'Ministère de tutelle', + 'ministry_ph' => 'Ex : Ministère des Enseignements Primaire, Secondaire et Technique', + 'ministry_hint' => 'Affiché en haut à gauche de l\'en-tête officiel des documents.', + 'devise_label' => 'Devise (slogan) de l\'école', + 'devise_ph' => 'Ex : Travail, Liberté, Patrie', + 'currency' => 'Monnaie (comptabilité)', + 'section_logo' => 'Logo et contact', + 'logo_label' => 'Logo de l\'école', + 'change_logo' => 'Changer le logo', + 'choose_image' => 'Choisir une image', + 'logo_hint' => 'JPG, PNG, WebP — max 2 Mo', + 'email_label' => 'E-mail', + 'email_ph' => 'contact@ecole.com', + 'phone_label' => 'Téléphone', + 'phone_ph' => '+228 XX XX XX XX', + 'region_label' => 'Région', + 'city_label' => 'Ville', + 'po_box_label' => 'Boîte postale', + 'po_box_ph' => 'BP 123', + 'address_label' => 'Adresse complète', + 'section_class_types' => 'Types de classe proposés', + 'no_class_types' => 'Aucun type de classe disponible. Créez-en d\'abord dans Paramètres → Types de classes.', + 'semester' => 'Semestre (2 périodes)', + 'trimester' => 'Trimestre (3 périodes)', + 'section_status' => 'Statut', + 'active_label' => 'École active', + 'portal_label' => 'Accès au portail parents / élèves', + 'portal_hint' => 'Lorsque désactivé, la connexion et toutes les fonctionnalités de l\'espace parents/élèves sont bloquées.', + 'submit_create' => 'Créer l\'école', + ], + + 'about' => [ + 'head' => 'À propos', + 'intro' => 'Système de gestion scolaire — de l\'inscription de l\'élève au bulletin, en passant par l\'écolage et la paie. Pensé pour les établissements togolais 🇹🇬.', + 'section_covers' => 'Ce que couvre l\'application', + 'section_license' => 'Éditeur & licence', + 'edited_by' => 'Édité par', + 'license' => 'Licence', + 'license_value' => 'GNU GPL v3 — logiciel libre', + 'rights' => 'Droits', + 'license_note' => 'Vous pouvez utiliser, étudier, modifier et redistribuer ce logiciel selon les termes de la GPL v3.', + 'section_tech' => 'Informations techniques', + 'app_version' => 'Version de l\'application', + 'environment' => 'Environnement', + 'section_support' => 'Besoin d\'aide ?', + 'support_before' => 'Consultez d\'abord la', + 'link_docs' => 'documentation', + 'support_middle' => '. Pour signaler un problème ou proposer une amélioration, ouvrez un ticket sur le', + 'link_repo' => 'dépôt GitHub', + 'modules' => [ + 'students' => ['title' => 'Élèves & inscriptions', 'desc' => 'Dossiers complets, inscriptions, passage de classe, bourses.'], + 'grades' => ['title' => 'Notes & bulletins', 'desc' => 'Saisie, moyennes paramétrables, bulletins PDF, réclamations.'], + 'attendance' => ['title' => 'Présences', 'desc' => 'Appel par séance, statistiques d\'assiduité, permissions d\'absence.'], + 'exams' => ['title' => 'Examens officiels', 'desc' => 'CEPD, BEPC, BAC : inscriptions, planning, résultats.'], + 'finance' => ['title' => 'Finances & recouvrement', 'desc' => 'Frais, factures, paiements (dont Mobile Money), reçus.'], + 'payroll' => ['title' => 'Personnel & paie', 'desc' => 'Fiches employés, cycles de paie, bulletins (CNSS, ITS).'], + 'timetable' => ['title' => 'Emploi du temps & calendrier', 'desc' => 'Grilles par classe, calendrier de l\'établissement.'], + 'backups' => ['title' => 'Sauvegardes & statistiques', 'desc' => 'Sauvegardes planifiées, tableaux de bord et exports.'], + ], + 'links' => [ + 'docs' => ['title' => 'Documentation', 'sub' => 'Guide de A à Z'], + 'website' => ['title' => 'Site web', 'sub' => 'Présentation & fonctionnalités'], + 'source' => ['title' => 'Code source', 'sub' => 'Dépôt GitHub'], + 'changelog' => ['title' => 'Journal des versions', 'sub' => 'Historique des évolutions'], + ], + ], + + 'evaluation_types' => [ + 'head' => 'Types d\'évaluation', + 'title' => 'Types d\'évaluation', + 'subtitle' => 'Gérez les types d\'évaluation', + 'new' => 'Nouveau type', + 'stat_total' => 'Types totaux', + 'stat_shown' => 'Résultats affichés', + 'stat_page' => 'Page', + 'search_ph' => 'Rechercher un type...', + 'th_name' => 'Nom', + 'th_description' => 'Description', + 'th_created' => 'Créé le', + 'empty' => 'Aucun type trouvé', + 'delete_q' => 'Supprimer ce type ?', + + 'create_head' => 'Créer un type d\'évaluation', + 'create_title' => 'Créer un type d\'évaluation', + 'create_subtitle' => 'Renseignez les informations du type d\'évaluation.', + 'edit_head' => 'Modifier type d\'évaluation', + 'edit_title' => 'Éditer un type d\'évaluation', + 'edit_subtitle' => 'Mettez à jour les informations de :name.', + + 'show_subtitle' => 'Détails du type d\'évaluation.', + 'main_info' => 'Informations principales', + 'name' => 'Nom', + 'description' => 'Description', + 'no_description' => 'Aucune description renseignée pour ce type d\'évaluation.', + 'history' => 'Historique', + 'created_on' => 'Créé le', + 'updated_on' => 'Modifié le', + + 'form_section' => 'Informations du type d\'évaluation', + 'name_label' => 'Nom *', + 'name_ph' => 'Ex: Devoir surveillé', + 'category_label' => 'Catégorie *', + 'category_ph' => 'Choisir une catégorie', + 'category_hint' => 'Détermine si les notes alimentent la « note de classe » ou la « composition » du bulletin.', + 'cat_continu' => 'Contrôle continu (note de classe)', + 'cat_composition' => 'Composition (examen)', + 'description_label' => 'Description', + 'description_ph' => 'Précisez le contexte pédagogique de ce type d\'évaluation...', + 'preview' => 'Aperçu', + 'preview_name' => 'Nom du type', + 'preview_no_description' => 'Aucune description renseignée.', + 'submit_create' => 'Créer le type', + ], + + 'grading_configs' => [ + 'head' => 'Configurations de calcul des moyennes', + 'title' => 'Calcul des moyennes', + 'subtitle' => 'Personnalisez la formule de calcul par établissement', + 'new' => 'Nouvelle configuration', + 'filter_all' => 'Tous les établissements', + 'th_name' => 'Nom', + 'th_school' => 'Établissement', + 'th_class_type' => 'Type de classe', + 'th_threshold' => 'Seuil', + 'th_max' => 'Note max', + 'th_round' => 'Arrondi', + 'th_status' => 'Statut', + 'all_default' => 'Toutes (défaut)', + 'decimals' => ':n déc.', + 'active' => 'Active', + 'inactive' => 'Inactive', + 'activate' => 'Activer', + 'empty' => 'Aucune configuration. Créez-en une pour personnaliser le calcul des moyennes.', + 'count' => ':n configuration(s)', + 'delete_q' => 'Supprimer cette configuration ?', + + 'create_head' => 'Nouvelle configuration de calcul', + 'create_title' => 'Nouvelle configuration', + 'create_subtitle' => 'Définissez la formule de calcul des moyennes (par type de classe)', + 'submit_create' => 'Créer la configuration', + 'edit_head' => 'Modifier — :name', + 'edit_title' => 'Modifier la configuration', + 'saving' => 'Enregistrement...', + + 'school_label' => 'Établissement *', + 'school_ph' => 'Sélectionner un établissement', + 'class_type_label' => 'Type de classe', + 'all_classes' => 'Toutes les classes (défaut école)', + 'class_type_hint' => 'Laissez « Toutes les classes » pour une formule par défaut de l\'école.', + 'name_label' => 'Nom *', + 'name_ph' => 'ex : Formule standard 2025', + 'passing_label' => 'Seuil de passage', + 'max_label' => 'Note maximale par défaut', + 'weight_label' => 'Pondération Note de classe / Composition', + 'class_weight_label' => 'Note de classe (contrôle continu)', + 'comp_weight_label' => 'Composition', + 'pct_class' => 'Classe', + 'pct_comp' => 'Composition', + 'formula' => 'Moyenne matière = (Classe × :cw + Composition × :pw) / :total.', + 'precision_label' => 'Précision d\'arrondi', + 'precision_0' => 'Entier (ex : 12)', + 'precision_1' => '1 décimale (ex : 12,5)', + 'precision_2' => '2 décimales (ex : 12,50)', + 'precision_3' => '3 décimales (ex : 12,500)', + 'mentions_label' => 'Mentions (seuils)', + 'add' => 'Ajouter', + 'no_mentions' => 'Aucune mention configurée.', + 'mention_label_ph' => 'Libellé (ex : Félicitations)', + 'mentions_hint' => 'La mention retenue est celle dont le seuil est le plus élevé et inférieur ou égal à la moyenne.', + ], + + 'subjects' => [ + 'head' => 'Matières', + 'title' => 'Matières', + 'subtitle' => 'Gérez les matières enseignées', + 'new' => 'Nouvelle matière', + 'stat_total' => 'Matières totales', + 'stat_shown' => 'Résultats affichés', + 'stat_page' => 'Page', + 'search_ph' => 'Rechercher une matière par nom ou code...', + 'search_results' => 'Résultats pour :', + 'th_code' => 'Code', + 'th_name' => 'Nom', + 'th_description' => 'Description', + 'no_description' => 'Aucune description', + 'empty' => 'Aucune matière trouvée', + 'page_showing' => 'Affichage', + 'page_to' => 'à', + 'page_of' => 'sur', + 'page_unit' => 'matières', + 'page_label' => 'Page', + 'delete_title' => 'Supprimer la matière', + 'delete_desc' => 'Êtes-vous sûr de vouloir supprimer cette matière ? Cette action est irréversible.', + 'deleting' => 'Suppression...', + + 'create_head' => 'Créer une matière', + 'create_title' => 'Créer une matière', + 'create_subtitle' => 'Renseignez les informations de la matière.', + 'edit_head' => 'Éditer :name', + 'edit_title' => 'Éditer une matière', + 'edit_subtitle' => 'Mettez à jour les informations de :name.', + + 'show_subtitle' => 'Détail et suivi de la matière.', + 'info_section' => 'Informations de la matière', + 'description' => 'Description', + 'show_no_description' => 'Aucune description renseignée pour cette matière.', + 'history' => 'Historique', + 'created_on' => 'Créée le', + 'updated_on' => 'Dernière modification', + 'summary' => 'Résumé', + 'summary_before' => 'La matière', + 'summary_middle' => 'est identifiée par le code', + 'summary_stats' => 'Les statistiques d\'utilisation seront affichées ici dès qu\'elles seront disponibles.', + 'edit_this' => 'Modifier cette matière', + + 'form_section' => 'Informations de la matière', + 'code_label' => 'Code *', + 'code_ph' => 'Ex: MATH101', + 'code_hint' => 'Code unique utilisé pour identifier la matière.', + 'name_label' => 'Nom *', + 'name_ph' => 'Ex: Mathématiques', + 'parent_label' => 'Matière parente (sous-matière)', + 'no_parent' => 'Aucune (matière principale)', + 'parent_hint' => 'Rattachez cette matière à une matière principale pour en faire une sous-matière (ex. « Dictée » sous « Français »).', + 'description_label' => 'Description', + 'description_ph' => 'Décrivez brièvement le contenu et les objectifs de la matière...', + 'description_hint' => 'Optionnel, maximum 1000 caractères.', + 'preview' => 'Aperçu', + 'preview_code' => 'CODE', + 'preview_name' => 'Nom de la matière', + 'submit_create' => 'Créer la matière', + ], + + 'fee_categories' => [ + 'head' => 'Catégories de frais', + 'title' => 'Catégories de frais', + 'subtitle' => 'Gérez les catégories de frais scolaires', + 'new' => 'Nouvelle catégorie', + 'stat_total' => 'Catégories totales', + 'search_ph' => 'Rechercher par nom ou description...', + 'th_name' => 'Nom', + 'th_description' => 'Description', + 'th_created' => 'Date de création', + 'empty' => 'Aucune catégorie trouvée', + 'delete_title' => 'Supprimer la catégorie', + 'delete_desc' => 'Cette action ne peut pas être annulée. Êtes-vous sûr de vouloir supprimer cette catégorie ?', + 'deleting' => 'Suppression...', + + 'create_head' => 'Nouvelle catégorie de frais', + 'create_title' => 'Créer une catégorie de frais', + 'edit_head' => 'Modifier la catégorie de frais', + 'edit_title' => 'Modifier la catégorie de frais', + 'identity' => 'Identité de la catégorie', + 'name_label' => 'Nom *', + 'name_ph' => 'Ex: Frais de scolarité, Frais de cantine...', + 'description' => 'Description', + 'description_ph' => 'Décrivez cette catégorie de frais...', + 'create' => 'Créer', + + 'show_head' => 'Catégorie : :name', + 'show_title' => 'Détail de la catégorie', + 'name' => 'Nom', + 'created_on' => 'Créée le', + 'show_no_description' => 'Aucune description fournie', + 'updated_on' => 'Dernière modification', + 'id' => 'ID', + 'delete_desc_show' => 'Cette action ne peut pas être annulée. Êtes-vous sûr de vouloir supprimer cette catégorie de frais ?', + ], + + 'classroom_types' => [ + 'head' => 'Types de classes', + 'title' => 'Types de classes', + 'subtitle' => 'Gérez les types de classes disponibles', + 'new' => 'Nouveau type', + 'stat_total' => 'Total des types', + 'stat_active' => 'Types actifs', + 'stat_page' => 'Page', + 'search_ph' => 'Rechercher un type...', + 'reset' => 'Réinitialiser', + 'th_name' => 'Nom', + 'th_description' => 'Description', + 'th_status' => 'État', + 'empty' => 'Aucun type trouvé', + 'active' => '✓ Actif', + 'inactive' => 'Inactif', + 'page_showing_before' => 'Affichage de', + 'page_showing_to' => 'à', + 'page_showing_on' => 'sur', + 'page_unit' => 'types', + 'page_label' => 'Page', + 'page_of' => 'sur', + 'delete_title' => 'Supprimer le type', + 'delete_desc' => 'Êtes-vous sûr de vouloir supprimer ce type de classe ? Cette action est irréversible.', + 'deleting' => 'Suppression...', + + 'create_head' => 'Nouveau type de classe', + 'create_title' => 'Créer un type de classe', + 'create_subtitle' => 'Renseignez les informations du type.', + 'edit_head' => 'Éditer :name', + 'edit_title' => 'Éditer un type de classe', + 'edit_subtitle' => 'Mettez à jour les informations de :name.', + + 'show_status' => 'État', + 'show_active' => '✓ Actif', + 'show_inactive' => 'Inactif', + 'linked_classrooms' => 'Classes associées', + 'created_on' => 'Créé le', + 'updated_on' => 'Dernière modification', + 'classes_using' => 'Classes utilisant ce type', + 'classes_using_sub' => 'Affichage de toutes les classes assignées à ce type', + 'no_classroom' => 'Aucune classe n\'utilise ce type pour le moment', + 'create_classroom' => 'Créer une classe', + 'cr_name' => 'Nom', + 'cr_code' => 'Code', + 'cr_capacity' => 'Capacité', + 'cr_status' => 'État', + 'cr_action' => 'Action', + 'cr_places' => ':n places', + 'cr_active' => '✓ Active', + 'cr_inactive' => 'Inactive', + 'cr_view' => 'Voir', + + 'form_section' => 'Informations du type', + 'name_label' => 'Nom *', + 'name_ph' => 'Ex: Classe générale', + 'description_label' => 'Description', + 'description_ph' => 'Description du type de classe...', + 'period_label' => 'Système de périodes *', + 'period_trimestre' => 'Trimestre (3 périodes)', + 'period_semestre' => 'Semestre (2 périodes)', + 'period_hint' => 'Détermine le découpage de l\'année (bulletins, évaluations) pour ce type de classe.', + 'status_section' => 'Statut', + 'active_label' => 'Type actif', + 'submit_create' => 'Créer le type', + ], + + 'scholarships' => [ + 'head' => 'Bourses', + 'title' => 'Bourses', + 'subtitle' => 'Gérez les réductions de frais de scolarité', + 'new' => 'Nouvelle bourse', + 'stat_total' => 'Bourses totales', + 'stat_percentage' => 'Bourses en %', + 'stat_fixed' => 'Montants fixes', + 'search_ph' => 'Rechercher une bourse...', + 'search_results' => 'Résultats pour :', + 'th_name' => 'Nom', + 'th_type' => 'Type', + 'th_value' => 'Valeur', + 'th_created' => 'Date de création', + 'empty' => 'Aucune bourse trouvée', + 'type_percentage' => 'Pourcentage', + 'type_fixed' => 'Montant fixe', + 'page_showing' => 'Affichage', + 'page_to' => 'à', + 'page_of' => 'sur', + 'page_unit' => 'bourses', + 'page_label' => 'Page', + 'delete_title' => 'Supprimer la bourse', + 'delete_desc' => 'Êtes-vous sûr de vouloir supprimer cette bourse ? Cette action est irréversible.', + 'deleting' => 'Suppression...', + + 'create_head' => 'Créer une bourse', + 'create_title' => 'Créer une nouvelle bourse', + 'edit_head' => 'Modifier :name', + 'edit_title' => 'Modifier :name', + 'form_section' => 'Informations de la bourse', + 'name_label' => 'Nom *', + 'name_ph' => 'Ex: Bourse d\'excellence', + 'type_label' => 'Type *', + 'type_ph' => 'Sélectionner un type', + 'value_label_pct' => 'Valeur (%) *', + 'value_label_fixed' => 'Valeur (montant) *', + 'value_ph_pct' => 'Ex: 15', + 'value_ph_fixed' => 'Ex: 100.00', + 'description' => 'Description', + 'description_ph' => 'Décrivez la bourse...', + 'submit_create' => 'Créer la bourse', + + 'show_value' => 'Valeur', + 'show_description' => 'Description', + 'show_no_description' => 'Aucune description disponible', + 'history' => 'Historique', + 'created_on' => 'Créée le', + 'updated_on' => 'Dernière modification', + 'summary' => 'Résumé', + 'current_value' => 'Valeur actuelle', + ], + + 'bulletin_templates' => [ + 'head' => 'Modèle de bulletin', + 'title' => 'Modèle de bulletin', + 'subtitle_before' => 'Composez les colonnes du bulletin de :name. Le jeton', + 'subtitle_after' => 'devient « Trim. » ou « Sem. » selon le type de classe.', + 'load_preset' => 'Charger un preset…', + 'saving' => 'Enregistrement…', + 'columns' => 'Colonnes (:n)', + 'total_width' => 'Largeur totale : :w%', + 'default_column' => 'Colonne', + 'label_ph' => 'Libellé', + 'type_prefix' => 'Type : :name', + 'add_column' => 'Ajouter une colonne', + 'footer' => 'Pied de bulletin', + 'opt_class_stats' => 'Afficher les statistiques de la classe (moyenne la plus forte / faible / générale)', + 'opt_period_recap' => 'Afficher le récapitulatif inter-périodes + moyenne annuelle', + 'opt_discipline' => 'Afficher le bloc discipline (retards, absences, décision du conseil)', + 'signatory_left' => 'Signataire (gauche)', + 'signatory_right' => 'Signataire (droite)', + 'legal_note' => 'Mention légale (N.B.)', + ], + + 'file_storage' => [ + 'head' => 'Fichiers & Stockage', + 'title' => 'Fichiers & Stockage', + 'subtitle' => 'Configurez où les fichiers uploadés (logos, documents) seront stockés.', + 'toast_saved_title' => 'Configuration sauvegardée', + 'toast_saved_desc' => 'Les paramètres de stockage ont été mis à jour.', + 'toast_error_title' => 'Erreur', + 'toast_error_desc' => 'Vérifiez les champs et réessayez.', + 'toast_test_ok_title' => 'Connexion réussie', + 'toast_test_ok_desc' => 'Le disque ":driver" est accessible et fonctionnel.', + 'toast_test_fail_title' => 'Connexion échouée', + 'toast_unknown_error' => 'Erreur inconnue.', + 'toast_unreachable' => 'Impossible de joindre le stockage.', + 'driver_local' => 'Stockage local', + 'driver_local_desc' => 'Fichiers sur le serveur Laravel (public/storage)', + 'driver_s3' => 'Stockage cloud S3', + 'driver_s3_desc' => 'AWS S3, Cloudflare R2, MinIO ou compatible S3', + 's3_section' => 'Configuration S3 / Cloudflare R2', + 'access_key' => 'Access Key', + 'secret_key' => 'Secret Key', + 'keep_hint' => '(laisser vide pour conserver)', + 'bucket' => 'Bucket *', + 'region' => 'Région *', + 'region_ph' => 'auto (R2) ou eu-west-1 (AWS)', + 'endpoint' => 'Endpoint', + 'endpoint_hint' => '(R2/MinIO — laisser vide pour AWS)', + 'public_url' => 'URL publique', + 'public_url_hint' => '(CDN ou domaine custom)', + 'r2_title' => 'Cloudflare R2 (recommandé)', + 'r2_region' => '• Région :', + 'r2_endpoint' => '• Endpoint :', + 'r2_bandwidth' => '• Aucun frais de bande passante sortante (idéal Afrique)', + 'local_active' => 'Stockage local actif', + 'local_active_before' => 'Les fichiers sont stockés dans', + 'local_active_middle' => 'et servis via', + 'test_connection' => 'Tester la connexion', + 'save' => 'Enregistrer', + ], + + 'backups' => [ + 'head' => 'Sauvegardes', + 'title' => 'Sauvegardes', + 'subtitle' => 'Sauvegardez la base de données (JSON / SQL) et planifiez des sauvegardes automatiques.', + 'size_mb' => ':n Mo', + 'size_kb' => ':n Ko', + 'day_1' => 'Lundi', + 'day_2' => 'Mardi', + 'day_3' => 'Mercredi', + 'day_4' => 'Jeudi', + 'day_5' => 'Vendredi', + 'day_6' => 'Samedi', + 'day_7' => 'Dimanche', + 'dest_label' => 'Destination :', + 'dest_remote' => 'Dépôt distant (S3 / R2)', + 'dest_local' => 'Stockage local du serveur', + 'dest_remote_desc' => 'Les sauvegardes sont envoyées sur votre stockage cloud configuré.', + 'dest_local_desc' => 'Configurez un stockage S3/R2 dans « Fichiers & Stockage » pour externaliser les sauvegardes.', + 'manual_title' => 'Sauvegarder maintenant', + 'include_media' => 'Inclure les médias (documents, photos)', + 'running' => 'Sauvegarde…', + 'run_backup' => 'Lancer la sauvegarde', + 'manual_hint_before' => 'Avec les médias, la sauvegarde est produite au format', + 'manual_hint_after' => '(base + fichiers uploadés). Sinon, seule la base est sauvegardée.', + 'archive_title' => 'Archiver une année scolaire', + 'archive_desc_before' => 'Génère un instantané complet', + 'archive_desc_locked' => 'verrouillé', + 'archive_desc_after' => 'et conservé à long terme (exclu de la rétention automatique). L\'archive est aussi créée automatiquement à la clôture d\'une année.', + 'academic_year' => 'Année scolaire', + 'choose_year' => 'Choisir une année…', + 'already_archived' => ' — déjà archivée', + 'archiving' => 'Archivage…', + 'archive_year' => 'Archiver l\'année', + 'schedule_title' => 'Planification automatique', + 'frequency' => 'Fréquence', + 'freq_none' => 'Désactivée', + 'freq_daily' => 'Quotidienne', + 'freq_weekly' => 'Hebdomadaire', + 'time' => 'Heure', + 'day' => 'Jour', + 'retention' => 'Rétention (nb conservés)', + 'formats_generated' => 'Formats générés', + 'schedule_hint_before' => 'La planification nécessite que le planificateur Laravel tourne sur le serveur', + 'schedule_hint_after' => 'via cron, chaque minute).', + 'save_schedule' => 'Enregistrer la planification', + 'restore_title' => 'Restaurer la base de données', + 'restore_warn_before' => 'La restauration', + 'restore_warn_bold' => 'écrase les données actuelles', + 'restore_warn_after' => 'avec le contenu du fichier. Une sauvegarde de sécurité (JSON) est créée automatiquement avant l\'opération.', + 'restoring' => 'Restauration…', + 'restore' => 'Restaurer', + 'restore_one_table' => 'Restaurer une seule table (optionnel)', + 'all_tables' => 'Toute la base', + 'restore_table_hint_before' => 'La restauration ciblée par table n\'est disponible qu\'avec les fichiers', + 'restore_table_hint_after' => '(ou .json.gz) et n\'écrase que la table choisie.', + 'accepted_formats' => 'Formats acceptés : .json, .sql, .gz (compressé), .dump (PostgreSQL) ou .zip (base + médias) issus de cette application — max 200 Mo.', + 'history_title' => 'Historique des sauvegardes', + 'th_file' => 'Fichier', + 'th_format' => 'Format', + 'th_size' => 'Taille', + 'th_origin' => 'Origine', + 'th_status' => 'Statut', + 'th_date' => 'Date', + 'empty' => 'Aucune sauvegarde pour le moment.', + 'badge_archive' => 'Archive', + 'badge_media' => '+ médias', + 'origin_scheduled' => 'Planifiée', + 'origin_manual' => 'Manuelle', + 'status_ok' => 'OK', + 'status_fail' => 'Échec', + 'verify_integrity' => 'Vérifier l\'intégrité', + 'restore_confirm_title' => 'Confirmer la restauration ?', + 'restore_confirm_desc' => 'Toutes les données actuelles seront remplacées par le contenu de « :name ». Une sauvegarde de sécurité est créée avant. Cette action est irréversible.', + 'restore_now' => 'Restaurer maintenant', + 'delete_title' => 'Supprimer cette sauvegarde ?', + 'delete_desc' => 'Le fichier sera définitivement supprimé du stockage.', + ], + + 'academic_years' => [ + 'head' => 'Années académiques', + 'title' => 'Années académiques', + 'subtitle' => 'Gérez les années académiques', + 'new' => 'Nouvelle année', + 'stat_total' => 'Années totales', + 'stat_active' => 'Années actives', + 'stat_page' => 'Page', + 'search_ph' => 'Rechercher une année...', + 'th_year' => 'Année', + 'th_start' => 'Date de début', + 'th_end' => 'Date de fin', + 'th_status' => 'Statut', + 'status_active' => 'Active', + 'status_inactive' => 'Inactive', + 'empty' => 'Aucune année académique trouvée', + 'delete_title' => 'Confirmer la suppression', + 'delete_desc' => 'Êtes-vous sûr de vouloir supprimer cette année académique ? Cette action est irréversible.', + 'deleting' => 'Suppression...', + 'create_head' => 'Créer une année académique', + 'create_title' => 'Créer une nouvelle année académique', + 'edit_head' => 'Modifier :year', + 'edit_title' => 'Modifier :year', + 'section_period' => 'Période académique', + 'label_year' => 'Année', + 'year_ph' => 'Ex: 2025-2026', + 'label_start' => 'Date de début', + 'label_end' => 'Date de fin', + 'section_activation' => 'Activation', + 'active_label' => 'Année active', + 'active_hint' => 'Cochez cette case si c\'est l\'année académique en cours', + 'submit_create' => 'Créer l\'année académique', + 'info_title' => 'Informations de l\'année académique', + 'field_status' => 'Statut', + 'field_status_active' => 'Active', + 'field_status_inactive' => 'Inactive', + 'period_title' => 'Période', + 'period_from' => 'Du', + 'period_to' => 'Au', + 'history_title' => 'Historique', + 'stats_title' => 'Statistiques', + 'stats_classes' => 'Classes', + 'stats_students' => 'Étudiants inscrits', + 'stats_teachers' => 'Enseignants', + 'state_title' => 'État', + 'state_active_title' => 'Année en cours', + 'state_active_desc' => 'Cette année est actuellement active', + 'state_inactive_title' => 'Année inactive', + 'state_inactive_desc' => 'Cette année n\'est pas active', + ], + + 'academic_periods' => [ + 'head' => 'Périodes académiques', + 'title' => 'Périodes académiques', + 'subtitle' => 'Gérez les périodes académiques (trimestres et semestres)', + 'new' => 'Nouvelle période', + 'stat_total' => 'Périodes totales', + 'stat_active' => 'Périodes actives', + 'stat_page' => 'Page', + 'search_ph' => 'Rechercher une période...', + 'th_name' => 'Nom', + 'th_year' => 'Année', + 'th_type' => 'Type', + 'th_order' => 'Ordre', + 'th_period' => 'Période', + 'th_status' => 'Statut', + 'type_trimester' => 'Trimestre', + 'type_semester' => 'Semestre', + 'status_active' => 'Active', + 'status_inactive' => 'Inactive', + 'empty' => 'Aucune période académique trouvée', + 'delete_title' => 'Confirmer la suppression', + 'delete_desc' => 'Êtes-vous sûr de vouloir supprimer cette période académique ? Cette action est irréversible.', + 'deleting' => 'Suppression...', + 'create_head' => 'Créer une période académique', + 'create_title' => 'Créer une période académique', + 'create_subtitle' => 'Définissez les informations de la nouvelle période.', + 'edit_head' => 'Modifier :name', + 'edit_title' => 'Modifier la période', + 'section_identification' => 'Identification', + 'label_name' => 'Nom', + 'name_ph' => 'Ex: Trimestre 1, Semestre 1...', + 'label_year' => 'Année académique', + 'year_ph' => 'Sélectionner une année', + 'section_classification' => 'Classification', + 'label_type' => 'Type', + 'label_order' => 'Ordre', + 'order_ph' => '1, 2, 3...', + 'label_class_type' => 'Type de classe', + 'class_type_all' => 'Toutes les classes (global)', + 'class_type_hint' => 'Laisser « global » pour appliquer à tous les types de classe.', + 'label_weight' => 'Poids (moyenne annuelle)', + 'section_calendar' => 'Calendrier', + 'label_start' => 'Date de début', + 'label_end' => 'Date de fin', + 'section_details' => 'Détails & Statut', + 'label_description' => 'Description', + 'description_ph' => 'Description de la période...', + 'active_label' => 'Période active (en cours)', + 'submit_create' => 'Créer la période', + 'saving' => 'Enregistrement...', + 'submit_update' => 'Enregistrer les modifications', + 'info_title' => 'Informations de la période', + 'system_title' => 'Informations système', + 'label_id' => 'Identifiant', + 'about_title' => 'À propos', + 'about_desc' => 'Les périodes académiques permettent de diviser l\'année scolaire en trimestres ou semestres pour organiser l\'évaluation et le suivi des élèves.', + ], + + 'classrooms' => [ + 'head' => 'Classes', + 'title' => 'Classes', + 'subtitle' => 'Gérez les classes de votre établissement', + 'new' => 'Nouvelle classe', + 'stat_total' => 'Total des classes', + 'stat_active' => 'Classes actives', + 'stat_page' => 'Page', + 'search_ph' => 'Rechercher une classe...', + 'reset' => 'Réinitialiser', + 'th_name' => 'Nom', + 'th_code' => 'Code', + 'th_type' => 'Type', + 'th_capacity' => 'Capacité', + 'empty' => 'Aucune classe trouvée', + 'assign_subjects' => 'Attribuer des matières', + 'showing_sep' => 'sur', + 'showing_unit' => 'classes', + 'delete_title' => 'Supprimer la classe', + 'delete_desc' => 'Êtes-vous sûr de vouloir supprimer cette classe ? Cette action est irréversible.', + 'deleting' => 'Suppression...', + 'create_head' => 'Nouvelle classe', + 'create_title' => 'Créer une classe', + 'create_subtitle' => 'Renseignez les informations de la classe.', + 'edit_head' => 'Éditer :name', + 'edit_title' => 'Éditer une classe', + 'edit_subtitle' => 'Mettez à jour les informations de :name.', + 'form_info' => 'Informations de la classe', + 'label_name' => 'Nom', + 'name_ph' => 'Ex: 6e A', + 'label_code' => 'Code', + 'code_ph' => 'Ex: 6A-2026', + 'label_capacity' => 'Capacité', + 'label_expected_age' => 'Âge attendu', + 'expected_age_ph' => 'Ex. 12', + 'expected_age_hint' => 'Âge officiel attendu (base du calcul de sur-âge).', + 'label_type' => 'Type de classe', + 'type_none' => 'Aucun type', + 'preview' => 'Aperçu', + 'preview_text_before' => 'Cette classe accueillera jusqu\'à', + 'preview_text_after' => 'élèves.', + 'submit_create' => 'Créer la classe', + 'creating' => 'Création...', + 'show_code' => 'Code', + 'show_capacity' => 'Capacité', + 'show_capacity_hint' => 'places disponibles', + 'show_type' => 'Type de classe', + 'show_created_on' => 'Créée le', + 'show_updated_on' => 'Modifiée le', + 'subjects_title' => 'Matières et Coefficients', + 'subjects_summary' => ':n matière(s) • Total des coefficients : :total', + 'select_year_ph' => 'Sélectionner une année', + 'coef' => 'Coef.', + 'no_assignments' => 'Aucune matière assignée pour cette année académique', + // Attributions + 'assign_head' => 'Attributions - :name', + 'assign_title' => 'Attribuer des matières', + 'assign_class_before' => 'Classe :', + 'assign_year_section' => 'Année académique', + 'assign_year_ph' => 'Sélectionnez une année académique', + 'assign_subjects_section' => 'Matières et coefficients', + 'assign_search_ph' => 'Rechercher une matière...', + 'assign_coef_ph' => 'Coef', + 'assign_selected_count' => ':n matière(s) sélectionnée(s)', + 'assign_submit' => 'Enregistrer les attributions', + 'assign_saving' => 'Enregistrement...', + 'err_year_required' => 'L\'année académique est requise.', + 'err_subjects_required' => 'Veuillez sélectionner au moins une matière.', + 'err_coef_invalid' => 'Coefficient invalide (doit être > 0).', + ], + + 'documents' => [ + // Index + 'head' => 'Modèles de documents', + 'title' => 'Modèles de documents', + 'subtitle' => 'Gérez les modèles de certificats, attestations et bulletins exportables en PDF.', + 'btn_header' => 'En-tête', + 'btn_registry' => 'Registre', + 'btn_new' => 'Nouveau modèle', + 'cat_empty' => 'Aucun modèle dans cette catégorie.', + 'badge_default' => 'Défaut', + 'status_active' => '● Actif', + 'status_inactive' => '● Inactif', + 'delete_title' => 'Supprimer ce modèle ?', + 'delete_desc' => 'Cette action est irréversible. Les documents déjà émis ne sont pas affectés.', + // Registry + 'registry_head' => 'Registre des documents', + 'registry_title' => 'Registre des documents', + 'registry_subtitle' => 'Traçabilité de tous les documents délivrés (:n).', + 'registry_search_ph' => 'N° de référence, nom ou matricule de l\'élève...', + 'registry_th_ref' => 'Référence', + 'registry_th_doc' => 'Document', + 'registry_th_student' => 'Élève', + 'registry_th_issuer' => 'Délivré par', + 'registry_th_date' => 'Date', + 'registry_empty' => 'Aucun document délivré.', + // Show + 'cat_certificat' => 'Certificat', + 'cat_attestation' => 'Attestation', + 'cat_bulletin' => 'Bulletin', + 'show_features' => 'Caractéristiques', + 'show_type' => 'Type', + 'show_orientation' => 'Orientation', + 'show_signatory' => 'Signataire', + 'orientation_portrait' => 'Portrait', + 'orientation_landscape' => 'Paysage', + 'flag_header' => 'En-tête officiel', + 'flag_signature' => 'Bloc signature', + 'show_default' => 'Modèle par défaut', + 'show_active' => 'Actif', + 'show_inactive' => 'Inactif', + 'show_sample_note' => 'L\'aperçu utilise des données d\'exemple. Les variables seront remplacées par les informations réelles de l\'élève à la génération.', + 'show_preview' => 'Aperçu du document', + // Edit + 'edit_head' => 'Modifier le modèle', + 'create_head' => 'Nouveau modèle', + 'edit_title' => 'Modifier le modèle', + 'create_title' => 'Nouveau modèle de document', + 'edit_subtitle' => 'Configurez le document et son contenu avec variables dynamiques.', + 'toast_saved_title' => 'Enregistré', + 'toast_saved_desc' => 'Modèle sauvegardé.', + 'toast_error_title' => 'Erreur', + 'toast_error_desc' => 'Vérifiez les champs.', + 'toast_preview_error' => 'Prévisualisation impossible.', + 'label_category' => 'Catégorie', + 'label_type' => 'Type', + 'type_ph' => 'Choisir un type', + 'label_name' => 'Nom du modèle', + 'name_ph' => 'Ex: Certificat de scolarité standard', + 'label_description' => 'Description', + 'description_ph' => 'Usage du modèle (optionnel)', + 'label_source' => 'Source du corps', + 'label_layout' => 'Mise en page prédéfinie', + 'layout_ph' => 'Choisir une mise en page', + 'layout_hint' => 'Mise en page fournie par l\'application (logique, tableaux). En-tête, signature et filigrane de l\'école restent appliqués.', + 'label_content' => 'Contenu du document', + 'content_tip' => 'Astuce : placez le curseur puis cliquez une variable à droite pour l\'insérer.', + 'variables_title' => 'Variables disponibles', + 'insert_var' => 'Insérer :var', + 'options_title' => 'Mise en page', + 'label_orientation' => 'Orientation', + 'label_signatory' => 'Titre du signataire', + 'signatory_ph' => 'Le Directeur', + 'opt_header' => 'En-tête officiel (logo, terme, devise)', + 'opt_signature' => 'Bloc signature', + 'opt_default' => 'Modèle par défaut pour ce type', + 'opt_active' => 'Actif', + 'btn_preview' => 'Prévisualiser', + 'preview_sample_title' => 'Aperçu (données d\'exemple)', + // HeaderDesigner + 'hd_head' => 'En-tête des documents', + 'hd_title' => 'En-tête des documents', + 'hd_subtitle' => 'Composez l\'en-tête utilisé pour les certificats, attestations et bulletins. Glissez les blocs pour les positionner.', + 'hd_reset' => 'Réinitialiser', + 'hd_saving' => 'Enregistrement…', + 'hd_preset_label' => 'Modèle d\'en-tête', + 'hd_watermark_both' => 'Le filigrane s\'applique aux deux modèles.', + 'hd_ministerial_title' => 'En-tête ministériel (officiel)', + 'hd_ministerial_p1' => 'L\'en-tête à trois colonnes (Ministère & établissement · logo · République & devise) est généré automatiquement à partir des informations de l\'école. Le glisser-déposer est désactivé pour ce modèle.', + 'hd_ministerial_p2' => 'Pour modifier le ministère, le nom, le logo ou la devise, rendez-vous dans les réglages de l\'école.', + 'hd_add_block' => 'Ajouter un bloc', + 'hd_block_logo' => 'Logo', + 'hd_block_name' => 'Nom', + 'hd_block_motto' => 'Devise', + 'hd_block_term' => 'Terme', + 'hd_block_contact' => 'Coordonnées', + 'hd_block_text' => 'Texte', + 'hd_default_text' => 'Texte libre', + 'hd_canvas_title' => 'Aperçu (largeur A4)', + 'hd_height' => 'Hauteur', + 'hd_canvas_hint' => 'Cliquez un bloc pour le modifier, glissez-le pour le déplacer.', + 'hd_block_props' => 'Propriétés du bloc', + 'hd_select_block' => 'Sélectionnez un bloc dans l\'aperçu.', + 'hd_size_px' => 'Taille (px)', + 'hd_delete' => 'Supprimer', + 'hd_content' => 'Contenu', + 'hd_insert_var_ph' => 'Insérer une variable…', + 'hd_size' => 'Taille', + 'hd_width' => 'Largeur', + 'hd_delete_block' => 'Supprimer le bloc', + 'hd_color' => 'Couleur', + 'hd_watermark' => 'Filigrane', + 'hd_enable' => 'Activer', + 'hd_wm_text' => 'Texte', + 'hd_wm_image' => 'Image / logo', + 'hd_wm_text_ph' => 'Texte du filigrane', + 'hd_opacity' => 'Opacité : :n%', + 'hd_wm_size' => 'Taille : :n', + 'hd_rotation' => 'Rotation : :n°', + 'hd_var_note_before' => 'Les variables comme', + 'hd_var_note_after' => 'sont remplacées à la génération de chaque document.', + ], + + 'class_subjects' => [ + // Index + 'head' => 'Attribution des matières aux classes', + 'title' => 'Attribution des matières aux classes', + 'subtitle' => 'Gérez l\'attribution des matières à chaque classe', + 'new' => 'Ajouter une attribution', + 'stat_assignments' => 'Attributions', + 'stat_classes' => 'Classes couvertes', + 'search_ph' => 'Rechercher une classe ou matière...', + 'search_results' => 'Résultats pour :', + 'th_class' => 'Classe', + 'th_subjects' => 'Matières attribuées', + 'th_year' => 'Année académique', + 'coef' => 'Coef', + 'empty' => 'Aucune attribution trouvée', + 'showing' => 'Affichage :from à :to sur :total attributions', + 'page_of' => 'Page :current sur :last', + 'delete_title' => 'Confirmer la suppression', + 'delete_desc' => 'Êtes-vous sûr de vouloir supprimer cette attribution ? Cette action est irréversible.', + 'deleting' => 'Suppression...', + // Create / Edit shared + 'create_head' => 'Assigner des matières à une classe', + 'create_title' => 'Assigner des matières à une classe', + 'create_subtitle' => 'Sélectionnez une classe, une année académique et les matières à enseigner', + 'edit_head' => 'Modifier les matières attribuées', + 'edit_title' => 'Modifier les matières attribuées', + 'edit_subtitle' => 'Mettez à jour les matières assignées à cette classe', + 'section_base' => 'Sélection de base', + 'label_class' => 'Classe', + 'class_ph' => 'Sélectionnez une classe', + 'label_year' => 'Année académique', + 'year_ph' => 'Sélectionnez une année académique', + 'section_subjects' => 'Sélection des matières et coefficients', + 'search_subjects' => 'Rechercher les matières', + 'search_subjects_ph' => 'Rechercher par nom ou code...', + 'code' => 'Code', + 'coef_ph' => 'Coef', + 'group_required' => 'Obligatoire', + 'group_optional' => 'Facultatif', + 'no_subjects' => 'Aucune matière trouvée', + 'selected_count' => ':n matière(s) sélectionnée(s) avec coefficient', + 'submit_create' => 'Attribuer les matières', + 'creating' => 'Attribution en cours...', + 'submit_update' => 'Mettre à jour', + 'updating' => 'Mise à jour en cours...', + 'err_class_required' => 'La classe est requise.', + 'err_year_required' => 'L\'année académique est requise.', + 'err_subjects_required' => 'Veuillez sélectionner au moins une matière.', + 'err_coef_invalid' => 'Coefficient invalide (doit être > 0).', + // Show + 'show_head' => 'Détails de l\'attribution', + 'show_title' => 'Détails de l\'attribution', + 'show_subtitle' => 'Informations sur l\'attribution de matière à la classe', + 'show_class' => 'Classe', + 'show_subject' => 'Matière', + 'show_name' => 'Nom', + 'show_coef' => 'Coefficient', + 'show_year' => 'Année académique', + 'show_year_label' => 'Année', + 'show_start' => 'Date de début', + 'show_end' => 'Date de fin', + 'show_tracking' => 'Informations de suivi', + 'show_created_on' => 'Créé le', + 'show_updated_on' => 'Modifié le', + ], + + 'fee_structures' => [ + // Index + 'head' => 'Structures de frais', + 'title' => 'Structures de frais', + 'subtitle' => 'Gérez les montants des frais par catégorie, classe et année académique', + 'replicate_year' => 'Répliquer une année', + 'new' => 'Nouvelle structure', + 'stat_total' => 'Montant total', + 'stat_average' => 'Montant moyen', + 'stat_classes' => 'Classes couvertes', + 'stat_categories' => 'Catégories couvertes', + 'stat_gap' => 'Écart min/max', + 'stat_gap_detail' => 'Min :min | Max :max', + 'search_ph' => 'Rechercher par catégorie, classe ou année...', + 'filter_year' => 'Année académique', + 'filter_all_years' => 'Toutes les années', + 'active_suffix' => ' (active)', + 'filter_category' => 'Catégorie', + 'filter_all_categories' => 'Toutes les catégories', + 'filter_class' => 'Classe', + 'filter_all_classes' => 'Toutes les classes', + 'filter_amount_min' => 'Montant min', + 'filter_amount_max' => 'Montant max', + 'search' => 'Rechercher', + 'reset' => 'Réinitialiser', + 'filtered_count' => ':count structure(s) filtrée(s) | Max : :max', + 'col_year' => 'Année académique', + 'col_category' => 'Catégorie', + 'col_class' => 'Classe', + 'col_amount' => 'Montant', + 'col_actions' => 'Actions', + 'empty' => 'Aucune structure de frais trouvée', + 'pagination' => 'Affichage de :from à :to sur :total résultats', + // Delete dialog + 'delete_title' => 'Confirmer la suppression', + 'delete_desc' => 'Êtes-vous sûr de vouloir supprimer cette structure de frais ? Cette action est irréversible.', + 'cancel' => 'Annuler', + 'deleting' => 'Suppression...', + 'delete' => 'Supprimer', + // Replicate dialog + 'replicate_title' => 'Répliquer les structures de frais', + 'replicate_desc' => 'Copie toutes les structures (et leurs tranches) d\'une année vers l\'année active. Les combinaisons catégorie / classe déjà présentes sont ignorées.', + 'replicate_target' => 'Année cible (active) :', + 'replicate_source' => 'Année source *', + 'replicate_source_ph' => 'Choisir l\'année à copier', + 'replicating' => 'Réplication...', + 'replicate' => 'Répliquer', + 'replicate_no_active' => 'Aucune année académique active. Activez une année avant de répliquer.', + // Create / Edit + 'create_head' => 'Nouvelle structure de frais', + 'create_title' => 'Nouvelle structure de frais', + 'create_subtitle' => 'Créez une nouvelle structure de frais pour une année académique, catégorie et classe', + 'edit_head' => 'Modifier la structure de frais', + 'edit_title' => 'Modifier la structure de frais', + 'edit_subtitle' => 'Modifiez les informations de cette structure de frais', + 'back' => 'Retour', + 'section_academic' => 'Contexte académique', + 'active_year' => 'Année académique active', + 'active_badge' => 'Active', + 'no_active_year' => 'Aucune année académique active. Activez-en une d\'abord.', + 'auto_attach' => 'La structure est automatiquement rattachée à l\'année active.', + 'field_year' => 'Année académique', + 'field_year_ph' => 'Sélectionner une année académique', + 'field_category' => 'Catégorie de frais', + 'field_category_ph' => 'Sélectionner une catégorie', + 'field_class_section' => 'Classe concernée', + 'field_class' => 'Classe', + 'field_class_ph' => 'Sélectionner une classe', + 'field_amount' => 'Montant', + 'field_amount_currency' => 'Montant (:currency)', + 'field_amount_ph' => 'Ex: 50000', + 'saving' => 'Enregistrement...', + 'save' => 'Enregistrer', + 'updating' => 'Mise à jour...', + 'update' => 'Mettre à jour', + // Show + 'show_head' => 'Détails de la structure de frais', + 'manage_installments' => 'Gérer les tranches', + 'edit' => 'Modifier', + 'show_heading' => 'Structure de frais', + 'show_amount_total' => 'Montant total', + 'installments_count' => ':count tranche(s)', + 'show_general' => 'Informations générales', + 'show_year' => 'Année académique', + 'show_category' => 'Catégorie de frais', + 'show_class' => 'Classe concernée', + 'show_code' => 'Code', + 'show_amount_section' => 'Montant et tranches', + 'installments' => 'Tranches', + 'manage' => 'Gérer', + 'installment_num' => 'Tranche :num', + 'no_installments' => 'Aucune tranche définie', + 'show_system' => 'Informations système', + 'show_created_on' => 'Créée le', + 'show_updated_on' => 'Dernière modification', + 'show_uid' => 'Identifiant unique', + // Installments modal + 'modal_title' => 'Gérer les tranches', + 'modal_desc' => 'Ajoutez ou modifiez les tranches pour cette structure de frais.', + 'current_structure' => 'Structure actuelle', + 'current_total' => 'Montant total:', + 'current_installments' => 'Tranches actuelles:', + 'configured_total' => 'Total des tranches configurées', + 'installments_total_label' => 'Montant total des tranches:', + 'installment_name' => 'Nom de la tranche', + 'installment_name_ph' => 'Ex: Première tranche', + 'installment_number_label' => 'N°', + 'installment_amount' => 'Montant', + 'add_installment' => 'Ajouter une tranche', + 'save_installments' => 'Enregistrer les tranches', + 'total_exceeds' => 'Le total des tranches (:total) dépasse le montant de la structure (:amount)', + 'err_valid_installment' => 'Veuillez ajouter au moins une tranche valide.', + 'err_total_exceeds' => 'Le total des tranches ne peut pas dépasser le montant de la structure de frais.', + 'err_saving' => 'Erreur lors de l\'enregistrement des tranches.', + ], +]; diff --git a/lang/fr/presences.php b/lang/fr/presences.php new file mode 100644 index 00000000..08b986a1 --- /dev/null +++ b/lang/fr/presences.php @@ -0,0 +1,146 @@ + [ + 'present' => 'Présent', + 'absent' => 'Absent', + 'late' => 'Retard', + 'excused' => 'Excusé', + ], + + 'reason' => [ + 'medical' => 'Médical', + 'familial' => 'Familial', + 'autre' => 'Autre', + ], + + 'session' => [ + 'journee' => 'Journée entière', + 'journee_short' => 'Journée', + 'matin' => 'Matin', + 'apres_midi' => 'Après-midi', + ], + + 'attendances' => [ + 'index' => [ + 'head' => 'Saisie des présences', + 'subtitle' => "Enregistrez l'appel quotidien par classe", + 'stats' => 'Statistiques', + 'class' => 'Classe', + 'period' => 'Période', + 'date' => 'Date', + 'session_label' => 'Session', + 'select' => 'Sélectionner', + 'class_dash' => '— Classe —', + 'period_dash' => '— Période —', + 'need_filters' => "Sélectionnez une classe et une période pour commencer l'appel.", + 'existing' => 'Appel déjà enregistré pour ce créneau — vous pouvez le modifier.', + 'all_present' => 'Tous présents', + 'all_absent' => 'Tous absents', + 'empty' => "Aucun élève inscrit dans cette classe pour l'année active.", + 'col_matricule' => 'Matricule', + 'col_student' => 'Élève', + 'col_status' => 'Statut', + 'col_late' => 'Retard (min)', + 'col_comment' => 'Observation', + 'permission' => 'Permission :dates', + 'comment_ph' => 'Observation...', + 'notes_ph' => "Remarques générales sur l'appel...", + 'saving' => 'Enregistrement...', + 'saved' => 'Enregistré !', + 'save' => "Enregistrer l'appel", + ], + + 'stats' => [ + 'head' => 'Statistiques de présence', + 'subtitle' => "Taux d'absence et suivi par élève", + 'back_to_entry' => "Saisie de l'appel", + 'class' => 'Classe', + 'period' => 'Période', + 'select' => 'Sélectionner', + 'all_classes' => '— Toutes —', + 'all_periods' => 'Toutes les périodes', + 'empty' => 'Sélectionnez une classe pour afficher les statistiques.', + 'recorded_calls' => 'Appels enregistrés', + 'col_date' => 'Date', + 'per_student' => 'Suivi par élève', + 'students_count' => ':count élèves', + 'col_student' => 'Élève', + 'col_present' => 'Présent', + 'col_absent' => 'Absent', + 'col_late' => 'Retard', + 'col_excused' => 'Excusé', + 'col_absence_rate' => "Taux absence", + ], + ], + + 'permissions' => [ + 'status' => [ + 'pending' => 'En attente', + 'approved' => 'Approuvée', + 'rejected' => 'Rejetée', + ], + + 'index' => [ + 'head' => 'Demandes de permission', + 'subtitle' => "Gérez les demandes d'absence justifiée", + 'new' => 'Nouvelle demande', + 'total' => 'Total', + 'pending' => 'En attente', + 'approved' => 'Approuvées', + 'rejected' => 'Rejetées', + 'search_ph' => "Nom ou matricule de l'élève...", + 'all_statuses' => 'Tous les statuts', + 'filter' => 'Filtrer', + 'col_student' => 'Élève', + 'col_reason' => 'Motif', + 'col_period' => 'Période', + 'col_duration' => 'Durée', + 'col_requested_by' => 'Demandé par', + 'col_status' => 'Statut', + 'col_actions' => 'Actions', + 'empty' => 'Aucune demande de permission.', + 'day_one' => ':count jour', + 'day_other' => ':count jours', + 'review' => 'Réviser', + 'view' => 'Voir', + 'total_count' => ':count demande(s) au total', + 'delete_title' => 'Supprimer cette demande ?', + 'delete_desc' => 'Cette action est irréversible.', + ], + + 'create' => [ + 'head' => 'Nouvelle demande de permission', + 'subtitle' => "Demande d'absence justifiée pour un élève", + 'student' => 'Élève *', + 'select_student' => 'Sélectionner un élève', + 'start_date' => 'Date de début *', + 'end_date' => 'Date de fin *', + 'reason' => 'Motif *', + 'description' => 'Description / Justification *', + 'description_ph' => "Expliquez la raison de l'absence...", + 'submitting' => 'Envoi...', + 'submit' => 'Soumettre la demande', + ], + + 'show' => [ + 'head' => 'Permission — :name', + 'details' => 'Détails de la demande', + 'reason' => 'Motif', + 'absence_period' => "Période d'absence", + 'day_one' => ':count jour', + 'day_other' => ':count jours', + 'requested_by' => 'Demandé par', + 'reviewed_by' => 'Révisé par', + 'reviewed_on' => 'le :date', + 'description' => 'Description', + 'review_comment' => 'Commentaire de révision', + 'linked_absences' => 'Absences excusées liées', + 'decision' => 'Décision', + 'comment_optional' => 'Commentaire (optionnel)', + 'comment_ph' => "Motif d'approbation ou de rejet...", + 'approve' => 'Approuver', + 'reject' => 'Rejeter', + ], + ], +]; diff --git a/lang/fr/promotion.php b/lang/fr/promotion.php new file mode 100644 index 00000000..0129240e --- /dev/null +++ b/lang/fr/promotion.php @@ -0,0 +1,32 @@ + 'Passage de classe', + 'subtitle' => "Réinscrivez en masse les élèves validés vers la classe et l'année suivantes.", + 'source_year' => 'Année source', + 'source_class' => 'Classe source', + 'target_year' => 'Année cible', + 'target_class' => 'Classe cible', + 'choose' => 'Choisir', + 'active' => 'active', + 'select_prompt' => 'Choisissez une année et une classe source', + + 'stat' => [ + 'students' => 'Élèves', + 'valide' => 'Validés', + 'non_valide' => 'Non validés', + 'reenrolled' => 'Déjà réinscrits', + ], + + 'selected' => ':count élève(s) sélectionné(s)', + 'choose_target' => "Choisissez l'année et la classe cible", + 'reenroll_selection' => 'Réinscrire la sélection', + + 'col_student' => 'Élève', + 'col_matricule' => 'Matricule', + 'col_status_source' => 'Statut (année source)', + 'col_target' => 'Cible', + 'empty' => 'Aucun élève dans cette classe.', + 'already_reenrolled' => 'Déjà réinscrit', + 'to_reenroll' => 'À réinscrire', +]; diff --git a/lang/fr/rh.php b/lang/fr/rh.php new file mode 100644 index 00000000..0f71b454 --- /dev/null +++ b/lang/fr/rh.php @@ -0,0 +1,253 @@ + [ + 'active' => 'Actif', + 'suspended' => 'Suspendu', + 'terminated' => 'Sorti', + ], + + 'pay_status' => [ + 'draft' => 'Brouillon', + 'validated' => 'Validé', + 'paid' => 'Payé', + 'cancelled' => 'Annulé', + ], + + 'personnel' => [ + 'index' => [ + 'head' => 'Personnel', + 'subtitle' => 'Assignez les grilles et gérez la paie de votre personnel.', + 'add' => 'Ajouter au personnel', + 'ungraded_banner' => ':count employé(s) sans grille — cliquer pour les filtrer.', + 'search_ph' => 'Nom, poste, matricule…', + 'all_statuses' => 'Tous les statuts', + 'status_active' => 'Actifs', + 'status_suspended' => 'Suspendus', + 'status_terminated' => 'Sortis', + 'all_grades' => 'Toutes les grilles', + 'ungraded' => 'Sans grille', + 'search' => 'Rechercher', + 'reset' => 'Réinit.', + 'count' => ':count employé(s)', + 'per_page' => 'Lignes par page :', + 'empty' => 'Aucun employé', + 'col_employee' => 'Employé', + 'col_job' => 'Poste', + 'col_grade' => 'Grille', + 'col_base' => 'Salaire de base', + 'col_status' => 'Statut', + 'choose_grade' => 'Choisir une grille', + 'off_grade_dash' => '— Hors grille —', + 'off_grade' => 'Hors grille', + 'open_sheet' => 'Ouvrir la fiche', + 'open_sheet_title' => 'Fiche complète (primes, bulletins…)', + 'range' => ':from–:to sur :total', + 'modal_title' => 'Ajouter au personnel', + 'user' => 'Utilisateur *', + 'search_user_ph' => 'Rechercher un utilisateur…', + 'no_result' => 'Aucun résultat', + 'no_assignable' => 'Tous les utilisateurs ont déjà un profil paie.', + 'job' => 'Poste *', + 'job_ph' => 'Ex: Enseignant', + 'contract' => 'Contrat *', + 'grade' => 'Grille', + 'base' => 'Salaire de base', + 'base_ignored' => '(ignoré)', + 'base_ph' => 'Ex: 120000', + 'cancel' => 'Annuler', + 'add_btn' => 'Ajouter', + ], + + 'overview' => [ + 'head' => "Personnel & Paie — Vue d'ensemble", + 'title' => 'Personnel & Paie', + 'subtitle' => "Vue d'ensemble du personnel et de la paie.", + 'new_run' => 'Nouveau cycle de paie', + 'kpi_active' => 'Employés actifs', + 'kpi_ungraded' => 'À classer (sans grille)', + 'kpi_ungraded_sub_some' => 'Cliquer pour les afficher', + 'kpi_ungraded_sub_none' => 'Tout est classé', + 'kpi_grades' => 'Grilles actives', + 'kpi_mass' => 'Masse salariale (base/mois)', + 'kpi_mass_sub' => 'Somme des salaires de base', + 'ungraded_banner' => ':count employé(s) actif(s) ne sont rattachés à aucune grille salariale.', + 'last_run' => 'Dernier cycle de paie', + 'all_runs' => 'Tous les cycles', + 'no_run' => 'Aucun cycle de paie généré pour le moment.', + 'net_mass' => 'Masse nette', + 'shortcut_personnel' => 'Personnel', + 'shortcut_personnel_desc' => 'Liste & assignation des grilles', + 'shortcut_grades' => 'Grilles salariales', + 'shortcut_grades_desc' => 'Catégories & montants', + 'shortcut_runs' => 'Cycles de paie', + 'shortcut_runs_desc' => 'Générer & décaisser', + ], + ], + + 'salary_components' => [ + 'head' => 'Rubriques de paie', + 'subtitle' => "Gains et retenues réutilisables. Les rubriques « auto » sont appliquées à la génération d'un cycle.", + 'new' => 'Nouvelle rubrique', + 'search_ph' => 'Nom, code...', + 'all_types' => 'Tous les types', + 'earnings' => 'Gains', + 'deductions' => 'Retenues', + 'search' => 'Rechercher', + 'reset' => 'Réinit.', + 'count' => ':count rubrique(s)', + 'filtered' => '— filtrées', + 'per_page' => 'Lignes par page :', + 'col_component' => 'Rubrique', + 'col_code' => 'Code', + 'col_type' => 'Type', + 'col_default_amount' => 'Montant par défaut', + 'col_application' => 'Application', + 'col_status' => 'Statut', + 'col_actions' => 'Actions', + 'empty' => 'Aucune rubrique', + 'type_earning' => 'Gain', + 'type_deduction' => 'Retenue', + 'auto' => 'Automatique', + 'manual' => 'Manuelle', + 'active' => 'Active', + 'inactive' => 'Inactive', + 'edit' => 'Modifier', + 'delete' => 'Supprimer', + 'range' => ':from–:to sur :total', + 'modal_edit' => 'Modifier la rubrique', + 'modal_new' => 'Nouvelle rubrique', + 'name' => 'Nom *', + 'name_ph' => 'Ex: Prime de transport', + 'type' => 'Type *', + 'code' => 'Code', + 'code_ph' => 'Ex: TRANSPORT', + 'default_amount' => 'Montant par défaut', + 'default_amount_ph' => 'Optionnel', + 'apply_auto' => 'Appliquer automatiquement à chaque bulletin', + 'active_label' => 'Active', + 'cancel' => 'Annuler', + 'saving' => 'Enregistrement...', + 'save' => 'Enregistrer', + 'add' => 'Ajouter', + 'delete_title' => 'Supprimer cette rubrique ?', + 'delete_desc' => 'Les bulletins déjà générés ne sont pas affectés (leurs lignes sont figées).', + ], + + 'salary_grades' => [ + 'index' => [ + 'head' => 'Grilles salariales', + 'subtitle' => "Chaque catégorie/échelon porte un salaire de base ; l'employé y est rattaché depuis sa fiche.", + 'settings' => 'Réglages de paie', + 'new' => 'Nouvelle grille', + 'seniority_active' => "Prime d'ancienneté", + 'seniority_active_state' => 'active', + 'seniority_active_desc' => '+:rate% du base par année', + 'seniority_cap' => ', plafonnée à :cap%', + 'seniority_disabled_state' => 'désactivée', + 'search_ph' => 'Nom, catégorie...', + 'all' => 'Toutes', + 'actives' => 'Actives', + 'inactives' => 'Inactives', + 'search' => 'Rechercher', + 'reset' => 'Réinit.', + 'count' => ':count grille(s)', + 'per_page' => 'Lignes par page :', + 'empty' => 'Aucune grille salariale', + 'create_first' => 'Créer la première grille', + 'col_grade' => 'Grille', + 'col_category' => 'Catégorie / échelon', + 'col_employees' => 'Employés', + 'col_base' => 'Salaire de base', + 'col_status' => 'Statut', + 'cat' => 'Cat. :category', + 'echelon' => 'échelon :echelon', + 'active' => 'Active', + 'inactive' => 'Inactive', + 'assign_employees' => 'Affecter des employés', + 'edit' => 'Modifier', + 'delete' => 'Supprimer', + 'range' => ':from–:to sur :total', + 'delete_title' => 'Supprimer cette grille ?', + 'delete_desc' => 'Impossible si des employés y sont rattachés. Les bulletins déjà générés ne sont pas affectés.', + 'cancel' => 'Annuler', + ], + + 'form' => [ + 'section' => 'Définition de la grille', + 'name' => 'Nom *', + 'name_ph' => 'Ex: Enseignant — Catégorie B, échelon 2', + 'category' => 'Catégorie', + 'category_ph' => 'Ex: B', + 'echelon' => 'Échelon', + 'echelon_ph' => 'Ex: 2', + 'base' => 'Salaire de base (F) *', + 'base_ph' => 'Ex: 145000', + 'sort_order' => "Ordre d'affichage", + 'active' => 'Grille active', + 'update' => 'Mettre à jour', + 'creating' => 'Création...', + 'updating' => 'Mise à jour...', + 'create' => 'Créer la grille', + 'cancel' => 'Annuler', + ], + + 'create' => [ + 'head' => 'Nouvelle grille salariale', + 'title' => 'Nouvelle grille salariale', + 'subtitle' => 'Une catégorie/échelon avec son salaire de base.', + ], + + 'edit' => [ + 'head' => 'Éditer :name', + 'title' => 'Éditer la grille', + ], + + 'employees' => [ + 'head' => 'Affecter — :name', + 'title' => 'Affecter des employés', + 'subtitle' => 'Grille :name — salaire de base :base.', + 'search_ph' => 'Rechercher un employé…', + 'selected' => ':count sélectionné(s)', + 'empty' => 'Aucun employé actif', + 'select_all' => 'Tout sélectionner', + 'select_all_filtered' => ' (résultats filtrés)', + 'no_result' => 'Aucun résultat.', + 'current' => 'Actuel : :name', + 'on_this_grade' => 'Sur cette grille', + 'moving' => ':count employé(s) seront déplacés depuis une autre grille.', + 'detach_note' => 'Les employés décochés qui étaient sur cette grille en seront détachés (repli sur leur salaire manuel).', + 'saving' => 'Enregistrement...', + 'save' => 'Enregistrer les affectations', + 'cancel' => 'Annuler', + ], + ], + + 'payroll_settings' => [ + 'head' => 'Réglages de paie', + 'title' => 'Réglages de paie', + 'subtitle' => 'Ancienneté, CNSS et impôt (ITS) appliqués automatiquement à la génération des bulletins.', + 'disclaimer' => 'Ces cotisations et impôts sont désactivés par défaut. Les taux/barèmes fournis sont indicatifs — vérifiez-les auprès de la réglementation en vigueur avant activation.', + 'seniority' => "Prime d'ancienneté", + 'seniority_enable' => "Activer la prime d'ancienneté", + 'seniority_rate' => 'Taux par année (% du base)', + 'seniority_cap' => 'Plafond (% du base, 0 = aucun)', + 'cnss' => 'CNSS (sécurité sociale)', + 'cnss_enable' => 'Activer la retenue CNSS', + 'cnss_employee' => 'Part salariale (%)', + 'cnss_employer' => 'Part patronale (%)', + 'cnss_ceiling' => 'Plafond mensuel (F, 0 = aucun)', + 'cnss_note' => "La part salariale est retenue sur le bulletin ; la part patronale est enregistrée comme charge employeur (information, non déduite du net).", + 'its' => 'ITS — Impôt sur les traitements et salaires', + 'its_enable' => "Activer l'ITS (barème progressif)", + 'its_desc' => "Barème par tranches sur le net imposable (brut − CNSS salariale). Laissez « Jusqu'à » vide pour la dernière tranche (au-delà).", + 'its_up_to' => "Jusqu'à (F)", + 'its_rate' => 'Taux (%)', + 'its_empty' => 'Aucune tranche. Ajoutez-en pour définir le barème.', + 'its_beyond' => 'au-delà', + 'its_add' => 'Ajouter une tranche', + 'saving' => 'Enregistrement...', + 'save' => 'Enregistrer les réglages', + 'back' => 'Retour', + ], +]; diff --git a/lang/fr/roster.php b/lang/fr/roster.php new file mode 100644 index 00000000..46135b31 --- /dev/null +++ b/lang/fr/roster.php @@ -0,0 +1,52 @@ + 'Effectifs / Listes de classe', + 'subtitle' => 'Élèves par année et classe, suivi de scolarité (validation, abandon, transfert).', + 'export_pdf' => 'Exporter en PDF', + 'year' => 'Année scolaire', + 'choose' => 'Choisir', + 'class' => 'Classe', + 'choose_class' => 'Choisir une classe', + 'active' => 'active', + 'status' => 'Statut', + 'all' => 'Tous', + 'search' => 'Recherche', + 'search_ph' => 'Nom ou matricule...', + 'select_prompt' => 'Sélectionnez une année et une classe', + + 'stat' => [ + 'enrolled' => 'Inscrits', + 'en_cours' => 'En cours', + 'valide' => 'Validés', + 'non_valide' => 'Non validés', + 'abandon' => 'Abandons', + ], + + 'selected' => ':count sélectionné(s)', + 'validate' => 'Valider', + 'invalidate' => 'Non valider', + + 'col_student' => 'Élève', + 'col_matricule' => 'Matricule', + 'col_payment' => 'Paiement', + 'col_schooling' => 'Statut scolarité', + 'col_action' => 'Action', + 'empty' => 'Aucun élève pour ce filtre.', + 'status_btn' => 'Statut', + + 'dialog' => [ + 'title' => 'Statut de scolarité', + 'status' => 'Statut', + 'reason' => 'Motif', + 'reason_ph' => "Raison de l'abandon / transfert...", + 'abandon_warning' => "L'élève sortira automatiquement des listes d'appel (présences).", + ], + + 'pay' => [ + 'PAID' => 'Payé', + 'PARTIALLY_PAID' => 'Partiel', + 'ISSUED' => 'Impayé', + 'NONE' => 'Sans facture', + ], +]; diff --git a/lang/fr/scholarships.php b/lang/fr/scholarships.php new file mode 100644 index 00000000..c7359bc6 --- /dev/null +++ b/lang/fr/scholarships.php @@ -0,0 +1,86 @@ + [ + 'title' => "Bourses d'élèves", + 'subtitle' => 'Gérez les attributions de bourses aux élèves', + 'new' => 'Nouvelle attribution', + 'total' => 'Total des attributions', + 'search' => 'Recherche', + 'search_ph' => 'Rechercher par élève, bourse...', + 'scholarship' => 'Bourse', + 'all_scholarships' => 'Toutes les bourses', + 'academic_year' => 'Année académique', + 'all_years' => 'Toutes les années', + 'clear_filters' => 'Effacer les filtres', + 'per_page' => 'Éléments par page', + 'col_student' => 'Élève', + 'col_scholarship' => 'Bourse', + 'col_year' => 'Année académique', + 'col_date' => "Date d'attribution", + 'col_actions' => 'Actions', + 'empty' => 'Aucune attribution de bourse trouvée', + 'showing' => 'Affichage de :from à :to sur :total résultats', + 'delete_title' => 'Confirmer la suppression', + 'delete_confirm' => 'Êtes-vous sûr de vouloir supprimer cette attribution de bourse ? Cette action est irréversible.', + 'deleting' => 'Suppression...', + ], + + 'form' => [ + 'create_head' => 'Nouvelle attribution de bourse', + 'create_title' => 'Nouvelle attribution de bourse', + 'create_subtitle' => 'Attribuez une bourse à un élève', + 'edit_head' => "Modifier l'attribution de bourse", + 'edit_title' => "Modifier l'attribution de bourse", + 'edit_subtitle' => "Modifiez les détails de l'attribution de bourse", + 'section_award' => 'Attribution de bourse', + 'student' => 'Élève *', + 'student_search_ph' => 'Rechercher et sélectionner un élève...', + 'select_student' => 'Sélectionnez un élève', + 'no_student' => 'Aucun élève trouvé', + 'more_students' => 'Et :count autres élèves...', + 'scholarship' => 'Bourse *', + 'select_scholarship' => 'Sélectionnez une bourse', + 'academic_year' => 'Année académique *', + 'select_year' => 'Sélectionnez une année', + 'number_of_year' => "Nombre d'années", + 'number_of_year_ph' => 'Ex: 1, 2, 3...', + 'start_date' => 'Date de début', + 'end_date' => 'Date de fin', + 'extra_info' => 'Informations complémentaires', + 'notes' => 'Notes (optionnel)', + 'notes_ph' => 'Ajoutez des notes sur cette attribution...', + 'saving' => 'Enregistrement...', + ], + + 'show' => [ + 'head' => 'Bourse - :name', + 'title' => 'Attribution de bourse', + 'subtitle' => "Détails de l'attribution de bourse à :name", + 'student_info' => "Informations de l'élève", + 'full_name' => 'Nom complet', + 'matricule' => 'Matricule', + 'gender' => 'Genre', + 'male' => 'Masculin', + 'female' => 'Féminin', + 'other' => 'Autre', + 'birth_date' => 'Date de naissance', + 'email' => 'Email', + 'phone' => 'Téléphone', + 'scholarship_info' => 'Informations de la bourse', + 'scholarship_name' => 'Nom de la bourse', + 'type' => 'Type', + 'max_value' => 'Valeur maximale', + 'description' => 'Description', + 'notes' => 'Notes', + 'academic_year' => 'Année académique', + 'year' => 'Année', + 'period' => 'Période', + 'details' => "Détails de l'attribution", + 'award_date' => "Date d'attribution", + 'last_modified' => 'Dernière modification', + 'number_of_year' => "Nombre d'années", + 'start_date' => 'Date de début', + 'end_date' => 'Date de fin', + ], +]; diff --git a/lang/fr/settings.php b/lang/fr/settings.php new file mode 100644 index 00000000..146362a8 --- /dev/null +++ b/lang/fr/settings.php @@ -0,0 +1,93 @@ + 'Paramètres', + 'account' => 'Mon compte', + 'account_desc' => 'Gérez votre profil et les paramètres de votre compte', + + 'profile' => [ + 'title' => 'Profil', + 'desc' => 'Vos informations personnelles', + 'page' => [ + 'breadcrumb' => 'Paramètres du compte', + 'head' => 'Mon profil', + 'heading' => 'Mon profil', + 'default_name' => 'Mon profil', + 'section_title' => 'Informations personnelles', + 'section_desc' => 'Gérez vos informations de profil', + 'firstname' => 'Prénom', + 'firstname_ph' => 'Votre prénom', + 'lastname' => 'Nom de famille', + 'lastname_ph' => 'Votre nom', + 'matricule' => 'Matricule', + 'matricule_ph' => 'Non attribué', + 'gender' => 'Genre', + 'gender_ph' => 'Sélectionner', + 'gender_male' => 'Masculin', + 'gender_female' => 'Féminin', + 'gender_other' => 'Autre', + 'birth_date' => 'Date de naissance', + 'email' => 'Adresse email', + 'email_ph' => 'exemple@email.com', + 'telephone' => 'Téléphone', + 'telephone_ph' => '+243 XXX XXX XXX', + 'address' => 'Adresse', + 'address_ph' => 'Votre adresse complète', + 'email_unverified' => "Votre adresse email n'est pas vérifiée.", + 'email_resend' => "Cliquez ici pour renvoyer l'email de vérification.", + 'email_link_sent' => 'Un nouveau lien de vérification a été envoyé à votre adresse email.', + 'saving' => 'Enregistrement...', + 'save' => 'Enregistrer', + 'saved' => '✓ Modifications enregistrées', + ], + ], + 'password' => [ + 'title' => 'Mot de passe', + 'desc' => 'Modifier votre mot de passe', + 'page' => [ + 'breadcrumb' => 'Mot de passe', + 'head' => 'Mot de passe', + 'heading' => 'Mot de passe', + 'card_desc' => "Assurez-vous d'utiliser un mot de passe long et sécurisé.", + 'current' => 'Mot de passe actuel', + 'current_ph' => 'Entrez votre mot de passe actuel', + 'new_section' => 'Nouveau mot de passe', + 'new' => 'Nouveau mot de passe', + 'new_ph' => 'Entrez un nouveau mot de passe', + 'confirm' => 'Confirmer le mot de passe', + 'confirm_ph' => 'Confirmez le nouveau mot de passe', + 'tip_label' => 'Conseil :', + 'tip' => 'Au moins 12 caractères mêlant majuscules, minuscules, chiffres et symboles. Évitez les mots de passe déjà utilisés ailleurs.', + 'saving' => 'Modification...', + 'submit' => 'Modifier le mot de passe', + 'saved' => '✓ Mot de passe modifié', + ], + ], + 'appearance' => [ + 'title' => 'Apparence', + 'desc' => 'Thème clair, sombre ou système', + ], + 'two_factor' => [ + 'title' => 'Double authentification', + 'desc' => 'Sécurisez votre compte avec un second facteur', + 'page' => [ + 'breadcrumb' => 'Authentification à deux facteurs', + 'head' => 'Authentification à deux facteurs', + 'heading' => 'Authentification à deux facteurs', + 'card_desc' => 'Renforcez la sécurité de votre compte avec un second facteur.', + 'enabled_badge' => 'Activée', + 'enabled_desc' => "Avec l'authentification à deux facteurs activée, vous serez invité à saisir un code de sécurité lors de la connexion, que vous pouvez récupérer depuis l'application TOTP sur votre téléphone.", + 'disable' => 'Désactiver 2FA', + 'disabled_badge' => 'Désactivée', + 'disabled_desc' => "Lorsque vous activez l'authentification à deux facteurs, vous serez invité à saisir un code de sécurité lors de la connexion. Ce code peut être récupéré depuis une application TOTP sur votre téléphone.", + 'continue_setup' => 'Continuer la configuration', + 'enable' => 'Activer 2FA', + ], + ], + 'language' => [ + 'title' => 'Langue', + 'desc' => "Langue de l'interface, enregistrée sur votre profil", + 'label' => "Langue de l'interface", + 'updated' => 'Langue mise à jour', + ], +]; diff --git a/lang/fr/statistiques.php b/lang/fr/statistiques.php new file mode 100644 index 00000000..29cbb930 --- /dev/null +++ b/lang/fr/statistiques.php @@ -0,0 +1,169 @@ + 'Statistiques', + 'subtitle' => 'Indicateurs de l\'établissement — effectifs, finances et réussite.', + 'year_ph' => 'Année', + 'class_ph' => 'Classe', + 'gender_ph' => 'Sexe', + 'all_classes' => 'Toutes les classes', + 'all' => 'Tous', + 'boys' => 'Garçons', + 'girls' => 'Filles', + 'other' => 'Autre', + 'active_suffix' => ' · active', + 'all_cycles' => 'Tous', + 'sort' => 'Tri', + 'sort_level' => 'Par niveau', + 'sort_headcount' => 'Par effectif', + 'sort_amount' => 'Par montant', + 'sort_rate' => 'Par taux', + 'years_old' => ':value ans', + + // Onglets + 'tab_effectifs' => 'Effectifs & parité', + 'tab_finances' => 'Finances & recouvrement', + 'tab_reussite' => 'Réussite & examens', + 'tab_encadrement' => 'Encadrement', + 'tab_assiduite' => 'Assiduité', + 'tab_comparaisons' => 'Comparaisons', + 'tab_geographie' => 'Géographie', + + // Effectifs + 'kpi_total' => 'Effectif total', + 'kpi_total_sub' => ':boys G · :girls F', + 'kpi_ips' => 'Indice de parité (IPS)', + 'kpi_ips_sub' => ':pct% de filles', + 'kpi_redoublement' => 'Taux de redoublement', + 'kpi_promotion_sub' => 'Promotion :pct%', + 'kpi_abandon' => 'Taux d\'abandon', + 'kpi_age_sub' => 'Âge moyen :age', + 'card_by_class' => 'Effectifs par classe (garçons / filles)', + 'card_by_gender' => 'Répartition par sexe', + 'card_age_pyramid' => 'Pyramide des âges (garçons / filles)', + 'overage_label' => 'Taux de sur-âge (≥ +:threshold ans)', + 'overage_hint' => 'Renseignez l\'âge attendu sur les classes pour activer ce calcul.', + 'card_city' => 'Origine géographique (top villes, par sexe)', + 'not_filled' => 'Non renseigné', + + // Finances + 'kpi_billed' => 'Total facturé', + 'kpi_billed_sub' => ':count factures', + 'kpi_collected' => 'Encaissé', + 'kpi_recovery' => 'Taux de recouvrement', + 'kpi_remaining_sub' => ':amount restant', + 'kpi_unpaid' => 'Impayés', + 'kpi_unpaid_sub' => ':count partiels', + 'card_monthly' => 'Encaissements mensuels', + 'card_methods' => 'Modes de paiement', + 'recovery_by_class' => 'Recouvrement par classe', + 'card_billed_split' => 'Facturé : encaissé / reste à recouvrer', + 'series_collected' => 'Encaissé', + 'series_remaining' => 'Reste à recouvrer', + 'card_recovery_rate' => 'Taux de recouvrement par classe', + 'rate_label' => 'Taux', + 'avg_prefix' => 'moy. :rate%', + 'legend_high' => '≥ 80 %', + 'legend_mid' => '60–79 %', + 'legend_low' => '< 60 %', + 'card_detail' => 'Détail par classe', + 'th_class' => 'Classe', + 'th_billed' => 'Facturé', + 'th_collected' => 'Encaissé', + 'th_remaining' => 'Reste', + 'th_rate' => 'Taux', + 'method_cash' => 'Espèces', + 'method_mobile_money' => 'Mobile Money', + 'method_bank_transfer' => 'Virement', + 'method_cheque' => 'Chèque', + + // Réussite + 'kpi_bulletins' => 'Bulletins validés', + 'kpi_average' => 'Moyenne générale', + 'kpi_passrate' => 'Réussite interne (≥10)', + 'kpi_admission' => 'Admission examens off.', + 'card_mentions' => 'Répartition des mentions', + 'mention_passable' => 'Passable', + 'mention_assez_bien' => 'Assez bien', + 'mention_bien' => 'Bien', + 'mention_tres_bien' => 'Très bien', + 'card_exams' => 'Examens officiels (CEPD / BEPC / BAC)', + 'no_exams' => 'Aucun examen officiel pour cette année', + 'th_exam' => 'Examen', + 'th_registered' => 'Inscrits', + 'th_admitted' => 'Admis', + 'th_admission' => 'Admission', + + // Encadrement + 'kpi_rem' => 'Ratio élèves / enseignant', + 'kpi_rem_sub' => ':count enseignants', + 'kpi_students' => 'Effectif total', + 'kpi_avg_class' => 'Taille moyenne / classe', + 'kpi_avg_class_sub' => ':count classes', + 'kpi_overcrowded' => 'Classes pléthoriques', + 'kpi_overcrowded_sub' => '> :threshold élèves', + 'card_class_sizes' => 'Effectif par classe (rouge = pléthorique)', + 'series_students' => 'Élèves', + 'card_overcrowded' => 'Classes pléthoriques à surveiller', + 'students_count' => ':count élèves', + + // Assiduité + 'no_attendance' => 'Aucune donnée de présence pour cette période.', + 'kpi_presence' => 'Taux de présence', + 'kpi_presence_sub' => ':count présences', + 'kpi_absence' => 'Taux d\'absence', + 'kpi_absence_sub' => ':count absences', + 'kpi_late' => 'Taux de retard', + 'kpi_late_sub' => ':count retards', + 'kpi_chronic' => 'Absentéisme chronique', + 'kpi_chronic_sub' => '> :threshold absences', + 'card_by_period' => 'Présences par période', + 'series_present' => 'Présents', + 'series_absent' => 'Absents', + 'series_late' => 'Retards', + 'card_absence_by_class' => 'Taux d\'absence par classe', + + // Comparaisons + 'need_two_years' => 'Les tendances se précisent avec au moins deux années académiques renseignées.', + 'kpi_effectif' => 'Effectif', + 'kpi_reussite' => 'Réussite', + 'kpi_recouvrement' => 'Recouvrement', + 'kpi_abandon_short' => 'Abandon', + 'card_effectif_evol' => 'Évolution de l\'effectif', + 'series_effectif' => 'Effectif', + 'card_performance' => 'Performance (%)', + 'series_reussite' => 'Réussite', + 'series_recouvrement' => 'Recouvrement', + 'card_deperdition' => 'Déperdition scolaire (%)', + 'series_redoublement' => 'Redoublement', + 'series_abandon' => 'Abandon', + 'deperdition_hint' => 'L\'année en cours n\'est pas tracée : les décisions de fin d\'année (redoublement, abandon) ne sont pas encore arrêtées.', + 'card_comparison' => 'Tableau comparatif', + 'th_year' => 'Année', + 'th_effectif' => 'Effectif', + 'th_girls_pct' => '% filles', + 'th_redoubl' => 'Redoubl.', + 'th_abandon' => 'Abandon', + 'th_recouvr' => 'Recouvr.', + 'th_reussite' => 'Réussite', + 'in_progress' => 'en cours', + + // Géographie + 'coverage_note_1' => 'Origine renseignée pour', + 'coverage_note_2' => 'des élèves', + 'coverage_note_3' => 'Complétez la région/préfecture sur la fiche élève pour affiner.', + 'no_geo' => 'Aucune origine géographique renseignée.', + 'kpi_coverage' => 'Couverture', + 'kpi_coverage_sub' => ':localized / :total élèves', + 'kpi_grand_lome' => 'Grand Lomé', + 'kpi_grand_lome_sub' => 'Golfe + Agoè-Nyivé', + 'kpi_regions' => 'Régions', + 'kpi_regions_sub' => 'représentées', + 'kpi_prefectures' => 'Préfectures', + 'kpi_prefectures_sub' => 'd\'origine', + 'card_origin' => 'Origine des élèves — régions et préfectures', + 'students_tooltip' => ':value élèves', + 'headcount_label' => 'Effectif', + 'shade_note' => '· nuance = effectif de la préfecture', + 'card_by_region' => 'Effectif par région', +]; diff --git a/lang/fr/students.php b/lang/fr/students.php new file mode 100644 index 00000000..a6c4aea9 --- /dev/null +++ b/lang/fr/students.php @@ -0,0 +1,368 @@ + 'Élèves', + 'subtitle' => 'Gérez les profils élèves de votre établissement', + 'import' => 'Importer', + 'new' => 'Nouvel élève', + + 'gender' => [ + 'male' => 'Masculin', + 'female' => 'Féminin', + 'unknown' => 'Non renseigné', + ], + + 'stats' => [ + 'total' => 'Total élèves', + 'active' => 'Élèves actifs', + 'male' => 'Élèves hommes', + 'female' => 'Élèves femmes', + ], + + 'bulk' => [ + 'selected' => ':count élève(s) sélectionné(s)', + 'choose' => 'Choisir une action', + 'activate' => 'Activer', + 'deactivate' => 'Désactiver', + 'apply' => 'Appliquer', + ], + + 'search' => [ + 'placeholder' => 'Rechercher (nom, matricule, e-mail)...', + 'nationality' => 'Nationalité', + 'filters' => 'Filtres', + 'reset_count' => 'Réinitialiser (:count)', + ], + + 'filter' => [ + 'all_classes' => 'Toutes les classes', + 'all_years' => 'Toutes les années', + 'schooling_all' => 'Scolarité (tous)', + 'gender' => 'Sexe', + 'status' => 'Statut', + 'region' => 'Région', + 'prefecture' => 'Préfecture', + 'active' => 'Actif', + 'inactive' => 'Inactif', + ], + + 'sort' => [ + 'label' => 'Trier :', + 'created_at' => "Date d'ajout", + 'lastname' => 'Nom', + 'firstname' => 'Prénom', + 'matricule' => 'Matricule', + 'birth_date' => 'Date de naissance', + 'asc' => 'Croissant', + 'desc' => 'Décroissant', + ], + + 'table' => [ + 'count' => ':count élève(s)', + 'unit' => 'élève(s)', + 'per_page' => 'Lignes par page :', + 'student' => 'Élève', + 'matricule' => 'Matricule', + 'gender' => 'Sexe', + 'nationality' => 'Nationalité', + 'contact' => 'Contact', + 'status' => 'Statut', + 'actions' => 'Actions', + 'empty' => 'Aucun élève trouvé', + 'select_all' => 'Sélectionner tous les élèves', + 'select_one' => 'Sélectionner :name', + 'view' => 'Voir la fiche', + 'history' => 'Historique', + 'range' => ':from–:to sur :total élèves', + ], + + 'delete' => [ + 'title' => "Supprimer l'élève", + 'confirm' => 'Êtes-vous sûr de vouloir supprimer cet élève ? Cette action est irréversible.', + 'deleting' => 'Suppression...', + ], + + 'admission' => [ + 'new' => 'Nouvelle inscription', + 'transfer' => 'Transfert', + 're_admission' => 'Ré-admission', + ], + + 'stats_page' => [ + 'title' => 'Statistiques élèves', + 'subtitle' => "Vue démographique et effectifs des élèves inscrits pour l'année sélectionnée.", + 'year' => 'Année', + 'enrolled' => 'Inscrits :year', + 'classes' => ':count classes', + 'active' => 'Actifs', + 'inactive' => ':count inactifs', + 'avg_age' => 'Âge moyen', + 'years' => ':n ans', + 'cohort' => 'de la cohorte', + 'over_age' => 'Sur-âge', + 'over_age_sub' => ':count élèves en retard', + 'boys' => 'Garçons', + 'girls' => 'Filles', + 'no_data' => 'Aucune donnée', + 'gender_dist' => 'Répartition par sexe', + 'girls_sub' => 'de filles', + 'ips' => 'IPS', + 'age_dist' => "Répartition par tranche d'âge", + 'headcount' => 'Effectif', + 'students_count' => ':v élèves', + 'by_class' => 'Effectifs par classe', + 'all' => 'Tous', + 'sort' => 'Tri', + 'by_level' => 'Par niveau', + 'by_headcount' => 'Par effectif', + 'no_class_filter' => 'Aucune classe pour ce filtre', + 'total' => 'Total', + 'seats' => 'places', + ], + + 'import_page' => [ + 'title' => 'Importer des élèves', + 'subtitle' => "Import en masse depuis un fichier CSV (idéal en début d'année).", + 'step1' => 'Télécharger le modèle', + 'tmpl_intro' => 'Utilisez le modèle CSV. Colonnes :', + 'tmpl_required_prefix' => 'Les colonnes', + 'tmpl_required_cols' => 'prénom, nom, sexe (M/F), date de naissance (AAAA-MM-JJ)', + 'tmpl_required_suffix' => 'sont obligatoires. Le matricule est généré si laissé vide.', + 'download_template' => 'Télécharger le modèle CSV', + 'step2' => 'Importer le fichier', + 'importing' => 'Import en cours…', + 'choose_file' => 'Cliquez pour choisir un fichier CSV', + 'rows_read' => 'Lignes lues', + 'imported' => 'Importés', + 'rows_error' => 'Lignes en erreur', + 'success' => ':count élève(s) importé(s) avec succès.', + 'view_list' => 'Voir la liste', + 'not_imported' => 'Lignes non importées — corrigez puis réimportez', + 'no_name' => '(sans nom)', + ], + + 'history' => [ + 'title' => 'Historique des inscriptions', + 'view_profile' => 'Voir le profil élève', + 'journey' => 'Parcours scolaire', + 'count' => ':count inscription(s)', + 'total' => 'Total inscriptions', + 'first_class' => 'Première classe', + 'current_class' => 'Classe actuelle', + 'by_class_year' => 'Parcours par classe et année scolaire', + 'col_class' => 'Classe', + 'col_year' => 'Année scolaire', + 'col_date' => "Date d'inscription", + 'col_code' => 'Code inscription', + 'col_payment' => 'Paiement', + 'col_schooling' => 'Scolarité', + 'empty' => 'Aucune inscription trouvée pour cet élève.', + 'pay' => [ + 'PAID' => 'Payé', + 'PARTIALLY_PAID' => 'Partiel', + 'ISSUED' => 'Impayé', + 'NONE' => 'Sans facture', + ], + ], + + 'field' => [ + 'full_name' => 'Nom complet', + 'matricule' => 'Matricule', + 'gender' => 'Genre', + 'status' => 'Statut', + 'birth_date' => 'Date de naissance', + 'place_of_birth' => 'Lieu de naissance', + 'user_account' => 'Compte utilisateur', + 'account_email' => 'Email compte', + 'phone' => 'Téléphone', + 'student_email' => 'Email élève', + 'city' => 'Ville', + 'nationality' => 'Nationalité', + 'address' => 'Adresse', + 'admission_type' => "Type d'admission", + 'birth_cert_number' => 'N° acte de naissance', + 'issue_date' => 'Date de délivrance', + 'issue_place' => 'Lieu de délivrance', + 'father' => 'Père', + 'mother' => 'Mère', + 'father_phone' => 'Téléphone père', + 'mother_phone' => 'Téléphone mère', + 'parent_email' => 'Email parent', + 'blood_group' => 'Groupe sanguin', + 'emergency_contact' => "Contact d'urgence", + 'emergency_phone' => "Téléphone d'urgence", + 'allergies' => 'Allergies', + ], + + 'show' => [ + 'title' => 'Détail élève', + 'subtitle' => "Consultez toutes les informations de l'élève", + 'deliver_document' => 'Délivrer un document', + 'change_photo' => 'Changer la photo', + 'change' => 'Changer', + 'add_photo' => 'Ajouter une photo', + 'photo_hint' => 'JPG, PNG ou WebP — max 2 Mo', + 'current_schooling' => 'Scolarité actuelle (:year)', + 'change_class' => 'Changer de classe', + 'identity' => 'Identité', + 'status' => 'Statut', + 'student_active' => 'Élève actif', + 'student_inactive' => 'Élève inactif', + 'account' => 'Compte : :name', + 'medical_emergency' => 'Urgence médicale', + 'no_emergency' => "Aucun contact d'urgence renseigné", + 'phone_missing' => 'Téléphone non renseigné', + 'contact_fallback' => 'Contact', + 'portal' => 'Portail élève', + 'portal_on' => 'Accès activé', + 'portal_off' => 'Accès non activé', + 'portal_login' => 'connexion avec le matricule', + 'portal_password' => 'Mot de passe (min 8)', + 'portal_reset' => 'Réinitialiser', + 'portal_activate' => 'Activer', + 'portal_deactivate' => 'Désactiver', + 'contact_account' => 'Contact & compte', + 'admin_file' => 'Dossier administratif', + 'parents_medical' => 'Parents & médical', + 'emergency_available' => "Contact d'urgence disponible", + 'docs_title' => 'Pièces du dossier', + 'doc_name_label' => 'Nom du document *', + 'doc_name_ph' => 'Ex: Extrait de naissance, Passeport...', + 'doc_file_label' => 'Fichier *', + 'adding' => 'Ajout...', + 'docs_hint' => "Stockés dans le dossier privé de l'élève. PDF, image ou Word — max 5 Mo.", + 'docs_empty' => 'Aucune pièce dans le dossier.', + 'issued_docs' => 'Documents délivrés', + 'issued_empty' => 'Aucun document délivré pour cet élève.', + 'issued_by' => 'par :name', + 'created_at' => 'Créé le :date', + 'updated_at' => 'Mis à jour le :date', + ], + + 'dialog' => [ + 'deliver_desc' => 'Générez un certificat ou une attestation pour :name.', + 'doc_type' => 'Type de document *', + 'choose_template' => 'Choisir un modèle', + 'class' => 'Classe', + 'class_ph' => 'Ex: 3ème A', + 'school_year' => 'Année scolaire', + 'generate_pdf' => 'Générer le PDF', + 'change_class_desc' => "Réaffecter :name pour l'année :year.", + 'new_class' => 'Nouvelle classe', + 'choose_class' => 'Choisir une classe', + 'reassign_hint' => 'Les frais déjà facturés ne sont pas recalculés automatiquement.', + 'reassign' => 'Réaffecter', + ], + + 'form' => [ + 'create_title' => 'Ajouter un élève', + 'create_subtitle' => 'Suivez les étapes pour ajouter un nouvel élève.', + 'edit_title' => 'Modifier un élève', + 'edit_subtitle' => 'Mettez à jour les informations via le même formulaire par étapes.', + 'edit_head' => 'Modifier élève', + 'adding' => 'Ajout...', + 'saving' => 'Enregistrement...', + 'step' => 'Étape :n', + 'select' => 'Sélectionner', + 'photo_selected' => 'Sélectionné : :name', + 'photo_hint' => 'Image (JPG, PNG…), 4 Mo max.', + 'photo_hint_keep' => ' Laissez vide pour conserver la photo actuelle.', + 'summary_hint' => "Vérifiez les informations avant d'ajouter l'élève.", + 'photo_kept' => 'Photo actuelle conservée', + 'photo_none' => 'Aucune', + + 'steps' => [ + 'student' => "Informations de l'élève", + 'admin' => 'Dossier administratif', + 'parents' => 'Parents', + 'health' => 'Santé', + 'summary' => 'Récapitulatif', + ], + + 'sec' => [ + 'identity' => "Identité de l'élève", + 'birth' => 'Naissance', + 'contact' => 'Coordonnées', + 'photo_status' => 'Photo et statut', + 'admission' => "Type d'admission", + 'birth_cert' => 'Acte de naissance', + 'father' => 'Informations du père', + 'mother' => 'Informations de la mère', + 'parents_contact' => 'Contact des parents', + 'medical' => 'Informations médicales', + 'emergency' => "Contact d'urgence", + 'allergies' => 'Allergies et sensibilités', + 'summary' => 'Récapitulatif', + ], + + 'lbl' => [ + 'matricule' => 'Matricule', + 'gender' => 'Genre *', + 'firstname' => 'Prénom *', + 'lastname' => 'Nom *', + 'birth_date' => 'Date de naissance *', + 'place_of_birth' => 'Lieu de naissance', + 'nationality' => 'Nationalité', + 'phone' => 'Téléphone', + 'email' => 'Email', + 'city' => 'Ville', + 'region' => 'Région', + 'prefecture' => 'Préfecture', + 'address' => 'Adresse', + 'profile_photo' => 'Photo de profil', + 'active' => 'Élève actif', + 'admission_type' => "Type d'admission *", + 'birth_cert_number' => 'N° acte de naissance', + 'issue_date' => 'Date de délivrance', + 'issue_place' => 'Lieu de délivrance', + 'father_firstname' => 'Prénom du père *', + 'father_lastname' => 'Nom du père *', + 'father_profession' => 'Profession du père', + 'father_phone' => 'Téléphone du père', + 'mother_firstname' => 'Prénom de la mère *', + 'mother_lastname' => 'Nom de la mère *', + 'mother_profession' => 'Profession de la mère', + 'mother_phone' => 'Téléphone de la mère', + 'parent_email' => 'Email parent/tuteur', + 'blood_group' => 'Groupe sanguin', + 'vaccinations' => 'Vaccinations', + 'emergency_contact' => "Contact d'urgence", + 'emergency_phone' => "Téléphone d'urgence", + 'allergies' => 'Allergies', + ], + + 'recap' => [ + 'identity' => 'Identité', + 'birth' => 'Naissance', + 'origin_contact' => 'Origine & contact', + 'admin' => 'Dossier administratif', + 'parents' => 'Parents', + 'health' => 'Santé', + 'photo_status' => 'Photo & statut', + 'matricule' => 'Matricule', + 'lastname' => 'Nom', + 'firstname' => 'Prénom', + 'gender' => 'Genre', + 'date' => 'Date', + 'place' => 'Lieu', + 'region' => 'Région', + 'prefecture' => 'Préfecture', + 'city' => 'Ville', + 'address' => 'Adresse', + 'nationality' => 'Nationalité', + 'phone' => 'Téléphone', + 'email' => 'E-mail', + 'admission_type' => "Type d'admission", + 'birth_cert' => 'N° extrait de naissance', + 'father' => 'Père', + 'father_phone' => 'Tél. père', + 'mother' => 'Mère', + 'mother_phone' => 'Tél. mère', + 'blood_group' => 'Groupe sanguin', + 'emergency' => "Contact d'urgence", + 'emergency_phone' => 'Tél. urgence', + 'photo' => 'Photo', + 'status' => 'Statut', + ], + ], +]; diff --git a/lang/fr/timetable.php b/lang/fr/timetable.php new file mode 100644 index 00000000..55bf2602 --- /dev/null +++ b/lang/fr/timetable.php @@ -0,0 +1,36 @@ + 'Emploi du temps', + 'title' => 'Emploi du temps', + 'subtitle' => 'Organisez les créneaux de cours par classe pour l\'année active.', + 'teacher_view' => 'Vue par enseignant', + 'choose_class' => 'Choisir une classe', + 'export_pdf' => 'Exporter en PDF', + 'select_class_prompt' => 'Sélectionnez une classe pour afficher son emploi du temps.', + 'add_slot' => 'Ajouter un créneau', + 'subject_fallback' => 'Matière', + 'edit_slot' => 'Modifier le créneau', + 'new_slot' => 'Nouveau créneau', + 'day' => 'Jour', + 'start' => 'Début', + 'end' => 'Fin', + 'subject' => 'Matière', + 'choose' => 'Choisir', + 'teacher' => 'Enseignant', + 'room' => 'Salle', + 'room_ph' => 'Ex : Salle 12', + 'cancel' => 'Annuler', + 'update' => 'Mettre à jour', + 'add' => 'Ajouter', + + 'head_teacher' => 'Emploi du temps — par enseignant', + 'subtitle_teacher' => 'Programmation d\'un enseignant pour l\'année active, toutes classes confondues.', + 'choose_teacher' => 'Choisir un enseignant', + 'select_teacher_prompt' => 'Sélectionnez un enseignant pour afficher son emploi du temps.', + 'no_slots_teacher' => 'Aucun créneau pour cet enseignant sur l\'année active.', + 'slot_one' => 'créneau', + 'slot_other' => 'créneaux', + 'class_one' => 'classe', + 'class_other' => 'classes', +]; diff --git a/lang/fr/welcome.php b/lang/fr/welcome.php new file mode 100644 index 00000000..c88a07a2 --- /dev/null +++ b/lang/fr/welcome.php @@ -0,0 +1,34 @@ + 'Dalibi — Gestion scolaire', + 'dashboard' => 'Tableau de bord', + 'logout' => 'Se déconnecter', + 'login' => 'Se connecter', + 'hello' => 'Bonjour', + 'welcome_to' => 'Bienvenue sur', + 'subtitle_authed' => 'Choisissez un module pour commencer.', + 'subtitle_guest' => 'Connectez-vous pour accéder aux modules de votre établissement.', + 'no_modules' => 'Aucun module accessible avec votre profil.', + 'open' => 'Ouvrir', + 'footer' => 'Dalibi — Logiciel de gestion scolaire open source pour l\'Afrique.', + + 'modules' => [ + 'students' => ['title' => 'Élèves', 'desc' => 'Inscriptions, dossiers et effectifs'], + 'bulletins' => ['title' => 'Notes & Bulletins', 'desc' => 'Saisie des notes et bulletins'], + 'exams' => ['title' => 'Examens', 'desc' => 'Évaluations et examens officiels'], + 'attendances' => ['title' => 'Présences', 'desc' => 'Appel et suivi de l\'assiduité'], + 'accounting' => ['title' => 'Comptabilité', 'desc' => 'Écolage, paiements et caisses'], + 'payroll' => ['title' => 'Personnel & Paie', 'desc' => 'Employés, cycles de paie et bulletins'], + 'archives' => ['title' => 'Documents & Archives', 'desc' => 'Certificats et archivage'], + 'statistics' => ['title' => 'Statistiques', 'desc' => 'Indicateurs et pilotage'], + 'calendar' => ['title' => 'Calendrier', 'desc' => 'Événements de l\'année scolaire'], + 'timetable' => ['title' => 'Emploi du temps', 'desc' => 'Grille hebdomadaire par classe'], + 'scholarships' => ['title' => 'Bourses', 'desc' => 'Bourses et aides d\'étudiants'], + 'note_reclamations' => ['title' => 'Réclamations de notes', 'desc' => 'Demandes et révisions de notes'], + 'absence_permissions' => ['title' => 'Permissions d\'absence', 'desc' => 'Demandes et justificatifs'], + 'portal' => ['title' => 'Accès portail', 'desc' => 'Comptes parents et élèves'], + 'users' => ['title' => 'Utilisateurs', 'desc' => 'Comptes et rôles du personnel'], + 'audit' => ['title' => 'Journal d\'audit', 'desc' => 'Traçabilité des actions'], + ], +]; diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 00000000..f3da84b7 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,24 @@ +includes: + - vendor/larastan/larastan/extension.neon + +parameters: + level: 5 + + paths: + - app + - routes + - database + + # Larastan a besoin de connaître le répertoire des vues pour analyser les + # vues Blade référencées depuis le code. + # viewDirectories: + # - resources/views + + # Faux positif de framework : Illuminate\Database\Seeder::$command est typé + # non-nul, mais vaut null quand run() est appelé directement (hors `db:seed`), + # ce que font les seeders invoqués programmatiquement et SeederHardeningTest. + # L'accès nullsafe `$this->command?->` est donc volontaire et correct. + ignoreErrors: + - + message: '#Using nullsafe method call on non-nullable type Illuminate\\Console\\Command\. Use -> instead\.#' + path: database/seeders/*.php diff --git a/resources/js/components/ClassroomTypes/classroom-type-form.tsx b/resources/js/components/ClassroomTypes/classroom-type-form.tsx index a17317f9..b67801d2 100644 --- a/resources/js/components/ClassroomTypes/classroom-type-form.tsx +++ b/resources/js/components/ClassroomTypes/classroom-type-form.tsx @@ -4,6 +4,7 @@ import { Checkbox } from '@/components/ui/checkbox'; import { Input } from '@/components/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Textarea } from '@/components/ui/textarea'; +import { useTranslation } from '@/helpers/i18n'; export interface ClassroomTypeFormData { name: string; @@ -31,14 +32,15 @@ export function ClassroomTypeForm({ onSubmit, setData, }: Readonly) { - let submitLabel = 'Mettre à jour'; + const { t } = useTranslation(); + let submitLabel = t('parametres.common.update'); if (processing && mode === 'create') { - submitLabel = 'Création...'; + submitLabel = t('parametres.common.creating'); } else if (processing) { - submitLabel = 'Mise à jour...'; + submitLabel = t('parametres.common.updating'); } else if (mode === 'create') { - submitLabel = 'Créer le type'; + submitLabel = t('parametres.classroom_types.submit_create'); } return ( @@ -48,25 +50,25 @@ export function ClassroomTypeForm({
-

Informations du type

+

{t('parametres.classroom_types.form_section')}

- + setData('name', event.target.value)} disabled={processing} className={errors.name ? 'border-red-500 bg-red-50/40' : 'border-gray-200 bg-white focus-visible:ring-blue-500'} - placeholder="Ex: Classe générale" + placeholder={t('parametres.classroom_types.name_ph')} /> {errors.name &&

{errors.name}

}
- +