Complete ChoreView and Settings translation for ES and Arabic

- Add all missing translation keys to ChoreView component
- Replace hardcoded strings with i18n translation calls
- Complete Spanish (es) translations for ChoreView and Settings
- Complete Arabic (ar) translations for ChoreView and Settings
- Update ProfileSettings component with full translation support
- Add translations for notifications, confirmations, and UI labels
This commit is contained in:
Mo Tarbin
2026-03-09 01:15:33 +00:00
parent 220c068822
commit e5504e0bdf
8 changed files with 361 additions and 58 deletions

View File

@@ -225,7 +225,7 @@ const ChoreView = () => {
// Show undo notification
showSuccess({
title: t('choreView.taskCompleted'),
message: 'Your task has been marked as complete',
message: t('choreView.taskCompletedMessage'),
undoAction: async () => {
try {
const undoResponse = await UndoChoreAction(choreId)
@@ -239,7 +239,7 @@ const ChoreView = () => {
}
showUndo({
title: t('choreView.undoSuccessful'),
message: 'Task completion has been undone.',
message: t('choreView.taskCompletionUndone'),
})
} else {
throw new Error('Failed to undo')
@@ -247,7 +247,7 @@ const ChoreView = () => {
} catch (error) {
showError({
title: t('choreView.undoFailed'),
message: 'Unable to undo the action. Please try again.',
message: t('choreView.undoFailedMessage'),
})
}
},
@@ -279,7 +279,7 @@ const ChoreView = () => {
}
showUndo({
title: t('choreView.undoSuccessful'),
message: 'Task skip has been undone.',
message: t('choreView.taskSkipUndone'),
})
} else {
throw new Error('Failed to undo')
@@ -287,7 +287,7 @@ const ChoreView = () => {
} catch (error) {
showError({
title: t('choreView.undoFailed'),
message: 'Unable to undo the action. Please try again.',
message: t('choreView.undoFailedMessage'),
})
}
},
@@ -324,8 +324,7 @@ const ChoreView = () => {
setTimerActionConfig({
isOpen: true,
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.',
message: t('choreView.resetTimerConfirmation'),
confirmText: t('choreView.resetTimer'),
cancelText: t('common:cancel'),
onClose: confirmed => {
@@ -349,8 +348,7 @@ const ChoreView = () => {
setTimerActionConfig({
isOpen: true,
title: t('choreView.clearAllTimeRecords'),
message:
'This will permanently delete all timers for this task and set it back to "not started".',
message: t('choreView.clearAllTimeConfirmation'),
confirmText: t('choreView.clearAllTimeRecords'),
cancelText: t('common:cancel'),
onClose: async confirmed => {
@@ -616,7 +614,7 @@ const ChoreView = () => {
variant='plain'
>
{chorePriority ? chorePriority.icon : <LowPriority />}
{chorePriority ? chorePriority.name : 'No Priority'}
{chorePriority ? chorePriority.name : t('choreView.noPriority')}
</MenuButton>
<Menu>
{Priorities.map((priority, index) => (
@@ -643,13 +641,13 @@ const ChoreView = () => {
}}
onClick={() => {
handleUpdatePriority({
name: 'No Priority',
name: t('choreView.noPriority'),
value: 0,
})
setChorePriority(null)
}}
>
No Priority
{t('choreView.noPriority')}
</MenuItem>
</Menu>
</Dropdown>
@@ -671,7 +669,7 @@ const ChoreView = () => {
}}
>
<History />
History
{t('choreView.history')}
</Button>
<Button
size='sm'
@@ -698,7 +696,7 @@ const ChoreView = () => {
{chore.description && (
<>
<Typography level='title-md' sx={{ mb: 1 }}>
Description :
{t('choreView.description')}
</Typography>
<Sheet
@@ -712,7 +710,7 @@ const ChoreView = () => {
onClick={() => {
setNoteViewerConfig({
isOpen: true,
title: 'Description',
title: t('choreView.descriptionTitle'),
content: chore.description,
onClose: () => setNoteViewerConfig({ isOpen: false }),
})
@@ -744,7 +742,7 @@ const ChoreView = () => {
{chore.notes && (
<>
<Typography level='title-md' sx={{ mb: 1 }}>
Previous note:
{t('choreView.previousNoteLabel')}
</Typography>
<Sheet
variant='plain'
@@ -757,7 +755,7 @@ const ChoreView = () => {
onClick={() => {
setNoteViewerConfig({
isOpen: true,
title: 'Previous Note',
title: t('choreView.previousNote'),
content: chore.notes,
onClose: () => setNoteViewerConfig({ isOpen: false }),
})
@@ -788,7 +786,7 @@ const ChoreView = () => {
{chore.subTasks && chore.subTasks.length > 0 && (
<Box sx={{ p: 0, m: 0, mb: 2 }}>
<Typography level='title-md' sx={{ mb: 1 }}>
Subtasks :
{t('choreView.subtasksLabel')}
</Typography>
<Sheet
variant='plain'
@@ -826,7 +824,7 @@ const ChoreView = () => {
variant='soft'
>
<Typography level='body-md' sx={{ mb: 1 }}>
Task Actions
{t('choreView.taskActions')}
</Typography>
<FormControl size='sm'>
@@ -850,7 +848,7 @@ const ChoreView = () => {
alignItems: 'center',
}}
>
Add a note
{t('choreView.addNote')}
</Typography>
}
/>
@@ -858,13 +856,13 @@ const ChoreView = () => {
{note !== null && (
<Box sx={{ mb: 1 }}>
<Typography level='body-sm' sx={{ mb: 1 }}>
Additional Notes:
{t('choreView.additionalNotes')}
</Typography>
<RichTextEditor
value={note || ''}
onChange={setNote}
entityType={'chore_completion_note'}
placeholder='Add a note about the completion...'
placeholder={t('choreView.notePlaceholder')}
/>
</Box>
)}
@@ -898,7 +896,7 @@ const ChoreView = () => {
alignItems: 'center',
}}
>
Set custom completion time
{t('choreView.setCustomCompletionTime')}
</Typography>
}
/>
@@ -932,7 +930,7 @@ const ChoreView = () => {
color='primary'
startDecorator={<Unarchive />}
>
Unarchive
{t('choreView.unarchive')}
</Button>
</Box>
) : (
@@ -970,7 +968,7 @@ const ChoreView = () => {
flex: 1,
}}
>
Approve
{t('choreView.approve')}
</Button>
<Button
fullWidth
@@ -982,7 +980,7 @@ const ChoreView = () => {
flex: 1,
}}
>
<Box>Reject</Box>
<Box>{t('choreView.reject')}</Box>
</Button>
</>
) : (
@@ -993,7 +991,7 @@ const ChoreView = () => {
color='neutral'
startDecorator={<HourglassEmpty />}
>
<Box>Pending Approval</Box>
<Box>{t('choreView.pendingApproval')}</Box>
</Button>
)
) : (
@@ -1014,7 +1012,7 @@ const ChoreView = () => {
flex: 4,
}}
>
<Box>Mark as done</Box>
<Box>{t('choreView.markAsDone')}</Box>
</Button>
<Button
@@ -1023,12 +1021,10 @@ const ChoreView = () => {
onClick={() => {
setConfirmModelConfig({
isOpen: true,
title: 'Skip Task',
message: 'Are you sure you want to skip this task?',
confirmText: 'Skip',
cancelText: 'Cancel',
title: t('choreView.skipTask'),
message: t('choreView.skipTaskConfirmation'),
confirmText: t('choreView.skip'),
cancelText: t('choreView.cancel'),
onClose: confirmed => {
if (confirmed) {
handleSkippingTask()
@@ -1046,7 +1042,7 @@ const ChoreView = () => {
flex: 1,
}}
>
<Box>Skip</Box>
<Box>{t('choreView.skip')}</Box>
</Button>
</>
)}
@@ -1090,7 +1086,7 @@ const ChoreView = () => {
flex: 1,
}}
>
Start
{t('choreView.start')}
</Button>
)}
</Box>

View File

@@ -13,6 +13,7 @@ import { useQueryClient } from '@tanstack/react-query'
import imageCompression from 'browser-image-compression'
import { useRef, useState } from 'react'
import Cropper from 'react-easy-crop'
import { useTranslation } from 'react-i18next'
import { useUserProfile } from '../../queries/UserQueries'
import { useNotification } from '../../service/NotificationProvider'
import { apiClient } from '../../utils/ApiClient'
@@ -22,6 +23,7 @@ import { getCroppedImg } from '../../utils/imageCropUtils'
import SettingsLayout from './SettingsLayout'
const ProfileSettings = () => {
const { t } = useTranslation('settings')
const queryClient = useQueryClient()
const { data: userProfile } = useUserProfile()
const { showSuccess, showError } = useNotification()
@@ -92,13 +94,13 @@ const ProfileSettings = () => {
setPhotoURL(url)
showSuccess({
title: 'Photo Updated',
message: 'Your profile photo has been updated successfully!',
title: t('profile.photoUpdated'),
message: t('profile.photoUpdatedMessage'),
})
} catch (err) {
showError({
title: 'Upload Failed',
message: 'Failed to upload your photo. Please try again.',
title: t('profile.uploadFailed'),
message: t('profile.uploadFailedMessage'),
})
} finally {
setIsUploading(false)
@@ -118,8 +120,8 @@ const ProfileSettings = () => {
if (response.ok) {
showSuccess({
title: 'Profile Updated',
message: 'Your profile information has been saved successfully!',
title: t('profile.profileUpdated'),
message: t('profile.profileUpdatedMessage'),
})
} else {
throw new Error('Failed to update profile')
@@ -128,9 +130,8 @@ const ProfileSettings = () => {
console.log(err)
showError({
title: 'Update Failed',
message:
'Unable to update your profile. Please check your connection and try again.',
title: t('profile.updateFailed'),
message: t('profile.updateFailedMessage'),
})
} finally {
setIsSaving(false)
@@ -140,10 +141,10 @@ const ProfileSettings = () => {
// Helper to resolve photoURL with baseURL if needed
return (
<SettingsLayout title='Profile Settings'>
<SettingsLayout title={t('profile.title')}>
<div className='grid gap-4 py-4' id='profile'>
<Typography level='body-md'>
Update your display name and profile photo.
{t('profile.description')}
</Typography>
<Card
sx={{
@@ -163,7 +164,7 @@ const ProfileSettings = () => {
loading={isUploading}
sx={{ mb: 1 }}
>
Change Photo
{t('profile.changePhoto')}
</Button>
<input
ref={fileInputRef}
@@ -227,7 +228,7 @@ const ProfileSettings = () => {
size='md'
sx={{ mr: 1 }}
>
Save
{t('profile.save')}
</Button>
<Button
onClick={() => {
@@ -237,24 +238,24 @@ const ProfileSettings = () => {
variant='soft'
color='neutral'
>
Cancel
{t('profile.cancel')}
</Button>
</Box>
</ModalDialog>
</Modal>
<Box sx={{ maxWidth: 400, mt: 3 }}>
<Typography level='body-sm' sx={{ mb: 0.5 }}>
Display Name
{t('profile.displayName')}
</Typography>
<Input
value={displayName}
onChange={e => setDisplayName(e.target.value)}
placeholder='Enter your display name'
placeholder={t('profile.displayNamePlaceholder')}
sx={{ mb: 2 }}
/>
<Typography level='body-sm' sx={{ mb: 0.5 }}>
Timezone
{t('profile.timezone')}
</Typography>
<Autocomplete
value={timezone}
@@ -283,7 +284,7 @@ const ProfileSettings = () => {
)
})
}}
placeholder='Select your timezone'
placeholder={t('profile.timezonePlaceholder')}
sx={{ mb: 2 }}
/>
@@ -294,7 +295,7 @@ const ProfileSettings = () => {
loading={isSaving}
sx={{ width: 120 }}
>
Save
{t('profile.save')}
</Button>
</Box>
</div>