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:
@@ -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) => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user