feat: enhance localization and user experience across various components
- Added new localization keys for project management, settings, and onboarding views. - Updated labels and placeholders in project modal, password change modal, and backup/restore modal for better clarity. - Improved user feedback messages for password mismatch and backup creation failures. - Enhanced the timer details view with localized strings for active work, sessions, and distribution. - Refined the due date picker modal with localized quick date and time options. - Updated photo task modal and scan panel to include localized text for better accessibility. - Ensured consistent use of translation function for all user-facing strings.
This commit is contained in:
@@ -104,7 +104,7 @@ function BackupRestoreModal({ isOpen, onClose, showNotification }) {
|
||||
handleClose()
|
||||
} else {
|
||||
const errorData = await response.json()
|
||||
setError(errorData.message || 'Failed to create backup')
|
||||
setError(errorData.message || t('backup.createFailed'))
|
||||
}
|
||||
} catch (err) {
|
||||
setError(t('backup.createFailed'))
|
||||
@@ -156,7 +156,7 @@ function BackupRestoreModal({ isOpen, onClose, showNotification }) {
|
||||
handleClose()
|
||||
} else {
|
||||
const errorData = await response.json()
|
||||
setError(errorData.message || 'Failed to restore backup')
|
||||
setError(errorData.message || t('backup.restoreFailed'))
|
||||
}
|
||||
} catch (err) {
|
||||
setError(t('backup.restoreFailed'))
|
||||
@@ -288,7 +288,7 @@ function BackupRestoreModal({ isOpen, onClose, showNotification }) {
|
||||
size='lg'
|
||||
fullWidth={true}
|
||||
unmountDelay={250}
|
||||
title='Backup & Restore'
|
||||
title={t('backup.title')}
|
||||
closeOnBackdrop={!loading}
|
||||
closeOnEscape={!loading}
|
||||
footer={
|
||||
@@ -299,7 +299,8 @@ function BackupRestoreModal({ isOpen, onClose, showNotification }) {
|
||||
disabled: loading,
|
||||
}}
|
||||
primary={{
|
||||
label: activeTab === 0 ? 'Create Backup' : 'Restore Backup',
|
||||
label:
|
||||
activeTab === 0 ? t('backup.createTab') : t('backup.restoreTab'),
|
||||
color: activeTab === 0 ? 'primary' : 'warning',
|
||||
onClick: activeTab === 0 ? handleCreateBackup : handleRestore,
|
||||
loading,
|
||||
@@ -320,7 +321,7 @@ function BackupRestoreModal({ isOpen, onClose, showNotification }) {
|
||||
>
|
||||
<CircularProgress />
|
||||
<Typography level='body-md' sx={{ ml: 2 }}>
|
||||
{activeTab === 0 ? 'Creating backup...' : 'Restoring backup...'}
|
||||
{activeTab === 0 ? t('backup.creating') : t('backup.restoring')}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : (
|
||||
@@ -329,8 +330,8 @@ function BackupRestoreModal({ isOpen, onClose, showNotification }) {
|
||||
onChange={(event, newValue) => setActiveTab(newValue)}
|
||||
>
|
||||
<TabList>
|
||||
<Tab>Create Backup</Tab>
|
||||
<Tab>Restore Backup</Tab>
|
||||
<Tab>{t('backup.createTab')}</Tab>
|
||||
<Tab>{t('backup.restoreTab')}</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanel value={0}>{renderBackupTab()}</TabPanel>
|
||||
|
||||
@@ -18,7 +18,7 @@ function PasswordChangeModal({ isOpen, onClose }) {
|
||||
if (!passwordTouched || !confirmPasswordTouched) return
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
setPasswordError('Passwords do not match')
|
||||
setPasswordError(t('passwordChange.mismatch'))
|
||||
} else if (password.length < 8) {
|
||||
setPasswordError(t('passwordChange.minLength'))
|
||||
} else if (password.length > 64) {
|
||||
@@ -42,7 +42,7 @@ function PasswordChangeModal({ isOpen, onClose }) {
|
||||
onClose={() => handleAction(false)}
|
||||
size='sm'
|
||||
title={t('accountSettings.changePassword')}
|
||||
description='Choose a password between 8 and 64 characters.'
|
||||
description={t('passwordChange.description')}
|
||||
footer={
|
||||
<ModalActions
|
||||
secondary={{
|
||||
@@ -58,13 +58,15 @@ function PasswordChangeModal({ isOpen, onClose }) {
|
||||
}
|
||||
>
|
||||
<FormControl sx={{ mb: 2 }}>
|
||||
<Typography level='body-sm'>New password</Typography>
|
||||
<Typography level='body-sm'>
|
||||
{t('passwordChange.newPassword')}
|
||||
</Typography>
|
||||
<Input
|
||||
required
|
||||
name='password'
|
||||
type='password'
|
||||
autoComplete='new-password'
|
||||
placeholder='Enter password'
|
||||
placeholder={t('passwordChange.newPasswordPlaceholder')}
|
||||
value={password}
|
||||
onChange={event => {
|
||||
setPasswordTouched(true)
|
||||
@@ -74,13 +76,15 @@ function PasswordChangeModal({ isOpen, onClose }) {
|
||||
</FormControl>
|
||||
|
||||
<FormControl error={Boolean(passwordError)}>
|
||||
<Typography level='body-sm'>Confirm password</Typography>
|
||||
<Typography level='body-sm'>
|
||||
{t('passwordChange.confirmPassword')}
|
||||
</Typography>
|
||||
<Input
|
||||
required
|
||||
name='confirmPassword'
|
||||
type='password'
|
||||
autoComplete='new-password'
|
||||
placeholder='Repeat password'
|
||||
placeholder={t('passwordChange.confirmPasswordPlaceholder')}
|
||||
value={confirmPassword}
|
||||
onChange={event => {
|
||||
setConfirmPasswordTouched(true)
|
||||
|
||||
@@ -151,7 +151,7 @@ const ProjectModal = ({ isOpen, onClose, onSave, project }) => {
|
||||
<Stack spacing={3}>
|
||||
{/* Project Name */}
|
||||
<FormControl required>
|
||||
<FormLabel>Project Name</FormLabel>
|
||||
<FormLabel>{t('modal.nameLabel')}</FormLabel>
|
||||
<Input
|
||||
value={projectName}
|
||||
onChange={e => setProjectName(e.target.value)}
|
||||
@@ -163,7 +163,7 @@ const ProjectModal = ({ isOpen, onClose, onSave, project }) => {
|
||||
|
||||
{/* Project Description */}
|
||||
<FormControl>
|
||||
<FormLabel>Description</FormLabel>
|
||||
<FormLabel>{t('modal.descriptionLabel')}</FormLabel>
|
||||
<Textarea
|
||||
value={projectDescription}
|
||||
onChange={e => setProjectDescription(e.target.value)}
|
||||
@@ -176,7 +176,7 @@ const ProjectModal = ({ isOpen, onClose, onSave, project }) => {
|
||||
|
||||
{/* Icon Selection */}
|
||||
<FormControl>
|
||||
<FormLabel>Project Icon</FormLabel>
|
||||
<FormLabel>{t('modal.iconLabel')}</FormLabel>
|
||||
<Button
|
||||
variant='outlined'
|
||||
onClick={() => setIsIconPickerOpen(true)}
|
||||
@@ -212,19 +212,20 @@ const ProjectModal = ({ isOpen, onClose, onSave, project }) => {
|
||||
}
|
||||
sx={{ justifyContent: 'flex-start' }}
|
||||
>
|
||||
{PROJECT_ICONS.find(icon => icon.value === projectIcon)?.name ||
|
||||
'Select Icon'}
|
||||
{PROJECT_ICONS.some(icon => icon.value === projectIcon)
|
||||
? t(`icons.${projectIcon}`)
|
||||
: t('modal.selectIcon')}
|
||||
</Button>
|
||||
</FormControl>
|
||||
|
||||
{/* Color Selection */}
|
||||
<FormControl>
|
||||
<FormLabel>Project Color</FormLabel>
|
||||
<FormLabel>{t('modal.colorLabel')}</FormLabel>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
||||
{PROJECT_COLORS.map(colorOption => (
|
||||
<Box
|
||||
key={colorOption.value}
|
||||
title={colorOption.name}
|
||||
title={t(`common:colors.${colorOption.name}`)}
|
||||
onClick={() => setProjectColor(colorOption.value)}
|
||||
sx={{
|
||||
width: 26,
|
||||
|
||||
@@ -17,6 +17,10 @@ import ModalActions from '../../../components/common/ModalActions'
|
||||
import { useResponsiveModal } from '../../../hooks/useResponsiveModal'
|
||||
import { CheckUserDeletion, DeleteUser } from '../../../utils/Fetcher'
|
||||
|
||||
// Sent verbatim to the delete endpoint, so it stays English in every locale —
|
||||
// the label around it is what gets translated.
|
||||
const CONFIRMATION_WORD = 'DELETE'
|
||||
|
||||
function UserDeletionModal({ isOpen, onClose }) {
|
||||
const { t } = useTranslation('settings')
|
||||
const { ResponsiveModal } = useResponsiveModal()
|
||||
@@ -99,7 +103,7 @@ function UserDeletionModal({ isOpen, onClose }) {
|
||||
}
|
||||
|
||||
const executeUserDeletion = async () => {
|
||||
if (password.trim() === '' || confirmation !== 'DELETE') {
|
||||
if (password.trim() === '' || confirmation !== CONFIRMATION_WORD) {
|
||||
setError(t('deletion.passwordAndDelete'))
|
||||
return
|
||||
}
|
||||
@@ -153,33 +157,27 @@ function UserDeletionModal({ isOpen, onClose }) {
|
||||
const renderWarningStep = () => (
|
||||
<>
|
||||
<Typography level='body-md' mb={2}>
|
||||
<strong>This action cannot be undone.</strong> Deleting your account
|
||||
will permanently remove:
|
||||
<strong>{t('deletion.warningLead')}</strong>{' '}
|
||||
{t('deletion.warningIntro')}
|
||||
</Typography>
|
||||
|
||||
<Box mb={3}>
|
||||
<Typography level='body-sm' mb={1}>
|
||||
• Your user profile and authentication data
|
||||
</Typography>
|
||||
<Typography level='body-sm' mb={1}>
|
||||
• All your chores, chore history, and time tracking sessions
|
||||
</Typography>
|
||||
<Typography level='body-sm' mb={1}>
|
||||
• API tokens, MFA sessions, and password reset tokens
|
||||
</Typography>
|
||||
<Typography level='body-sm' mb={1}>
|
||||
• Storage files and usage data
|
||||
</Typography>
|
||||
<Typography level='body-sm' mb={1}>
|
||||
• Points history and notifications
|
||||
</Typography>
|
||||
<Typography level='body-sm' mb={1}>
|
||||
• Circle memberships and relationships
|
||||
</Typography>
|
||||
{[
|
||||
'removesProfile',
|
||||
'removesChores',
|
||||
'removesTokens',
|
||||
'removesStorage',
|
||||
'removesPoints',
|
||||
'removesCircles',
|
||||
].map(key => (
|
||||
<Typography key={key} level='body-sm' mb={1}>
|
||||
• {t(`deletion.${key}`)}
|
||||
</Typography>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
<FormControl sx={{ mb: 2 }}>
|
||||
<FormLabel>Enter your password to continue</FormLabel>
|
||||
<FormLabel>{t('deletion.passwordToContinue')}</FormLabel>
|
||||
<Input
|
||||
type='password'
|
||||
value={password}
|
||||
@@ -200,16 +198,15 @@ function UserDeletionModal({ isOpen, onClose }) {
|
||||
<>
|
||||
<Typography level='body-md' mb={3}>
|
||||
{t('deletion.transferIntro')}
|
||||
select new owners:
|
||||
</Typography>
|
||||
|
||||
{circlesRequiringTransfer.map(circle => (
|
||||
<Card key={circle.id} sx={{ mb: 2, p: 2 }}>
|
||||
<Typography level='title-sm' mb={1}>
|
||||
Circle: {circle.name}
|
||||
{t('deletion.circleLabel', { name: circle.name })}
|
||||
</Typography>
|
||||
<FormControl>
|
||||
<FormLabel>New Owner</FormLabel>
|
||||
<FormLabel>{t('deletion.newOwner')}</FormLabel>
|
||||
<Select
|
||||
placeholder={t('deletion.selectOwner')}
|
||||
value={
|
||||
@@ -243,12 +240,11 @@ function UserDeletionModal({ isOpen, onClose }) {
|
||||
{t('deletion.confirmPrompt')}
|
||||
</Typography>
|
||||
<Typography level='body-sm' mb={2}>
|
||||
on successful deletion, you will be logged out and redirected to the
|
||||
login page.
|
||||
{t('deletion.logoutNotice')}
|
||||
</Typography>
|
||||
|
||||
<FormControl sx={{ mb: 2 }}>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormLabel>{t('deletion.passwordLabel')}</FormLabel>
|
||||
<Input
|
||||
type='password'
|
||||
value={password}
|
||||
@@ -258,7 +254,9 @@ function UserDeletionModal({ isOpen, onClose }) {
|
||||
</FormControl>
|
||||
|
||||
<FormControl sx={{ mb: 3 }}>
|
||||
<FormLabel>Type "DELETE" to confirm</FormLabel>
|
||||
<FormLabel>
|
||||
{t('deletion.typeToConfirm', { word: CONFIRMATION_WORD })}
|
||||
</FormLabel>
|
||||
<Input
|
||||
value={confirmation}
|
||||
onChange={e => setConfirmation(e.target.value)}
|
||||
@@ -323,7 +321,7 @@ function UserDeletionModal({ isOpen, onClose }) {
|
||||
? !password
|
||||
: step === 2
|
||||
? circlesRequiringTransfer.length !== transferOptions.length
|
||||
: !password || confirmation !== 'DELETE',
|
||||
: !password || confirmation !== CONFIRMATION_WORD,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user