i18n: extract the remaining task, history, filter and timer screens

Part of #145.

Sixteen files that were left out of my earlier PRs because my branch also
carried unrelated changes in them. Those are stripped here: each file is
your current `develop` version with the string extraction applied on top,
nothing else.

Covered: the chore action hook and its toasts, activities and smart-insight
cards, the chore toolbar, chore history and its card, saved filters, the
timer details view, project and label modals, the notification picker,
the pending badge, the sync status indicator, the SSE settings and hook,
and the profile avatar menu.

All namespaces already exist, so `src/i18n/config.js` is untouched.
Keys added: 83 `chores`, 31 `common`, 10 `timer`, 9 `history`, 6 `labels`,
5 `projects`, 2 `filters`, 1 `settings`.

English only — no translations, no behaviour change. Every t() value is
checked to appear character-for-character in the code it replaces, or to
match the value already in your dictionary for the same key: 221 call
sites, no mismatches.

Five files from the same batch are deliberately left out. They build
translated labels in module-level constant tables, where the hook cannot
be called — `FilterBar`, `RepeatSection`, `RepeatPickerField`,
`FilterBuilderContent` and `AdvancedOptionsSection`. Those need the key
to travel as data and be resolved inside the component, which is a design
change rather than an extraction, so it deserves its own PR.
This commit is contained in:
everysingletear
2026-08-14 11:26:24 +08:00
parent 677376983b
commit ede1a11099
25 changed files with 496 additions and 247 deletions

View File

@@ -8,8 +8,10 @@ import { useNotification } from '../../../service/NotificationProvider.jsx'
import LABEL_COLORS from '../../../utils/Colors.jsx'
import { CreateLabel, UpdateLabel } from '../../../utils/Fetcher'
import { useLabels } from '../../Labels/LabelQueries'
import { useTranslation } from 'react-i18next'
function LabelModal({ isOpen, onClose, label }) {
const { t } = useTranslation('labels')
const { ResponsiveModal } = useResponsiveModal()
const [labelName, setLabelName] = useState('')
@@ -34,7 +36,7 @@ function LabelModal({ isOpen, onClose, label }) {
// Validation logic
const validateLabel = () => {
if (!labelName.trim()) {
setError('Name cannot be empty')
setError(t('modal.errorEmptyName'))
return false
}
if (
@@ -42,11 +44,11 @@ function LabelModal({ isOpen, onClose, label }) {
userLabel => userLabel.name === labelName && userLabel.id !== label?.id,
)
) {
setError('Label with this name already exists')
setError(t('modal.errorDuplicate'))
return false
}
if (!color) {
setError('Please select a color')
setError(t('modal.errorNoColor'))
return false
}
return true
@@ -71,13 +73,13 @@ function LabelModal({ isOpen, onClose, label }) {
.catch(err => {
if (err.queued) {
showError({
title: 'Failed to save label',
message: 'Unable to save label. Please try again.',
title: t('modal.saveFailedTitle'),
message: t('modal.saveFailedMessage'),
})
} else {
showError({
title: 'Failed to save label',
message: 'Unable to save label. Please try again.',
title: t('modal.saveFailedTitle'),
message: t('modal.saveFailedMessage'),
})
}
})
@@ -92,7 +94,7 @@ function LabelModal({ isOpen, onClose, label }) {
title={label ? 'Edit Label' : 'Add Label'}
footer={
<ModalActions
secondary={{ label: 'Cancel', onClick: onClose }}
secondary={{ label: t('common:cancel'), onClick: onClose }}
primary={{
label: label ? 'Save Changes' : 'Add Label',
onClick: handleSave,
@@ -103,7 +105,7 @@ function LabelModal({ isOpen, onClose, label }) {
<Box>
<FormControl>
<Typography gutterBottom level='body-sm' alignSelf='start'>
Name
{t('modal.name')}
</Typography>
<Input
fullWidth

View File

@@ -21,8 +21,10 @@ import {
useUpdateProject,
} from '../../Projects/ProjectQueries'
import IconPickerModal from './IconPickerModal'
import { useTranslation } from 'react-i18next'
const ProjectModal = ({ isOpen, onClose, onSave, project }) => {
const { t } = useTranslation('projects')
const { ResponsiveModal } = useResponsiveModal()
const [projectName, setProjectName] = useState('')
const [projectDescription, setProjectDescription] = useState('')
@@ -58,7 +60,7 @@ const ProjectModal = ({ isOpen, onClose, onSave, project }) => {
e.preventDefault()
if (!projectName.trim()) {
setError('Project name is required')
setError(t('modal.errorNameRequired'))
return
}
@@ -82,7 +84,7 @@ const ProjectModal = ({ isOpen, onClose, onSave, project }) => {
},
onError: error => {
console.error('Error updating project:', error)
setError('Failed to update project')
setError(t('modal.errorUpdate'))
},
},
)
@@ -95,7 +97,7 @@ const ProjectModal = ({ isOpen, onClose, onSave, project }) => {
},
onError: error => {
console.error('Error creating project:', error)
setError('Failed to create project')
setError(t('modal.errorCreate'))
},
})
}
@@ -130,7 +132,7 @@ const ProjectModal = ({ isOpen, onClose, onSave, project }) => {
footer={
<ModalActions
secondary={{
label: 'Cancel',
label: t('common:cancel'),
onClick: handleClose,
disabled: isSubmitting,
}}
@@ -152,7 +154,7 @@ const ProjectModal = ({ isOpen, onClose, onSave, project }) => {
<Input
value={projectName}
onChange={e => setProjectName(e.target.value)}
placeholder='Enter project name...'
placeholder={t('modal.namePlaceholder')}
autoFocus
disabled={isSubmitting}
/>
@@ -164,7 +166,7 @@ const ProjectModal = ({ isOpen, onClose, onSave, project }) => {
<Textarea
value={projectDescription}
onChange={e => setProjectDescription(e.target.value)}
placeholder='Optional project description...'
placeholder={t('modal.descriptionPlaceholder')}
minRows={2}
maxRows={4}
disabled={isSubmitting}