added multilanguage setting

This commit is contained in:
idittrich-valueon 2025-06-20 10:20:43 +02:00
parent 8ca6f28bcd
commit 7f9f5b0539
31 changed files with 1049 additions and 230 deletions

View file

@ -9,6 +9,7 @@ import Register from './pages/Register';
import { AuthProvider } from './auth/authProvider';
import { ProtectedRoute } from './auth/ProtectedRoute';
import { LanguageProvider } from './contexts/LanguageContext';
import Home from './pages/Home';
import Dateien from './pages/Dateien/Dateien';
import TeamBereich from './pages/TeamBereich/TeamBereich';
@ -33,6 +34,7 @@ function App() {
document.documentElement.setAttribute('data-theme', prefersDark ? 'dark' : 'light');
}, []);
return (
<LanguageProvider>
<AuthProvider>
<Router>
<Routes>
@ -52,6 +54,7 @@ function App() {
</Routes>
</Router>
</AuthProvider>
</LanguageProvider>
);
}

View file

@ -2,6 +2,7 @@ import React, { useState } from "react";
import { BsArrowsAngleExpand, BsArrowsAngleContract } from "react-icons/bs";
import { motion, AnimatePresence } from "framer-motion";
import { Prompt } from "../../../hooks/usePrompts";
import { useLanguage } from '../../../contexts/LanguageContext';
import DashboardChatArea from './DashboardChatArea/DashboardChatArea';
import DashboardChatHistory from './DashboardChatHistory/DashboardChatHistory';
@ -27,12 +28,13 @@ const DashboardChat: React.FC<DashboardChatProps> = ({
onWorkflowCompletedChange,
onWorkflowResume
}) => {
const [activeTab, setActiveTab] = useState("Chatbereich");
const { t } = useLanguage();
const [activeTab, setActiveTab] = useState(t('dashboard.chat.area'));
const [resumeWorkflowId, setResumeWorkflowId] = useState<string | null>(null);
const handleWorkflowResume = (workflowId: string) => {
// Switch to Chat Area tab first
setActiveTab("Chatbereich");
setActiveTab(t('dashboard.chat.area'));
// Set the workflow ID to resume
setResumeWorkflowId(workflowId);
// Then call the parent's resume handler
@ -57,7 +59,7 @@ const DashboardChat: React.FC<DashboardChatProps> = ({
transition={{ duration: 0.3, ease: "easeOut" }}
>
<div className={styles.chat_button_div}>
{["Chatbereich", "Workflow-Verlauf"].map((tab) => (
{[t('dashboard.chat.area'), t('dashboard.chat.history')].map((tab) => (
<div key={tab} className={styles.buttonWrapper}>
<motion.button
key={tab}
@ -116,7 +118,7 @@ const DashboardChat: React.FC<DashboardChatProps> = ({
}}
style={{ overflow: "hidden" }}
>
{activeTab === "Chatbereich" ? (
{activeTab === t('dashboard.chat.area') ? (
<DashboardChatArea
selectedPrompt={selectedPrompt}
onPromptUsed={onPromptUsed}

View file

@ -1,6 +1,7 @@
import React from "react";
import { motion, AnimatePresence } from "framer-motion";
import { WorkflowStatusDisplayProps } from "./dashboardChatAreaTypes";
import { useLanguage } from "../../../../contexts/LanguageContext";
import styles from './DashboardChatArea.module.css';
const WorkflowStatusDisplay: React.FC<WorkflowStatusDisplayProps> = ({
@ -11,6 +12,7 @@ const WorkflowStatusDisplay: React.FC<WorkflowStatusDisplayProps> = ({
handleRetry,
shouldShowRetryButton
}) => {
const { t } = useLanguage();
return (
<AnimatePresence>
{currentWorkflowId && shouldShowRetryButton() && (
@ -23,13 +25,13 @@ const WorkflowStatusDisplay: React.FC<WorkflowStatusDisplayProps> = ({
>
<div className={styles.retry_container}>
<span className={styles.failed_message}>
Workflow failed.
{t('chat.workflow_failed')}
</span>
<button
onClick={handleRetry}
className={styles.retry_button}
>
Nochmal versuchen
{t('chat.retry_workflow')}
</button>
</div>
</motion.div>

View file

@ -1,6 +1,7 @@
import React, { useEffect } from "react";
import { DashboardChatAreaProps } from "./dashboardChatAreaTypes";
import { useChatLogic } from "./dashboardChatAreaLogic";
import { useLanguage } from "../../../../contexts/LanguageContext";
import MessageList from "./DashboardChatAreaMessageList";
import ChatInput from "./DashboardChatAreaInput";
import styles from './DashboardChatArea.module.css';
@ -53,6 +54,8 @@ const DashboardChatArea: React.FC<DashboardChatAreaProps> = ({
resumeWorkflowId
});
const { t } = useLanguage();
// Notify parent component when workflow completion status changes
useEffect(() => {
if (onWorkflowCompletedChange) {
@ -60,7 +63,7 @@ const DashboardChatArea: React.FC<DashboardChatAreaProps> = ({
}
}, [workflowCompleted, onWorkflowCompletedChange]);
const placeholder = workflowCompleted ? "Gespräch fortsetzen..." : "Nachricht eingeben...";
const placeholder = workflowCompleted ? t('chat.continue_conversation') : t('chat.enter_message');
return (
<div className={styles.chat_area}>

View file

@ -5,6 +5,7 @@ import { FaStop } from "react-icons/fa";
import { IoAttach, IoClose } from "react-icons/io5";
import { ChatInputProps } from "./dashboardChatAreaTypes";
import { FileInfo } from "../../../../hooks/useFiles";
import { useLanguage } from "../../../../contexts/LanguageContext";
import DateienSelector from "../../../Dateien/DateienHinzufügen/DateienSelector";
import styles from './DashboardChatArea.module.css';
@ -44,6 +45,7 @@ const ChatInput: React.FC<ChatInputProps> = ({
onFileRemove,
onFilesSelect
}) => {
const { t } = useLanguage();
const [isUploadModalOpen, setIsUploadModalOpen] = useState(false);
const [isDragOver, setIsDragOver] = useState(false);
@ -169,7 +171,7 @@ const ChatInput: React.FC<ChatInputProps> = ({
<button
className={styles.attached_file_remove}
onClick={() => handleFileRemove(file.id)}
title="Datei entfernen"
title={t('chat.remove_file')}
>
<IoClose size={12} />
</button>
@ -203,7 +205,7 @@ const ChatInput: React.FC<ChatInputProps> = ({
disabled={isDisabled || isWorkflowRunning}
whileTap={{ scale: 0.95 }}
transition={{ duration: 0.2, ease: "easeOut" }}
title="Datei anhängen"
title={t('chat.attach_file')}
>
<IoAttach size={26} />
</motion.button>

View file

@ -4,6 +4,7 @@ import { MdOutlineRemoveRedEye } from "react-icons/md";
import { Message, Document } from "./dashboardChatAreaTypes";
import FilePreviewPopup from "./FilePreviewPopup";
import { useFileDownload } from "../../../../hooks/useWorkflows";
import { useLanguage } from "../../../../contexts/LanguageContext";
import styles from './DashboardChatArea.module.css';
interface MessageItemProps {
@ -62,6 +63,7 @@ const getFileIcon = (type?: string, ext?: string): string => {
};
const MessageItem: React.FC<MessageItemProps> = ({ message, index }) => {
const { t } = useLanguage();
const [previewDocument, setPreviewDocument] = useState<Document | null>(null);
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
const { downloadFile, isDownloading, error: downloadError } = useFileDownload();
@ -119,7 +121,7 @@ const MessageItem: React.FC<MessageItemProps> = ({ message, index }) => {
className={`${styles.message} ${styles[`message_${message.role}`]}`}
>
<div className={styles.message_role}>
{message.role === 'user' ? 'You' : message.agentName}
{message.role === 'user' ? t('chat.you') : message.agentName}
</div>
<div className={styles.message_content}>
{message.content}
@ -132,7 +134,7 @@ const MessageItem: React.FC<MessageItemProps> = ({ message, index }) => {
key={document.id || docIndex}
className={styles.document_item}
onClick={() => handleDocumentClick(document)}
title={`Click to open ${document.name}`}
title={`${t('chat.click_to_open')} ${document.name}`}
>
<span className={styles.document_icon}>
{getFileIcon(document.type, document.ext)}
@ -153,14 +155,14 @@ const MessageItem: React.FC<MessageItemProps> = ({ message, index }) => {
<button
className={styles.document_action_button}
onClick={(e) => handlePreview(document, e)}
title="Preview document"
title={t('chat.preview_document')}
>
<MdOutlineRemoveRedEye />
</button>
<button
className={styles.document_action_button}
onClick={(e) => handleDownload(document, e)}
title="Download document"
title={t('chat.download_document')}
>
<FaDownload />
</button>

View file

@ -3,6 +3,7 @@ import { motion } from "framer-motion";
import { MessageListProps } from "./dashboardChatAreaTypes";
import MessageItem from "./DashboardChatAreaMessageItem";
import WorkflowStatusDisplay from "./DashbaordChatAreaStatusDisplay";
import { useLanguage } from "../../../../contexts/LanguageContext";
import styles from './DashboardChatArea.module.css';
const MessageList: React.FC<MessageListProps> = ({
@ -19,6 +20,8 @@ const MessageList: React.FC<MessageListProps> = ({
handleRetry,
shouldShowRetryButton
}) => {
const { t } = useLanguage();
return (
<motion.div
className={styles.chat_messages}
@ -29,22 +32,22 @@ const MessageList: React.FC<MessageListProps> = ({
<div className={styles.messages_container}>
{startingWorkflow && (
<div className={styles.loading_message}>
<p>{workflowCompleted && currentWorkflowId ? 'Sending follow-up message...' : 'Sending message...'}</p>
<p>{workflowCompleted && currentWorkflowId ? t('chat.sending_followup', 'Sending follow-up message...') : t('chat.sending_message', 'Sending message...')}</p>
</div>
)}
{startError && (
<div className={styles.error_message}>
<p>Error: {startError}</p>
<p>{t('chat.error_prefix', 'Error:')} {startError}</p>
</div>
)}
{messagesError && (
<div className={styles.error_message}>
<p>Error loading messages: {messagesError}</p>
<p>{t('chat.error_loading_messages', 'Error loading messages:')} {messagesError}</p>
</div>
)}
{currentWorkflowId && messagesLoading && messages.length === 0 && (
<div className={styles.loading_message}>
<p>Loading workflow messages...</p>
<p>{t('chat.loading_workflow_messages', 'Loading workflow messages...')}</p>
</div>
)}
{messages.length > 0 ? (
@ -56,7 +59,7 @@ const MessageList: React.FC<MessageListProps> = ({
/>
))
) : !currentWorkflowId ? (
<p className={styles.placeholder_text}>Beginne ein Gespräch, indem du eine Nachricht eingibst, eine Vorlage auswählst oder einen vorherigen Workflow fortsetzt </p>
<p className={styles.placeholder_text}>{t('chat.start_conversation', 'Start a conversation by entering a message, selecting a template, or continuing a previous workflow...')}</p>
) : null}
{/* Spacer to push workflow status to bottom when there are fewer messages */}

View file

@ -4,7 +4,7 @@
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.7);
background-color: var(--color-bg);
display: flex;
align-items: center;
justify-content: center;
@ -134,7 +134,7 @@
.text_preview {
width: 100%;
height: 100%;
background-color: var(--color-surface);
background-color: var(--color-bg);
border: 1px solid var(--color-gray-disabled);
border-radius: 8px;
padding: 16px;
@ -185,14 +185,14 @@
.text_numbered {
margin: 8px 0;
padding-left: 8px;
color: var(--color-gray);
color: var(--color-text);
font-family: var(--font-family);
}
.text_bullet {
margin: 4px 0;
padding-left: 8px;
color: var(--color-gray);
color: var(--color-text);
font-family: var(--font-family);
}
@ -204,7 +204,7 @@
border-radius: 4px;
font-family: 'Courier New', monospace;
font-size: 0.9em;
color: var(--color-gray);
color: var(--color-text);
}
.code_preview {

View file

@ -3,6 +3,7 @@ import ReactMarkdown from 'react-markdown';
import { MdClose } from "react-icons/md";
import { Document } from "./dashboardChatAreaTypes";
import { useFilePreview } from "../../../../hooks/useWorkflows";
import { useLanguage } from "../../../../contexts/LanguageContext";
import styles from './FilePreviewPopup.module.css';
interface FilePreviewPopupProps {
@ -12,6 +13,7 @@ interface FilePreviewPopupProps {
}
const FilePreviewPopup: React.FC<FilePreviewPopupProps> = ({ document, isOpen, onClose }) => {
const { t } = useLanguage();
const { previewContent, fileMetadata, isLoading, error, fetchPreview, clearPreview } = useFilePreview();
useEffect(() => {
@ -38,13 +40,13 @@ const FilePreviewPopup: React.FC<FilePreviewPopupProps> = ({ document, isOpen, o
const getPreviewComponent = () => {
if (isLoading) {
return <div className={styles.loading}>Loading preview...</div>;
return <div className={styles.loading}>{t('file_preview.loading')}</div>;
}
if (error) {
return (
<div className={styles.error}>
<div>Error: {error}</div>
<div>{t('file_preview.error')}: {error}</div>
{fileMetadata && (
<div style={{ marginTop: '10px', fontSize: '12px', opacity: 0.7 }}>
Debug: {JSON.stringify(fileMetadata, null, 2)}
@ -57,7 +59,7 @@ const FilePreviewPopup: React.FC<FilePreviewPopupProps> = ({ document, isOpen, o
if (!previewContent) {
return (
<div className={styles.no_preview}>
<div>No preview available</div>
<div>{t('file_preview.no_preview')}</div>
{fileMetadata && (
<div style={{ marginTop: '10px', fontSize: '12px', opacity: 0.7 }}>
Available metadata: {Object.keys(fileMetadata).join(', ')}
@ -165,7 +167,7 @@ const FilePreviewPopup: React.FC<FilePreviewPopupProps> = ({ document, isOpen, o
return (
<div className={styles.python_code_preview}>
<div className={styles.code_header}>
<span className={styles.code_language}>Python</span>
<span className={styles.code_language}>{t('file_preview.python')}</span>
<span className={styles.code_filename}>
{document.ext ? `${document.name}.${document.ext}` : document.name}
</span>
@ -229,7 +231,7 @@ const FilePreviewPopup: React.FC<FilePreviewPopupProps> = ({ document, isOpen, o
<button
className={styles.close_button}
onClick={onClose}
title="Close preview"
title={t('file_preview.close_preview')}
>
<MdClose />
</button>

View file

@ -1,6 +1,7 @@
import React from "react";
import { motion } from "framer-motion";
import { useWorkflows } from "../../../../hooks/useWorkflows";
import { useLanguage } from "../../../../contexts/LanguageContext";
import DashboardChatHistoryItem from "./DashboardChatHistoryItem";
import styles from './DashboardChatHistory.module.css';
@ -11,6 +12,7 @@ interface DashboardChatHistoryProps {
const DashboardChatHistory: React.FC<DashboardChatHistoryProps> = ({ onWorkflowResume }) => {
const { workflows, loading, error, refetch } = useWorkflows();
const { t } = useLanguage();
const handleWorkflowResume = (workflowId: string) => {
if (onWorkflowResume) {
@ -28,7 +30,7 @@ const DashboardChatHistory: React.FC<DashboardChatHistoryProps> = ({ onWorkflowR
transition={{ delay: 0.2, duration: 0.3, ease: "easeOut" }}
>
<div className={styles.loadingContainer}>
<div className={styles.loadingText}>Workflows werden geladen...</div>
<div className={styles.loadingText}>{t('chat_history.loading', 'Loading workflows...')}</div>
</div>
</motion.div>
);
@ -43,12 +45,12 @@ const DashboardChatHistory: React.FC<DashboardChatHistoryProps> = ({ onWorkflowR
transition={{ delay: 0.2, duration: 0.3, ease: "easeOut" }}
>
<div className={styles.errorContainer}>
<div className={styles.errorText}>Fehler beim Laden der Workflows: {error}</div>
<div className={styles.errorText}>{t('chat_history.error_loading', 'Error loading workflows:')} {error}</div>
<button
onClick={refetch}
className={styles.retryButton}
>
Try Again
{t('chat_history.try_again', 'Try Again')}
</button>
</div>
</motion.div>
@ -64,16 +66,16 @@ const DashboardChatHistory: React.FC<DashboardChatHistoryProps> = ({ onWorkflowR
>
<div className={styles.container}>
<div className={styles.header}>
<h2 className={styles.history_title}>Workflow-Verlauf</h2>
<h2 className={styles.history_title}>{t('chat_history.title', 'Workflow History')}</h2>
<div className={styles.workflowCount}>
{workflows.length} {workflows.length === 1 ? 'Workflow' : 'Workflows'}
{workflows.length} {workflows.length === 1 ? t('chat_history.workflow_count', 'Workflow') : t('chat_history.workflow_count_plural', 'Workflows')}
</div>
</div>
<div className={styles.scrollableContent}>
{workflows.length === 0 ? (
<div className={styles.emptyState}>
Keine Workflows verfügbar
{t('chat_history.empty_state', 'No workflows available')}
</div>
) : (
<div className={styles.workflowsList}>

View file

@ -3,6 +3,7 @@ import { FaArrowRight } from 'react-icons/fa';
import { AiOutlineDelete } from 'react-icons/ai';
import { motion } from 'framer-motion';
import { useWorkflowOperations, useWorkflowMessages, Workflow } from '../../../../hooks/useWorkflows';
import { useLanguage } from '../../../../contexts/LanguageContext';
import styles from './DashboardChatHistoryItem.module.css';
interface DashboardChatHistoryItemProps {
@ -14,15 +15,17 @@ interface DashboardChatHistoryItemProps {
function DashboardChatHistoryItem({ workflow, onDelete, onResume }: DashboardChatHistoryItemProps) {
const { deleteWorkflow, deletingWorkflows } = useWorkflowOperations();
const { messages } = useWorkflowMessages(workflow.id);
const { t } = useLanguage();
const isDeleting = deletingWorkflows.has(workflow.id);
// Get the first user message as preview
const firstUserMessage = messages.find(msg => msg.role === 'user');
const messagePreview = firstUserMessage?.content || 'No message content available';
const messagePreview = firstUserMessage?.content || t('chat_history.no_message_content', 'No message content available');
const handleDelete = async () => {
if (window.confirm(`Are you sure you want to delete workflow "${workflow.id.substring(0, 8)}..."?`)) {
const confirmMessage = t('chat_history.confirm_delete', 'Are you sure you want to delete workflow "{id}..."?').replace('{id}', workflow.id.substring(0, 8));
if (window.confirm(confirmMessage)) {
const success = await deleteWorkflow(workflow.id);
if (success && onDelete) {
onDelete();
@ -35,7 +38,7 @@ function DashboardChatHistoryItem({ workflow, onDelete, onResume }: DashboardCha
};
const formatDate = (dateString?: string) => {
if (!dateString) return 'Unknown date';
if (!dateString) return t('chat_history.unknown_date', 'Unknown date');
try {
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric',
@ -45,7 +48,7 @@ function DashboardChatHistoryItem({ workflow, onDelete, onResume }: DashboardCha
minute: '2-digit'
});
} catch (error) {
return 'Invalid date';
return t('chat_history.invalid_date', 'Invalid date');
}
};
@ -86,6 +89,11 @@ function DashboardChatHistoryItem({ workflow, onDelete, onResume }: DashboardCha
return statusLower === 'running' || statusLower === 'processing';
};
const getTranslatedStatus = (status: string): string => {
const statusKey = `status.${status.toLowerCase()}`;
return t(statusKey, status.toUpperCase());
};
const renderStatusIndicator = () => {
if (isRunning(workflow.status)) {
return (
@ -118,7 +126,7 @@ function DashboardChatHistoryItem({ workflow, onDelete, onResume }: DashboardCha
backgroundColor: getStatusBackgroundColor(workflow.status)
}}
>
{workflow.status.toUpperCase()}
{getTranslatedStatus(workflow.status)}
</span>
);
}
@ -143,19 +151,19 @@ function DashboardChatHistoryItem({ workflow, onDelete, onResume }: DashboardCha
{renderStatusIndicator()}
{workflow.currentRound && (
<span className={styles.workflowRound}>
Round {workflow.currentRound}
{t('chat_history.round', 'Round')} {workflow.currentRound}
</span>
)}
</div>
<div className={styles.workflowDates}>
{workflow.startedAt && (
<p className={styles.workflowDate}>
Started: {formatDate(workflow.startedAt)}
{t('chat_history.started', 'Started:')} {formatDate(workflow.startedAt)}
</p>
)}
{workflow.lastActivity && (
<p className={styles.workflowDate}>
Last Activity: {formatDate(workflow.lastActivity)}
{t('chat_history.last_activity', 'Last Activity:')} {formatDate(workflow.lastActivity)}
</p>
)}
</div>
@ -175,7 +183,7 @@ function DashboardChatHistoryItem({ workflow, onDelete, onResume }: DashboardCha
<button
onClick={handleResume}
className={`${styles.actionButton} ${styles.resumeButton}`}
title="Resume workflow"
title={t('chat_history.resume_tooltip', 'Resume workflow')}
>
<FaArrowRight size={16} />
</button>
@ -184,7 +192,7 @@ function DashboardChatHistoryItem({ workflow, onDelete, onResume }: DashboardCha
onClick={handleDelete}
disabled={isDeleting}
className={`${styles.actionButton} ${styles.deleteButton}`}
title="Delete workflow"
title={t('chat_history.delete_tooltip', 'Delete workflow')}
>
<AiOutlineDelete size={16} />
</button>
@ -193,7 +201,7 @@ function DashboardChatHistoryItem({ workflow, onDelete, onResume }: DashboardCha
{isDeleting && (
<div className={styles.deletingMessage}>
Deleting workflow...
{t('chat_history.deleting', 'Deleting workflow...')}
</div>
)}
</div>

View file

@ -3,6 +3,7 @@ import { motion } from "framer-motion";
import styles from './DashboardLog.module.css';
import { useApiRequest } from '../../../hooks/useApi';
import { useLanguage } from '../../../contexts/LanguageContext';
interface DashboardLogProps {
isExpanded: boolean;
@ -11,6 +12,7 @@ interface DashboardLogProps {
}
const DashboardLog: React.FC<DashboardLogProps> = ({ isExpanded, workflowId, workflowCompleted = false }) => {
const { t } = useLanguage();
const [logs, setLogs] = useState<any[]>([]);
const [isPolling, setIsPolling] = useState(false);
const [logsError, setLogsError] = useState<string | null>(null);
@ -33,7 +35,7 @@ const DashboardLog: React.FC<DashboardLogProps> = ({ isExpanded, workflowId, wor
setLogsError(null);
} catch (error: any) {
console.error('Error fetching logs:', error);
setLogsError(error.message || 'Failed to fetch logs');
setLogsError(error.message || t('dashboard.log.fetch_failed'));
}
};
@ -108,7 +110,7 @@ const DashboardLog: React.FC<DashboardLogProps> = ({ isExpanded, workflowId, wor
return (
<div className={styles.console_placeholder}>
<span className={styles.console_prompt}>$</span>
<span className={styles.console_text}>No workflow selected</span>
<span className={styles.console_text}>{t('dashboard.log.no_workflow')}</span>
</div>
);
}
@ -117,7 +119,7 @@ const DashboardLog: React.FC<DashboardLogProps> = ({ isExpanded, workflowId, wor
return (
<div className={styles.console_placeholder}>
<span className={styles.console_prompt}>$</span>
<span className={styles.console_text}>Loading logs...</span>
<span className={styles.console_text}>{t('dashboard.log.loading')}</span>
</div>
);
}
@ -126,15 +128,15 @@ const DashboardLog: React.FC<DashboardLogProps> = ({ isExpanded, workflowId, wor
return (
<div className={styles.console_placeholder}>
<span className={styles.console_prompt}>$</span>
<span className={styles.console_text}>Error loading logs: {logsError}</span>
<span className={styles.console_text}>{t('dashboard.log.error')}: {logsError}</span>
</div>
);
}
if (logs.length === 0) {
const statusText = workflowCompleted
? "No logs available for this workflow"
: "Workflow running... Waiting for logs...";
? t('dashboard.log.no_logs')
: t('dashboard.log.waiting');
return (
<div className={styles.console_placeholder}>
@ -152,7 +154,7 @@ const DashboardLog: React.FC<DashboardLogProps> = ({ isExpanded, workflowId, wor
<span className={styles.log_timestamp}>
{log.timestamp ? new Date(log.timestamp).toLocaleTimeString() : ''}
</span>
<span className={styles.log_level}>[{log.level || 'INFO'}]</span>
<span className={styles.log_level}>[{log.level || t('dashboard.log.level.info')}]</span>
<span className={styles.log_message}>{log.message || log.content || JSON.stringify(log)}</span>
</div>
))}
@ -177,7 +179,7 @@ const DashboardLog: React.FC<DashboardLogProps> = ({ isExpanded, workflowId, wor
whileHover={{ scale: 1.05 }}
transition={{ duration: 0.2, ease: "easeOut" }}
>
Log {workflowId && `- Workflow ${workflowId.substring(0, 8)}...`}
{t('dashboard.log.title')} {workflowId && `- ${t('dashboard.log.workflow')} ${workflowId.substring(0, 8)}...`}
</motion.div>
<motion.div
className={styles.horizontalLine}

View file

@ -5,6 +5,7 @@ import { MdExpandMore, MdExpandLess } from "react-icons/md";
import DashboardPromptSettings from './DashboardPromptSettings/DashboardPromptSettings';
import DashboardPromptSet from './DashboardPromptSet/DashboardPromptSet';
import { Prompt } from '../../../hooks/usePrompts';
import { useLanguage } from '../../../contexts/LanguageContext';
import styles from './DashboardPrompt.module.css';
@ -19,7 +20,8 @@ const DashboardPrompt: React.FC<DashboardPromptProps> = ({
isCollapsed,
onToggleCollapse
}) => {
const [activeTab, setActiveTab] = useState("Prompt Vorlage");
const { t } = useLanguage();
const [activeTab, setActiveTab] = useState(t('dashboard.prompt.template'));
const [searchParams] = useSearchParams();
useEffect(() => {
@ -27,19 +29,19 @@ const DashboardPrompt: React.FC<DashboardPromptProps> = ({
const promptId = searchParams.get('promptId');
if (expandedPrompt) {
setActiveTab("Prompt Vorlage");
setActiveTab(t('dashboard.prompt.template'));
} else if (promptId) {
setActiveTab("Einstellungen");
setActiveTab(t('dashboard.prompt.settings'));
}
}, [searchParams]);
}, [searchParams, t]);
return (
<div className={`${styles.dashboard_prompt} ${isCollapsed ? styles.collapsed : ''}`}>
<div className={ styles.prompt_header }>
<div className={ styles.prompt_button_div }>
{[
"Prompt Vorlage",
"Einstellungen"
t('dashboard.prompt.template'),
t('dashboard.prompt.settings')
].map((tab) => (
<div key={tab} className={styles.buttonWrapper}>
<button
@ -83,7 +85,7 @@ const DashboardPrompt: React.FC<DashboardPromptProps> = ({
}}
>
<div style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }}>
{activeTab === "Prompt Vorlage" ? (
{activeTab === t('dashboard.prompt.template') ? (
<DashboardPromptSet onPromptRun={onPromptRun} />
) : (
<DashboardPromptSettings />

View file

@ -1,5 +1,6 @@
import React, { useState } from 'react';
import { usePrompts, usePromptOperations, Prompt } from '../../../../hooks/usePrompts';
import { useLanguage } from '../../../../contexts/LanguageContext';
import DashboardPromptSetItem from './DashboardPromptSetItem';
import DashboardPromptSetModal from './DashboardPromptSetModal';
import styles from './DashboardPromptSet.module.css';
@ -10,6 +11,7 @@ interface DashboardPromptSetProps {
}
function DashboardPromptSet({ onPromptRun }: DashboardPromptSetProps) {
const { t } = useLanguage();
const { prompts, loading, error, refetch } = usePrompts();
const { handlePromptCreate, creatingPrompt } = usePromptOperations();
const [isModalOpen, setIsModalOpen] = useState(false);
@ -27,7 +29,7 @@ function DashboardPromptSet({ onPromptRun }: DashboardPromptSetProps) {
if (loading) {
return (
<div className={styles.loadingContainer}>
<div className={styles.loadingText}>Prompts werden geladen...</div>
<div className={styles.loadingText}>{t('promptset.loading')}</div>
</div>
);
}
@ -35,12 +37,12 @@ function DashboardPromptSet({ onPromptRun }: DashboardPromptSetProps) {
if (error) {
return (
<div className={styles.errorContainer}>
<div className={styles.errorText}>Fehler beim Laden der Prompts: {error}</div>
<div className={styles.errorText}>{t('promptset.error.loading')}: {error}</div>
<button
onClick={refetch}
className={styles.retryButton}
>
Erneut versuchen
{t('promptset.retry')}
</button>
</div>
);
@ -52,11 +54,11 @@ function DashboardPromptSet({ onPromptRun }: DashboardPromptSetProps) {
<div className={styles.headerButtons}>
<button className={styles.addButton} onClick={() => setIsModalOpen(true)}>
<FaPlus />
Neuer Prompt
{t('promptset.new_prompt')}
</button>
</div>
<div className={styles.promptCount}>
{prompts.length} {prompts.length === 1 ? 'Prompt' : 'Prompts'}
{prompts.length} {prompts.length === 1 ? t('promptset.prompt_count') : t('promptset.prompt_count_plural')}
</div>
</div>
@ -64,7 +66,7 @@ function DashboardPromptSet({ onPromptRun }: DashboardPromptSetProps) {
<div className={styles.scrollableContent}>
{prompts.length === 0 ? (
<div className={styles.emptyState}>
Keine Prompts verfügbar
{t('promptset.no_prompts')}
</div>
) : (
<div className={styles.promptsList}>

View file

@ -3,6 +3,7 @@ import { FaArrowRight } from 'react-icons/fa';
import { AiOutlineDelete } from 'react-icons/ai';
import { BsShareFill } from 'react-icons/bs';
import { usePromptOperations, Prompt } from '../../../../hooks/usePrompts';
import { useLanguage } from '../../../../contexts/LanguageContext';
import styles from './DashboardPromptSetItem.module.css';
interface DashboardPromptSetItemProps {
@ -12,6 +13,7 @@ interface DashboardPromptSetItemProps {
}
function DashboardPromptSetItem({ prompt, onDelete, onRun }: DashboardPromptSetItemProps) {
const { t } = useLanguage();
const { handlePromptDelete, deletingPrompts, deleteError } = usePromptOperations();
const contentRef = useRef<HTMLDivElement>(null);
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
@ -54,7 +56,7 @@ function DashboardPromptSetItem({ prompt, onDelete, onRun }: DashboardPromptSetI
</h3>
{prompt.createdAt && (
<p className={styles.promptDate}>
Erstellt: {new Date(prompt.createdAt).toLocaleDateString('de-DE')}
{t('promptset.created')}: {new Date(prompt.createdAt).toLocaleDateString('de-DE')}
</p>
)}
</div>
@ -70,7 +72,7 @@ function DashboardPromptSetItem({ prompt, onDelete, onRun }: DashboardPromptSetI
<button
onClick={handleRun}
className={`${styles.actionButton} ${styles.runButton}`}
title="Prompt ausführen"
title={t('promptset.run_tooltip')}
>
<FaArrowRight size={16} />
</button>
@ -78,7 +80,7 @@ function DashboardPromptSetItem({ prompt, onDelete, onRun }: DashboardPromptSetI
<button
onClick={handleShare}
className={`${styles.actionButton} ${styles.shareButton}`}
title="Prompt teilen"
title={t('promptset.share_tooltip')}
>
<BsShareFill size={16} />
</button>
@ -87,25 +89,25 @@ function DashboardPromptSetItem({ prompt, onDelete, onRun }: DashboardPromptSetI
onClick={handleDeleteClick}
disabled={isDeleting}
className={`${styles.actionButton} ${styles.deleteButton} ${showDeleteConfirm ? styles.confirm : ''}`}
title={showDeleteConfirm ? "Klicken Sie erneut zum Bestätigen" : "Prompt löschen"}
title={showDeleteConfirm ? t('promptset.confirm_delete') : t('promptset.delete_tooltip')}
onBlur={handleCancelDelete}
>
<AiOutlineDelete size={16} />
{isDeleting && <span className={styles.actionText}>Löschen...</span>}
{showDeleteConfirm && <span className={styles.actionText}>Zum Bestätigen klicken</span>}
{isDeleting && <span className={styles.actionText}>{t('promptset.deleting')}</span>}
{showDeleteConfirm && <span className={styles.actionText}>{t('promptset.confirm_click')}</span>}
</button>
</div>
</div>
{deleteError && (
<div className={styles.errorMessage}>
Fehler beim Löschen: {deleteError}
{t('promptset.delete_error')}: {deleteError}
</div>
)}
{isDeleting && (
<div className={styles.deletingMessage}>
Prompt wird gelöscht...
{t('promptset.deleting_message')}
</div>
)}
</div>

View file

@ -1,5 +1,6 @@
import React, { useState } from 'react';
import { FaTimes } from 'react-icons/fa';
import { useLanguage } from '../../../../contexts/LanguageContext';
import styles from './DashboardPromptSetModal.module.css';
interface DashboardPromptSetModalProps {
@ -10,6 +11,7 @@ interface DashboardPromptSetModalProps {
}
function DashboardPromptSetModal({ isOpen, onClose, onSubmit, isLoading = false }: DashboardPromptSetModalProps) {
const { t } = useLanguage();
const [name, setName] = useState('');
const [content, setContent] = useState('');
const [error, setError] = useState<string | null>(null);
@ -18,12 +20,12 @@ function DashboardPromptSetModal({ isOpen, onClose, onSubmit, isLoading = false
e.preventDefault();
if (!name.trim()) {
setError('Name ist erforderlich');
setError(t('modal.name_required'));
return;
}
if (!content.trim()) {
setError('Inhalt ist erforderlich');
setError(t('modal.content_required'));
return;
}
@ -36,7 +38,7 @@ function DashboardPromptSetModal({ isOpen, onClose, onSubmit, isLoading = false
setContent('');
onClose();
} catch (err: any) {
setError(err.message || 'Fehler beim Erstellen des Prompts');
setError(err.message || t('modal.create_error'));
}
};
@ -53,7 +55,7 @@ function DashboardPromptSetModal({ isOpen, onClose, onSubmit, isLoading = false
<div className={styles.overlay}>
<div className={styles.modal}>
<div className={styles.header}>
<h2 className={styles.title}>Neuen Prompt erstellen</h2>
<h2 className={styles.title}>{t('modal.create_prompt')}</h2>
<button
onClick={handleClose}
className={styles.closeButton}
@ -66,7 +68,7 @@ function DashboardPromptSetModal({ isOpen, onClose, onSubmit, isLoading = false
<form onSubmit={handleSubmit} className={styles.form}>
<div className={styles.formGroup}>
<label htmlFor="promptName" className={styles.label}>
Name *
{t('modal.name_label')} *
</label>
<input
id="promptName"
@ -74,7 +76,7 @@ function DashboardPromptSetModal({ isOpen, onClose, onSubmit, isLoading = false
value={name}
onChange={(e) => setName(e.target.value)}
className={styles.input}
placeholder="Geben Sie einen Namen für den Prompt ein"
placeholder={t('modal.name_placeholder')}
disabled={isLoading}
maxLength={100}
/>
@ -82,14 +84,14 @@ function DashboardPromptSetModal({ isOpen, onClose, onSubmit, isLoading = false
<div className={styles.formGroup}>
<label htmlFor="promptContent" className={styles.label}>
Inhalt *
{t('modal.content_label')} *
</label>
<textarea
id="promptContent"
value={content}
onChange={(e) => setContent(e.target.value)}
className={styles.textarea}
placeholder="Geben Sie den Inhalt des Prompts ein"
placeholder={t('modal.content_placeholder')}
disabled={isLoading}
rows={8}
/>
@ -108,14 +110,14 @@ function DashboardPromptSetModal({ isOpen, onClose, onSubmit, isLoading = false
className={styles.cancelButton}
disabled={isLoading}
>
Abbrechen
{t('modal.cancel')}
</button>
<button
type="submit"
className={styles.submitButton}
disabled={isLoading}
>
{isLoading ? 'Erstellen...' : 'Prompt erstellen'}
{isLoading ? t('modal.creating') : t('modal.create')}
</button>
</div>
</form>

View file

@ -1,12 +1,15 @@
import React from 'react';
import { useLanguage } from '../../../../contexts/LanguageContext';
import styles from './DashboardPromptSettings.module.css';
function DashboardPromptSettings() {
const { t } = useLanguage();
return (
<div className={styles.container}>
<div className={styles.content}>
<h1 className={styles.title}>Dashboard Prompt Settings</h1>
<p>Settings content will be added here in future updates.</p>
<h1 className={styles.title}>{t('prompt_settings.title')}</h1>
<p>{t('prompt_settings.content_placeholder')}</p>
</div>
</div>
);

View file

@ -3,6 +3,7 @@ import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa";
import { motion, AnimatePresence } from "framer-motion";
import DateienItem from './DateienItem';
import { UserFile } from '../../hooks/useFiles';
import { useLanguage } from '../../contexts/LanguageContext';
import styles from './DateienLists.module.css';
// Sort types
@ -16,6 +17,7 @@ interface DateienAllProps {
}
const DateienAll: React.FC<DateienAllProps> = ({ files, onFileDeleted, onOptimisticDelete }) => {
const { t } = useLanguage();
const [sortField, setSortField] = useState<SortField>('created_at');
const [sortDirection, setSortDirection] = useState<SortDirection>('desc');
@ -72,7 +74,7 @@ const DateienAll: React.FC<DateienAllProps> = ({ files, onFileDeleted, onOptimis
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
>
<p>Keine Dateien gefunden.</p>
<p>{t('files.no_files', 'No files found.')}</p>
</motion.div>
);
}
@ -87,19 +89,19 @@ const DateienAll: React.FC<DateienAllProps> = ({ files, onFileDeleted, onOptimis
{/* Table Headers */}
<div className={styles.tableHeader}>
<div className={styles.headerCell} onClick={() => handleSort('file_name')}>
<span>Name</span>
<span>{t('files.header.name', 'Name')}</span>
{renderSortIcon('file_name')}
</div>
<div className={styles.headerCell} onClick={() => handleSort('action')}>
<span>Typ</span>
<span>{t('files.header.type', 'Type')}</span>
{renderSortIcon('action')}
</div>
<div className={styles.headerCell} onClick={() => handleSort('size')}>
<span>Größe</span>
<span>{t('files.header.size', 'Size')}</span>
{renderSortIcon('size')}
</div>
<div className={styles.headerCell} onClick={() => handleSort('created_at')}>
<span>Datum</span>
<span>{t('files.header.date', 'Date')}</span>
{renderSortIcon('created_at')}
</div>
</div>

View file

@ -3,6 +3,7 @@ import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa";
import { motion, AnimatePresence } from "framer-motion";
import DateienItem from './DateienItem';
import { UserFile } from '../../hooks/useFiles';
import { useLanguage } from '../../contexts/LanguageContext';
import styles from './DateienLists.module.css';
// Sort types
@ -16,6 +17,7 @@ interface DateienCreatedProps {
}
const DateienCreated: React.FC<DateienCreatedProps> = ({ files, onFileDeleted, onOptimisticDelete }) => {
const { t } = useLanguage();
const [sortField, setSortField] = useState<SortField>('created_at');
const [sortDirection, setSortDirection] = useState<SortDirection>('desc');
@ -72,7 +74,7 @@ const DateienCreated: React.FC<DateienCreatedProps> = ({ files, onFileDeleted, o
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
>
<p>Keine von der KI erstellten Dateien gefunden.</p>
<p>{t('files.no_ai_files', 'No AI-created files found.')}</p>
</motion.div>
);
}
@ -87,19 +89,19 @@ const DateienCreated: React.FC<DateienCreatedProps> = ({ files, onFileDeleted, o
{/* Table Headers */}
<div className={styles.tableHeader}>
<div className={styles.headerCell} onClick={() => handleSort('file_name')}>
<span>Name</span>
<span>{t('files.header.name', 'Name')}</span>
{renderSortIcon('file_name')}
</div>
<div className={styles.headerCell} onClick={() => handleSort('action')}>
<span>Typ</span>
<span>{t('files.header.type', 'Type')}</span>
{renderSortIcon('action')}
</div>
<div className={styles.headerCell} onClick={() => handleSort('size')}>
<span>Größe</span>
<span>{t('files.header.size', 'Size')}</span>
{renderSortIcon('size')}
</div>
<div className={styles.headerCell} onClick={() => handleSort('created_at')}>
<span>Datum</span>
<span>{t('files.header.date', 'Date')}</span>
{renderSortIcon('created_at')}
</div>
</div>

View file

@ -218,22 +218,22 @@
display: flex;
align-items: center;
font-size: 18px;
color: #9ca3af;
color: var(--color-text);
}
.checkedIcon {
color: var(--Brand-Green-Green, #3A8088);
color: var(--color-secondary);
}
.uncheckedIcon {
color: #d1d5db;
color: var(--color-text);
}
.fileIcon {
display: flex;
align-items: center;
font-size: 16px;
color: #6b7280;
color: var(--color-text);
}
.fileInfo {
@ -243,7 +243,7 @@
.fileName {
font-weight: 500;
color: #111827;
color: var(--color-text);
margin-bottom: 2px;
overflow: hidden;
text-overflow: ellipsis;
@ -252,7 +252,7 @@
.fileDetails {
font-size: 12px;
color: #6b7280;
color: var(--color-text);
}
.footer {
@ -261,17 +261,17 @@
justify-content: flex-end;
gap: 12px;
padding: 20px;
border-top: 1px solid #e5e7eb;
background-color: #f9fafb;
border-top: 1px solid var(--color-gray-disabled);
background-color: var(--color-bg);
flex-shrink: 0;
}
.cancelButton {
padding: 10px 16px;
border: 1px solid #d1d5db;
border: 1px solid var(--color-red);
border-radius: 8px;
background-color: #ffffff;
color: #374151;
background-color: var(--color-bg);
color: var(--color-text);
font-size: 14px;
font-weight: 500;
cursor: pointer;
@ -279,16 +279,16 @@
}
.cancelButton:hover {
background-color: #f9fafb;
border-color: #9ca3af;
background-color: var(--color-red);
border-color: var(--color-red);
}
.confirmButton {
padding: 10px 16px;
border: none;
border-radius: 8px;
background-color: var(--Brand-Green-Green, #3A8088);
color: white;
background-color: var(--color-secondary);
color: var(--color-bg);
font-size: 14px;
font-weight: 500;
cursor: pointer;
@ -296,11 +296,11 @@
}
.confirmButton:hover:not(:disabled) {
background-color: #2d6b73;
background-color: var(--color-secondary-hover);
}
.confirmButton:disabled {
background-color: #9ca3af;
background-color: var(--color-secondary-disabled);
cursor: not-allowed;
}

View file

@ -8,6 +8,7 @@ import DateienAll from '../DateienAll';
import DateienUploads from '../DateienUploads';
import DateienCreated from '../DateienCreated';
import DateienShared from '../DateienShared';
import { useLanguage } from '../../../contexts/LanguageContext';
import styles from './DateienSelector.module.css';
type FileListType = 'all' | 'uploads' | 'created' | 'shared';
@ -25,6 +26,7 @@ const DateienSelector: React.FC<DateienSelectorProps> = ({
onFilesSelected,
allowMultiple = true
}) => {
const { t } = useLanguage();
const [selectedFiles, setSelectedFiles] = useState<Set<number>>(new Set());
const [activeTab, setActiveTab] = useState<FileListType>('all');
const [showUploadTool, setShowUploadTool] = useState(false);
@ -156,10 +158,10 @@ const DateienSelector: React.FC<DateienSelectorProps> = ({
};
const labels = {
all: 'Alle Dateien',
uploads: 'Hochgeladen',
created: 'KI-erstellt',
shared: 'Geteilt'
all: t('files.selector.tab.all', 'All files'),
uploads: t('files.selector.tab.uploads', 'Uploaded'),
created: t('files.selector.tab.created', 'AI-created'),
shared: t('files.selector.tab.shared', 'Shared')
};
return `${labels[type]} (${counts[type]})`;
@ -187,7 +189,7 @@ const DateienSelector: React.FC<DateienSelectorProps> = ({
transition={{ duration: 0.3 }}
>
<div className={styles.header}>
<h2>Dateien auswählen</h2>
<h2>{t('files.selector.title', 'Select files')}</h2>
<button className={styles.closeButton} onClick={onClose}>
<IoClose />
</button>
@ -217,18 +219,18 @@ const DateienSelector: React.FC<DateienSelectorProps> = ({
>
{selectedFiles.size === filteredFiles.length ? (
<>
<IoCheckbox /> Alle abwählen
<IoCheckbox /> {t('files.selector.deselect_all', 'Deselect all')}
</>
) : (
<>
<IoSquareOutline /> Alle auswählen
<IoSquareOutline /> {t('files.selector.select_all', 'Select all')}
</>
)}
</button>
)}
{selectedFiles.size > 0 && (
<span className={styles.selectionCount}>
{selectedFiles.size} Datei{selectedFiles.size !== 1 ? 'en' : ''} ausgewählt
{selectedFiles.size} {selectedFiles.size === 1 ? t('files.selector.file_selected', 'File') : t('files.selector.files_selected', 'Files')} {t('files.selector.selected_suffix', 'selected')}
</span>
)}
</div>
@ -238,18 +240,18 @@ const DateienSelector: React.FC<DateienSelectorProps> = ({
onClick={() => setShowUploadTool(true)}
>
<IoCloudUploadOutline />
Neue Datei hochladen
{t('files.selector.upload_new', 'Upload new file')}
</button>
</div>
{/* File List */}
<div className={styles.fileListContainer}>
{loading ? (
<div className={styles.loading}>Dateien werden geladen...</div>
<div className={styles.loading}>{t('files.selector.loading', 'Loading files...')}</div>
) : error ? (
<div className={styles.error}>Fehler beim Laden der Dateien: {error}</div>
<div className={styles.error}>{t('files.selector.error_loading', 'Error loading files:')} {error}</div>
) : filteredFiles.length === 0 ? (
<div className={styles.noFiles}>Keine Dateien in dieser Kategorie gefunden.</div>
<div className={styles.noFiles}>{t('files.no_files', 'No files found.')}</div>
) : (
<div className={styles.selectableFileList}>
{filteredFiles.map(file => (
@ -273,7 +275,7 @@ const DateienSelector: React.FC<DateienSelectorProps> = ({
<div className={styles.fileInfo}>
<div className={styles.fileName}>{file.file_name}</div>
<div className={styles.fileDetails}>
{file.action} {file.size ? `${Math.round(file.size / 1024)} KB` : 'Unbekannte Größe'}
{file.action} {file.size ? `${Math.round(file.size / 1024)} KB` : t('files.unknown_size', 'Unknown Size')}
</div>
</div>
</div>
@ -289,7 +291,7 @@ const DateienSelector: React.FC<DateienSelectorProps> = ({
className={styles.cancelButton}
onClick={onClose}
>
Abbrechen
{t('common.cancel', 'Cancel')}
</button>
<button
className={styles.confirmButton}
@ -297,8 +299,8 @@ const DateienSelector: React.FC<DateienSelectorProps> = ({
disabled={selectedFiles.size === 0}
>
{selectedFiles.size === 0
? 'Dateien auswählen'
: `${selectedFiles.size} Datei${selectedFiles.size !== 1 ? 'en' : ''} hinzufügen`
? t('files.selector.title', 'Select files')
: `${selectedFiles.size} ${selectedFiles.size === 1 ? t('files.selector.file_selected', 'File') : t('files.selector.files_selected', 'Files')} ${t('common.save', 'Save')}`
}
</button>
</div>

View file

@ -4,6 +4,7 @@ import styles from './DateienUploadTool.module.css';
import { IoCloudUploadOutline } from "react-icons/io5";
import { IoClose } from "react-icons/io5";
import { useFileOperations } from '../../../hooks/useFiles';
import { useLanguage } from '../../../contexts/LanguageContext';
interface DateienUploadToolProps {
isOpen: boolean;
@ -12,6 +13,7 @@ interface DateienUploadToolProps {
}
function DateienUploadTool({ isOpen, onClose, onFileUpload }: DateienUploadToolProps) {
const { t } = useLanguage();
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [isUploading, setIsUploading] = useState(false);
const [uploadStatus, setUploadStatus] = useState<{ success: boolean; message: string } | null>(null);
@ -41,7 +43,7 @@ function DateienUploadTool({ isOpen, onClose, onFileUpload }: DateienUploadToolP
if (result.success) {
setUploadStatus({
success: true,
message: 'Datei erfolgreich hochgeladen!'
message: t('files.upload.success', 'File uploaded successfully!')
});
onFileUpload(selectedFile);
setSelectedFile(null);
@ -52,13 +54,13 @@ function DateienUploadTool({ isOpen, onClose, onFileUpload }: DateienUploadToolP
} else {
setUploadStatus({
success: false,
message: uploadError || 'Beim Hochladen ist ein Fehler aufgetreten.'
message: uploadError || t('files.upload.error', 'An error occurred while uploading.')
});
}
} catch (error) {
setUploadStatus({
success: false,
message: 'Beim Hochladen ist ein unerwarteter Fehler aufgetreten.'
message: t('files.upload.unexpected_error', 'An unexpected error occurred while uploading.')
});
} finally {
setIsUploading(false);
@ -72,7 +74,7 @@ function DateienUploadTool({ isOpen, onClose, onFileUpload }: DateienUploadToolP
<div className={styles.overlay}>
<div className={styles.modal}>
<div className={styles.modalHeader}>
<h2>Datei hochladen</h2>
<h2>{t('files.upload.title', 'Upload file')}</h2>
<button className={styles.closeButton} onClick={onClose} disabled={isUploading}>
<IoClose />
</button>
@ -91,15 +93,15 @@ function DateienUploadTool({ isOpen, onClose, onFileUpload }: DateienUploadToolP
<input {...getInputProps()} disabled={isUploading} />
<IoCloudUploadOutline className={styles.uploadIcon} />
{isDragActive ? (
<p>Datei hier ablegen...</p>
<p>{t('files.upload.drop_here', 'Drop file here...')}</p>
) : isUploading ? (
<p>Lädt hoch...</p>
<p>{t('files.upload.uploading', 'Uploading...')}</p>
) : (
<div className={styles.dropzoneText}>
<p>Dateien hierher ziehen</p>
<p>oder</p>
<p>{t('files.upload.drag_files', 'Drag files here')}</p>
<p>{t('files.upload.or', 'or')}</p>
<button className={styles.browseButton} disabled={isUploading}>
Durchsuchen
{t('files.upload.browse', 'Browse')}
</button>
</div>
)}
@ -107,13 +109,13 @@ function DateienUploadTool({ isOpen, onClose, onFileUpload }: DateienUploadToolP
{selectedFile && !isUploading && !uploadStatus?.success && (
<div className={styles.selectedFile}>
<p>Ausgewählte Datei: {selectedFile.name}</p>
<p>{t('files.upload.selected_file', 'Selected file:')} {selectedFile.name}</p>
<button
className={styles.uploadButton}
onClick={handleUpload}
disabled={isUploading}
>
{isUploading ? 'Wird hochgeladen...' : 'Hochladen'}
{isUploading ? t('files.upload.uploading_button', 'Uploading...') : t('files.upload.upload_button', 'Upload')}
</button>
</div>
)}

View file

@ -5,6 +5,7 @@ import { useState } from "react";
import { useFileOperations } from "../../hooks/useFiles";
import FilePreviewPopup from "../Dashboard/DashboardChat/DashboardChatArea/FilePreviewPopup";
import { Document } from "../Dashboard/DashboardChat/DashboardChatArea/dashboardChatAreaTypes";
import { useLanguage } from "../../contexts/LanguageContext";
type DateienItemProps = {
file: {
@ -19,11 +20,20 @@ type DateienItemProps = {
onOptimisticDelete?: () => void; // New prop for immediate UI update
};
/**
const DateienItem = ({ file, onDelete, onOptimisticDelete }: DateienItemProps) => {
const { t } = useLanguage();
const { downloadingFiles, deletingFiles, handleFileDownload, handleFileDelete } = useFileOperations();
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [previewDocument, setPreviewDocument] = useState<Document | null>(null);
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
const isDownloading = downloadingFiles.has(file.id);
const isDeleting = deletingFiles.has(file.id);
/**
* Formats a file size in bytes to a human-readable string (KB, MB, etc.)
*/
const formatFileSize = (bytes?: number): string => {
if (bytes === undefined || bytes === null) return 'Unbekannte Größe';
const formatFileSize = (bytes?: number): string => {
if (bytes === undefined || bytes === null) return t('files.unknown_size', 'Unknown Size');
if (bytes === 0) return '0 Bytes';
@ -33,31 +43,23 @@ const formatFileSize = (bytes?: number): string => {
if (i === 0) return `${bytes} ${sizes[i]}`;
return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`;
};
};
/**
/**
* Formats the file source for display
*/
const formatFileSource = (source?: string): string => {
const formatFileSource = (source?: string): string => {
switch (source) {
case 'user_uploaded':
return 'Hochgeladen';
return t('files.source.uploaded', 'Uploaded');
case 'agent_created':
return 'KI-erstellt';
return t('files.source.ai_created', 'AI-created');
case 'shared_with_me':
return 'Geteilt';
return t('files.source.shared', 'Shared');
default:
return 'Unbekannt';
return t('files.source.unknown', 'Unknown');
}
};
const DateienItem = ({ file, onDelete, onOptimisticDelete }: DateienItemProps) => {
const { downloadingFiles, deletingFiles, handleFileDownload, handleFileDelete } = useFileOperations();
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [previewDocument, setPreviewDocument] = useState<Document | null>(null);
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
const isDownloading = downloadingFiles.has(file.id);
const isDeleting = deletingFiles.has(file.id);
};
// Format the date properly
const formatDate = (dateString: string) => {
@ -65,7 +67,7 @@ const DateienItem = ({ file, onDelete, onOptimisticDelete }: DateienItemProps) =
const date = new Date(dateString);
// Check if date is valid
if (isNaN(date.getTime())) {
return 'Unbekanntes Datum';
return t('files.unknown_date', 'Unknown Date');
}
return date.toLocaleDateString('de-DE', {
@ -75,7 +77,7 @@ const DateienItem = ({ file, onDelete, onOptimisticDelete }: DateienItemProps) =
});
} catch (e) {
console.error('Error formatting date:', e);
return 'Unbekanntes Datum';
return t('files.unknown_date', 'Unknown Date');
}
};
@ -153,7 +155,7 @@ const DateienItem = ({ file, onDelete, onOptimisticDelete }: DateienItemProps) =
className={styles.previewButton}
onClick={handlePreview}
disabled={isDownloading || isDeleting}
title="Preview file"
title={t('files.preview_tooltip', 'Preview file')}
>
<MdOutlineRemoveRedEye className={styles.actionIcon} />
</button>
@ -161,21 +163,21 @@ const DateienItem = ({ file, onDelete, onOptimisticDelete }: DateienItemProps) =
className={`${styles.downloadButton} ${isDownloading ? styles.downloading : ''}`}
onClick={() => handleFileDownload(file.id, file.file_name)}
disabled={isDownloading || isDeleting}
title="Download file"
title={t('files.download_tooltip', 'Download file')}
>
<FaDownload className={styles.actionIcon} />
{isDownloading && <span className={styles.actionText}>Laden...</span>}
{isDownloading && <span className={styles.actionText}>{t('files.downloading', 'Downloading...')}</span>}
</button>
<button
className={`${styles.deleteButton} ${isDeleting ? styles.deleting : ''} ${showDeleteConfirm ? styles.confirm : ''}`}
onClick={handleDeleteClick}
disabled={isDownloading || isDeleting}
title={showDeleteConfirm ? "Click again to confirm deletion" : "Delete file"}
title={showDeleteConfirm ? t('files.delete_confirm_tooltip', 'Click again to confirm deletion') : t('files.delete_tooltip', 'Delete file')}
onBlur={handleCancelDelete}
>
<FaTrash className={styles.actionIcon} />
{isDeleting && <span className={styles.actionText}>Löschen...</span>}
{showDeleteConfirm && <span className={styles.actionText}>Zum Bestätigen klicken...</span>}
{isDeleting && <span className={styles.actionText}>{t('files.deleting', 'Deleting...')}</span>}
{showDeleteConfirm && <span className={styles.actionText}>{t('files.delete_confirm', 'Click to confirm...')}</span>}
</button>
</div>
</div>

View file

@ -3,6 +3,7 @@ import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa";
import { motion, AnimatePresence } from "framer-motion";
import DateienItem from './DateienItem';
import { UserFile } from '../../hooks/useFiles';
import { useLanguage } from '../../contexts/LanguageContext';
import styles from './DateienLists.module.css';
// Sort types
@ -16,6 +17,7 @@ interface DateienSharedProps {
}
const DateienShared: React.FC<DateienSharedProps> = ({ files, onFileDeleted, onOptimisticDelete }) => {
const { t } = useLanguage();
const [sortField, setSortField] = useState<SortField>('created_at');
const [sortDirection, setSortDirection] = useState<SortDirection>('desc');
@ -72,7 +74,7 @@ const DateienShared: React.FC<DateienSharedProps> = ({ files, onFileDeleted, onO
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
>
<p>Keine mit Ihnen geteilten Dateien gefunden.</p>
<p>{t('files.no_shared_files', 'No shared files found.')}</p>
</motion.div>
);
}
@ -87,19 +89,19 @@ const DateienShared: React.FC<DateienSharedProps> = ({ files, onFileDeleted, onO
{/* Table Headers */}
<div className={styles.tableHeader}>
<div className={styles.headerCell} onClick={() => handleSort('file_name')}>
<span>Name</span>
<span>{t('files.header.name', 'Name')}</span>
{renderSortIcon('file_name')}
</div>
<div className={styles.headerCell} onClick={() => handleSort('action')}>
<span>Typ</span>
<span>{t('files.header.type', 'Type')}</span>
{renderSortIcon('action')}
</div>
<div className={styles.headerCell} onClick={() => handleSort('size')}>
<span>Größe</span>
<span>{t('files.header.size', 'Size')}</span>
{renderSortIcon('size')}
</div>
<div className={styles.headerCell} onClick={() => handleSort('created_at')}>
<span>Datum</span>
<span>{t('files.header.date', 'Date')}</span>
{renderSortIcon('created_at')}
</div>
</div>

View file

@ -3,6 +3,7 @@ import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa";
import { motion, AnimatePresence } from "framer-motion";
import DateienItem from './DateienItem';
import { UserFile } from '../../hooks/useFiles';
import { useLanguage } from '../../contexts/LanguageContext';
import styles from './DateienLists.module.css';
// Sort types
@ -16,6 +17,7 @@ interface DateienUploadsProps {
}
const DateienUploads: React.FC<DateienUploadsProps> = ({ files, onFileDeleted, onOptimisticDelete }) => {
const { t } = useLanguage();
const [sortField, setSortField] = useState<SortField>('created_at');
const [sortDirection, setSortDirection] = useState<SortDirection>('desc');
@ -72,7 +74,7 @@ const DateienUploads: React.FC<DateienUploadsProps> = ({ files, onFileDeleted, o
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
>
<p>Keine hochgeladenen Dateien gefunden.</p>
<p>{t('files.no_uploaded_files', 'No uploaded files found.')}</p>
</motion.div>
);
}
@ -87,19 +89,19 @@ const DateienUploads: React.FC<DateienUploadsProps> = ({ files, onFileDeleted, o
{/* Table Headers */}
<div className={styles.tableHeader}>
<div className={styles.headerCell} onClick={() => handleSort('file_name')}>
<span>Name</span>
<span>{t('files.header.name', 'Name')}</span>
{renderSortIcon('file_name')}
</div>
<div className={styles.headerCell} onClick={() => handleSort('action')}>
<span>Typ</span>
<span>{t('files.header.type', 'Type')}</span>
{renderSortIcon('action')}
</div>
<div className={styles.headerCell} onClick={() => handleSort('size')}>
<span>Größe</span>
<span>{t('files.header.size', 'Size')}</span>
{renderSortIcon('size')}
</div>
<div className={styles.headerCell} onClick={() => handleSort('created_at')}>
<span>Datum</span>
<span>{t('files.header.date', 'Date')}</span>
{renderSortIcon('created_at')}
</div>
</div>

View file

@ -8,24 +8,27 @@ import { FaRegFileAlt } from "react-icons/fa";
import { TbLogs } from "react-icons/tb";
import { useMemo } from 'react';
import { useLanguage } from '../../contexts/LanguageContext';
const useSidebarData = () => {
const { t } = useLanguage();
return useMemo(() => [
{
id: '1',
name: 'Team-Bereich',
name: t('nav.team'),
link: '/team-bereich',
icon: MdOutlineWorkOutline,
},
{
id: '2',
name: 'Aktivitätszentrum',
name: t('nav.dashboard'),
link: '/dashboard',
icon: LuTicket,
},
{
id: '3',
name: 'Dateien',
name: t('nav.files'),
link: '/dateien',
icon: FaRegFileAlt,
},
@ -37,7 +40,7 @@ const useSidebarData = () => {
},*/
{
id: '7',
name: 'Einstellungen',
name: t('nav.settings'),
link: '/einstellungen',
icon: GoGear,
},
@ -47,7 +50,7 @@ const useSidebarData = () => {
link: '',
icon: BiInfoSquare,
},*/
], []);
], [t]);
}
export default useSidebarData;

View file

@ -0,0 +1,67 @@
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import { Language, translations } from './languageContextData';
// Re-export Language type for convenience
export type { Language };
interface LanguageContextType {
currentLanguage: Language;
setLanguage: (language: Language) => void;
t: (key: string, fallback?: string) => string;
}
const LanguageContext = createContext<LanguageContextType | undefined>(undefined);
interface LanguageProviderProps {
children: ReactNode;
}
export const LanguageProvider: React.FC<LanguageProviderProps> = ({ children }) => {
const [currentLanguage, setCurrentLanguage] = useState<Language>('de');
// Load saved language preference on mount
useEffect(() => {
const savedLanguage = localStorage.getItem('language') as Language;
if (savedLanguage && ['de', 'en', 'fr'].includes(savedLanguage)) {
setCurrentLanguage(savedLanguage);
} else {
// Detect browser language
const browserLang = navigator.language.split('-')[0] as Language;
if (['de', 'en', 'fr'].includes(browserLang)) {
setCurrentLanguage(browserLang);
}
}
}, []);
const setLanguage = (language: Language) => {
setCurrentLanguage(language);
localStorage.setItem('language', language);
};
const t = (key: string, fallback?: string): string => {
const translation = translations[currentLanguage]?.[key] || translations['de'][key] || fallback || key;
return translation;
};
const contextValue: LanguageContextType = {
currentLanguage,
setLanguage,
t
};
return (
<LanguageContext.Provider value={contextValue}>
{children}
</LanguageContext.Provider>
);
};
export const useLanguage = (): LanguageContextType => {
const context = useContext(LanguageContext);
if (!context) {
throw new Error('useLanguage must be used within a LanguageProvider');
}
return context;
};

View file

@ -0,0 +1,626 @@
// Language type definition
export type Language = 'de' | 'en' | 'fr';
// Translation keys and their values for each language
export type TranslationKeys = {
[key: string]: string;
};
export type Translations = {
[K in Language]: TranslationKeys;
};
export const translations: Translations = {
de: {
// Navigation
'nav.dashboard': 'Aktivitätszentrum',
'nav.files': 'Dateien',
'nav.team': 'Team-Bereich',
'nav.settings': 'Einstellungen',
// Settings page
'settings.title': 'Einstellungen',
'settings.appearance': 'Darstellung',
'settings.language': 'Sprache',
'settings.about': 'Über',
'settings.version': 'Version',
'settings.theme': 'Theme',
'settings.theme.description': 'Wechseln Sie zwischen hellem und dunklem Modus',
'settings.language.description': 'Wählen Sie Ihre bevorzugte Sprache',
'settings.theme.light': 'Hell',
'settings.theme.dark': 'Dunkel',
'settings.theme.toggle.light': 'Zu hellem Modus wechseln',
'settings.theme.toggle.dark': 'Zu dunklem Modus wechseln',
// Languages
'language.german': 'Deutsch',
'language.english': 'English',
'language.french': 'Français',
// Common
'common.loading': 'Laden...',
'common.error': 'Fehler',
'common.success': 'Erfolgreich',
'common.cancel': 'Abbrechen',
'common.save': 'Speichern',
'common.delete': 'Löschen',
'common.edit': 'Bearbeiten',
'common.close': 'Schließen',
// Auth
'auth.login': 'Anmelden',
'auth.register': 'Registrieren',
'auth.logout': 'Abmelden',
'auth.email': 'E-Mail',
'auth.password': 'Passwort',
// Dashboard
'dashboard.prompt.template': 'Prompt Vorlage',
'dashboard.prompt.settings': 'Einstellungen',
'dashboard.chat.area': 'Chatbereich',
'dashboard.chat.history': 'Workflow-Verlauf',
'dashboard.log.title': 'Log',
'dashboard.log.workflow': 'Workflow',
'dashboard.log.no_workflow': 'Kein Workflow ausgewählt',
'dashboard.log.loading': 'Logs werden geladen...',
'dashboard.log.error': 'Fehler beim Laden der Logs',
'dashboard.log.no_logs': 'Keine Logs für diesen Workflow verfügbar',
'dashboard.log.waiting': 'Workflow läuft... Warte auf Logs...',
'dashboard.log.fetch_failed': 'Logs konnten nicht geladen werden',
'dashboard.log.level.info': 'INFO',
// Prompt Set
'promptset.loading': 'Prompts werden geladen...',
'promptset.error.loading': 'Fehler beim Laden der Prompts',
'promptset.retry': 'Erneut versuchen',
'promptset.new_prompt': 'Neuer Prompt',
'promptset.prompt_count': 'Prompt',
'promptset.prompt_count_plural': 'Prompts',
'promptset.no_prompts': 'Keine Prompts verfügbar',
'promptset.created': 'Erstellt',
'promptset.run_tooltip': 'Prompt ausführen',
'promptset.share_tooltip': 'Prompt teilen',
'promptset.delete_tooltip': 'Prompt löschen',
'promptset.confirm_delete': 'Klicken Sie erneut zum Bestätigen',
'promptset.deleting': 'Löschen...',
'promptset.confirm_click': 'Zum Bestätigen klicken',
'promptset.delete_error': 'Fehler beim Löschen',
'promptset.deleting_message': 'Prompt wird gelöscht...',
// Prompt Modal
'modal.create_prompt': 'Neuen Prompt erstellen',
'modal.name_required': 'Name ist erforderlich',
'modal.content_required': 'Inhalt ist erforderlich',
'modal.create_error': 'Fehler beim Erstellen des Prompts',
'modal.name_label': 'Name',
'modal.content_label': 'Inhalt',
'modal.name_placeholder': 'Geben Sie einen Namen für den Prompt ein',
'modal.content_placeholder': 'Geben Sie den Inhalt des Prompts ein',
'modal.cancel': 'Abbrechen',
'modal.creating': 'Erstellen...',
'modal.create': 'Prompt erstellen',
// Prompt Settings
'prompt_settings.title': 'Prompt Einstellungen',
'prompt_settings.content_placeholder': 'Einstellungen werden in zukünftigen Updates hinzugefügt.',
// Chat Area
'chat.continue_conversation': 'Gespräch fortsetzen...',
'chat.enter_message': 'Nachricht eingeben...',
'chat.remove_file': 'Datei entfernen',
'chat.attach_file': 'Datei anhängen',
'chat.you': 'You',
'chat.click_to_open': 'Klicken Sie, um zu öffnen',
'chat.preview_document': 'Dokument vorschauen',
'chat.download_document': 'Dokument herunterladen',
'chat.workflow_failed': 'Workflow fehlgeschlagen.',
'chat.retry_workflow': 'Nochmal versuchen',
'chat.sending_followup': 'Folgenachricht wird gesendet...',
'chat.sending_message': 'Nachricht wird gesendet...',
'chat.error_prefix': 'Fehler:',
'chat.error_loading_messages': 'Fehler beim Laden der Nachrichten:',
'chat.loading_workflow_messages': 'Workflow-Nachrichten werden geladen...',
'chat.start_conversation': 'Beginne ein Gespräch, indem du eine Nachricht eingibst, eine Vorlage auswählst oder einen vorherigen Workflow fortsetzt …',
// File Preview
'file_preview.loading': 'Vorschau wird geladen...',
'file_preview.error': 'Fehler',
'file_preview.no_preview': 'Keine Vorschau verfügbar',
'file_preview.close_preview': 'Vorschau schließen',
'file_preview.python': 'Python',
// Chat History
'chat_history.loading': 'Workflows werden geladen...',
'chat_history.error_loading': 'Fehler beim Laden der Workflows:',
'chat_history.try_again': 'Nochmal versuchen',
'chat_history.title': 'Workflow-Verlauf',
'chat_history.workflow_count': 'Workflow',
'chat_history.workflow_count_plural': 'Workflows',
'chat_history.empty_state': 'Keine Workflows verfügbar',
'chat_history.confirm_delete': 'Sind Sie sicher, dass Sie Workflow "{id}..." löschen möchten?',
'chat_history.no_message_content': 'Kein Nachrichteninhalt verfügbar',
'chat_history.unknown_date': 'Unbekanntes Datum',
'chat_history.invalid_date': 'Ungültiges Datum',
'chat_history.started': 'Gestartet:',
'chat_history.last_activity': 'Letzte Aktivität:',
'chat_history.round': 'Runde',
'chat_history.resume_tooltip': 'Workflow fortsetzen',
'chat_history.delete_tooltip': 'Workflow löschen',
'chat_history.deleting': 'Workflow wird gelöscht...',
// Workflow Status
'status.error': 'FEHLER',
'status.failed': 'FEHLGESCHLAGEN',
'status.stopped': 'GESTOPPT',
'status.cancelled': 'ABGEBROCHEN',
'status.running': 'LÄUFT',
'status.processing': 'VERARBEITUNG',
'status.completed': 'ABGESCHLOSSEN',
'status.pending': 'WARTEND',
// Files
'files.unknown_size': 'Unbekannte Größe',
'files.unknown_date': 'Unbekanntes Datum',
'files.source.uploaded': 'Hochgeladen',
'files.source.ai_created': 'KI-erstellt',
'files.source.shared': 'Geteilt',
'files.source.unknown': 'Unbekannt',
'files.preview_tooltip': 'Datei vorschauen',
'files.download_tooltip': 'Datei herunterladen',
'files.delete_tooltip': 'Datei löschen',
'files.delete_confirm_tooltip': 'Klicken Sie erneut zum Bestätigen der Löschung',
'files.downloading': 'Laden...',
'files.deleting': 'Löschen...',
'files.delete_confirm': 'Zum Bestätigen klicken...',
'files.no_files': 'Keine Dateien gefunden.',
'files.no_shared_files': 'Keine mit Ihnen geteilten Dateien gefunden.',
'files.no_ai_files': 'Keine von der KI erstellten Dateien gefunden.',
'files.no_uploaded_files': 'Keine hochgeladenen Dateien gefunden.',
'files.header.name': 'Name',
'files.header.type': 'Typ',
'files.header.size': 'Größe',
'files.header.date': 'Datum',
'files.selector.title': 'Dateien auswählen',
'files.selector.tab.all': 'Alle Dateien',
'files.selector.tab.uploads': 'Hochgeladen',
'files.selector.tab.created': 'KI-erstellt',
'files.selector.tab.shared': 'Geteilt',
'files.selector.select_all': 'Alle auswählen',
'files.selector.deselect_all': 'Alle abwählen',
'files.selector.file_selected': 'Datei',
'files.selector.files_selected': 'Dateien',
'files.selector.selected_suffix': 'ausgewählt',
'files.selector.upload_new': 'Neue Datei hochladen',
'files.selector.loading': 'Dateien werden geladen...',
'files.selector.error_loading': 'Fehler beim Laden der Dateien:',
'files.upload.title': 'Datei hochladen',
'files.upload.drop_here': 'Datei hier ablegen...',
'files.upload.uploading': 'Lädt hoch...',
'files.upload.drag_files': 'Dateien hierher ziehen',
'files.upload.or': 'oder',
'files.upload.browse': 'Durchsuchen',
'files.upload.selected_file': 'Ausgewählte Datei:',
'files.upload.upload_button': 'Hochladen',
'files.upload.uploading_button': 'Wird hochgeladen...',
'files.upload.success': 'Datei erfolgreich hochgeladen!',
'files.upload.error': 'Beim Hochladen ist ein Fehler aufgetreten.',
'files.upload.unexpected_error': 'Beim Hochladen ist ein unerwarteter Fehler aufgetreten.',
// Files Page
'files.page.tab.all': 'Alle Dateien',
'files.page.tab.uploads': 'Meine Uploads',
'files.page.tab.created': 'Erstellte Dateien',
'files.page.tab.shared': 'Geteilte Dateien',
'files.page.add_file': 'Datei hinzufügen',
'files.page.loading': 'Dateien werden geladen...',
'files.page.error': 'Fehler:',
},
en: {
// Navigation
'nav.dashboard': 'Activity Center',
'nav.files': 'Files',
'nav.team': 'Team Area',
'nav.settings': 'Settings',
// Settings page
'settings.title': 'Settings',
'settings.appearance': 'Appearance',
'settings.language': 'Language',
'settings.about': 'About',
'settings.version': 'Version',
'settings.theme': 'Theme',
'settings.theme.description': 'Switch between light and dark mode',
'settings.language.description': 'Choose your preferred language',
'settings.theme.light': 'Light',
'settings.theme.dark': 'Dark',
'settings.theme.toggle.light': 'Switch to light mode',
'settings.theme.toggle.dark': 'Switch to dark mode',
// Languages
'language.german': 'Deutsch',
'language.english': 'English',
'language.french': 'Français',
// Common
'common.loading': 'Loading...',
'common.error': 'Error',
'common.success': 'Success',
'common.cancel': 'Cancel',
'common.save': 'Save',
'common.delete': 'Delete',
'common.edit': 'Edit',
'common.close': 'Close',
// Auth
'auth.login': 'Login',
'auth.register': 'Register',
'auth.logout': 'Logout',
'auth.email': 'Email',
'auth.password': 'Password',
// Dashboard
'dashboard.prompt.template': 'Prompt Template',
'dashboard.prompt.settings': 'Settings',
'dashboard.chat.area': 'Chat Area',
'dashboard.chat.history': 'Workflow History',
'dashboard.log.title': 'Log',
'dashboard.log.workflow': 'Workflow',
'dashboard.log.no_workflow': 'No workflow selected',
'dashboard.log.loading': 'Loading logs...',
'dashboard.log.error': 'Error loading logs',
'dashboard.log.no_logs': 'No logs available for this workflow',
'dashboard.log.waiting': 'Workflow running... Waiting for logs...',
'dashboard.log.fetch_failed': 'Failed to fetch logs',
'dashboard.log.level.info': 'INFO',
// Prompt Set
'promptset.loading': 'Loading prompts...',
'promptset.error.loading': 'Error loading prompts',
'promptset.retry': 'Try again',
'promptset.new_prompt': 'New Prompt',
'promptset.prompt_count': 'Prompt',
'promptset.prompt_count_plural': 'Prompts',
'promptset.no_prompts': 'No prompts available',
'promptset.created': 'Created',
'promptset.run_tooltip': 'Run prompt',
'promptset.share_tooltip': 'Share prompt',
'promptset.delete_tooltip': 'Delete prompt',
'promptset.confirm_delete': 'Click again to confirm',
'promptset.deleting': 'Deleting...',
'promptset.confirm_click': 'Click to confirm',
'promptset.delete_error': 'Error deleting',
'promptset.deleting_message': 'Deleting prompt...',
// Prompt Modal
'modal.create_prompt': 'Create New Prompt',
'modal.name_required': 'Name is required',
'modal.content_required': 'Content is required',
'modal.create_error': 'Error creating prompt',
'modal.name_label': 'Name',
'modal.content_label': 'Content',
'modal.name_placeholder': 'Enter a name for the prompt',
'modal.content_placeholder': 'Enter the prompt content',
'modal.cancel': 'Cancel',
'modal.creating': 'Creating...',
'modal.create': 'Create Prompt',
// Prompt Settings
'prompt_settings.title': 'Prompt Settings',
'prompt_settings.content_placeholder': 'Settings content will be added here in future updates.',
// Chat Area
'chat.continue_conversation': 'Continue conversation...',
'chat.enter_message': 'Enter message...',
'chat.remove_file': 'Remove file',
'chat.attach_file': 'Attach file',
'chat.you': 'You',
'chat.click_to_open': 'Click to open',
'chat.preview_document': 'Preview document',
'chat.download_document': 'Download document',
'chat.workflow_failed': 'Workflow failed.',
'chat.retry_workflow': 'Try again',
'chat.sending_followup': 'Sending follow-up message...',
'chat.sending_message': 'Sending message...',
'chat.error_prefix': 'Error:',
'chat.error_loading_messages': 'Error loading messages:',
'chat.loading_workflow_messages': 'Loading workflow messages...',
'chat.start_conversation': 'Start a conversation by entering a message, selecting a template, or continuing a previous workflow...',
// File Preview
'file_preview.loading': 'Loading preview...',
'file_preview.error': 'Error',
'file_preview.no_preview': 'No preview available',
'file_preview.close_preview': 'Close preview',
'file_preview.python': 'Python',
// Chat History
'chat_history.loading': 'Loading workflows...',
'chat_history.error_loading': 'Error loading workflows:',
'chat_history.try_again': 'Try Again',
'chat_history.title': 'Workflow History',
'chat_history.workflow_count': 'Workflow',
'chat_history.workflow_count_plural': 'Workflows',
'chat_history.empty_state': 'No workflows available',
'chat_history.confirm_delete': 'Are you sure you want to delete workflow "{id}..."?',
'chat_history.no_message_content': 'No message content available',
'chat_history.unknown_date': 'Unknown date',
'chat_history.invalid_date': 'Invalid date',
'chat_history.started': 'Started:',
'chat_history.last_activity': 'Last Activity:',
'chat_history.round': 'Round',
'chat_history.resume_tooltip': 'Resume workflow',
'chat_history.delete_tooltip': 'Delete workflow',
'chat_history.deleting': 'Deleting workflow...',
// Workflow Status
'status.error': 'ERROR',
'status.failed': 'FAILED',
'status.stopped': 'STOPPED',
'status.cancelled': 'CANCELLED',
'status.running': 'RUNNING',
'status.processing': 'PROCESSING',
'status.completed': 'COMPLETED',
'status.pending': 'PENDING',
// Files
'files.unknown_size': 'Unknown Size',
'files.unknown_date': 'Unknown Date',
'files.source.uploaded': 'Uploaded',
'files.source.ai_created': 'AI-created',
'files.source.shared': 'Shared',
'files.source.unknown': 'Unknown',
'files.preview_tooltip': 'Preview file',
'files.download_tooltip': 'Download file',
'files.delete_tooltip': 'Delete file',
'files.delete_confirm_tooltip': 'Click again to confirm deletion',
'files.downloading': 'Downloading...',
'files.deleting': 'Deleting...',
'files.delete_confirm': 'Click to confirm...',
'files.no_files': 'No files found.',
'files.no_shared_files': 'No shared files found.',
'files.no_ai_files': 'No AI-created files found.',
'files.no_uploaded_files': 'No uploaded files found.',
'files.header.name': 'Name',
'files.header.type': 'Type',
'files.header.size': 'Size',
'files.header.date': 'Date',
'files.selector.title': 'Select files',
'files.selector.tab.all': 'All files',
'files.selector.tab.uploads': 'Uploaded',
'files.selector.tab.created': 'AI-created',
'files.selector.tab.shared': 'Shared',
'files.selector.select_all': 'Select all',
'files.selector.deselect_all': 'Deselect all',
'files.selector.file_selected': 'File',
'files.selector.files_selected': 'Files',
'files.selector.selected_suffix': 'selected',
'files.selector.upload_new': 'Upload new file',
'files.selector.loading': 'Loading files...',
'files.selector.error_loading': 'Error loading files:',
'files.upload.title': 'Upload file',
'files.upload.drop_here': 'Drop file here...',
'files.upload.uploading': 'Uploading...',
'files.upload.drag_files': 'Drag files here',
'files.upload.or': 'or',
'files.upload.browse': 'Browse',
'files.upload.selected_file': 'Selected file:',
'files.upload.upload_button': 'Upload',
'files.upload.uploading_button': 'Uploading...',
'files.upload.success': 'File uploaded successfully!',
'files.upload.error': 'An error occurred while uploading.',
'files.upload.unexpected_error': 'An unexpected error occurred while uploading.',
// Files Page
'files.page.tab.all': 'All Files',
'files.page.tab.uploads': 'My Uploads',
'files.page.tab.created': 'Created Files',
'files.page.tab.shared': 'Shared Files',
'files.page.add_file': 'Add File',
'files.page.loading': 'Loading files...',
'files.page.error': 'Error:',
},
fr: {
// Navigation
'nav.dashboard': 'Centre d\'activité',
'nav.files': 'Fichiers',
'nav.team': 'Espace équipe',
'nav.settings': 'Paramètres',
// Settings page
'settings.title': 'Paramètres',
'settings.appearance': 'Apparence',
'settings.language': 'Langue',
'settings.about': 'À propos',
'settings.version': 'Version',
'settings.theme': 'Thème',
'settings.theme.description': 'Basculer entre le mode clair et sombre',
'settings.language.description': 'Choisissez votre langue préférée',
'settings.theme.light': 'Clair',
'settings.theme.dark': 'Sombre',
'settings.theme.toggle.light': 'Passer en mode clair',
'settings.theme.toggle.dark': 'Passer en mode sombre',
// Languages
'language.german': 'Deutsch',
'language.english': 'English',
'language.french': 'Français',
// Common
'common.loading': 'Chargement...',
'common.error': 'Erreur',
'common.success': 'Succès',
'common.cancel': 'Annuler',
'common.save': 'Enregistrer',
'common.delete': 'Supprimer',
'common.edit': 'Modifier',
'common.close': 'Fermer',
// Auth
'auth.login': 'Se connecter',
'auth.register': 'S\'inscrire',
'auth.logout': 'Se déconnecter',
'auth.email': 'E-mail',
'auth.password': 'Mot de passe',
// Dashboard
'dashboard.prompt.template': 'Modèle de prompt',
'dashboard.prompt.settings': 'Paramètres',
'dashboard.chat.area': 'Zone de chat',
'dashboard.chat.history': 'Historique des workflows',
'dashboard.log.title': 'Journal',
'dashboard.log.workflow': 'Workflow',
'dashboard.log.no_workflow': 'Aucun workflow sélectionné',
'dashboard.log.loading': 'Chargement des logs...',
'dashboard.log.error': 'Erreur lors du chargement des logs',
'dashboard.log.no_logs': 'Aucun log disponible pour ce workflow',
'dashboard.log.waiting': 'Workflow en cours... En attente des logs...',
'dashboard.log.fetch_failed': 'Échec du chargement des logs',
'dashboard.log.level.info': 'INFO',
// Prompt Set
'promptset.loading': 'Chargement des prompts...',
'promptset.error.loading': 'Erreur lors du chargement des prompts',
'promptset.retry': 'Réessayer',
'promptset.new_prompt': 'Nouveau prompt',
'promptset.prompt_count': 'Prompt',
'promptset.prompt_count_plural': 'Prompts',
'promptset.no_prompts': 'Aucun prompt disponible',
'promptset.created': 'Créé',
'promptset.run_tooltip': 'Exécuter le prompt',
'promptset.share_tooltip': 'Partager le prompt',
'promptset.delete_tooltip': 'Supprimer le prompt',
'promptset.confirm_delete': 'Cliquez à nouveau pour confirmer',
'promptset.deleting': 'Suppression...',
'promptset.confirm_click': 'Cliquez pour confirmer',
'promptset.delete_error': 'Erreur lors de la suppression',
'promptset.deleting_message': 'Suppression du prompt...',
// Prompt Modal
'modal.create_prompt': 'Créer un nouveau prompt',
'modal.name_required': 'Le nom est requis',
'modal.content_required': 'Le contenu est requis',
'modal.create_error': 'Erreur lors de la création du prompt',
'modal.name_label': 'Nom',
'modal.content_label': 'Contenu',
'modal.name_placeholder': 'Entrez un nom pour le prompt',
'modal.content_placeholder': 'Entrez le contenu du prompt',
'modal.cancel': 'Annuler',
'modal.creating': 'Création...',
'modal.create': 'Créer le prompt',
// Prompt Settings
'prompt_settings.title': 'Paramètres de prompt',
'prompt_settings.content_placeholder': 'Le contenu des paramètres sera ajouté dans les futures mises à jour.',
// Chat Area
'chat.continue_conversation': 'Continuer la conversation...',
'chat.enter_message': 'Entrez votre message...',
'chat.remove_file': 'Supprimer le fichier',
'chat.attach_file': 'Joindre un fichier',
'chat.you': 'Vous',
'chat.click_to_open': 'Cliquez pour ouvrir',
'chat.preview_document': 'Aperçu du document',
'chat.download_document': 'Télécharger le document',
'chat.workflow_failed': 'Échec du workflow.',
'chat.retry_workflow': 'Réessayer',
'chat.sending_followup': 'Envoi du message de suivi...',
'chat.sending_message': 'Envoi du message...',
'chat.error_prefix': 'Erreur:',
'chat.error_loading_messages': 'Erreur lors du chargement des messages:',
'chat.loading_workflow_messages': 'Chargement des messages de workflow...',
'chat.start_conversation': 'Commencez une conversation en entrant un message, en sélectionnant un modèle ou en continuant un workflow précédent...',
// File Preview
'file_preview.loading': 'Chargement de l\'aperçu...',
'file_preview.error': 'Erreur',
'file_preview.no_preview': 'Aucun aperçu disponible',
'file_preview.close_preview': 'Fermer l\'aperçu',
'file_preview.python': 'Python',
// Chat History
'chat_history.loading': 'Chargement des workflows...',
'chat_history.error_loading': 'Erreur lors du chargement des workflows:',
'chat_history.try_again': 'Réessayer',
'chat_history.title': 'Historique des workflows',
'chat_history.workflow_count': 'Workflow',
'chat_history.workflow_count_plural': 'Workflows',
'chat_history.empty_state': 'Aucun workflow disponible',
'chat_history.confirm_delete': 'Êtes-vous sûr de vouloir supprimer le workflow "{id}..."?',
'chat_history.no_message_content': 'Aucun contenu de message disponible',
'chat_history.unknown_date': 'Date inconnue',
'chat_history.invalid_date': 'Date invalide',
'chat_history.started': 'Démarré:',
'chat_history.last_activity': 'Dernière activité:',
'chat_history.round': 'Tour',
'chat_history.resume_tooltip': 'Reprendre le workflow',
'chat_history.delete_tooltip': 'Supprimer le workflow',
'chat_history.deleting': 'Suppression du workflow...',
// Workflow Status
'status.error': 'ERREUR',
'status.failed': 'ÉCHEC',
'status.stopped': 'ARRÊTÉ',
'status.cancelled': 'ANNULÉ',
'status.running': 'EN COURS',
'status.processing': 'TRAITEMENT',
'status.completed': 'TERMINÉ',
'status.pending': 'EN ATTENTE',
// Files
'files.unknown_size': 'Taille inconnue',
'files.unknown_date': 'Date inconnue',
'files.source.uploaded': 'Téléchargé',
'files.source.ai_created': 'Créé par IA',
'files.source.shared': 'Partagé',
'files.source.unknown': 'Inconnu',
'files.preview_tooltip': 'Aperçu du fichier',
'files.download_tooltip': 'Télécharger le fichier',
'files.delete_tooltip': 'Supprimer le fichier',
'files.delete_confirm_tooltip': 'Cliquez à nouveau pour confirmer la suppression',
'files.downloading': 'Téléchargement...',
'files.deleting': 'Suppression...',
'files.delete_confirm': 'Cliquez pour confirmer...',
'files.no_files': 'Aucun fichier trouvé.',
'files.no_shared_files': 'Aucun fichier partagé trouvé.',
'files.no_ai_files': 'Aucun fichier créé par IA trouvé.',
'files.no_uploaded_files': 'Aucun fichier téléchargé trouvé.',
'files.header.name': 'Nom',
'files.header.type': 'Type',
'files.header.size': 'Taille',
'files.header.date': 'Date',
'files.selector.title': 'Sélectionner des fichiers',
'files.selector.tab.all': 'Tous les fichiers',
'files.selector.tab.uploads': 'Téléchargés',
'files.selector.tab.created': 'Créés par IA',
'files.selector.tab.shared': 'Partagés',
'files.selector.select_all': 'Tout sélectionner',
'files.selector.deselect_all': 'Tout désélectionner',
'files.selector.file_selected': 'Fichier',
'files.selector.files_selected': 'Fichiers',
'files.selector.selected_suffix': 'sélectionné(s)',
'files.selector.upload_new': 'Télécharger un nouveau fichier',
'files.selector.loading': 'Chargement des fichiers...',
'files.selector.error_loading': 'Erreur lors du chargement des fichiers:',
'files.upload.title': 'Télécharger un fichier',
'files.upload.drop_here': 'Déposer le fichier ici...',
'files.upload.uploading': 'Téléchargement...',
'files.upload.drag_files': 'Glisser les fichiers ici',
'files.upload.or': 'ou',
'files.upload.browse': 'Parcourir',
'files.upload.selected_file': 'Fichier sélectionné:',
'files.upload.upload_button': 'Télécharger',
'files.upload.uploading_button': 'Téléchargement...',
'files.upload.success': 'Fichier téléchargé avec succès!',
'files.upload.error': 'Une erreur s\'est produite lors du téléchargement.',
'files.upload.unexpected_error': 'Une erreur inattendue s\'est produite lors du téléchargement.',
// Files Page
'files.page.tab.all': 'Tous les fichiers',
'files.page.tab.uploads': 'Mes téléchargements',
'files.page.tab.created': 'Fichiers créés',
'files.page.tab.shared': 'Fichiers partagés',
'files.page.add_file': 'Ajouter un fichier',
'files.page.loading': 'Chargement des fichiers...',
'files.page.error': 'Erreur:',
}
};

View file

@ -8,22 +8,24 @@ import DateienShared from '../../components/Dateien/DateienShared';
import { useState } from 'react';
import { useUserFiles, useFileOperations } from '../../hooks/useFiles';
import { motion, AnimatePresence } from "framer-motion";
import { useLanguage } from '../../contexts/LanguageContext';
// Tab types
type TabType = 'alle' | 'uploads' | 'erstellt' | 'geteilt';
function Dateien() {
const { t } = useLanguage();
const { files, loading, error, refetch, removeFileOptimistically } = useUserFiles();
const [isUploadOpen, setIsUploadOpen] = useState(false);
const { uploadError, downloadError, deleteError } = useFileOperations();
const [activeTab, setActiveTab] = useState<TabType>('alle');
// Tab configuration
// Tab configuration with translations
const tabs = [
{ key: 'alle' as TabType, label: 'Alle Dateien' },
{ key: 'uploads' as TabType, label: 'Meine Uploads' },
{ key: 'erstellt' as TabType, label: 'Erstellte Dateien' },
{ key: 'geteilt' as TabType, label: 'Geteilte Dateien' }
{ key: 'alle' as TabType, label: t('files.page.tab.all', 'All Files') },
{ key: 'uploads' as TabType, label: t('files.page.tab.uploads', 'My Uploads') },
{ key: 'erstellt' as TabType, label: t('files.page.tab.created', 'Created Files') },
{ key: 'geteilt' as TabType, label: t('files.page.tab.shared', 'Shared Files') }
];
// Single function to handle file refresh
@ -112,7 +114,7 @@ function Dateien() {
onClick={() => setIsUploadOpen(true)}
>
<IoAddCircleOutline className={styles.add_icon}/>
Datei hinzufügen
{t('files.page.add_file', 'Add File')}
</button>
</motion.div>
<div className={styles.horizontalLineLight}></div>
@ -130,8 +132,8 @@ function Dateien() {
</p>
)}
{loading && <p>Loading files...</p>}
{error && <p>Error: {error}</p>}
{loading && <p>{t('files.page.loading', 'Loading files...')}</p>}
{error && <p>{t('files.page.error', 'Error:')} {error}</p>}
{!loading && !error && (
<motion.div

View file

@ -148,6 +148,40 @@
font-weight: 500;
}
.languageSelect {
display: flex;
align-items: center;
padding: 12px 20px;
border-radius: 25px;
border: 2px solid var(--color-gray-disabled);
background: var(--color-bg);
color: var(--color-text);
cursor: pointer;
transition: all 0.3s ease;
font-family: var(--font-family);
font-size: 0.875rem;
font-weight: 500;
min-width: 140px;
outline: none;
}
.languageSelect:hover {
border-color: var(--color-secondary);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(63, 81, 181, 0.15);
}
.languageSelect:focus {
border-color: var(--color-primary);
box-shadow: 0 0 0 3px rgba(63, 81, 181, 0.1);
}
.languageSelect option {
background: var(--color-bg);
color: var(--color-text);
padding: 10px;
}
/* Responsive design */
@media (max-width: 768px) {
.einstellungenContainer {

View file

@ -1,8 +1,10 @@
import React, { useState, useEffect } from 'react';
import styles from './Einstellungen.module.css';
import { useLanguage, Language } from '../../contexts/LanguageContext';
function Einstellungen() {
const [isDarkMode, setIsDarkMode] = useState(false);
const { currentLanguage, setLanguage, t } = useLanguage();
// Sync component state with current theme on mount
useEffect(() => {
@ -29,27 +31,36 @@ function Einstellungen() {
localStorage.setItem('theme', newIsDarkMode ? 'dark' : 'light');
};
const getLanguageLabel = (lang: Language): string => {
switch (lang) {
case 'de': return t('language.german');
case 'en': return t('language.english');
case 'fr': return t('language.french');
default: return lang;
}
};
return (
<div className={styles.einstellungenContainer}>
<div className={styles.contentWrapper}>
<div className={styles.settingsCard}>
<h1 className={styles.title}>Einstellungen</h1>
<h1 className={styles.title}>{t('settings.title')}</h1>
<div className={styles.settingsSection}>
<h2 className={styles.sectionTitle}>Darstellung</h2>
<h2 className={styles.sectionTitle}>{t('settings.appearance')}</h2>
<div className={styles.settingItem}>
<div className={styles.settingInfo}>
<span className={styles.settingLabel}>Theme</span>
<span className={styles.settingLabel}>{t('settings.theme')}</span>
<span className={styles.settingDescription}>
Wechseln Sie zwischen hellem und dunklem Modus
{t('settings.theme.description')}
</span>
</div>
<button
className={`${styles.themeToggle} ${isDarkMode ? styles.dark : styles.light}`}
onClick={toggleTheme}
aria-label={isDarkMode ? 'Zu hellem Modus wechseln' : 'Zu dunklem Modus wechseln'}
aria-label={isDarkMode ? t('settings.theme.toggle.light') : t('settings.theme.toggle.dark')}
>
<div className={styles.toggleSlider}>
<div className={styles.toggleIcon}>
@ -57,17 +68,37 @@ function Einstellungen() {
</div>
</div>
<span className={styles.toggleLabel}>
{isDarkMode ? 'Dunkel' : 'Hell'}
{isDarkMode ? t('settings.theme.dark') : t('settings.theme.light')}
</span>
</button>
</div>
<div className={styles.settingItem}>
<div className={styles.settingInfo}>
<span className={styles.settingLabel}>{t('settings.language')}</span>
<span className={styles.settingDescription}>
{t('settings.language.description')}
</span>
</div>
<select
className={styles.languageSelect}
value={currentLanguage}
onChange={(e) => setLanguage(e.target.value as Language)}
aria-label={t('settings.language')}
>
<option value="de">{getLanguageLabel('de')}</option>
<option value="en">{getLanguageLabel('en')}</option>
<option value="fr">{getLanguageLabel('fr')}</option>
</select>
</div>
</div>
<div className={styles.settingsSection}>
<h2 className={styles.sectionTitle}>Über</h2>
<h2 className={styles.sectionTitle}>{t('settings.about')}</h2>
<div className={styles.settingItem}>
<div className={styles.settingInfo}>
<span className={styles.settingLabel}>Version</span>
<span className={styles.settingLabel}>{t('settings.version')}</span>
<span className={styles.settingDescription}>1.0.0</span>
</div>
</div>