Add missing translations for task management features

Integrates i18next for internationalization, adding translated strings for task details, search, filters, and calendar views in both English and German.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: ceced2fc-aa46-458d-ba87-ddd4b7bb1518
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/659922a9-0087-461c-90dd-6d9a58b81d4d/ceced2fc-aa46-458d-ba87-ddd4b7bb1518/E3NLe8e
This commit is contained in:
paul-nothaft
2025-10-24 09:33:15 +00:00
parent f65eea2d25
commit a9efeeafbf
5 changed files with 121 additions and 54 deletions
+4
View File
@@ -18,6 +18,10 @@ externalPort = 80
localPort = 35345
externalPort = 3002
[[ports]]
localPort = 38309
externalPort = 4200
[[ports]]
localPort = 39063
externalPort = 3001
+40 -37
View File
@@ -10,6 +10,7 @@ import { Separator } from '@/components/ui/separator';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Clock, Timer, FileText, Plus, Edit2, Save, X, Calendar, Tag } from 'lucide-react';
import { Task, Label } from '@shared/schema';
import { useTranslation } from 'react-i18next';
interface TimeEntry {
id: string;
@@ -33,6 +34,8 @@ export default function TaskDetailsModal({
onSave,
labels = []
}: TaskDetailsModalProps) {
const { t } = useTranslation();
// Edit form state
const [editedTitle, setEditedTitle] = useState('');
const [editedDescription, setEditedDescription] = useState('');
@@ -73,7 +76,7 @@ export default function TaskDetailsModal({
id: 'tracked-time',
date: new Date(),
timeSpent: task.timeTracked,
description: 'Previously tracked time'
description: t('taskDetails.previouslyTrackedTime')
}]);
} else {
setTimeEntries([]);
@@ -237,7 +240,7 @@ export default function TaskDetailsModal({
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<FileText className="w-5 h-5" />
Task Details
{t('taskDetails.title')}
</DialogTitle>
</DialogHeader>
@@ -257,14 +260,14 @@ export default function TaskDetailsModal({
<div className="flex flex-wrap items-center gap-2">
<Badge variant="outline" className={`text-xs ${getPriorityColor(task.priority)}`}>
{task.priority} priority
{t(`taskDetails.priorityValue.${task.priority}`)}
</Badge>
<Badge variant="outline" className={`text-xs ${getStatusColor(task.status)}`}>
{task.status}
{t(`taskDetails.statusValue.${task.status}`)}
</Badge>
{getTotalTrackedTime() > 0 && (
<Badge variant="outline" className="text-xs">
{formatMinutesToHours(getTotalTrackedTime())} tracked
{formatMinutesToHours(getTotalTrackedTime())} {t('taskDetails.tracked')}
</Badge>
)}
</div>
@@ -275,41 +278,41 @@ export default function TaskDetailsModal({
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="edit" data-testid="tab-edit">
<Edit2 className="w-4 h-4 mr-2" />
Edit
{t('taskDetails.editTab')}
</TabsTrigger>
<TabsTrigger value="notes" data-testid="tab-notes">
<FileText className="w-4 h-4 mr-2" />
Notes
{t('taskDetails.notesTab')}
</TabsTrigger>
<TabsTrigger value="time" data-testid="tab-time">
<Clock className="w-4 h-4 mr-2" />
Time Tracking
{t('taskDetails.timeTab')}
</TabsTrigger>
</TabsList>
{/* Edit Tab */}
<TabsContent value="edit" className="space-y-4">
<Card className="p-4">
<h4 className="font-medium mb-4">Edit Task</h4>
<h4 className="font-medium mb-4">{t('taskDetails.editTask')}</h4>
<div className="space-y-4">
{/* Title */}
<div>
<label className="text-sm font-medium block mb-2">Title</label>
<label className="text-sm font-medium block mb-2">{t('taskDetails.titleLabel')}</label>
<Input
value={editedTitle}
onChange={(e) => setEditedTitle(e.target.value)}
placeholder="Task title"
placeholder={t('taskDetails.titlePlaceholder')}
data-testid="input-task-title"
/>
</div>
{/* Description */}
<div>
<label className="text-sm font-medium block mb-2">Description</label>
<label className="text-sm font-medium block mb-2">{t('taskDetails.description')}</label>
<Textarea
value={editedDescription}
onChange={(e) => setEditedDescription(e.target.value)}
placeholder="Task description"
placeholder={t('taskDetails.descriptionPlaceholder')}
rows={3}
data-testid="input-task-description"
/>
@@ -318,29 +321,29 @@ export default function TaskDetailsModal({
{/* Status and Priority */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="text-sm font-medium block mb-2">Status</label>
<label className="text-sm font-medium block mb-2">{t('taskDetails.status')}</label>
<Select value={editedStatus} onValueChange={(value) => setEditedStatus(value as 'todo' | 'inProgress' | 'done')}>
<SelectTrigger data-testid="select-status">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="todo">To Do</SelectItem>
<SelectItem value="inProgress">In Progress</SelectItem>
<SelectItem value="done">Done</SelectItem>
<SelectItem value="todo">{t('taskDetails.statusValue.todo')}</SelectItem>
<SelectItem value="inProgress">{t('taskDetails.statusValue.inProgress')}</SelectItem>
<SelectItem value="done">{t('taskDetails.statusValue.done')}</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<label className="text-sm font-medium block mb-2">Priority</label>
<label className="text-sm font-medium block mb-2">{t('taskDetails.priority')}</label>
<Select value={editedPriority} onValueChange={(value) => setEditedPriority(value as 'low' | 'medium' | 'high')}>
<SelectTrigger data-testid="select-priority">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="low">Low</SelectItem>
<SelectItem value="medium">Medium</SelectItem>
<SelectItem value="high">High</SelectItem>
<SelectItem value="low">{t('taskDetails.priorityValue.low')}</SelectItem>
<SelectItem value="medium">{t('taskDetails.priorityValue.medium')}</SelectItem>
<SelectItem value="high">{t('taskDetails.priorityValue.high')}</SelectItem>
</SelectContent>
</Select>
</div>
@@ -348,13 +351,13 @@ export default function TaskDetailsModal({
{/* Label */}
<div>
<label className="text-sm font-medium block mb-2">Label</label>
<label className="text-sm font-medium block mb-2">{t('taskDetails.label')}</label>
<Select value={editedLabelId || 'none'} onValueChange={(value) => setEditedLabelId(value === 'none' ? null : value)}>
<SelectTrigger data-testid="input-label-selector">
<SelectValue placeholder="Select a label" />
<SelectValue placeholder={t('taskDetails.selectLabel')} />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">No Label</SelectItem>
<SelectItem value="none">{t('taskDetails.noLabel')}</SelectItem>
{labels.map((label) => (
<SelectItem key={label.id} value={label.id}>
<div className="flex items-center gap-2">
@@ -372,7 +375,7 @@ export default function TaskDetailsModal({
{/* Due Date */}
<div>
<label className="text-sm font-medium block mb-2">Due Date</label>
<label className="text-sm font-medium block mb-2">{t('taskDetails.dueDate')}</label>
<Input
type="date"
value={editedDueDate}
@@ -389,7 +392,7 @@ export default function TaskDetailsModal({
data-testid="button-save-task-edit"
>
<Save className="w-4 h-4 mr-1" />
Save Changes
{t('taskDetails.save')}
</Button>
</div>
</div>
@@ -400,7 +403,7 @@ export default function TaskDetailsModal({
<TabsContent value="notes" className="space-y-4">
<Card className="p-4">
<div className="flex items-center justify-between mb-3">
<h4 className="font-medium">Task Notes</h4>
<h4 className="font-medium">{t('taskDetails.taskNotes')}</h4>
{!isEditingNotes && (
<Button
variant="outline"
@@ -409,7 +412,7 @@ export default function TaskDetailsModal({
data-testid="button-edit-notes"
>
<Edit2 className="w-4 h-4 mr-1" />
Edit
{t('taskDetails.edit')}
</Button>
)}
</div>
@@ -419,7 +422,7 @@ export default function TaskDetailsModal({
<Textarea
value={notes}
onChange={(e) => setNotes(e.target.value)}
placeholder="Add your notes for this task..."
placeholder={t('taskDetails.notesPlaceholder')}
rows={4}
data-testid="textarea-task-notes"
/>
@@ -430,7 +433,7 @@ export default function TaskDetailsModal({
data-testid="button-save-notes"
>
<Save className="w-4 h-4 mr-1" />
Save
{t('taskDetails.save')}
</Button>
<Button
variant="outline"
@@ -442,7 +445,7 @@ export default function TaskDetailsModal({
data-testid="button-cancel-notes"
>
<X className="w-4 h-4 mr-1" />
Cancel
{t('taskDetails.cancel')}
</Button>
</div>
</div>
@@ -454,7 +457,7 @@ export default function TaskDetailsModal({
</div>
) : (
<p className="text-sm text-muted-foreground italic">
No notes added yet. Click "Edit" to add notes.
{t('taskDetails.noNotesYet')}
</p>
)}
</div>
@@ -468,7 +471,7 @@ export default function TaskDetailsModal({
<Card className="p-4">
<div className="flex items-center justify-between">
<div>
<h4 className="font-medium">Total Time Tracked</h4>
<h4 className="font-medium">{t('taskDetails.totalTimeTracked')}</h4>
<p className="text-2xl font-mono text-primary" data-testid="text-total-time">
{formatMinutesToHours(getTotalTrackedTime())}
</p>
@@ -479,7 +482,7 @@ export default function TaskDetailsModal({
data-testid="button-add-time"
>
<Plus className="w-4 h-4 mr-1" />
Add Time
{t('taskDetails.addTime')}
</Button>
</div>
</Card>
@@ -489,7 +492,7 @@ export default function TaskDetailsModal({
<Card className="p-4">
<div className="space-y-4">
<div className="flex items-center justify-between">
<h4 className="font-medium">Add Time Entry</h4>
<h4 className="font-medium">{t('taskDetails.addTimeEntry')}</h4>
<Button
variant="ghost"
size="sm"
@@ -504,11 +507,11 @@ export default function TaskDetailsModal({
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="clock" data-testid="tab-clock-picker">
<Clock className="w-4 h-4 mr-2" />
Clock
{t('taskDetails.clock')}
</TabsTrigger>
<TabsTrigger value="manual" data-testid="tab-manual-input">
<Timer className="w-4 h-4 mr-2" />
Manual
{t('taskDetails.manual')}
</TabsTrigger>
</TabsList>
+16 -16
View File
@@ -219,16 +219,16 @@ export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit, onT
{/* Title Bar */}
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold" data-testid="text-task-list-title">
My Tasks
{t('taskList.title')}
</h2>
<Badge variant="secondary" data-testid="badge-task-count">
{filteredAndSortedTasks.length} tasks
{t('taskList.taskCount', { count: filteredAndSortedTasks.length })}
</Badge>
</div>
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground" />
<Input
placeholder="Search tasks..."
placeholder={t('taskList.searchPlaceholder')}
value={searchQuery}
onChange={(e) => {
setSearchQuery(e.target.value);
@@ -251,11 +251,11 @@ export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit, onT
</div>
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All ({getFilterCount('all')})</SelectItem>
<SelectItem value="todo">To Do ({getFilterCount('todo')})</SelectItem>
<SelectItem value="inProgress">In Progress ({getFilterCount('inProgress')})</SelectItem>
<SelectItem value="done">Done ({getFilterCount('done')})</SelectItem>
<SelectItem value="overdue">Overdue ({getFilterCount('overdue')})</SelectItem>
<SelectItem value="all">{t('taskList.filter.all')} ({getFilterCount('all')})</SelectItem>
<SelectItem value="todo">{t('taskList.filter.todo')} ({getFilterCount('todo')})</SelectItem>
<SelectItem value="inProgress">{t('taskList.filter.inProgress')} ({getFilterCount('inProgress')})</SelectItem>
<SelectItem value="done">{t('taskList.filter.done')} ({getFilterCount('done')})</SelectItem>
<SelectItem value="overdue">{t('taskList.filter.overdue')} ({getFilterCount('overdue')})</SelectItem>
</SelectContent>
</Select>
@@ -270,10 +270,10 @@ export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit, onT
</div>
</SelectTrigger>
<SelectContent>
<SelectItem value="dueDate">Due Date</SelectItem>
<SelectItem value="priority">Priority</SelectItem>
<SelectItem value="title">Title</SelectItem>
<SelectItem value="status">Status</SelectItem>
<SelectItem value="dueDate">{t('taskList.sort.dueDate')}</SelectItem>
<SelectItem value="priority">{t('taskList.sort.priority')}</SelectItem>
<SelectItem value="title">{t('taskList.sort.title')}</SelectItem>
<SelectItem value="status">{t('taskList.sort.status')}</SelectItem>
</SelectContent>
</Select>
</div>
@@ -286,15 +286,15 @@ export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit, onT
<div className="text-center py-8">
<p className="text-muted-foreground" data-testid="text-no-tasks">
{searchQuery || filterBy !== 'all'
? 'No unscheduled tasks match your filters'
: 'No unscheduled tasks. Drag tasks to the calendar below to schedule them!'
? t('taskList.noMatchingTasks')
: t('taskList.noUnscheduledTasks')
}
</p>
</div>
) : (
<>
<div className="text-sm font-medium text-muted-foreground mb-2">
Unscheduled Tasks - Drag to calendar below to schedule
{t('taskList.unscheduledTasksLabel')}
</div>
{unscheduledTasks.map((task) => (
<div
@@ -342,7 +342,7 @@ export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit, onT
<div className="flex items-center gap-2">
<Calendar className="w-4 h-4 text-primary" />
<h3 className="text-sm font-semibold" data-testid="text-calendar-title">
Next {visibleDays} Days
{t('taskList.calendarTitle', { count: visibleDays })}
</h3>
</div>
+30
View File
@@ -17,6 +17,9 @@
"searchPlaceholder": "Aufgaben suchen...",
"noTasks": "Noch keine Aufgaben. Erstelle deine erste Aufgabe!",
"noMatchingTasks": "Keine Aufgaben entsprechen deinen Filtern",
"noUnscheduledTasks": "Keine ungeplanten Aufgaben. Ziehe Aufgaben in den Kalender unten, um sie zu planen!",
"unscheduledTasksLabel": "Ungeplante Aufgaben - In den Kalender ziehen zum Planen",
"calendarTitle": "Nächste {{count}} Tage",
"filter": {
"all": "Alle",
"todo": "Zu erledigen",
@@ -68,6 +71,33 @@
},
"taskDetails": {
"title": "Aufgabendetails",
"selectLabel": "Label auswählen",
"tracked": "erfasst",
"priorityValue": {
"low": "Niedrige Priorität",
"medium": "Mittlere Priorität",
"high": "Hohe Priorität"
},
"statusValue": {
"todo": "Zu erledigen",
"inProgress": "In Bearbeitung",
"done": "Erledigt"
},
"editTab": "Bearbeiten",
"notesTab": "Notizen",
"timeTab": "Zeiterfassung",
"editTask": "Aufgabe bearbeiten",
"titleLabel": "Titel",
"titlePlaceholder": "Aufgabentitel",
"taskNotes": "Aufgabennotizen",
"edit": "Bearbeiten",
"noNotesYet": "Noch keine Notizen hinzugefügt. Klicke auf \"Bearbeiten\", um Notizen hinzuzufügen.",
"totalTimeTracked": "Gesamt erfasste Zeit",
"addTime": "Zeit hinzufügen",
"addTimeEntry": "Zeiteintrag hinzufügen",
"clock": "Uhr",
"manual": "Manuell",
"previouslyTrackedTime": "Zuvor erfasste Zeit",
"description": "Beschreibung",
"descriptionPlaceholder": "Beschreibung hinzufügen...",
"priority": "Priorität",
+31 -1
View File
@@ -17,6 +17,9 @@
"searchPlaceholder": "Search tasks...",
"noTasks": "No tasks yet. Create your first task!",
"noMatchingTasks": "No tasks match your filters",
"noUnscheduledTasks": "No unscheduled tasks. Drag tasks to the calendar below to schedule them!",
"unscheduledTasksLabel": "Unscheduled Tasks - Drag to calendar below to schedule",
"calendarTitle": "Next {{count}} Days",
"filter": {
"all": "All",
"todo": "To Do",
@@ -74,6 +77,7 @@
"status": "Status",
"label": "Label",
"noLabel": "No Label",
"selectLabel": "Select a label",
"dueDate": "Due Date",
"selectDate": "Select date",
"notes": "Notes",
@@ -81,7 +85,33 @@
"timeTracked": "Time Tracked",
"delete": "Delete Task",
"cancel": "Cancel",
"save": "Save Changes"
"save": "Save Changes",
"tracked": "tracked",
"priorityValue": {
"low": "Low Priority",
"medium": "Medium Priority",
"high": "High Priority"
},
"statusValue": {
"todo": "To Do",
"inProgress": "In Progress",
"done": "Done"
},
"editTab": "Edit",
"notesTab": "Notes",
"timeTab": "Time Tracking",
"editTask": "Edit Task",
"titleLabel": "Title",
"titlePlaceholder": "Task title",
"taskNotes": "Task Notes",
"edit": "Edit",
"noNotesYet": "No notes added yet. Click \"Edit\" to add notes.",
"totalTimeTracked": "Total Time Tracked",
"addTime": "Add Time",
"addTimeEntry": "Add Time Entry",
"clock": "Clock",
"manual": "Manual",
"previouslyTrackedTime": "Previously tracked time"
},
"taskCard": {
"play": "Start timer",