From 1214c173897d6915d470940c8e3534ee227afeb7 Mon Sep 17 00:00:00 2001 From: Mo Tarbin Date: Sun, 8 Mar 2026 16:51:18 +0000 Subject: [PATCH] feat: Add RTL drawer support, translate ChoreView, and apply date format preferences Navigation: - Fix drawer to slide from right in RTL languages (Arabic, Hebrew) - Add anchor prop based on isRTL flag - Drawer now properly positioned for RTL languages ChoreView Translations: - Add comprehensive Arabic and Spanish translations for ChoreView - Translate info cards (Assignment, Schedule, Statistics, Details) - Translate notification messages (Task Completed, Undo, etc.) - Translate action buttons and confirmation modals - Translate timer-related modals (Reset Timer, Clear All Time Records) Date Format Application: - Replace hardcoded moment date formats with formatDate() - Apply user's selected date format preference in ChoreView - Due date chip now respects user's date format setting - Dates display consistently according to user preference All UI text in ChoreView now translates to Arabic and Spanish. Navigation drawer slides from correct side for RTL languages. --- public/locales/ar/chores.json | 43 ++++++++++++++++++++- public/locales/es/chores.json | 43 ++++++++++++++++++++- src/views/ChoreEdit/ChoreView.jsx | 62 ++++++++++++++++--------------- src/views/components/NavBar.jsx | 5 ++- 4 files changed, 121 insertions(+), 32 deletions(-) diff --git a/public/locales/ar/chores.json b/public/locales/ar/chores.json index 23abebb..496e424 100644 --- a/public/locales/ar/chores.json +++ b/public/locales/ar/chores.json @@ -10,5 +10,46 @@ "assignedTo": "مُسند إلى", "priority": "الأولوية", "status": "الحالة", - "description": "الوصف" + "description": "الوصف", + "choreView": { + "assignment": "التعيين", + "assigned": "المعين", + "last": "الأخير", + "schedule": "الجدول", + "due": "الاستحقاق", + "statistics": "الإحصائيات", + "completed": "مكتمل", + "times": "مرات", + "details": "التفاصيل", + "createdBy": "أنشئ بواسطة", + "na": "غير متوفر", + "taskCompleted": "المهمة مكتملة", + "undoSuccessful": "التراجع ناجح", + "undoFailed": "فشل التراجع", + "resetTimer": "إعادة تعيين المؤقت", + "clearAllTimeRecords": "مسح جميع سجلات الوقت", + "descriptionTitle": "الوصف", + "previousNote": "الملاحظة السابقة", + "skipTask": "تخطي المهمة", + "markComplete": "وضع علامة مكتمل", + "edit": "تعديل", + "archive": "أرشفة", + "unarchive": "إلغاء الأرشفة", + "viewHistory": "عرض السجل", + "startTimer": "بدء المؤقت", + "pauseTimer": "إيقاف المؤقت مؤقتاً", + "approve": "موافقة", + "reject": "رفض", + "undo": "تراجع", + "skip": "تخطي", + "addNote": "أضف ملاحظة...", + "subtasks": "المهام الفرعية", + "noDescription": "لا يوجد وصف متاح", + "timer": { + "active": "المؤقت نشط", + "paused": "المؤقت متوقف مؤقتاً", + "reset": "إعادة تعيين المؤقت", + "delete": "حذف الجلسة" + } + } } diff --git a/public/locales/es/chores.json b/public/locales/es/chores.json index 74100f1..b9fc278 100644 --- a/public/locales/es/chores.json +++ b/public/locales/es/chores.json @@ -10,5 +10,46 @@ "assignedTo": "Asignado a", "priority": "Prioridad", "status": "Estado", - "description": "Descripción" + "description": "Descripción", + "choreView": { + "assignment": "Asignación", + "assigned": "Asignado", + "last": "Último", + "schedule": "Horario", + "due": "Vencimiento", + "statistics": "Estadísticas", + "completed": "Completado", + "times": "veces", + "details": "Detalles", + "createdBy": "Creado por", + "na": "N/D", + "taskCompleted": "Tarea Completada", + "undoSuccessful": "Deshacer Exitoso", + "undoFailed": "Deshacer Fallido", + "resetTimer": "Reiniciar Temporizador", + "clearAllTimeRecords": "Borrar Todos los Registros de Tiempo", + "descriptionTitle": "Descripción", + "previousNote": "Nota Anterior", + "skipTask": "Saltar Tarea", + "markComplete": "Marcar como Completada", + "edit": "Editar", + "archive": "Archivar", + "unarchive": "Desarchivar", + "viewHistory": "Ver Historial", + "startTimer": "Iniciar Temporizador", + "pauseTimer": "Pausar Temporizador", + "approve": "Aprobar", + "reject": "Rechazar", + "undo": "Deshacer", + "skip": "Saltar", + "addNote": "Agregar una nota...", + "subtasks": "Subtareas", + "noDescription": "No hay descripción disponible", + "timer": { + "active": "Temporizador Activo", + "paused": "Temporizador Pausado", + "reset": "Reiniciar Temporizador", + "delete": "Eliminar Sesión" + } + } } diff --git a/src/views/ChoreEdit/ChoreView.jsx b/src/views/ChoreEdit/ChoreView.jsx index 51f8748..f9e6c77 100644 --- a/src/views/ChoreEdit/ChoreView.jsx +++ b/src/views/ChoreEdit/ChoreView.jsx @@ -39,9 +39,11 @@ import { Divider } from '@mui/material' import { useQueryClient } from '@tanstack/react-query' import moment from 'moment' import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' import { useNavigate, useParams, useSearchParams } from 'react-router-dom' import { useImpersonateUser } from '../../contexts/ImpersonateUserContext.jsx' +import { useLocalization } from '../../contexts/LocalizationContext' import { useChoreDetails } from '../../queries/ChoreQueries.jsx' import { useChoreTimer, @@ -75,6 +77,8 @@ import TimePassedCard from './TimePassedCard.jsx' import TimerSplitButton from './TimerSplitButton.jsx' const ChoreView = () => { + const { t } = useTranslation('chores') + const { formatDate } = useLocalization() const [chore, setChore] = useState({}) const navigate = useNavigate() const [performers, setPerformers] = useState([]) @@ -143,12 +147,12 @@ const ChoreView = () => { { size: 6, icon: , - title: 'Assignment', - text: `Assigned: ${ + title: t('choreView.assignment'), + text: `${t('choreView.assigned')}: ${ performers.find(p => p.userId === chore.assignedTo)?.displayName || - 'N/A' + t('choreView.na') }`, - subtext: ` Last: ${ + subtext: ` ${t('choreView.last')}: ${ chore.lastCompletedDate ? performers.find(p => p.userId === chore.lastCompletedBy) ?.displayName @@ -158,29 +162,29 @@ const ChoreView = () => { { size: 6, icon: , - title: 'Schedule', - text: `Due: ${ - chore.nextDueDate ? moment(chore.nextDueDate).fromNow() : 'N/A' + title: t('choreView.schedule'), + text: `${t('choreView.due')}: ${ + chore.nextDueDate ? moment(chore.nextDueDate).fromNow() : t('choreView.na') }`, - subtext: `Last: ${ + subtext: `${t('choreView.last')}: ${ chore.lastCompletedDate ? moment(chore.lastCompletedDate).fromNow() - : 'N/A' + : t('choreView.na') }`, }, { size: 6, icon: , - title: 'Statistics', - text: `Completed: ${chore.totalCompletedCount || 0} times`, + title: t('choreView.statistics'), + text: `${t('choreView.completed')}: ${chore.totalCompletedCount || 0} ${t('choreView.times')}`, }, { size: 6, icon: , - title: 'Details', - subtext: `Created By: ${ + title: t('choreView.details'), + subtext: `${t('choreView.createdBy')}: ${ performers.find(p => p.userId === chore.createdBy)?.displayName || - 'N/A' + t('choreView.na') }`, }, ] @@ -220,7 +224,7 @@ const ChoreView = () => { .then(() => { // Show undo notification showSuccess({ - title: 'Task Completed', + title: t('choreView.taskCompleted'), message: 'Your task has been marked as complete', undoAction: async () => { try { @@ -234,7 +238,7 @@ const ChoreView = () => { queryClient.invalidateQueries(['chores']) } showUndo({ - title: 'Undo Successful', + title: t('choreView.undoSuccessful'), message: 'Task completion has been undone.', }) } else { @@ -242,7 +246,7 @@ const ChoreView = () => { } } catch (error) { showError({ - title: 'Undo Failed', + title: t('choreView.undoFailed'), message: 'Unable to undo the action. Please try again.', }) } @@ -261,7 +265,7 @@ const ChoreView = () => { // Show undo notification showSuccess({ - message: 'Task skipped', + message: t('choreView.skipTask'), undoAction: async () => { try { const undoResponse = await UndoChoreAction(choreId) @@ -274,7 +278,7 @@ const ChoreView = () => { queryClient.invalidateQueries(['chores']) } showUndo({ - title: 'Undo Successful', + title: t('choreView.undoSuccessful'), message: 'Task skip has been undone.', }) } else { @@ -282,7 +286,7 @@ const ChoreView = () => { } } catch (error) { showError({ - title: 'Undo Failed', + title: t('choreView.undoFailed'), message: 'Unable to undo the action. Please try again.', }) } @@ -319,11 +323,11 @@ const ChoreView = () => { const handleResetTimer = () => { setTimerActionConfig({ isOpen: true, - title: 'Reset Timer', + title: t('choreView.resetTimer'), message: 'Are you sure you want to reset the timer? This will clear all time records since you started the task.', - confirmText: 'Reset Timer', - cancelText: 'Cancel', + confirmText: t('choreView.resetTimer'), + cancelText: t('common:cancel'), onClose: confirmed => { if (confirmed) { resetChoreTimer.mutate(choreId, { @@ -344,11 +348,11 @@ const ChoreView = () => { const handleClearAllTime = () => { setTimerActionConfig({ isOpen: true, - title: 'Clear All Time Records', + title: t('choreView.clearAllTimeRecords'), message: 'This will permanently delete all timers for this task and set it back to "not started".', - confirmText: 'Clear All Time', - cancelText: 'Cancel', + confirmText: t('choreView.clearAllTimeRecords'), + cancelText: t('common:cancel'), onClose: async confirmed => { if (confirmed) { if (choreTimer?.res?.id) { @@ -468,13 +472,13 @@ const ChoreView = () => { color='warning' sx={{ mb: 1 }} > - Archived + {t('choreView.archive')} )} } size='md' sx={{ mb: 1 }}> {chore.nextDueDate - ? `Due at ${moment(chore.nextDueDate).format('MM/DD/YYYY hh:mm A')}` - : 'N/A'} + ? `${t('choreView.due')} ${formatDate(chore.nextDueDate, true)}` + : t('choreView.na')} { const { t } = useTranslation('common') + const { isRTL } = useLocalization() const { data: resource } = useResource() const navigate = useNavigate() @@ -202,13 +204,14 @@ const NavBar = () => {