From 571245e2d92265588aba3bc0be9d834bf8e25cc8 Mon Sep 17 00:00:00 2001 From: paul-nothaft <40865108-paul-nothaft@users.noreply.replit.com> Date: Thu, 23 Oct 2025 20:54:00 +0000 Subject: [PATCH] Add full language translation support and settings option Integrates i18next and react-i18next for internationalization, adds German language support, and implements a language selection feature in the settings page. 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 --- .replit | 4 + client/src/App.tsx | 14 ++- client/src/i18n/config.ts | 26 ++++++ client/src/i18n/locales/de.json | 146 ++++++++++++++++++++++++++++++++ client/src/i18n/locales/en.json | 146 ++++++++++++++++++++++++++++++++ client/src/main.tsx | 1 + client/src/pages/settings.tsx | 82 ++++++++++++++++++ package-lock.json | 94 +++++++++++++++++--- package.json | 2 + 9 files changed, 499 insertions(+), 16 deletions(-) create mode 100644 client/src/i18n/config.ts create mode 100644 client/src/i18n/locales/de.json create mode 100644 client/src/i18n/locales/en.json create mode 100644 client/src/pages/settings.tsx diff --git a/.replit b/.replit index a572720..da1c583 100644 --- a/.replit +++ b/.replit @@ -22,6 +22,10 @@ externalPort = 3002 localPort = 39063 externalPort = 3001 +[[ports]] +localPort = 41155 +externalPort = 4200 + [[ports]] localPort = 41353 externalPort = 3000 diff --git a/client/src/App.tsx b/client/src/App.tsx index 7e6b82d..c6d7192 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,4 +1,5 @@ import { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { queryClient } from "./lib/queryClient"; import { QueryClientProvider } from "@tanstack/react-query"; import { Toaster } from "@/components/ui/toaster"; @@ -15,6 +16,7 @@ import CalendarView from './components/CalendarView'; import KanbanBoard from './components/KanbanBoard'; import ProjectTemplate from './components/ProjectTemplate'; import ThemeToggle from './components/ThemeToggle'; +import Settings from './pages/settings'; import { Task, Label } from '@shared/schema'; import { addDays, subDays } from 'date-fns'; import { useTimer } from './hooks/useTimer'; @@ -89,6 +91,7 @@ const initialTasks: Task[] = [ ]; function App() { + const { t } = useTranslation(); const [currentTab, setCurrentTab] = useState('tasks'); const [tasks, setTasks] = useState(initialTasks); const [labels, setLabels] = useState([]); @@ -266,7 +269,7 @@ function App() { /> ); - case 'settings': + case 'templates': return ( ); + case 'settings': + return ; + default: return (
@@ -295,19 +301,19 @@ function App() { T

- TaskFlow + {t('app.title')}

- {currentTab === 'settings' && ( + {(currentTab === 'settings' || currentTab === 'templates') && ( )} diff --git a/client/src/i18n/config.ts b/client/src/i18n/config.ts new file mode 100644 index 0000000..e064e5f --- /dev/null +++ b/client/src/i18n/config.ts @@ -0,0 +1,26 @@ +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import en from './locales/en.json'; +import de from './locales/de.json'; + +const savedLanguage = localStorage.getItem('taskflow-language') || 'en'; + +i18n + .use(initReactI18next) + .init({ + resources: { + en: { + translation: en + }, + de: { + translation: de + } + }, + lng: savedLanguage, + fallbackLng: 'en', + interpolation: { + escapeValue: false + } + }); + +export default i18n; diff --git a/client/src/i18n/locales/de.json b/client/src/i18n/locales/de.json new file mode 100644 index 0000000..18b8e29 --- /dev/null +++ b/client/src/i18n/locales/de.json @@ -0,0 +1,146 @@ +{ + "app": { + "title": "TaskFlow", + "backToTasks": "Zurück zu Aufgaben" + }, + "navigation": { + "tasks": "Aufgaben", + "calendar": "Kalender", + "kanban": "Kanban", + "settings": "Einstellungen" + }, + "taskList": { + "title": "Meine Aufgaben", + "taskCount": "{{count}} Aufgaben", + "searchPlaceholder": "Aufgaben suchen...", + "noTasks": "Noch keine Aufgaben. Erstelle deine erste Aufgabe!", + "noMatchingTasks": "Keine Aufgaben entsprechen deinen Filtern", + "filter": { + "all": "Alle", + "todo": "Zu erledigen", + "inProgress": "In Bearbeitung", + "done": "Erledigt", + "overdue": "Überfällig" + }, + "sort": { + "dueDate": "Fälligkeitsdatum", + "priority": "Priorität", + "title": "Titel", + "status": "Status" + } + }, + "taskCreation": { + "title": "Neue Aufgabe", + "titlePlaceholder": "Was muss erledigt werden?", + "descriptionPlaceholder": "Beschreibung hinzufügen (optional)", + "priority": "Priorität", + "label": "Label", + "noLabel": "Kein Label", + "dueDate": "Fälligkeitsdatum", + "cancel": "Abbrechen", + "create": "Aufgabe erstellen" + }, + "taskDetails": { + "title": "Aufgabendetails", + "description": "Beschreibung", + "descriptionPlaceholder": "Beschreibung hinzufügen...", + "priority": "Priorität", + "status": "Status", + "label": "Label", + "noLabel": "Kein Label", + "dueDate": "Fälligkeitsdatum", + "selectDate": "Datum wählen", + "notes": "Notizen", + "notesPlaceholder": "Notizen hinzufügen...", + "timeTracked": "Erfasste Zeit", + "delete": "Aufgabe löschen", + "cancel": "Abbrechen", + "save": "Änderungen speichern" + }, + "taskCard": { + "play": "Timer starten", + "pause": "Timer pausieren", + "edit": "Aufgabe bearbeiten", + "overdue": "Überfällig", + "timeTracked": "{{hours}}Std {{minutes}}Min erfasst" + }, + "calendar": { + "title": "Kalenderansicht", + "today": "Heute", + "tasksDue": "{{count}} Aufgabe fällig", + "tasksDue_plural": "{{count}} Aufgaben fällig" + }, + "kanban": { + "title": "Kanban-Board", + "todo": "Zu erledigen", + "inProgress": "In Bearbeitung", + "done": "Erledigt", + "taskCount": "{{count}} Aufgabe", + "taskCount_plural": "{{count}} Aufgaben" + }, + "settings": { + "title": "Einstellungen", + "language": { + "title": "Sprache", + "description": "Wähle deine bevorzugte Sprache", + "english": "Englisch (English)", + "german": "Deutsch" + }, + "appearance": { + "title": "Erscheinungsbild", + "description": "Passe das Aussehen an" + }, + "templates": { + "title": "Projektvorlagen", + "description": "Verwende Vorlagen zum schnellen Erstellen von Projekten" + } + }, + "projectTemplate": { + "title": "Projektvorlagen", + "description": "Wähle eine Vorlage, um ein neues Projekt mit vorkonfigurierten Aufgaben zu erstellen", + "websiteRedesign": "Website-Redesign", + "websiteDescription": "Umfassendes Website-Redesign-Projekt mit Design-, Entwicklungs- und Testphasen", + "mobileApp": "Mobile App-Launch", + "mobileDescription": "End-to-End Mobile App-Entwicklung von der Planung bis zur Bereitstellung", + "marketingCampaign": "Marketingkampagne", + "marketingDescription": "Vollständiger Marketingkampagnen-Workflow von der Planung bis zur Analyse", + "projectName": "Projektname", + "projectNamePlaceholder": "Projektnamen eingeben", + "startDate": "Startdatum", + "selectStartDate": "Startdatum wählen", + "createProject": "Projekt erstellen", + "weeks": "{{count}} Wochen", + "tasks": "{{count}} Aufgaben" + }, + "timeCompletion": { + "title": "Aufgabe abgeschlossen!", + "congratulations": "Großartige Arbeit! Du hast abgeschlossen", + "timeSpent": "Zeit für diese Aufgabe", + "addTime": "Zusätzliche Zeit hinzufügen (optional)", + "hours": "Stunden", + "minutes": "Minuten", + "notes": "Notizen", + "notesPlaceholder": "Abschlussnotizen hinzufügen...", + "finish": "Fertig" + }, + "priority": { + "low": "Niedrig", + "medium": "Mittel", + "high": "Hoch" + }, + "status": { + "todo": "Zu erledigen", + "inProgress": "In Bearbeitung", + "done": "Erledigt" + }, + "common": { + "cancel": "Abbrechen", + "save": "Speichern", + "delete": "Löschen", + "edit": "Bearbeiten", + "create": "Erstellen", + "close": "Schließen", + "loading": "Laden...", + "error": "Ein Fehler ist aufgetreten" + } +} diff --git a/client/src/i18n/locales/en.json b/client/src/i18n/locales/en.json new file mode 100644 index 0000000..8675d73 --- /dev/null +++ b/client/src/i18n/locales/en.json @@ -0,0 +1,146 @@ +{ + "app": { + "title": "TaskFlow", + "backToTasks": "Back to Tasks" + }, + "navigation": { + "tasks": "Tasks", + "calendar": "Calendar", + "kanban": "Kanban", + "settings": "Settings" + }, + "taskList": { + "title": "My Tasks", + "taskCount": "{{count}} tasks", + "searchPlaceholder": "Search tasks...", + "noTasks": "No tasks yet. Create your first task!", + "noMatchingTasks": "No tasks match your filters", + "filter": { + "all": "All", + "todo": "To Do", + "inProgress": "In Progress", + "done": "Done", + "overdue": "Overdue" + }, + "sort": { + "dueDate": "Due Date", + "priority": "Priority", + "title": "Title", + "status": "Status" + } + }, + "taskCreation": { + "title": "New Task", + "titlePlaceholder": "What needs to be done?", + "descriptionPlaceholder": "Add a description (optional)", + "priority": "Priority", + "label": "Label", + "noLabel": "No Label", + "dueDate": "Due date", + "cancel": "Cancel", + "create": "Create Task" + }, + "taskDetails": { + "title": "Task Details", + "description": "Description", + "descriptionPlaceholder": "Add a description...", + "priority": "Priority", + "status": "Status", + "label": "Label", + "noLabel": "No Label", + "dueDate": "Due Date", + "selectDate": "Select date", + "notes": "Notes", + "notesPlaceholder": "Add notes...", + "timeTracked": "Time Tracked", + "delete": "Delete Task", + "cancel": "Cancel", + "save": "Save Changes" + }, + "taskCard": { + "play": "Start timer", + "pause": "Pause timer", + "edit": "Edit task", + "overdue": "Overdue", + "timeTracked": "{{hours}}h {{minutes}}m tracked" + }, + "calendar": { + "title": "Calendar View", + "today": "Today", + "tasksDue": "{{count}} task due", + "tasksDue_plural": "{{count}} tasks due" + }, + "kanban": { + "title": "Kanban Board", + "todo": "To Do", + "inProgress": "In Progress", + "done": "Done", + "taskCount": "{{count}} task", + "taskCount_plural": "{{count}} tasks" + }, + "settings": { + "title": "Settings", + "language": { + "title": "Language", + "description": "Choose your preferred language", + "english": "English", + "german": "German (Deutsch)" + }, + "appearance": { + "title": "Appearance", + "description": "Customize the look and feel" + }, + "templates": { + "title": "Project Templates", + "description": "Use templates to quickly create projects" + } + }, + "projectTemplate": { + "title": "Project Templates", + "description": "Select a template to create a new project with pre-configured tasks", + "websiteRedesign": "Website Redesign", + "websiteDescription": "Comprehensive website redesign project with design, development, and testing phases", + "mobileApp": "Mobile App Launch", + "mobileDescription": "End-to-end mobile app development from planning to deployment", + "marketingCampaign": "Marketing Campaign", + "marketingDescription": "Complete marketing campaign workflow from planning to analysis", + "projectName": "Project Name", + "projectNamePlaceholder": "Enter project name", + "startDate": "Start Date", + "selectStartDate": "Select start date", + "createProject": "Create Project", + "weeks": "{{count}} weeks", + "tasks": "{{count}} tasks" + }, + "timeCompletion": { + "title": "Task Completed!", + "congratulations": "Great job! You've completed", + "timeSpent": "Time spent on this task", + "addTime": "Add additional time (optional)", + "hours": "Hours", + "minutes": "Minutes", + "notes": "Notes", + "notesPlaceholder": "Add completion notes...", + "finish": "Finish" + }, + "priority": { + "low": "Low", + "medium": "Medium", + "high": "High" + }, + "status": { + "todo": "To Do", + "inProgress": "In Progress", + "done": "Done" + }, + "common": { + "cancel": "Cancel", + "save": "Save", + "delete": "Delete", + "edit": "Edit", + "create": "Create", + "close": "Close", + "loading": "Loading...", + "error": "An error occurred" + } +} diff --git a/client/src/main.tsx b/client/src/main.tsx index 696e0d2..e77c81c 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -1,5 +1,6 @@ import { createRoot } from "react-dom/client"; import App from "./App"; import "./index.css"; +import "./i18n/config"; createRoot(document.getElementById("root")!).render(); diff --git a/client/src/pages/settings.tsx b/client/src/pages/settings.tsx new file mode 100644 index 0000000..669528c --- /dev/null +++ b/client/src/pages/settings.tsx @@ -0,0 +1,82 @@ +import { useTranslation } from 'react-i18next'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Globe } from 'lucide-react'; + +export default function Settings() { + const { t, i18n } = useTranslation(); + + const changeLanguage = (lng: string) => { + i18n.changeLanguage(lng); + localStorage.setItem('taskflow-language', lng); + console.log('Language changed to:', lng); + }; + + return ( +
+
+

+ {t('settings.title')} +

+
+ + {/* Language Settings */} + + + + + {t('settings.language.title')} + + + {t('settings.language.description')} + + + + + + + + {/* Appearance Settings */} + + + {t('settings.appearance.title')} + + {t('settings.appearance.description')} + + + +

+ Theme toggle available in the header +

+
+
+ + {/* Project Templates */} + + + {t('settings.templates.title')} + + {t('settings.templates.description')} + + + +

+ Access templates from the navigation menu +

+
+
+
+ ); +} diff --git a/package-lock.json b/package-lock.json index d1849d4..4217197 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,6 +52,7 @@ "express": "^4.21.2", "express-session": "^1.18.1", "framer-motion": "^11.13.1", + "i18next": "^25.6.0", "input-otp": "^1.4.2", "lucide-react": "^0.453.0", "memorystore": "^1.6.7", @@ -63,6 +64,7 @@ "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", "react-hook-form": "^7.55.0", + "react-i18next": "^16.1.6", "react-icons": "^5.4.0", "react-resizable-panels": "^2.1.7", "recharts": "^2.15.2", @@ -352,12 +354,10 @@ } }, "node_modules/@babel/runtime": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", - "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -5621,6 +5621,15 @@ "node": ">= 0.4" } }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -5637,6 +5646,37 @@ "node": ">= 0.8" } }, + "node_modules/i18next": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.6.0.tgz", + "integrity": "sha512-tTn8fLrwBYtnclpL5aPXK/tAYBLWVvoHM1zdfXoRNLcI+RvtMsoZRV98ePlaW3khHYKuNh/Q65W/+NVFUeIwVw==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -7002,6 +7042,32 @@ "react": "^16.8.0 || ^17 || ^18 || ^19" } }, + "node_modules/react-i18next": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-16.1.6.tgz", + "integrity": "sha512-62Iy0TO/2hJpTa80XaTIbHM4yjpile1YNieeg70vmdi91N2L3Q3MuxXBT0n6JpsryT88utpkqv0QbnIrDSxbAQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 25.5.2", + "react": ">= 16.8.0", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/react-icons": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.4.0.tgz", @@ -7183,11 +7249,6 @@ "decimal.js-light": "^2.4.1" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" - }, "node_modules/regexparam": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/regexparam/-/regexparam-3.0.0.tgz", @@ -8270,7 +8331,7 @@ "version": "5.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -8936,6 +8997,15 @@ "@esbuild/win32-x64": "0.21.5" } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 15fdbf3..9c0cf26 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "express": "^4.21.2", "express-session": "^1.18.1", "framer-motion": "^11.13.1", + "i18next": "^25.6.0", "input-otp": "^1.4.2", "lucide-react": "^0.453.0", "memorystore": "^1.6.7", @@ -65,6 +66,7 @@ "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", "react-hook-form": "^7.55.0", + "react-i18next": "^16.1.6", "react-icons": "^5.4.0", "react-resizable-panels": "^2.1.7", "recharts": "^2.15.2",