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')} 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')}
) : ( - 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 }) : ''}.

@@ -131,26 +133,26 @@ export default function Index({ documents, tags, categories, classrooms, filters
- + - setSearch(e.target.value)} onKeyDown={e => e.key === 'Enter' && apply()} placeholder="Titre, référence…" className="pl-9 bg-white" /> + setSearch(e.target.value)} onKeyDown={e => e.key === 'Enter' && apply()} placeholder={t('archives.search_ph')} className="pl-9 bg-white" />
- +
- + setDateFrom(e.target.value)} onBlur={() => apply()} className="bg-white" />
- + setDateTo(e.target.value)} onBlur={() => apply()} className="bg-white" />
@@ -169,7 +171,7 @@ export default function Index({ documents, tags, categories, classrooms, filters })} {activeFilters > 0 && ( )}
@@ -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 && (
- - Page {documents.current_page} / {documents.last_page} - + + {t('archives.page_of', { current: documents.current_page, last: documents.last_page })} +
)} @@ -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')}
- + setData('title', e.target.value)} className={errors.title ? 'border-red-400' : ''} /> {errors.title &&

{errors.title}

}
- +
- + setData('retention_until', e.target.value)} />
- +