From c0270705febe1ca3c0bf3f493103d15a5a13f96c Mon Sep 17 00:00:00 2001
From: horacioskrp
Date: Wed, 11 Nov 2026 15:53:08 +0000
Subject: [PATCH 1/3] =?UTF-8?q?feat(i18n):=20divers=20=E2=80=94=20page=20d?=
=?UTF-8?q?'accueil=20&=20pages=20d'erreur=20(FR/EN)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
lang/en/errors.php | 36 ++++++++++++++++++
lang/en/welcome.php | 34 +++++++++++++++++
lang/fr/errors.php | 36 ++++++++++++++++++
lang/fr/welcome.php | 34 +++++++++++++++++
resources/js/pages/Error.tsx | 22 +++++------
resources/js/pages/welcome.tsx | 67 +++++++++++++++++-----------------
6 files changed, 183 insertions(+), 46 deletions(-)
create mode 100644 lang/en/errors.php
create mode 100644 lang/en/welcome.php
create mode 100644 lang/fr/errors.php
create mode 100644 lang/fr/welcome.php
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/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/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/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/resources/js/pages/Error.tsx b/resources/js/pages/Error.tsx
index 49473245..bfa9d574 100644
--- a/resources/js/pages/Error.tsx
+++ b/resources/js/pages/Error.tsx
@@ -2,27 +2,23 @@ import { Head, Link, usePage } from '@inertiajs/react';
import { ArrowLeft, LayoutGrid } from 'lucide-react';
import AppLogo from '@/components/app-logo';
import { ErrorArt } from '@/components/error-art';
+import { useTranslation } from '@/helpers/i18n';
import { dashboard } from '@/routes';
interface AuthProps {
user: { id: string } | null;
}
-const INFO: Record = {
- 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.' },
-};
+const KNOWN_STATUSES = [403, 404, 419, 429, 500, 503];
export default function ErrorPage({ status }: Readonly<{ status: number }>) {
+ const { t } = useTranslation();
const auth = (usePage().props as { auth?: AuthProps }).auth;
const isAuthed = Boolean(auth?.user);
- const info = INFO[status] ?? {
- title: 'Une erreur est survenue',
- message: "Quelque chose s'est mal passé. Veuillez réessayer.",
+ const group = KNOWN_STATUSES.includes(status) ? String(status) : 'fallback';
+ const info = {
+ title: t(`errors.${group}.title`),
+ message: t(`errors.${group}.message`),
};
return (
@@ -53,14 +49,14 @@ export default function ErrorPage({ status }: Readonly<{ status: number }>) {
href={isAuthed ? dashboard() : '/'}
className="inline-flex items-center justify-center gap-2 rounded-lg bg-blue-600 px-5 py-2.5 text-sm font-medium text-white transition hover:bg-blue-700"
>
- {isAuthed ? 'Tableau de bord' : "Retour à l'accueil"}
+ {isAuthed ? t('errors.dashboard') : t('errors.home')}
window.history.back()}
className="inline-flex items-center justify-center gap-2 rounded-lg border border-gray-200 px-5 py-2.5 text-sm font-medium text-gray-700 transition hover:bg-gray-100 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-800"
>
- Page précédente
+ {t('errors.previous')}
diff --git a/resources/js/pages/welcome.tsx b/resources/js/pages/welcome.tsx
index 57a4ec3d..7cd8135e 100644
--- a/resources/js/pages/welcome.tsx
+++ b/resources/js/pages/welcome.tsx
@@ -7,6 +7,7 @@ import {
import AppLogo from '@/components/app-logo';
import { dashboard, login, logout } from '@/routes';
import { route } from '@/helpers/route';
+import { useTranslation } from '@/helpers/i18n';
interface AuthProps {
user: { id: string; name?: string } | null;
@@ -14,8 +15,7 @@ interface AuthProps {
}
interface ModuleCard {
- title: string;
- description: string;
+ key: string;
icon: typeof Users;
href: string;
permission: string;
@@ -23,25 +23,26 @@ interface ModuleCard {
}
const modules: ModuleCard[] = [
- { title: 'Élèves', description: 'Inscriptions, dossiers et effectifs', icon: Users, href: route('students.index'), permission: 'view_students', color: 'from-blue-500 to-blue-600' },
- { title: 'Notes & Bulletins', description: 'Saisie des notes et bulletins', icon: NotebookPen, href: route('bulletins.index'), permission: 'view_bulletins', color: 'from-violet-500 to-purple-600' },
- { title: 'Examens', description: 'Évaluations et examens officiels', icon: GraduationCap, href: route('evaluations.index'), permission: 'view_evaluations', color: 'from-indigo-500 to-blue-600' },
- { title: 'Présences', description: "Appel et suivi de l'assiduité", icon: UserCheck, href: route('attendances.index'), permission: 'view_attendances', color: 'from-emerald-500 to-teal-600' },
- { title: 'Comptabilité', description: 'Écolage, paiements et caisses', icon: Wallet, href: route('accounting.index'), permission: 'view_finances', color: 'from-amber-500 to-orange-600' },
- { title: 'Personnel & Paie', description: 'Employés, cycles de paie et bulletins', icon: Briefcase, href: route('payroll.overview'), permission: 'view_payroll', color: 'from-cyan-500 to-sky-600' },
- { title: 'Documents & Archives', description: 'Certificats et archivage', icon: Archive, href: route('archives.index'), permission: 'view_archives', color: 'from-rose-500 to-pink-600' },
- { title: 'Statistiques', description: 'Indicateurs et pilotage', icon: BarChart3, href: route('statistics.index'), permission: 'view_statistics', color: 'from-cyan-500 to-blue-600' },
- { title: 'Calendrier', description: "Événements de l'année scolaire", icon: Calendar, href: route('calendar.index'), permission: 'view_calendar', color: 'from-fuchsia-500 to-purple-600' },
- { title: 'Emploi du temps', description: 'Grille hebdomadaire par classe', icon: CalendarRange, href: route('timetable.index'), permission: 'view_timetable', color: 'from-sky-500 to-cyan-600' },
- { title: 'Bourses', description: "Bourses et aides d'étudiants", icon: Percent, href: route('student-scholarships.index'), permission: 'view_student_scholarships', color: 'from-lime-500 to-emerald-600' },
- { title: 'Réclamations de notes', description: 'Demandes et révisions de notes', icon: AlertCircle, href: route('note-reclamations.index'), permission: 'view_note_reclamations', color: 'from-orange-500 to-red-500' },
- { title: "Permissions d'absence", description: 'Demandes et justificatifs', icon: ShieldCheck, href: route('absence-permissions.index'), permission: 'view_absence_permissions', color: 'from-teal-500 to-emerald-600' },
- { title: 'Accès portail', description: 'Comptes parents et élèves', icon: Lock, href: route('guardians.index'), permission: 'view_portal_accounts', color: 'from-slate-500 to-slate-700' },
- { title: 'Utilisateurs', description: 'Comptes et rôles du personnel', icon: Shield, href: route('users.index'), permission: 'view_users', color: 'from-indigo-500 to-violet-600' },
- { title: "Journal d'audit", description: 'Traçabilité des actions', icon: ScanLine, href: route('audit-logs.index'), permission: 'view_audit_logs', color: 'from-gray-500 to-gray-700' },
+ { key: 'students', icon: Users, href: route('students.index'), permission: 'view_students', color: 'from-blue-500 to-blue-600' },
+ { key: 'bulletins', icon: NotebookPen, href: route('bulletins.index'), permission: 'view_bulletins', color: 'from-violet-500 to-purple-600' },
+ { key: 'exams', icon: GraduationCap, href: route('evaluations.index'), permission: 'view_evaluations', color: 'from-indigo-500 to-blue-600' },
+ { key: 'attendances', icon: UserCheck, href: route('attendances.index'), permission: 'view_attendances', color: 'from-emerald-500 to-teal-600' },
+ { key: 'accounting', icon: Wallet, href: route('accounting.index'), permission: 'view_finances', color: 'from-amber-500 to-orange-600' },
+ { key: 'payroll', icon: Briefcase, href: route('payroll.overview'), permission: 'view_payroll', color: 'from-cyan-500 to-sky-600' },
+ { key: 'archives', icon: Archive, href: route('archives.index'), permission: 'view_archives', color: 'from-rose-500 to-pink-600' },
+ { key: 'statistics', icon: BarChart3, href: route('statistics.index'), permission: 'view_statistics', color: 'from-cyan-500 to-blue-600' },
+ { key: 'calendar', icon: Calendar, href: route('calendar.index'), permission: 'view_calendar', color: 'from-fuchsia-500 to-purple-600' },
+ { key: 'timetable', icon: CalendarRange, href: route('timetable.index'), permission: 'view_timetable', color: 'from-sky-500 to-cyan-600' },
+ { key: 'scholarships', icon: Percent, href: route('student-scholarships.index'), permission: 'view_student_scholarships', color: 'from-lime-500 to-emerald-600' },
+ { key: 'note_reclamations', icon: AlertCircle, href: route('note-reclamations.index'), permission: 'view_note_reclamations', color: 'from-orange-500 to-red-500' },
+ { key: 'absence_permissions', icon: ShieldCheck, href: route('absence-permissions.index'), permission: 'view_absence_permissions', color: 'from-teal-500 to-emerald-600' },
+ { key: 'portal', icon: Lock, href: route('guardians.index'), permission: 'view_portal_accounts', color: 'from-slate-500 to-slate-700' },
+ { key: 'users', icon: Shield, href: route('users.index'), permission: 'view_users', color: 'from-indigo-500 to-violet-600' },
+ { key: 'audit', icon: ScanLine, href: route('audit-logs.index'), permission: 'view_audit_logs', color: 'from-gray-500 to-gray-700' },
];
export default function Welcome() {
+ const { t } = useTranslation();
const auth = (usePage().props as { auth?: AuthProps }).auth;
const isAuthed = Boolean(auth?.user);
const permissions = auth?.permissions ?? [];
@@ -52,7 +53,7 @@ export default function Welcome() {
return (
<>
-
+
{/* Barre supérieure */}
@@ -65,13 +66,13 @@ export default function Welcome() {
{isAuthed ? (
- Tableau de bord
+ {t('welcome.dashboard')}
router.post(logout.url())}
- title="Se déconnecter"
- aria-label="Se déconnecter"
+ title={t('welcome.logout')}
+ aria-label={t('welcome.logout')}
className="inline-flex h-10 w-10 items-center justify-center rounded-lg border border-gray-200 text-gray-500 transition hover:bg-red-50 hover:text-red-600 dark:border-gray-700 dark:hover:bg-red-950/40"
>
@@ -79,7 +80,7 @@ export default function Welcome() {
) : (
-
Se connecter
+
{t('welcome.login')}
)}
@@ -90,38 +91,38 @@ export default function Welcome() {
{isAuthed ? (
- <>Bonjour{auth?.user?.name ? `, ${auth.user.name}` : ''} 👋>
+ <>{t('welcome.hello')}{auth?.user?.name ? `, ${auth.user.name}` : ''} 👋>
) : (
- <>Bienvenue sur Dalibi >
+ <>{t('welcome.welcome_to')} Dalibi >
)}
{isAuthed
- ? 'Choisissez un module pour commencer.'
- : 'Connectez-vous pour accéder aux modules de votre établissement.'}
+ ? t('welcome.subtitle_authed')
+ : t('welcome.subtitle_guest')}
{/* Cartes des modules */}
{visibleModules.length === 0 ? (
- Aucun module accessible avec votre profil.
+ {t('welcome.no_modules')}
) : (
{visibleModules.map((m) => (
-
{m.title}
-
{m.description}
+
{t(`welcome.modules.${m.key}.title`)}
+
{t(`welcome.modules.${m.key}.desc`)}
- {isAuthed ? 'Ouvrir' : 'Se connecter'}
+ {isAuthed ? t('welcome.open') : t('welcome.login')}
))}
@@ -130,7 +131,7 @@ export default function Welcome() {
- © {new Date().getFullYear()} Dalibi — Logiciel de gestion scolaire open source pour l'Afrique.
+ © {new Date().getFullYear()} {t('welcome.footer')}
>
From 8df12f9f16bdac2e3c892a66a6ae4294b4ae263f Mon Sep 17 00:00:00 2001
From: horacioskrp
Date: Wed, 11 Nov 2026 15:58:29 +0000
Subject: [PATCH 2/3] =?UTF-8?q?feat(i18n):=20archives=20documentaires=20?=
=?UTF-8?q?=E2=80=94=20liste=20&=20tags=20(FR/EN)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
lang/en/archives.php | 89 +++++++++++++++++++
lang/fr/archives.php | 89 +++++++++++++++++++
resources/js/pages/Archives/Index.tsx | 118 +++++++++++++-------------
resources/js/pages/Archives/Tags.tsx | 74 ++++++++--------
4 files changed, 276 insertions(+), 94 deletions(-)
create mode 100644 lang/en/archives.php
create mode 100644 lang/fr/archives.php
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/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/resources/js/pages/Archives/Index.tsx b/resources/js/pages/Archives/Index.tsx
index 1bd43597..68b18e7a 100644
--- a/resources/js/pages/Archives/Index.tsx
+++ b/resources/js/pages/Archives/Index.tsx
@@ -15,6 +15,7 @@ import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { route } from '@/helpers/route';
+import { useTranslation } from '@/helpers/i18n';
import AppLayout from '@/layouts/app-layout';
interface Tag { id: string; name: string; color: string; }
@@ -39,6 +40,7 @@ const ALL = 'all';
const fmtSize = (b: number) => b > 1048576 ? `${(b / 1048576).toFixed(1)} Mo` : `${Math.max(1, Math.round(b / 1024))} Ko`;
export default function Index({ documents, tags, categories, classrooms, filters, stats }: Readonly) {
+ const { t } = useTranslation();
const [search, setSearch] = useState(filters.search || '');
const [category, setCategory] = useState(filters.category || ALL);
const [selectedTags, setSelectedTags] = useState(filters.tags || []);
@@ -55,7 +57,7 @@ export default function Index({ documents, tags, categories, classrooms, filters
};
const toggleTag = (id: string) => {
- const next = selectedTags.includes(id) ? selectedTags.filter(t => t !== id) : [...selectedTags, id];
+ const next = selectedTags.includes(id) ? selectedTags.filter(x => x !== id) : [...selectedTags, id];
setSelectedTags(next);
apply({ tags: next });
};
@@ -74,7 +76,7 @@ export default function Index({ documents, tags, categories, classrooms, filters
setEditing(d);
setData({
title: d.title, description: d.description ?? '', category: d.category,
- file: null as File | null, tags: d.tags.map(t => t.name),
+ file: null as File | null, tags: d.tags.map(x => x.name),
retention_until: d.retention_until ?? '',
link_type: '', link_classroom_id: '', link_student_matricule: '',
});
@@ -94,7 +96,7 @@ export default function Index({ documents, tags, categories, classrooms, filters
if (n && !data.tags.includes(n)) setData('tags', [...data.tags, n]);
setTagInput('');
};
- const removeTag = (name: string) => setData('tags', data.tags.filter(t => t !== name));
+ const removeTag = (name: string) => setData('tags', data.tags.filter(x => x !== name));
const submit = (e: React.FormEvent) => {
e.preventDefault();
@@ -106,23 +108,23 @@ export default function Index({ documents, tags, categories, classrooms, filters
return (
-
+
- Archives documentaires
+ {t('archives.title')}
-
{stats.total} document(s) archivé(s){stats.trashed > 0 ? ` · ${stats.trashed} en corbeille` : ''}.
+
{t('archives.subtitle_docs', { count: stats.total })}{stats.trashed > 0 ? t('archives.trashed_suffix', { count: stats.trashed }) : ''}.
apply({ trashed: filters.trashed ? 0 : 1 })}
className={filters.trashed ? 'border-amber-300 text-amber-600' : ''}>
- {filters.trashed ? 'Voir actifs' : 'Corbeille'}
+ {filters.trashed ? t('archives.view_active') : t('archives.trash')}
- Archiver un document
+ {t('archives.archive_doc')}
@@ -131,26 +133,26 @@ export default function Index({ documents, tags, categories, classrooms, filters
@@ -169,7 +171,7 @@ export default function Index({ documents, tags, categories, classrooms, filters
})}
{activeFilters > 0 && (
- Réinitialiser ({activeFilters})
+ {t('archives.reset')} ({activeFilters})
)}
@@ -181,20 +183,20 @@ export default function Index({ documents, tags, categories, classrooms, filters
- Référence
- Document
- Tags
- Lié à
- Taille
- Archivé
- Actions
+ {t('archives.th_reference')}
+ {t('archives.th_document')}
+ {t('archives.th_tags')}
+ {t('archives.th_linked')}
+ {t('archives.th_size')}
+ {t('archives.th_archived')}
+ {t('archives.th_actions')}
{documents.data.length === 0 ? (
- {filters.trashed ? 'Corbeille vide.' : 'Aucun document archivé.'}
+ {filters.trashed ? t('archives.empty_trash') : t('archives.empty')}
) : documents.data.map(d => (
@@ -217,16 +219,16 @@ export default function Index({ documents, tags, categories, classrooms, filters
{filters.trashed ? (
<>
-
} className="gap-1 text-xs" onClick={() => router.post(route('archives.restore', d.id), {}, { preserveScroll: true })} />
-
} className="border-red-200 text-red-500 hover:bg-red-50" onClick={() => setDeleteDoc(d)} />
+
} className="gap-1 text-xs" onClick={() => router.post(route('archives.restore', d.id), {}, { preserveScroll: true })} />
+
} className="border-red-200 text-red-500 hover:bg-red-50" onClick={() => setDeleteDoc(d)} />
>
) : (
<>
- } className="text-xs" />
+ } className="text-xs" />
-
} onClick={() => openEdit(d)} />
-
} className="border-red-200 text-red-500 hover:bg-red-50" onClick={() => router.delete(route('archives.destroy', d.id), { preserveScroll: true })} />
+
} onClick={() => openEdit(d)} />
+
} className="border-red-200 text-red-500 hover:bg-red-50" onClick={() => router.delete(route('archives.destroy', d.id), { preserveScroll: true })} />
>
)}
@@ -239,9 +241,9 @@ export default function Index({ documents, tags, categories, classrooms, filters
{documents.last_page > 1 && (
- apply({ page: documents.current_page - 1 })}>Précédent
- Page {documents.current_page} / {documents.last_page}
- = documents.last_page} onClick={() => apply({ page: documents.current_page + 1 })}>Suivant
+ apply({ page: documents.current_page - 1 })}>{t('archives.prev')}
+ {t('archives.page_of', { current: documents.current_page, last: documents.last_page })}
+ = documents.last_page} onClick={() => apply({ page: documents.current_page + 1 })}>{t('archives.next')}
)}
@@ -250,38 +252,38 @@ export default function Index({ documents, tags, categories, classrooms, filters
- {editing ? 'Modifier le document' : 'Archiver un document'}
+ {editing ? t('archives.edit_title') : t('archives.create_title')}
- {editing ? 'Mettez à jour les métadonnées et les tags.' : 'Ajoutez un fichier au coffre documentaire avec ses métadonnées et tags.'}
+ {editing ? t('archives.edit_desc') : t('archives.create_desc')}