From 72aa57f0189c18fd750ae1b5f60d82d72b999d8a Mon Sep 17 00:00:00 2001 From: everysingletear Date: Thu, 13 Aug 2026 10:21:57 +0800 Subject: [PATCH] i18n: extract things, history, projects, labels, filters, timer and points MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of #145. Fourteen files across seven feature areas that had no namespace yet: the things create/edit modals and their history, the chore history detail and edit modals, the activity feed, the points view and its redemption modal, the project view with its selector and icon picker, the label view, the advanced filter builder and the timer edit modal. Seven new namespaces registered in `src/i18n/config.js` in one change rather than one per PR, so the `ns:` array is touched once and my other extraction PRs cannot conflict with this one. Namespaces stay feature-scoped as described in #145; if you'd rather fold any of these into `common` or `chores`, say which and I'll rework it. Dictionaries: `history` 57 keys, `points` 50, `timer` 25, `projects` 16, `things` 14, `filters` 13, `labels` 5. English only — no translations, no behaviour change. Every t() value is checked against this branch's base: the string must appear character-for-character in the code it replaces (226 call sites). Three values in `UserPoints` are matched loosely and worth naming. The base builds the leaderboard heading and subtitle around a ternary — `{mode === 'points' ? 'Points' : 'Tasks'} Leaderboard` and `Rankings based on {…} during the selected time period` — so neither full sentence exists contiguously in the source. Each key holds exactly what one branch renders. The sentences are kept whole rather than split around the ternary, since a sentence assembled from fragments cannot be reordered by a translator. --- public/locales/en/chores.json | 6 + public/locales/en/filters.json | 17 +++ public/locales/en/history.json | 89 +++++++++++++ public/locales/en/labels.json | 9 ++ public/locales/en/points.json | 74 +++++++++++ public/locales/en/projects.json | 24 ++++ public/locales/en/things.json | 18 +++ public/locales/en/timer.json | 29 +++++ src/i18n/config.js | 13 +- src/views/Labels/LabelView.jsx | 28 ++-- src/views/Modals/EditHistoryModal.jsx | 26 ++-- src/views/Modals/HistoryDetailModal.jsx | 42 +++--- .../Modals/Inputs/AdvancedFilterBuilder.jsx | 28 ++-- src/views/Modals/Inputs/CreateThingModal.jsx | 24 ++-- src/views/Modals/Inputs/EditThingState.jsx | 14 +- src/views/Modals/Inputs/IconPickerModal.jsx | 10 +- src/views/Modals/Inputs/TimerEditModal.jsx | 66 +++++----- src/views/Modals/RedeemPointsModal.jsx | 26 ++-- src/views/Projects/ProjectView.jsx | 34 ++--- src/views/Things/ThingsHistory.jsx | 10 +- src/views/User/UserActivities.jsx | 114 ++++++++-------- src/views/User/UserPoints.jsx | 122 +++++++++--------- src/views/components/ProjectSelector.jsx | 14 +- 23 files changed, 579 insertions(+), 258 deletions(-) create mode 100644 public/locales/en/filters.json create mode 100644 public/locales/en/history.json create mode 100644 public/locales/en/labels.json create mode 100644 public/locales/en/points.json create mode 100644 public/locales/en/projects.json create mode 100644 public/locales/en/things.json create mode 100644 public/locales/en/timer.json diff --git a/public/locales/en/chores.json b/public/locales/en/chores.json index ec82037..50b151f 100644 --- a/public/locales/en/chores.json +++ b/public/locales/en/chores.json @@ -71,5 +71,11 @@ "reset": "Reset Timer", "delete": "Delete Session" } + }, + "toolbar": { + "defaultProject": "Default Project" + }, + "sort": { + "assignedToMe": "Assigned to me" } } diff --git a/public/locales/en/filters.json b/public/locales/en/filters.json new file mode 100644 index 0000000..6b10047 --- /dev/null +++ b/public/locales/en/filters.json @@ -0,0 +1,17 @@ +{ + "builder": { + "errorName": "Please enter a filter name", + "errorConditions": "Please configure at least one filter condition", + "editTitle": "Edit Filter", + "newTitle": "New Filter", + "addConditions": "Add conditions to preview", + "save": "Save Filter", + "name": "Filter Name", + "namePlaceholder": "e.g. Overdue tasks for Alice", + "color": "Color" + }, + "nameExists": "A filter with this name already exists", + "tasks_one": "{{count}} task", + "tasks_other": "{{count}} tasks", + "overdue": "{{count}} overdue" +} diff --git a/public/locales/en/history.json b/public/locales/en/history.json new file mode 100644 index 0000000..1452849 --- /dev/null +++ b/public/locales/en/history.json @@ -0,0 +1,89 @@ +{ + "edit": { + "title": "Edit History", + "completedDate": "Completed Date", + "note": "Note", + "additionalNotes": "Additional Notes", + "deleteTitle": "Delete History", + "deleteMessage": "Are you sure you want to delete this history?" + }, + "status": { + "inProgress": "In Progress", + "completed": "Completed", + "skipped": "Skipped", + "pendingApproval": "Pending Approval", + "rejected": "Rejected", + "missed": "Missed", + "rescheduled": "Rescheduled", + "scheduled": "Scheduled" + }, + "badge": { + "onTime": "On Time", + "onTimeLabel": "On time" + }, + "detail": { + "title": "Activity Detail", + "openTask": "Open Task", + "editEntry": "Edit Entry", + "performedBy": "Performed by", + "assignedTo": "Assigned to", + "lastUpdated": "Last updated", + "duration": "Duration", + "pointsEarned": "Points earned", + "points_one": "{{count}} pt", + "points_other": "{{count}} pts", + "note": "Note" + }, + "charts": { + "noData": "No data available", + "noLabels": "No Labels", + "unassigned": "Unassigned", + "unknownTask": "Unknown Task", + "late": "Late", + "status": { + "title": "Status", + "description": "Completed tasks status" + }, + "due": { + "title": "Due Date", + "description": "Current tasks due date" + }, + "priority": { + "title": "Priority", + "description": "Tasks by priority" + }, + "labels": { + "title": "Labels", + "description": "Tasks by labels" + }, + "labelsDuration": { + "title": "Labels (time)", + "description": "Time spent by labels (hours)" + }, + "tasksTime": { + "title": "Tasks (time)", + "description": "Time spent by individual tasks (hours)" + }, + "assigneeBreakdown": { + "title": "by Assignee", + "description": "Tasks grouped by assignee" + } + }, + "filter": { + "status": "Status", + "pending": "Pending", + "labels": "Labels", + "hasNotes": "Has Notes", + "hasPoints": "Has Points", + "timePeriod": "Time Period", + "user": "User" + }, + "period": { + "days_one": "{{count}} Day", + "days_other": "{{count}} Days", + "allTime": "All Time" + }, + "activities": { + "title": "Activities" + } +} diff --git a/public/locales/en/labels.json b/public/locales/en/labels.json new file mode 100644 index 0000000..93f96cb --- /dev/null +++ b/public/locales/en/labels.json @@ -0,0 +1,9 @@ +{ + "shared": "Shared", + "delete": { + "title": "Delete Label", + "message": "Are you sure you want to delete this label? This will remove the label from all tasks." + }, + "loadError": "Failed to load labels. Please try again.", + "blurb": "Manage your labels and organize your tasks effectively. Labels will be automatically shared with your circle if they are used on a shared task." +} diff --git a/public/locales/en/points.json b/public/locales/en/points.json new file mode 100644 index 0000000..a6ccd40 --- /dev/null +++ b/public/locales/en/points.json @@ -0,0 +1,74 @@ +{ + "redeem": "Redeem Points", + "redeemModal": { + "redeemButton": "Redeem", + "pointsAvailable": "{{count}} points available", + "pointsToRedeem": "Points to Redeem", + "placeholder": "Enter points...", + "cannotExceed": "Cannot exceed available points", + "quickAdd": "Quick Add:", + "aboutToRedeem": "You are about to redeem", + "amount": "{{count}} points", + "remaining": "Remaining: {{count}} points" + }, + "leaderboard": { + "titlePoints": "Points Leaderboard", + "titleTasks": "Tasks Leaderboard", + "subtitlePoints": "Rankings based on points earned during the selected time period", + "subtitleTasks": "Rankings based on tasks completed during the selected time period", + "modePoints": "Points", + "modeTasks": "Tasks", + "you": "You", + "tasksAndAvg": "{{tasks}} tasks • {{avg}} avg per task", + "available": "{{count}} available", + "points_one": "{{count}} point", + "points_other": "{{count}} points" + }, + "tabs": { + "short7d": "7D", + "short6m": "6M", + "shortAll": "All", + "days7": "7 Days", + "months6": "6 Months", + "allTime": "All Time" + }, + "filter": { + "analysisTitle": "Filter & Analysis", + "title": "Filter Points", + "showFor": "Show points for:", + "timePeriod": "Time period:" + }, + "cards": { + "available": { + "title": "Available", + "subtext": "Ready to redeem" + }, + "redeemed": { + "title": "Redeemed", + "subtext": "Previously used" + }, + "total": { + "title": "Total", + "subtext": "All time earned" + }, + "period": { + "title": "Period Points", + "allTime": "All time", + "last6Months": "Last 6 months", + "lastDays_one": "Last {{count}} day", + "lastDays_other": "Last {{count}} days" + } + }, + "summary": { + "showingFor": "Showing points for", + "unknownUser": "Unknown User", + "overThe": "over the", + "allTime": "All Time", + "last6Months": "Last 6 Months", + "lastDays_one": "Last {{count}} Day", + "lastDays_other": "Last {{count}} Days" + }, + "trend": { + "title": "Points Trend" + } +} diff --git a/public/locales/en/projects.json b/public/locales/en/projects.json new file mode 100644 index 0000000..0e7eed4 --- /dev/null +++ b/public/locales/en/projects.json @@ -0,0 +1,24 @@ +{ + "iconPicker": { + "chooseIcon": "Choose Project Icon", + "availableIcons": "Available Icons" + }, + "defaultChip": "Default", + "tasks_one": "{{count}} task", + "tasks_other": "{{count}} tasks", + "shared": "Shared", + "delete": { + "title": "Delete Project", + "message": "Are you sure you want to delete \"{{name}}\"? This will remove the project but keep all tasks (they'll move to the Default Project)." + }, + "loadError": "Failed to load projects. Please try again.", + "blurb": "Organize your tasks into projects. Create custom workspaces to keep your tasks organized and easily accessible.", + "defaultDescription": "All tasks without a specific project", + "selector": { + "title": "Projects", + "createNew": "Create New Project", + "createNewDescription": "Add a custom project workspace", + "manage": "Manage Projects", + "manageDescription": "View, edit, and organize all projects" + } +} diff --git a/public/locales/en/things.json b/public/locales/en/things.json new file mode 100644 index 0000000..1181eb5 --- /dev/null +++ b/public/locales/en/things.json @@ -0,0 +1,18 @@ +{ + "errName": "Name is required", + "errStateNumber": "State must be a number", + "errStateBool": "State must be true or false", + "errStateRequired": "State is required", + "name": "Name", + "namePlaceholder": "Thing name", + "value": "Value", + "valuePlaceholder": "Thing value", + "updateState": "Update state", + "update": "Update", + "history": { + "overview": "Things Overview", + "visualization": "Data Visualization", + "changeHistory": "Change History", + "updated": "Updated" + } +} diff --git a/public/locales/en/timer.json b/public/locales/en/timer.json new file mode 100644 index 0000000..848fd53 --- /dev/null +++ b/public/locales/en/timer.json @@ -0,0 +1,29 @@ +{ + "toast": { + "sessionUpdatedTitle": "Session updated", + "sessionUpdatedMessage": "Timer session has been updated successfully.", + "sessionUpdateFailTitle": "Failed to update session", + "tryAgain": "Please try again.", + "sessionUpdateErrorTitle": "Error updating session", + "sessionDeletedTitle": "Session deleted", + "sessionDeletedMessage": "Timer session has been deleted successfully.", + "sessionDeleteErrorTitle": "Error deleting session", + "deleteConfirmTitle": "Delete Timer Session", + "deleteConfirmMessage": "Are you sure you want to delete this timer session?" + }, + "loading": "Loading timer data...", + "noData": "No timer data found for this chore.", + "activeWork": "Active Work", + "breakTime": "Break Time", + "workSessions": "Work Sessions", + "totalTime": "Total Time", + "distribution": "Work vs Break Distribution", + "noActiveTime": "No active time yet", + "sessionBreakdown": "Session Breakdown", + "sessions": "Sessions", + "addSession": "Add Session", + "startTime": "Start Time", + "endTime": "End Time", + "leaveEmpty": "Leave empty if session is ongoing", + "noSessionForChore": "No timer session found for this chore." +} diff --git a/src/i18n/config.js b/src/i18n/config.js index 698fc83..5e30e52 100644 --- a/src/i18n/config.js +++ b/src/i18n/config.js @@ -22,7 +22,18 @@ i18n loadPath: '/locales/{{lng}}/{{ns}}.json', }, - ns: ['common', 'settings', 'chores'], + ns: [ + 'common', + 'settings', + 'chores', + 'labels', + 'history', + 'filters', + 'things', + 'projects', + 'timer', + 'points', + ], defaultNS: 'common', detection: { diff --git a/src/views/Labels/LabelView.jsx b/src/views/Labels/LabelView.jsx index 1f05020..841215e 100644 --- a/src/views/Labels/LabelView.jsx +++ b/src/views/Labels/LabelView.jsx @@ -11,6 +11,7 @@ import { Typography, } from '@mui/joy' import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' import LabelModal from '../Modals/Inputs/LabelModal' import { @@ -32,6 +33,7 @@ import ConfirmationModal from '../Modals/Inputs/ConfirmationModal' import { useLabels } from './LabelQueries' const LabelCardContent = ({ label, currentUserId, onToggleActions }) => { + const { t } = useTranslation('labels') // Check if current user owns this label const isOwnedByCurrentUser = label.created_by === currentUserId @@ -124,7 +126,7 @@ const LabelCardContent = ({ label, currentUserId, onToggleActions }) => { fontWeight: 'md', }} > - Shared + {t('shared')} )} @@ -149,6 +151,7 @@ const LabelCardContent = ({ label, currentUserId, onToggleActions }) => { } const LabelView = () => { + const { t } = useTranslation('labels') const { data: labels, isLabelsLoading, isError } = useLabels() const { data: userProfile } = useUserProfile() @@ -173,14 +176,11 @@ const LabelView = () => { const handleDeleteClicked = id => { setConfirmationModel({ isOpen: true, - title: 'Delete Label', - - message: - 'Are you sure you want to delete this label? This will remove the label from all tasks.', - - confirmText: 'Delete', + title: t('delete.title'), + message: t('delete.message'), + confirmText: t('common:delete'), color: 'danger', - cancelText: 'Cancel', + cancelText: t('common:cancel'), onClose: confirmed => { if (confirmed === true) { handleDeleteLabel(id) @@ -230,7 +230,7 @@ const LabelView = () => { if (isError) { return ( - Failed to load labels. Please try again. + {t('loadError')} ) } @@ -244,12 +244,10 @@ const LabelView = () => { level='h3' sx={{ fontWeight: 'lg', color: 'text.primary' }} > - Labels + {t('common:navigation.labels')} - Manage your labels and organize your tasks effectively. Labels will - be automatically shared with your circle if they are used on a - shared task. + {t('blurb')} @@ -300,7 +298,7 @@ const LabelView = () => { > - Edit + {t('common:edit')} @@ -319,7 +317,7 @@ const LabelView = () => { > - Delete + {t('common:delete')} diff --git a/src/views/Modals/EditHistoryModal.jsx b/src/views/Modals/EditHistoryModal.jsx index 6321ea6..381828c 100644 --- a/src/views/Modals/EditHistoryModal.jsx +++ b/src/views/Modals/EditHistoryModal.jsx @@ -1,12 +1,14 @@ import { FormLabel, Input } from '@mui/joy' import moment from 'moment' import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' import ModalActions from '../../components/common/ModalActions' import { useResponsiveModal } from '../../hooks/useResponsiveModal' import ConfirmationModal from './Inputs/ConfirmationModal' function EditHistoryModal({ config, historyRecord }) { + const { t } = useTranslation('history') const { ResponsiveModal } = useResponsiveModal() const [completedDate, setCompletedDate] = useState('') @@ -40,12 +42,12 @@ function EditHistoryModal({ config, historyRecord }) { onClose={config?.onClose} size='lg' // fullWidth={true} - title='Edit History' + title={t('edit.title')} footer={ config.onSave({ id: historyRecord.id, @@ -57,7 +59,7 @@ function EditHistoryModal({ config, historyRecord }) { /> } > - Due Date + {t('chores:dueDate')} - Completed Date + {t('edit.completedDate')} - Note + {t('edit.note')} { if (e.target.value.trim() === '') { @@ -104,10 +106,10 @@ function EditHistoryModal({ config, historyRecord }) { } setIsDeleteModalOpen(false) }, - title: 'Delete History', - message: 'Are you sure you want to delete this history?', - confirmText: 'Delete', - cancelText: 'Cancel', + title: t('edit.deleteTitle'), + message: t('edit.deleteMessage'), + confirmText: t('common:delete'), + cancelText: t('common:cancel'), color: 'danger', }} /> diff --git a/src/views/Modals/HistoryDetailModal.jsx b/src/views/Modals/HistoryDetailModal.jsx index 23400c6..2744314 100644 --- a/src/views/Modals/HistoryDetailModal.jsx +++ b/src/views/Modals/HistoryDetailModal.jsx @@ -14,6 +14,7 @@ import { } from '@mui/icons-material' import { Avatar, Box, Button, Chip, Divider, Stack, Typography } from '@mui/joy' import moment from 'moment' +import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' import ModalActions from '../../components/common/ModalActions' import { useLocalization } from '../../contexts/LocalizationContext' @@ -22,13 +23,13 @@ import { TASK_COLOR } from '../../utils/Colors.jsx' import RichTextEditor from '../components/RichTextEditor.jsx' const STATUS_CONFIG = { - 0: { label: 'In Progress', color: 'primary', icon: }, - 1: { label: 'Completed', color: 'success', icon: }, - 2: { label: 'Skipped', color: 'warning', icon: }, - 3: { label: 'Pending Approval', color: 'neutral', icon: }, - 4: { label: 'Rejected', color: 'danger', icon: }, - 5: { label: 'Missed', color: 'danger', icon: }, - 6: { label: 'Rescheduled', color: 'warning', icon: }, + 0: { label: t('status.inProgress'), color: 'primary', icon: }, + 1: { label: t('status.completed'), color: 'success', icon: }, + 2: { label: t('status.skipped'), color: 'warning', icon: }, + 3: { label: t('status.pendingApproval'), color: 'neutral', icon: }, + 4: { label: t('status.rejected'), color: 'danger', icon: }, + 5: { label: t('status.missed'), color: 'danger', icon: }, + 6: { label: t('status.rescheduled'), color: 'warning', icon: }, } const DetailRow = ({ icon, label, value, children }) => ( @@ -55,6 +56,8 @@ const DetailRow = ({ icon, label, value, children }) => ( ) const TimingBadge = ({ historyEntry }) => { + const { t } = useTranslation('history') + if (!historyEntry.dueDate || !historyEntry.performedAt) return null if ([0, 5, 6].includes(historyEntry.status)) return null @@ -71,7 +74,7 @@ const TimingBadge = ({ historyEntry }) => { sx={{ backgroundColor: TASK_COLOR.COMPLETED, color: 'white' }} startDecorator={} > - On Time + {t('badge.onTime')} ) } else if (performedAt.isBefore(dueDate)) { @@ -103,6 +106,7 @@ const TimingBadge = ({ historyEntry }) => { } function HistoryDetailModal({ config }) { + const { t } = useTranslation('history') const { ResponsiveModal } = useResponsiveModal() const { fmt } = useLocalization() const navigate = useNavigate() @@ -114,7 +118,9 @@ function HistoryDetailModal({ config }) { const statusCfg = STATUS_CONFIG[entry.status] ?? STATUS_CONFIG[1] const isFirstSchedule = entry.status === 6 && !entry.dueDate - const statusLabel = isFirstSchedule ? 'Scheduled' : statusCfg.label + const statusLabel = isFirstSchedule + ? t('status.scheduled') + : t(statusCfg.labelKey) const performer = performers.find(p => p.userId === entry.completedBy) const assignedTo = performers.find(p => p.userId === entry.assignedTo) const isDifferentAssignee = @@ -138,7 +144,7 @@ function HistoryDetailModal({ config }) { {entry.choreId && ( @@ -151,7 +157,7 @@ function HistoryDetailModal({ config }) { navigate(`/chores/${entry.choreId}`) }} > - Open Task + {t('detail.openTask')} )} {config?.onEdit && ( @@ -159,7 +165,7 @@ function HistoryDetailModal({ config }) { startDecorator={} onClick={() => config.onEdit(entry)} > - Edit Entry + {t('detail.editEntry')} )} @@ -196,7 +202,7 @@ function HistoryDetailModal({ config }) { {performer && ( } - label='Performed by' + label={t('detail.performedBy')} > } - label='Assigned to' + label={t('detail.assignedTo')} value={assignedTo.displayName} /> )} @@ -259,7 +265,7 @@ function HistoryDetailModal({ config }) { {showUpdatedAt && ( } - label='Last updated' + label={t('detail.lastUpdated')} value={fmt.dateTime(entry.updatedAt)} /> )} @@ -268,7 +274,7 @@ function HistoryDetailModal({ config }) { {entry.duration > 0 && ( } - label='Duration' + label={t('detail.duration')} value={formatDuration(entry.duration)} /> )} @@ -277,8 +283,8 @@ function HistoryDetailModal({ config }) { {entry.points > 0 && ( ★} - label='Points earned' - value={`${entry.points} pt${entry.points > 1 ? 's' : ''}`} + label={t('detail.pointsEarned')} + value={t('detail.points', { count: entry.points })} /> )} diff --git a/src/views/Modals/Inputs/AdvancedFilterBuilder.jsx b/src/views/Modals/Inputs/AdvancedFilterBuilder.jsx index 431ac71..8fa8e16 100644 --- a/src/views/Modals/Inputs/AdvancedFilterBuilder.jsx +++ b/src/views/Modals/Inputs/AdvancedFilterBuilder.jsx @@ -11,6 +11,7 @@ import FilterBuilderContent, { import { FILTER_COLORS } from '../../../utils/Colors' import { applyFilter } from '../../../utils/FilterEngine' import { useFilters } from '../../Filters/FilterQueries' +import { useTranslation } from 'react-i18next' const EMPTY_FILTERS = [] @@ -25,6 +26,7 @@ const AdvancedFilterBuilder = ({ userProfile = null, editingFilter = null, }) => { + const { t } = useTranslation('filters') const [filterName, setFilterName] = useState('') const [filterColor, setFilterColor] = useState(FILTER_COLORS[0].value) const [selections, setSelections] = useState(defaultSelections()) @@ -76,15 +78,15 @@ const AdvancedFilterBuilder = ({ const handleSave = () => { if (!filterName.trim()) { - setError('Please enter a filter name') + setError(t('builder.errorName')) return } if (filterNameExists(filterName.trim(), editingFilter?.id)) { - setError('A filter with this name already exists') + setError(t('nameExists')) return } if (conditions.length === 0) { - setError('Please configure at least one filter condition') + setError(t('builder.errorConditions')) return } onSave({ @@ -106,7 +108,7 @@ const AdvancedFilterBuilder = ({ maxHeight='92vh' title={ - {editingFilter ? 'Edit Filter' : 'New Filter'} + {editingFilter ? t('builder.editTitle') : t('builder.newTitle')} {activeConditionCount > 0 && ( {activeConditionCount} condition @@ -129,17 +131,17 @@ const AdvancedFilterBuilder = ({ {conditions.length > 0 ? ( <> - {previewCount} task{previewCount !== 1 ? 's' : ''} + {t('tasks', { count: previewCount })} {previewOverdueCount > 0 && ( - {previewOverdueCount} overdue + {t('overdue', { count: previewOverdueCount })} )} ) : ( - Add conditions to preview + {t('builder.addConditions')} )} @@ -147,7 +149,7 @@ const AdvancedFilterBuilder = ({ {/* Actions */} @@ -168,10 +170,10 @@ const AdvancedFilterBuilder = ({ level='body-xs' sx={{ mb: 0.75, color: 'text.secondary', fontWeight: 600 }} > - Filter Name + {t('builder.name')} { setFilterName(e.target.value) @@ -193,13 +195,13 @@ const AdvancedFilterBuilder = ({ level='body-xs' sx={{ mb: 0.75, color: 'text.secondary', fontWeight: 600 }} > - Color + {t('builder.color')} {FILTER_COLORS.map(c => ( setFilterColor(c.value)} sx={{ width: 26, diff --git a/src/views/Modals/Inputs/CreateThingModal.jsx b/src/views/Modals/Inputs/CreateThingModal.jsx index 9c2f367..ab2cb6f 100644 --- a/src/views/Modals/Inputs/CreateThingModal.jsx +++ b/src/views/Modals/Inputs/CreateThingModal.jsx @@ -10,8 +10,10 @@ import { import { useEffect, useState } from 'react' import ModalActions from '../../../components/common/ModalActions' import { useResponsiveModal } from '../../../hooks/useResponsiveModal' +import { useTranslation } from 'react-i18next' function CreateThingModal({ isOpen, onClose, onSave, currentThing }) { + const { t } = useTranslation('things') const { ResponsiveModal } = useResponsiveModal() const [name, setName] = useState(currentThing?.name || '') @@ -33,17 +35,17 @@ function CreateThingModal({ isOpen, onClose, onSave, currentThing }) { const isValid = () => { const newErrors = {} if (!name || name.trim() === '') { - newErrors.name = 'Name is required' + newErrors.name = t('errName') } if (type === 'number' && isNaN(state)) { - newErrors.state = 'State must be a number' + newErrors.state = t('errStateNumber') } if (type === 'boolean' && !['true', 'false'].includes(state)) { - newErrors.state = 'State must be true or false' + newErrors.state = t('errStateBool') } if ((type === 'text' && !state) || state.trim() === '') { - newErrors.state = 'State is required' + newErrors.state = t('errStateRequired') } setErrors(newErrors) @@ -67,7 +69,7 @@ function CreateThingModal({ isOpen, onClose, onSave, currentThing }) { footer={ - Name + {t('name')}