Add support for multiple languages in the application interface

Integrates `react-i18next` to enable language translations for UI elements across the BottomNavigation, TaskCreationModal, and TaskList components.

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/un1X8jl
This commit is contained in:
paul-nothaft
2025-10-23 20:56:05 +00:00
parent 571245e2d9
commit 32d3126dc9
3 changed files with 36 additions and 30 deletions
+6 -4
View File
@@ -1,3 +1,4 @@
import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Home, Calendar, LayoutGrid, Plus, Settings } from 'lucide-react';
@@ -10,14 +11,15 @@ interface BottomNavigationProps {
}
export default function BottomNavigation({ onTabChange, onCreateTask, activeTab = 'tasks' }: BottomNavigationProps) {
const { t } = useTranslation();
const [currentTab, setCurrentTab] = useState(activeTab);
const tabs = [
{ id: 'tasks', label: 'Tasks', icon: Home },
{ id: 'calendar', label: 'Calendar', icon: Calendar },
{ id: 'tasks', label: t('navigation.tasks'), icon: Home },
{ id: 'calendar', label: t('navigation.calendar'), icon: Calendar },
{ id: 'create', label: 'Create', icon: Plus, isCreate: true },
{ id: 'kanban', label: 'Board', icon: LayoutGrid },
{ id: 'settings', label: 'Settings', icon: Settings }
{ id: 'kanban', label: t('navigation.kanban'), icon: LayoutGrid },
{ id: 'settings', label: t('navigation.settings'), icon: Settings }
];
const handleTabClick = (tabId: string, isCreate?: boolean) => {
+14 -12
View File
@@ -1,4 +1,5 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@@ -17,6 +18,7 @@ interface TaskCreationModalProps {
}
export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreationModalProps) {
const { t } = useTranslation();
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [priority, setPriority] = useState<'low' | 'medium' | 'high'>('medium');
@@ -77,14 +79,14 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Plus className="w-4 h-4" />
New Task
{t('taskCreation.title')}
</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div>
<Input
placeholder="What needs to be done?"
placeholder={t('taskCreation.titlePlaceholder')}
value={title}
onChange={(e) => setTitle(e.target.value)}
onKeyDown={handleKeyDown}
@@ -96,7 +98,7 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
<div>
<Textarea
placeholder="Add a description (optional)"
placeholder={t('taskCreation.descriptionPlaceholder')}
value={description}
onChange={(e) => setDescription(e.target.value)}
className="resize-none text-sm"
@@ -109,12 +111,12 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
<div className="flex-1">
<Select value={priority} onValueChange={(value: 'low' | 'medium' | 'high') => setPriority(value)}>
<SelectTrigger data-testid="select-task-priority">
<SelectValue placeholder="Priority" />
<SelectValue placeholder={t('taskCreation.priority')} />
</SelectTrigger>
<SelectContent>
<SelectItem value="low">Low</SelectItem>
<SelectItem value="medium">Medium</SelectItem>
<SelectItem value="high">High</SelectItem>
<SelectItem value="low">{t('priority.low')}</SelectItem>
<SelectItem value="medium">{t('priority.medium')}</SelectItem>
<SelectItem value="high">{t('priority.high')}</SelectItem>
</SelectContent>
</Select>
</div>
@@ -122,10 +124,10 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
<div className="flex-1">
<Select value={labelId || 'none'} onValueChange={(value) => setLabelId(value === 'none' ? undefined : value)}>
<SelectTrigger data-testid="select-task-label">
<SelectValue placeholder="Label" />
<SelectValue placeholder={t('taskCreation.label')} />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">No Label</SelectItem>
<SelectItem value="none">{t('taskCreation.noLabel')}</SelectItem>
{labels.map((label) => (
<SelectItem key={label.id} value={label.id}>
<div className="flex items-center gap-2">
@@ -149,7 +151,7 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
data-testid="button-due-date"
>
<CalendarIcon className="w-4 h-4" />
{dueDate ? dueDate.toLocaleDateString() : 'Due date'}
{dueDate ? dueDate.toLocaleDateString() : t('taskCreation.dueDate')}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="end">
@@ -174,7 +176,7 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
className="flex-1"
data-testid="button-cancel"
>
Cancel
{t('taskCreation.cancel')}
</Button>
<Button
onClick={handleSave}
@@ -182,7 +184,7 @@ export default function TaskCreationModal({ isOpen, onClose, onSave }: TaskCreat
className="flex-1"
data-testid="button-save-task"
>
Create Task
{t('taskCreation.create')}
</Button>
</div>
</div>
+16 -14
View File
@@ -1,4 +1,5 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
@@ -17,6 +18,7 @@ type SortOption = 'dueDate' | 'priority' | 'title' | 'status';
type FilterOption = 'all' | 'todo' | 'inProgress' | 'done' | 'overdue';
export default function TaskList({ tasks, onTaskUpdate, onTaskEdit }: TaskListProps) {
const { t } = useTranslation();
const [searchQuery, setSearchQuery] = useState('');
const [sortBy, setSortBy] = useState<SortOption>('dueDate');
const [filterBy, setFilterBy] = useState<FilterOption>('all');
@@ -84,10 +86,10 @@ export default function TaskList({ tasks, onTaskUpdate, onTaskEdit }: TaskListPr
{/* Header */}
<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>
@@ -96,7 +98,7 @@ export default function TaskList({ tasks, onTaskUpdate, onTaskEdit }: TaskListPr
<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);
@@ -119,11 +121,11 @@ export default function TaskList({ tasks, onTaskUpdate, onTaskEdit }: TaskListPr
</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>
@@ -138,10 +140,10 @@ export default function TaskList({ tasks, onTaskUpdate, onTaskEdit }: TaskListPr
</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>
@@ -153,8 +155,8 @@ export default function TaskList({ tasks, onTaskUpdate, onTaskEdit }: TaskListPr
<div className="text-center py-8">
<p className="text-muted-foreground" data-testid="text-no-tasks">
{searchQuery || filterBy !== 'all'
? 'No tasks match your filters'
: 'No tasks yet. Create your first task!'
? t('taskList.noMatchingTasks')
: t('taskList.noTasks')
}
</p>
</div>