diff --git a/packages/ui/package.json b/packages/ui/package.json index b04a3194f49..a9694aa77dd 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -48,6 +48,9 @@ "framer-motion": "^4.1.13", "history": "^5.0.0", "html-react-parser": "^3.0.4", + "i18next": "^26.0.4", + "i18next-browser-languagedetector": "^8.2.1", + "i18next-http-backend": "^3.0.4", "lodash": "^4.17.21", "lowlight": "^3.3.0", "moment": "^2.29.3", @@ -59,6 +62,7 @@ "react-datepicker": "^4.21.0", "react-device-detect": "^1.17.0", "react-dom": "^18.2.0", + "react-i18next": "^17.0.2", "react-markdown": "^8.0.6", "react-perfect-scrollbar": "^1.5.8", "react-redux": "^8.0.5", diff --git a/packages/ui/public/locales/en.json b/packages/ui/public/locales/en.json new file mode 100644 index 00000000000..5ab35ef72a8 --- /dev/null +++ b/packages/ui/public/locales/en.json @@ -0,0 +1,3393 @@ +{ + "common": { + "locale": "en-US", + "validation": { + "name": { + "required": "Name is required" + }, + "email": { + "required": "Email is required", + "invalid": "Invalid email address" + }, + "password": { + "atLeast8": "Password must be at least 8 characters", + "notMoreThan128": "Password must not be more than 128 characters", + "lowercase": "Password must contain at least one lowercase letter", + "uppercase": "Password must contain at least one uppercase letter", + "digit": "Password must contain at least one digit", + "special": "Password must contain at least one special character" + }, + "confirm": { + "required": "Confirm Password is required", + "match": "Passwords don't match" + }, + "invite": { + "required": "Invite Code is required" + } + }, + "labels": { + "output": "Output", + "description": "Description", + "documentation": "Documentation", + "error": "Error", + "language": "Language", + "model": "Model", + "name": "Name", + "none": "None", + "prompt": "Prompt", + "simpleTable": "simple table", + "state": "State", + "status": "Status", + "tokens": "Tokens", + "tools": "Tools", + "type": "Type", + "workspace": "Workspace", + "agents": "Agents", + "chatflow": "Chatflow", + "emailAddress": "Email Address", + "email": "Email", + "phoneNumber": "Phone Number", + "tabs": "tabs", + "phone": "Phone", + "variables": "Variables", + "chatflows": "Chatflows", + "user": "User", + "selectModel": "Select Model", + "weight": "weight" + }, + "actions": { + "add": "Add", + "addNew": "Add New", + "apply": "Apply", + "back": "Back", + "cancel": "Cancel", + "cardView": "Card View", + "clear": "Clear", + "clearAll": "Clear All", + "clearSearch": "Clear Search", + "close": "Close", + "confirm": "Confirm", + "delete": "Delete", + "download": "Download", + "edit": "Edit", + "editName": "Edit Name", + "expand": "Expand", + "export": "Export", + "generate": "Generate", + "listView": "List View", + "load": "Load", + "manageLinks": "Manage Links", + "next": "Next", + "no": "No", + "refresh": "Refresh", + "save": "Save", + "saveName": "Save Name", + "search": "Search", + "share": "Share", + "submit": "Submit", + "yes": "Yes", + "selectAll": "Select All", + "expandRow": "expand row", + "copy": "Copy", + "saveAsTemplate": "Save As Template" + }, + "dialogs": { + "delete": "$t(common.actions.delete)" + }, + "errors": { + "unknownError": "Unknown error", + "internalServerError": "Internal Server Error" + }, + "providers": { + "langSmith": "LangSmith", + "langFuse": "LangFuse", + "lunary": "Lunary", + "langWatch": "LangWatch", + "arize": "Arize", + "phoenix": "Phoenix", + "opik": "Opik", + "anthropic": "Anthropic Claude", + "azureOpenAI": "Azure ChatOpenAI", + "googleGemini": "Google Gemini", + "groq": "Groq", + "mistralAI": "Mistral AI", + "openAI": "OpenAI", + "ollama": "Ollama", + "none": "None", + "openAIWhisper": "OpenAI Whisper", + "assemblyAi": "Assembly AI", + "localAiSTT": "LocalAi STT", + "azureCognitive": "Azure Cognitive Services", + "groqWhisper": "Groq Whisper", + "openAiTts": "OpenAI TTS", + "elevenlabs": "Eleven Labs TTS", + "microsoft": "Microsoft", + "google": "Google", + "auth0": "Auth0", + "github": "Github" + }, + "formats": { + "dateMonthDayYear": "MMMM Do, YYYY", + "dateMonthDayYearTime24Long": "MMMM Do, YYYY HH:mm:ss", + "dateMonthDayYearTime12Seconds": "MMMM Do YYYY, h:mm:ss a", + "dateMonthDayYearTime12Short": "MMMM Do YYYY, hh:mm A", + "dateMonthShortDayYearTime12": "MMM D, YYYY h:mm A", + "dateDayMonthShortYearTime12Seconds": "DD-MMM-YYYY, hh:mm:ss A", + "dateDayMonthYearTime24": "DD/MM/YYYY HH:mm" + }, + "messages": { + "copied": "Copied!" + } + }, + "components": { + "header": { + "star": "Star", + "actions": { + "upgrade": "Upgrade" + }, + "messages": { + "signout": { + "success": "Logging out..." + } + } + }, + "mcpServer": { + "loading": "Loading MCP Server configuration...", + "saving": "Saving...", + "dialogs": { + "rotate": { + "title": "Rotate Token", + "description": "This will invalidate the existing token. Any clients using the old token will need to be updated with the new one. Are you sure?" + } + }, + "messages": { + "save": { + "success": "MCP Server settings saved", + "disabled": "MCP Server disabled", + "error": "Failed to save MCP Server settings: {{msg}}" + }, + "copyUrl": { + "success": "URL copied to clipboard" + }, + "copyToken": { + "success": "Token copied to clipboard" + }, + "rotate": { + "success": "Token rotated successfully", + "error": "Failed to rotate token: {{msg}}" + }, + "load": { + "error": "Failed to load MCP Server configuration: {{msg}}" + } + }, + "validation": { + "toolRequired": "Tool name is required", + "length": "Tool name must be 64 characters or less", + "only": "Only letters, numbers, underscores, and hyphens allowed" + }, + "actions": { + "rotate": "Rotate", + "expose": "Expose as MCP Server", + "copyUrl": "Copy URL to clipboard", + "copyToken": "Copy token", + "rotateToken": "Rotate token" + }, + "inputs": { + "toolName": { + "title": "Tool Name", + "placeholder": "e.g. product_qa", + "caption": "Used as the MCP tool identifier by LLM clients." + }, + "description": { + "placeholder": "e.g. Answers product catalog questions", + "caption": "Helps LLMs understand when to route queries to this tool. Good descriptions improve tool selection accuracy." + }, + "endpoint": { + "title": "Streamable HTTP Endpoint", + "caption": "For clients that support the Streamable HTTP transport" + }, + "token": { + "title": "Token (Bearer Token)", + "alert": { + "prefix": "Use the URL above as the MCP endpoint and pass the token as a Bearer token in the Authorization header. Configure your MCP client with:", + "suffix": "Authorization: Bearer" + } + } + } + }, + "dialogs": { + "about": { + "title": "Flowise Version", + "version": { + "current": "Current Version", + "latest": "Latest Version" + }, + "published": "Published At" + }, + "chatFeedback": { + "title": "Provide additional feedback", + "placeholder": "What do you think of the response?", + "actions": { + "submit": "Submit Feedback", + "enableChatFeedback": "Enable chat feedback" + } + }, + "agentFlows": { + "generating": "Generating your Agentflow...", + "placeholder": "Describe your agent here", + "selectModel": "Select model to generate agentflow", + "chooseAnOption": "choose an option", + "messages": { + "errors": { + "fields": { + "fill": "Please fill in all mandatory fields.", + "fillFields": "Please fill in the following required fields: {{fields}}", + "fillFIeldsForModel": "Please fill in all mandatory fields for the selected model." + }, + "failGenerateAgentflow": "Failed to generate agentflow" + } + } + }, + "chatflow": { + "statusBadgeEnabled": "ON", + "menu": { + "general": { + "title": "General" + }, + "rateLimit": { + "title": "Rate Limit", + "description": "Limit API requests per time window" + }, + "allowedDomains": { + "title": "Allowed Domains", + "description": "Restrict chatbot to specific domains" + }, + "leads": { + "title": "Leads", + "description": "Capture visitor contact information" + }, + "chat": { + "title": "Chat" + }, + "conversationStarters": { + "title": "Starter Prompts", + "description": "Suggested prompts for new conversations" + }, + "followUpPrompts": { + "title": "Follow-up Prompts", + "description": "Auto-generate follow-up questions" + }, + "chatFeedback": { + "title": "Chat Feedback", + "description": "Allow users to rate responses" + }, + "mediaAndFiles": { + "title": "Media & Files" + }, + "speechToText": { + "title": "Speech to Text", + "description": "Voice input transcription" + }, + "textToSpeech": { + "title": "Text to Speech", + "description": "Audio response playback" + }, + "fileUpload": { + "title": "File Upload", + "description": "Allow file uploads in chat" + }, + "advanced": { + "title": "Advanced" + }, + "analyseChatflow": { + "title": "Analytics", + "description": "Connect analytics providers" + }, + "postProcessing": { + "title": "Post Processing", + "description": "Custom JavaScript post-processing" + }, + "mcpServer": { + "title": "MCP Server", + "description": "Model Context Protocol server" + }, + "overrideConfig": { + "title": "Override Config", + "description": "Override flow configuration via API" + } + } + }, + "expand": { + "actions": { + "source": "Source", + "execute": "Execute" + } + }, + "exportAsTemplate": { + "title": "Export As Template", + "badge": "Badge", + "usecases": "Usecases", + "usecaseTooltip": "Type a usecase and press enter to add it to the list. You can add as many items as you want.", + "messages": { + "success": "Saved as template successfully!", + "errors": { + "requiredName": "Template Name is mandatory!", + "failedSave": "Failed to save as template!" + } + }, + "actions": { + "saveTemplate": "Save Template" + } + }, + "formatPrompt": { + "title": "Format Prompt Values" + }, + "inviteUsers": { + "title": "Invite Users", + "select": "Select Users", + "invite": "Invite {{email}}", + "notFound": "No results found", + "placeholder": "Invite users by name or email", + "tooltip": { + "sending": "An invitation will be sent to this email address", + "alreadyIn": "{{name}} is already a member of this workspace and won't be invited again." + }, + "roles": "Role to Assign", + "selectRole": "Select Role", + "messages": { + "success": "Users invited to workspace", + "errors": { + "alreadyIn": "The following users are already in the workspace or organization: {{users}}", + "failedSave": "Failed to invite users to workspace: {{msg}}" + }, + "warning": "One or more invalid emails were removed." + } + }, + "manageScrapedLinks": { + "title": "Manage Scraped Links - {{url}}", + "actions": { + "fetch": "Fetch Links", + "clearAll": { + "tooltip": "Clear All Links" + } + }, + "messages": { + "success": "Successfully fetched links" + }, + "links": "Scraped Links", + "placeholder": "Links scraped from the URL will appear here" + }, + "nodeInfo": { + "version": "version {{version}}", + "actions": { + "documentation": { + "tooltip": "Open Documentation" + } + } + }, + "nvidiaNim": { + "title": "NIM Setup", + "steps": { + "download": { + "title": "Download Installer", + "msg": "Would you like to download the NIM installer? Click Next if it has been installed" + }, + "pull": { + "title": "Pull Image", + "pulling": "Pulling image..." + }, + "start": { + "title": "Start Container", + "label": "Relax Memory Constraints", + "starting": "Starting container...", + "port": "Host Port", + "tooltip": "Click Next to start the container." + } + }, + "errors": { + "download": "Failed to download installer: {{msg}}", + "preload": "Failed to preload: {{msg}}", + "checkImage": "Failed to check image status: {{msg}}", + "pullImage": "Failed to pull image: {{msg}}", + "portAlreadyInUse": "Port {{port}} is already in use by another container. Please choose a different port.", + "containerStatus": "Failed to check container status: {{msg}}", + "containerStart": "Failed to start container: {{msg}}", + "invalidPort": "Please enter a valid port number between 1 and 65535", + "enterImageTag": "Please enter an image tag" + }, + "actions": { + "viewLicense": "View License", + "useExisting": "Use Existing" + }, + "exists": { + "title": "Container Already Exists", + "containerAlreadyExists": "A container for this image already exists:", + "name": "Name: {{name}}", + "status": "Status: {{status}}", + "notAvailable": "N/A", + "youCan": "You can:", + "useExisting": "Use the existing container (recommended)", + "changePort": "Change the port and try again" + } + }, + "promptGenerator": { + "describeTaskPlaceholder": "Describe your task here" + }, + "promptLangsmithHub": { + "usecase": "Usecase", + "mailboxFolder": "secondary mailbox folder", + "readme": "Readme", + "cases": { + "agentStimulation": "Agent Stimulation", + "autonomousAgents": "Autonomous agents", + "classification": "Classification", + "chatbots": "Chatbots", + "codeUnderstanding": "Code understanding", + "codeWriting": "Code writing", + "evaluation": "Evaluation", + "extraction": "Extraction", + "interactingApi": "Interacting with APIs", + "multiModal": "Multi-modal", + "qaDocuments": "QA over documents", + "selfChecking": "Self-checking", + "sql": "SQL", + "summarization": "Summarization", + "tagging": "Tagging" + }, + "langs": { + "chinese": "Chinese", + "english": "English", + "french": "French", + "german": "German", + "russian": "Russian", + "spanish": "Spanish" + }, + "loading": { + "wait": "Please wait....loading Prompts", + "notFound": "No Available Prompts", + "found": "Available Prompts" + } + }, + "saveChatFlow": { + "placeholder": "My New Chatflow" + }, + "shareWithWorkspace": { + "messages": { + "success": "Items Shared Successfully", + "error": "Failed to share Item: {{msg}}" + } + }, + "sourceDoc": { + "title": "Source Documents", + "errorLabel": "Error:" + }, + "starterPrompts": { + "title": "Conversation Starter Prompts" + }, + "tag": { + "title": "Set Chatflow Category Tags", + "addLabel": "Add a tag", + "tooltip": "Enter a tag and press enter to add it to the list. You can add as many tags as you want." + }, + "viewLeads": { + "placeholder": "Search Name or Email or Phone", + "noLeads": "No Leads", + "leadsTable": { + "created": "Created Date" + } + }, + "viewMessages": { + "removeMegLabel": "Remove messages from 3rd party Memory Node", + "delete": { + "title": "Delete Messages", + "description": "Are you sure you want to delete messages? This action cannot be undone.", + "messages": { + "success": "Succesfully deleted messages" + } + }, + "export": { + "messages": { + "success": "Messages exported successfully", + "error": "Error exporting messages" + } + }, + "clearChat": { + "title": "Clear Session", + "description": "Are you sure you want to clear messages?", + "descriptionSession": "Are you sure you want to clear session id: {{sessionId}} from {{memoryType}}?", + "messages": { + "success": { + "simple": "Succesfully cleared messages", + "session": "Succesfully cleared session id: {{sessionId}} from {{memoryType}}" + } + } + }, + "logs": { + "user": "User: {{msg}}", + "bot": "Bot: {{msg}}" + }, + "labels": { + "positive": "Positive", + "negative": "Negative", + "total": { + "sessions": "Total Sessions", + "messages": "Total Messages", + "received": "Total Feedback Received" + }, + "positiveFeedback": "Positive Feedback", + "noMessages": "No Messages", + "totalSessions": "Sessions {{current}} - {{limit}} of {{total}}", + "sessionId": "Session Id:\u00A0{{id}}", + "sourceType": "Source:\u00A0{{type}}", + "memoryType": "Memory:\u00A0{{type}}", + "emailType": "Email:\u00A0{{email}}", + "blubTooltip": "On the left 👈, you’ll see the Memory node used in this conversation. To delete the session conversations stored on that Memory node, you must have a matching Memory node with identical parameters in the canvas.", + "usedTools": "Used Tools", + "apiEmbed": "API/Embed", + "evaluations": "Evaluations", + "ui": "UI", + "mcp": "MCP", + "finished": "Finished" + }, + "actions": { + "exportJSON": "Export to JSON", + "deleteAll": "Delete All", + "clearMessage": "Clear Message", + "moreActions": "More Actions" + }, + "errors": { + "audioNotSupport": "Your browser does not support the <audio> tag." + }, + "inputs": { + "fromDate": "From Date", + "toDate": "To Date" + } + }, + "pricing": { + "title": "Pricing Plans", + "currentPlan": "Current Plan", + "mostPopular": "Most Popular", + "firstMonthFree": "First Month Free", + "confirmPlanChange": "Confirm Plan Change", + "additionalSeatsWarning": "You must remove additional seats and users before changing your plan.", + "workspacesWarning": "You must remove all workspaces except the default workspace before changing your plan.", + "apiKeysWarning": "You must remove all API keys with sharing permissions before changing your plan.", + "noPaymentMethod": "No payment method found", + "addPaymentMethod": "Add Payment Method in Billing Portal", + "openingBillingPortal": "Opening Billing Portal...", + "updatingPlan": "Updating Plan...", + "dueToday": "Due today", + "appliedAccountBalance": "Applied account balance", + "creditBalance": "Credit balance", + "creditNotice": "Your available credit will automatically apply to your next invoice.", + "firstMonthDiscount": "First Month Discount", + "eligibleFirstMonthFree": "You're eligible for your first month free!", + "paymentMethod": "Payment Method", + "messages": { + "updatePlan": { + "success": "Subscription updated successfully!", + "errors": { + "failedUpdate": "Subscription failed to update", + "failedVerify": "Failed to verify subscription" + } + } + }, + "actions": { + "confirmChange": "Confirm Change" + } + } + }, + "allowedDomains": { + "title": "Allowed Domains", + "shortTitle": "Domains", + "messages": { + "saved": "Allowed Origins Saved", + "failed": "Failed to save Allowed Origins: {{msg}}" + }, + "tooltip": "Your chatbot will only work when used from the following domains.", + "error": { + "title": "Error Message", + "tooltip": "Custom error message that will be shown when for unauthorized domain", + "placeholder": "Unauthorized domain!" + } + }, + "analyzeFlow": { + "on": "ON", + "messages": { + "save": { + "success": "Analytic Configuration Saved", + "error": "Failed to save Analytic Configuration: {{msg}}" + } + }, + "inputs": { + "credential": "Connect Credential", + "projectName": { + "title": "Project Name", + "descriptions": { + "langSmith": "If not provided, default will be used", + "arize": "If not provided, default will be used.", + "phoenix": "If not provided, default will be used.", + "opik": "Name of your Opik project" + } + }, + "release": { + "title": "Release", + "description": "The release number/hash of the application to provide analytics grouped by release" + }, + "status": "On/Off" + } + }, + "chatFeedback": { + "fallBackTitle": "Allowed Domains", + "messages": { + "success": "Chat Feedback Settings Saved", + "error": "Failed to save Chat Feedback Settings: {{msg}}" + } + }, + "fileUpload": { + "docsHelp": "The full contents of uploaded files will be converted to text and sent to the Agent.
Referdocs for more details.", + "messages": { + "success": "File Upload Configuration Saved", + "error": "Failed to save File Upload Configuration: {{msg}}" + }, + "enableLabel": "Enable Full File Upload", + "allowUploadsType": "Allow Uploads of Type", + "configuration": { + "title": "Advanced Settings", + "usage": { + "title": "PDF Processing", + "oneDocPerPage": "One document per page", + "oneDocPerFile": "One document per file" + } + } + }, + "followUpPrompts": { + "enableLabel": "Enable Follow-up Prompts", + "providersLabel": "Providers", + "messages": { + "success": "Follow-up Prompts configuration saved", + "error": "Failed to save follow-up prompts configuration: {{msg}}" + }, + "inputs": { + "connectCredential": "Connect Credential", + "modelName": { + "title": "Model Name", + "description": "Name of the Ollama model to use" + }, + "baseUrl": { + "title": "Base URL", + "description": "Base URL of your Ollama instance" + }, + "prompt": { + "descriptions": { + "prompt": "Prompt to generate questions based on the conversation history. You can use variable {history} to refer to the conversation history.", + "default": "Given the following conversations: {history}. Please help me predict the three most likely questions that human would ask and keeping each question short and concise." + } + }, + "temperature": "Temperature" + } + }, + "leads": { + "messages": { + "success": "Leads configuration Saved", + "error": "Failed to save Leads configuration: {{msg}}" + }, + "enableLeadCapture": "Enable Lead Capture", + "form": { + "title": "Form Title", + "placeholder": "Hey 👋 thanks for your interest!\nLet us know where we can reach you" + }, + "messageAfterLeadCaptured": { + "title": "Message after lead captured", + "placeholder": "Thank you!\nWhat can I do for you?" + }, + "formFields": "Form fields" + }, + "overrideConfig": { + "title": "Override Configuration", + "docsHelp": "Enable or disable which properties of the flow configuration can be overridden. Refer to the documentation for more information.", + "enableLabel": "Enable Override Configuration", + "nodes": "Nodes", + "messages": { + "success": "Override Configuration Saved", + "error": "Failed to save Override Configuration: {{msg}}" + }, + "schemaUnavailable": "No schema available", + "schema": "Schema" + }, + "postProcessing": { + "enableLabel": "Enable Post Processing", + "functionLabel": "JS Function", + "postProcessionLabel": "Post Processing Function", + "seeExample": "See Example", + "availableVariables": "Available Variables", + "variables": { + "rawOutput": { + "description": "The raw output response from the flow" + }, + "input": { + "description": "The user input message" + }, + "chatHistory": { + "description": "Array of previous messages in the conversation" + }, + "chatflowId": { + "description": "Unique identifier for the chatflow" + }, + "sessionId": { + "description": "Current session identifier" + }, + "chatId": { + "description": "Current chat identifier" + }, + "sourceDocuments": { + "description": "Source documents used in retrieval (if applicable)" + }, + "usedTools": { + "description": "List of tools used during execution" + }, + "artifacts": { + "description": "List of artifacts generated during execution" + }, + "fileAnnotations": { + "description": "File annotations associated with the response" + } + }, + "variablesTable": { + "variable": "Variable" + }, + "types": { + "string": "string", + "array": "array" + }, + "messages": { + "success": "Post Processing Settings Saved", + "error": "Failed to save Post Processing Settings: {{msg}}" + } + }, + "rateLimit": { + "title": "Rate Limit", + "docsHelp": "Visit Rate Limit Setup Guide to set up Rate Limit correctly in your hosting environment.", + "enableLabel": "Enable Rate Limit", + "limitMax": "Message Limit per Duration", + "limitDuration": "Duration in Second", + "limitMsg": { + "title": "Limit Message", + "placeholder": "You have reached the quota" + }, + "messages": { + "success": "Rate Limit Configuration Saved", + "error": "Failed to save Rate Limit Configuration: {{msg}}" + } + }, + "speechToText": { + "fallBackTitle": "Allowed Domains", + "providersLabel": "Providers", + "inputs": { + "connectCredential": "Connect Credential", + "language": { + "description": "The language of the input audio. Supplying the input language in ISO-639-1 format will improve accuracy and latency." + }, + "prompt": { + "description": "An optional text to guide the model's style or continue a previous audio segment. The prompt should match the audio language." + }, + "temperature": { + "title": "Temperature", + "description": "The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic." + }, + "baseUrl": { + "title": "Base URL", + "description": "The base URL of the local AI server" + }, + "model": { + "description": { + "localAiSTT": "The STT model to load. Defaults to whisper-1 if left blank.", + "groq": "The STT model to load. Defaults to whisper-large-v3 if left blank." + } + }, + "profanityFilterMode": { + "title": "Profanity Filter Mode", + "description": "How to handle profanity in the transcription", + "options": { + "masked": "Masked", + "removed": "Removed" + } + }, + "audioChannels": { + "title": "Audio Channels", + "description": "Comma-separated list of audio channels to process (e.g., \"0,1\")" + } + }, + "messages": { + "success": "Speech To Text Configuration Saved", + "error": "Failed to save Speech To Text Configuration: {{msg}}" + } + }, + "starterPrompts": { + "info": "Starter prompts will only be shown when there is no messages on the chat", + "messages": { + "success": "Conversation Starter Prompts Saved", + "error": "Failed to save Conversation Starter Prompts: {{msg}}" + } + }, + "textToSpeech": { + "providersLabel": "Providers", + "inputs": { + "connectCredential": "Connect Credential", + "voice": { + "title": "Voice", + "description": { + "openai": "The voice to use when generating the audio", + "elevenlabs": "The voice to use for text-to-speech" + } + } + }, + "autoPlay": { + "title": "Automatically play audio", + "tooltip": "When enabled, bot responses will be automatically converted to speech and played" + }, + "testAudio": { + "title": "Test Audio", + "text": "Test text: \"{{text}}\"" + }, + "testAudioPlaceholder": "Choose a voice", + "testAudioLoading": "Loading voices...", + "messages": { + "onSave": { + "success": "Text To Speech Configuration Saved", + "error": "Failed to save Text To Speech Configuration: {{msg}}" + }, + "testTTS": { + "error": { + "provider": "Please select a provider and configure credentials first", + "failed": "TTS test failed: {{msg}}" + } + } + } + }, + "file": { + "chooseFile": "Choose a file to upload", + "uploadFile": "Upload File" + }, + "dataGrid": { + "addItem": "Add Item" + }, + "selectVariable": { + "title": "Select Variable", + "items": { + "question": "User's question from chatbox", + "chatHistory": "Past conversation history between user and AI", + "fileAttachment": "Files uploaded from the chat when Full File Upload is enabled on the Configuration" + }, + "sequentialState": { + "messages": "All messages from the start of the conversation till now", + "key": "Current value of the state variable with specified key", + "firstMessage": "First message content", + "lastMessage": "Last message content" + }, + "output": "{{output}} from {{node}}" + }, + "documentStoreTable": { + "connectedFlows": "Connected flows", + "totalCharacters": "Total characters", + "totalChunks": "Total chunks", + "loaderTypes": "Loader Types", + "more": "+ {{count}} More", + "options": "Document store options" + }, + "flowListTable": { + "category": "Category", + "nodes": "Nodes", + "lastModifiedDate": "Last Modified Date", + "actions": "Actions", + "more": "+ {{count}} More" + }, + "filesTable": { + "path": "Path", + "size": "Size", + "actions": "Actions" + }, + "executionsListTable": { + "lastUpdated": "Last Updated", + "agentflow": "Agentflow", + "session": "Session", + "created": "Created", + "selectAll": "select all executions" + }, + "marketplaceTable": { + "framework": "Framework", + "useCases": "Use cases", + "badges": "Badges", + "sharedTemplate": "Shared Template", + "aDenseTable": "a dense table" + }, + "table": { + "override": "Override", + "tooltip": "If enabled, this variable can be overridden in API calls and embeds. If disabled, any overrides will be ignored. To change this, go to Security settings in Chatflow Configuration.", + "enabled": "Enabled", + "disabled": "Disabled", + "schemaUnavailable": "No schema available", + "schema": "Schema" + }, + "copyToClipboard": { + "title": "Copy to clipboard" + }, + "dropdown": { + "createNewItemLabel": "- Create New -", + "selected": "Selected Option", + "chooseOption": "choose an option" + }, + "input": { + "hidePassword": "Hide password", + "showPassword": "Show password" + }, + "documentStoreCard": { + "chars": "{{totalChars}} chars", + "chunks": "{{totalChunks}} chunks" + } + }, + "suggestOptions": { + "description": { + "question": "User's question from chatbox", + "chatHistory": "Past conversation history between user and AI", + "currentDateTime": "Current date and time", + "runtimeMessagesLength": "Total messages between LLM and Agent", + "loopCount": "Current loop count", + "fileAttachment": "Files uploaded from the chat", + "sessionId": "Current session ID", + "chatId": "Current session ID", + "chatflowId": "Current session ID", + "iteration": "Iteration item. For JSON, use dot notation: $iteration.name", + "output": "Output from the current node", + "variable": "Variable: {{value}} ({{type}})", + "formInput": "Form Input: {{label}}", + "nodeOutput": "{{output}} from {{node}}" + } + }, + "menu": { + "agentflows": "Agentflows", + "agentflowsV2": "Agentflows V2", + "assistants": { + "title": "Assistants", + "custom": "Assistants Custom", + "openAi": "Assistants OpenAI", + "azure": "Assistants Azure" + }, + "chatMessages": "Chat Messages", + "chatFeedbacks": "Chat Feedbacks", + "customTemplates": "Custom Templates", + "documentStores": "Document Stores", + "executions": "Executions", + "evaluations": "Evaluations", + "viewMessages": "View Messages", + "viewLeads": "View Leads", + "configuration": "Configuration", + "duplicateChatflow": "Duplicate Agents", + "loadChatflow": "Load Agents", + "exportChatflow": "Export Agents", + "deleteChatflow": "Delete Agents", + "marketplaces": "Marketplaces", + "credentials": "Credentials", + "apiKeys": "API Keys", + "datasets": "Datasets", + "evaluators": "Evaluators", + "management": "User & Workspace Management", + "sso": "SSO Config", + "roles": "Roles", + "loginActivity": "Login Activity", + "others": "Others", + "logs": "Logs", + "files": "Files", + "account": "Account Settings", + "viewUpsertHistory": "Upsert History", + "itemsError": "Menu Items Error", + "beta": "BETA", + "deleteAssistant": "Delete Assistant", + "starterPromptsTitle": "Starter Prompts - {{name}}", + "chatFeedbackTitle": "Chat Feedback - {{name}}", + "allowedDomainsTitle": "Allowed Domains - {{name}}", + "speechToTextTitle": "Speech To Text - {{name}}", + "users": "Users", + "workspaces": "Workspaces", + "dialogs": { + "delete": { + "description": "Delete {{title}} {{name}}?" + } + }, + "actions": { + "options": "Options", + "rename": { + "title": "Rename", + "renameThis": "Rename {{name}}" + }, + "duplicate": "Duplicate", + "starterPrompts": "Starter Prompts", + "chatFeedback": "Chat Feedback", + "allowedDomains": "Allowed Domains", + "speechToText": "Speech To Text", + "updateCategory": "Update Category" + } + }, + "errors": { + "manyRequests": "You're making a lot of requests. Please wait and try again later.", + "unexpectedError": "Unexpected Error!", + "specifyVariable": "Please specify a Variable. Try connecting Condition node to a previous node and select the variable", + "specifyOutputName": "Please specify a Variable. Try connecting Condition node to a previous node and select the variable", + "selectOperation": "Please select an operation for the condition", + "addItem": "Please add an item for the condition", + "addReturn": "Please add a return statement in the condition code to define the output. You can refer to How to Use for more information." + }, + "errorBoundary": { + "title": "Oh snap!", + "loadErrorMessage": "The following error occurred when loading this page.", + "statusLabel": "Status: {{status}}", + "retryMessage": "Please retry after some time. If the issue persists, reach out to us on our Discord server.", + "githubIssueMessage": "Alternatively, you can raise an issue on Github." + }, + "license": { + "updatePaymentHeader": "Update Payment Method", + "updatePaymentMessage": "Update your payment method to avoid service interruption.", + "trialDaysLeftMessage_one": "There is {{count}} day left in your trial.", + "trialDaysLeftMessage_other": "There are {{count}} days left in your trial." + }, + "profile": { + "logout": "Logout", + "logoutWait": "Logging out...", + "version": "Version", + "accountSetting": "Account Settings", + "contactTo": "Please contact your administrator for assistance.", + "subscription": { + "title": "Subscription & Billing", + "currentPlan": "Current Organization Plan:", + "updatePlan": "Update your billing details and subscription", + "loading": "Loading", + "actions": { + "billing": "Billing", + "changePlan": "Change Plan" + } + }, + "seats": { + "title": "Seats", + "includedInPlan": "Seats Included in Plan:", + "additionalPurchased": "Additional Seats Purchased:", + "occupiedSeats": "Occupied Seats:" + }, + "usage": { + "title": "Usage", + "predictions": "Predictions", + "storage": { + "title": "Storage", + "current": "{{usage}}MB / {{limit}}MB" + } + }, + "messages": { + "profile": { + "success": { + "simple": "Profile updated", + "check": "Check your current email ({{email}}) to confirm the change to {{pendingEmail}}." + }, + "error": "Failed to update profile: {{msg}}" + }, + "password": { + "success": "Password updated", + "errors": { + "oldPassword": "Old Password cannot be left blank", + "passwordsNotMatch": "New Password and Confirm Password do not match", + "failedUpdate": "Failed to update password: {{msg}}" + } + }, + "seats": { + "success": "Seats updated successfully", + "error": "Failed to update seats: {{msg}}" + }, + "errors": { + "failedAccessPortal": "Failed to access billing portal" + } + }, + "inputs": { + "name": { + "placeholder": "Your Name" + }, + "oldPassword": "Old Password", + "newPassword": { + "title": "New Password", + "caption": "Password must be at least 8 characters long and contain at least one lowercase letter, one uppercase letter, one digit, and one special character." + }, + "confirmNewPassword": "Confirm New Password" + }, + "dialog": { + "removeSeats": "Remove Additional Seats", + "addSeats": "Add Additional Seats", + "removeUsersBefore": "You must remove users from your organization before removing seats.", + "occupiedSeats": "Occupied Seats", + "includedInPlan": "Seats Included with Plan", + "additionalPurchased": "Additional Seats Purchased", + "emptySeats": "Empty Seats", + "numberToRemove": "Number of Empty Seats to Remove", + "numberToAdd": "Number of Additional Seats to Add", + "newTotalSeats": "New Total Seats", + "paymentMethod": "Payment Method", + "noPaymentMethod": "No payment method found", + "appliedAccountBalance": "Applied account balance", + "actions": { + "addPaymentMethod": "Add Payment Method in Billing Portal" + }, + "additionalSeats": { + "title": { + "current": "Additional Seats (Prorated)", + "left": "Additional Seats Left (Prorated)" + }, + "qty": "Qty {{value}}", + "each": "{{currency}} {{seatPerUnitPrice}} each" + }, + "creditBalance": "Credit balance", + "nextPayment": { + "due": "Due today", + "availableCredit": "Your available credit will automatically apply to your next invoice." + }, + "updating": "Updating...", + "expires": "expires {{month}} / {{year}}" + }, + "actions": { + "import": "Import", + "removeSeats": "Remove Seats", + "addSeats": { + "title": "Add Seats", + "tooltip": "Add Seats is available only for PRO plan" + }, + "save": { + "tooltips": { + "profile": "Profile", + "security": "Security" + } + }, + "delete": "Delete your account" + }, + "organizations": { + "organization": "Organization", + "organizationName": "{{name}}'s Organization", + "switching": "Switching organization...", + "select": "Select Organization" + }, + "workspaces": { + "switching": "Switching workspace...", + "unavailable": "Workspace Unavailable", + "unavailableContinue": "Your current workspace is no longer available. Please select another workspace to continue.", + "unavailableChange": "Workspace is no longer available. Please select a different organization/workspace to continue.", + "select": "Select Workspace", + "switchError": "Workspace Switch Error", + "failedToSwitch": "Failed to switch workspace" + }, + "export": { + "selectData": "Select Data to Export", + "exporting": "Exporting...", + "exportingWhile": "Exporting data might takes a while", + "failToExport": "Failed to export all: {{msg}}" + }, + "import": { + "importing": "Importing...", + "importingWhile": "Importing data might takes a while", + "importSuccess": "Import All successful", + "invalidImport": "Invalid Imported File", + "failToImport": "Failed to import: {{msg}}" + }, + "deleteAccount": { + "title": "Delete Account", + "description": "Permanently deletes all your data and cancels your subscription. This action cannot be undone.", + "warn": "This will permanently delete your account and all associated data. Your subscription will be cancelled immediately and you will be logged out. This action cannot be undone and there is no way to recover your data.", + "loading": "Deleting...", + "confirm": "To confirm, please type permanently delete below:", + "permanentlyDelete": "permanently delete" + } + }, + "agentExecution": { + "title": "Agent Executions", + "description": "Monitor and manage agentflows executions", + "filters": { + "state": { + "items": { + "all": "All", + "inProgress": "In Progress", + "finished": "Finished", + "terminated": "Terminated", + "timeout": "Timeout", + "stopped": "Stopped" + } + }, + "startDate": "Start date", + "endDate": "End date", + "agentflow": "Agentflow", + "sessionId": "Session ID" + }, + "actions": { + "reset": "Reset", + "deleteSelected": "Delete selected executions", + "copy": { + "title": { + "id": "Copy ID", + "link": "Copy link" + } + }, + "unshare": "Unshare" + }, + "dialogs": { + "confirmDeletion": { + "title": "Confirm Deletion", + "description_one": "Are you sure you want to delete {{count}} execution? This action cannot be undone.", + "description_other": "Are you sure you want to delete {{count}} executions? This action cannot be undone.", + "executionEmpty": "No Executions Yet" + }, + "provideFeedback": { + "title": "Provide Feedback", + "feedback": "Feedback" + } + }, + "details": { + "nodeIteration": "Iteration #{{index}}", + "content": { + "goToAgentFlow": "Go to AgentFlow", + "execution": { + "title": "Execution ID: {{id}}" + }, + "updating": "Updating...", + "public": "Public", + "notAvailable": "N/A", + "actions": {}, + "noDataAvailable": "No data available for this item" + }, + "resize": "Resize drawer", + "submit": { + "loading": "Submitting feedback...", + "success": "Successfully submitted response", + "error": "Failed to submit response" + }, + "fulfilledConditions": { + "condition": "Condition {{index}}", + "else": "Else condition fulfilled", + "fulfilled": "Fulfilled", + "notFulfilled": "Not Fulfilled" + }, + "toolCall": "Tool Call", + "input": "Input", + "usedTools": "Used Tools", + "noData": "*No data*", + "used": "Used", + "called": "Called", + "error": { + "noDetails": "*No error details*" + }, + "actions": { + "rendered": "Rendered", + "raw": "Raw", + "reject": "Reject", + "proceed": "Proceed", + "refresh": "Refresh execution data" + } + }, + "messages": { + "copyToClipboard": { + "id": "ID copied to clipboard", + "link": "Link copied to clipboard" + }, + "sharePublicly": { + "shared": "Execution shared publicly", + "notShared": "Execution is no longer public" + } + }, + "invalidExecution": { + "title": "Invalid Execution", + "caption": "The execution you're looking for doesn't exist or you don't have permission to view it." + }, + "publicTraceLink": { + "title": "Public Trace Link", + "description": "Anyone with the link below can view this execution trace." + } + }, + "agentFlows": { + "v1": { + "title": "Agentflows", + "searchPlaceholder": "Search Name or Category", + "description": "Multi-agent systems, workflow orchestration", + "new": "NEW", + "toggle": { + "v1": "V1", + "v2": "V2" + }, + "deprecationNotice": "V1 Agentflows are deprecated. We recommend migrating to V2 for improved performance and continued support.", + "dismiss": "dismiss", + "empty": "No Agents Yet" + }, + "v2": { + "untitled": "Untitled", + "unsavedWarn": "You have unsaved changes! Do you want to navigate away?", + "syncNodes": "Sync Nodes", + "sync": "sync", + "parameters": "{{label}} Parameters", + "buttonGroup": "Basic button group", + "agent": "Agent", + "messages": { + "nodeVersion": { + "outdated": "Node version {{oldVersion}} outdated\nUpdate to latest version {{newVersion}}", + "versionEmpty": "Node outdated\nUpdate to latest version {{newVersion}}", + "deprecated": "This node will be deprecated in the next release. Change to a new node tagged with NEW" + }, + "dragAndDrop": { + "errors": { + "onlyStart": "Only one start node is allowed", + "nestedNotSupport": "Nested iteration node is not supported yet", + "humanNotSupport": "Human input node is not supported inside Iteration node" + } + }, + "save": { + "success": "{{canvasTitle}} saved", + "error": "Failed to save {{canvasTitle}}: {{msg}}" + }, + "errors": { + "failedRetrieve": "Failed to retrieve {{canvasTitle}}: {{msg}}" + } + }, + "dialogs": { + "delete": { + "description": "Delete {{canvasTitle}} {{name}}?" + } + }, + "actions": { + "duplicate": "Duplicate", + "toggleBackground": "toggle background", + "toggleSnapping": "toggle snapping", + "info": "Info" + } + } + }, + "apiKey": { + "title": "API Key", + "description": "Flowise API & SDK authentication keys", + "searchPlaceholder": "Search API Keys", + "notFound": "No API Keys Yet", + "keyTable": { + "keyName": "Key Name", + "apiKey": "API Key", + "permissions": "Permissions", + "usage": "Usage", + "updated": "Updated" + }, + "chatflowTable": { + "title": "chatflow table", + "name": "Chatflow Name", + "modifiedOn": "Modified On", + "category": "Category" + }, + "messages": { + "add": { + "success": "New API key added", + "error": "Failed to add new API key: {{msg}}" + }, + "save": { + "success": "API Key saved", + "error": "Failed to save API key: {{msg}}" + }, + "delete": { + "success": "API key deleted", + "error": "Failed to delete API key: {{msg}}" + } + }, + "dialogs": { + "delete": { + "description": { + "simple": "Delete key [{{name}}] ?", + "inUse": "Delete key [{{name}}] ?\nThere are {{count}} chatflows using this key." + } + } + }, + "actions": { + "copyKey": "Copy API Key", + "show": "Show", + "addKey": "Add New API Key", + "editKey": "Edit API Key", + "createKey": "Create Key" + }, + "inputs": { + "key": { + "title": "Key Name", + "placeholder": "My New Key" + }, + "permissions": "Permissions" + } + }, + "assistants": { + "title": "Assistants", + "description": "Chat assistants with instructions, tools, and files to respond to user queries", + "deprecating": "Deprecating", + "cards": { + "customAssistant": { + "title": "Custom Assistant", + "description": "Create custom assistant using your choice of LLMs", + "searchPlaceholder": "Search Assistants", + "notFound": "No Custom Assistants Added Yet", + "label": "Custom", + "instructions": "Instructions", + "messages": { + "create": { + "success": "New Custom Assistant created.", + "error": "Failed to add new Custom Assistant: {{msg}}" + }, + "save": { + "success": "Assistant saved successfully", + "error": "Failed to save assistant: {{msg}}" + }, + "docStore": { + "success": "Document Store Tool Description generated successfully" + }, + "warn": "Please fill in all mandatory fields.", + "errors": { + "failedRetrieve": "Failed to retrieve: {{msg}}" + } + }, + "settings": { + "viewMessages": "View Messages", + "viewLeads": "View Leads", + "configuration": "Assistant Configuration" + }, + "generateInstructions": { + "title": "Generate Instructions", + "description": "You can generate a prompt template by sharing basic details about your task." + }, + "embedInWebsite": "Embed in website or use as API", + "inputs": { + "knowledge": { + "title": "Knowledge (Document Stores)", + "tooltip": "Give your assistant context about different document sources. Document stores must be upserted in advance." + }, + "describeKnowledge": { + "title": "Describe Knowledge", + "tooltip": "Describe what the knowledge base is about, this is useful for the AI to know when and how to search for correct information" + }, + "returnSource": { + "title": "Return Source Documents", + "tooltip": "Return the actual source documents that were used to answer the question" + }, + "tools": { + "tooltip": "Tools are actions that your assistant can perform", + "tool": "Tool" + } + }, + "agentFlow": { + "bufferMemory": { + "title": "Buffer Memory", + "description": "Retrieve chat messages stored in database", + "inputs": { + "sessionId": { + "title": "Session Id", + "description": "If not specified, a random id will be used. Learn more" + }, + "memoryKey": { + "title": "Memory Key" + } + }, + "outputs": { + "bufferMemory": { + "title": "BufferMemory" + } + } + }, + "chatOpenAI": { + "title": "ChatOpenAI", + "description": "Wrapper around OpenAI large language models that use the Chat endpoint", + "inputs": { + "credential": { + "title": "Connect Credential" + }, + "modelName": { + "title": "Model Name" + }, + "temperature": { + "title": "Temperature" + }, + "streaming": { + "title": "Streaming" + }, + "maxTokens": { + "title": "Max Tokens" + }, + "topP": { + "title": "Top Probability" + }, + "frequencyPenalty": { + "title": "Frequency Penalty" + }, + "presencePenalty": { + "title": "Presence Penalty" + }, + "timeout": { + "title": "Timeout" + }, + "basepath": { + "title": "BasePath" + }, + "proxyUrl": { + "title": "Proxy Url" + }, + "stopSequence": { + "title": "Stop Sequence", + "description": "List of stop words to use when generating. Use comma to separate multiple stop words." + }, + "baseOptions": { + "title": "Base Options" + }, + "allowImageUploads": { + "title": "Allow Image Uploads", + "description": "Allow image input. Refer to the docs for more details." + } + }, + "inputAnchors": { + "cache": { + "title": "Cache" + } + }, + "outputs": { + "chatOpenAI": { + "title": "ChatOpenAI" + } + } + }, + "toolAgent": { + "title": "Tool Agent", + "description": "Agent that uses Function Calling to pick the tools and args to call", + "inputs": { + "systemMessage": { + "title": "System Message", + "description": "If Chat Prompt Template is provided, this will be ignored" + }, + "maxIterations": { + "title": "Max Iterations" + } + }, + "inputAnchors": { + "memory": { + "title": "Memory" + }, + "model": { + "title": "Tool Calling Chat Model", + "description": "Only compatible with models that are capable of function calling: ChatOpenAI, ChatMistral, ChatAnthropic, ChatGoogleGenerativeAI, ChatVertexAI, GroqChat" + }, + "chatPromptTemplate": { + "title": "Chat Prompt Template", + "description": "Override existing prompt with Chat Prompt Template. Human Message must includes {input} variable" + }, + "inputModeration": { + "title": "Input Moderation", + "description": "Detect text that could generate harmful output and prevent it from being sent to the language model" + } + }, + "outputs": { + "toolAgent": { + "title": "AgentExecutor" + } + } + } + } + }, + "openAi": { + "title": "OpenAI Assistant", + "description": { + "simple": "Create assistants using OpenAI Assistant API", + "deprecated": "Create assistant using OpenAI Assistant API. This option is being deprecated; consider using Custom Assistant instead." + }, + "searchPlaceholder": "Search Assistants", + "notFound": "No OpenAI Assistants Added Yet", + "messages": { + "addAssistant": { + "success": "New Assistant added", + "error": "Failed to add new Assistant: {{msg}}" + }, + "loadAssistant": { + "error": "Failed to get assistant: {{msg}}" + }, + "save": { + "success": "Assistant saved", + "error": "Failed to save assistant: {{msg}}" + }, + "sync": { + "success": "Assistant successfully synced!", + "error": "Failed to sync Assistant: {{msg}}" + }, + "upload": { + "success": "File uploaded successfully!", + "error": "Failed to upload file: {{msg}}" + }, + "delete": { + "success": "Assistant deleted", + "error": "Failed to delete Assistant: {{msg}}" + } + }, + "agentFlow": { + "openAi": { + "title": "OpenAI Credential" + }, + "connectCredential": { + "title": "Connect Credential" + }, + "assistants": { + "title": "Assistants" + }, + "assistantModel": { + "title": "Assistant Model" + }, + "name": { + "title": "Assistant Name", + "tooltip": "The name of the assistant. The maximum length is 256 characters.", + "placeholder": "My New Assistant" + }, + "description": { + "title": "Assistant Description", + "tooltip": "The description of the assistant. The maximum length is 512 characters.", + "placeholder": "Description of what the Assistant does" + }, + "icon": { + "title": "Assistant Icon Src" + }, + "instruction": { + "title": "Assistant Instruction", + "tooltip": "The system instructions that the assistant uses. The maximum length is 32768 characters.", + "placeholder": "You are a personal math tutor. When asked a question, write and run Python code to answer the question." + }, + "temperature": { + "title": "Assistant Temperature", + "tooltip": "Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive." + }, + "topP": { + "title": "Assistant Top P", + "tooltip": "Controls diversity via nucleus sampling: 0.5 means half of all likelihood-weighted options are considered." + }, + "tools": { + "title": "Assistant Tools", + "tooltip": "A list of tool enabled on the assistant. There can be a maximum of 128 tools per assistant.", + "options": { + "codeInterpreter": "Code Interpreter", + "fileSearch": "File Search" + } + }, + "codeInterpreterFiles": { + "title": "Code Interpreter Files", + "tooltip": "Code Interpreter enables the assistant to write and run code. This tool can process files with diverse data and formatting, and generate files such as graphs" + }, + "fileSearchFiles": { + "title": "File Search Files", + "tooltip": "File search enables the assistant with knowledge from files that you or your users upload. Once a file is uploaded, the assistant automatically decides when to retrieve content based on user requests" + } + } + }, + "vectorStorage": { + "inputs": { + "select": { + "title": "Select Vector Store" + }, + "name": { + "title": "Vector Store Name", + "placeholder": "My Vector Store" + }, + "expiration": { + "title": "Vector Store Expiration" + }, + "date": { + "title": "Expiration Days" + } + }, + "messages": { + "delete": { + "success": "Vector Store deleted", + "error": "Failed to delete Vector Store: {{msg}}" + }, + "add": { + "success": "New Vector Store added", + "error": "Failed to add new Vector Store: {{msg}}" + }, + "save": { + "success": "Vector Store saved", + "error": "Failed to save Vector Store: {{msg}}" + } + }, + "files": "{{count}} files ({{size}})" + } + }, + "dialogs": { + "delete": { + "description": "Delete {{name}}?" + } + }, + "actions": { + "settings": "Settings", + "deleteAssistant": { + "title": "Delete Assistant", + "description": "Select delete method for {{name}}" + }, + "generate": { + "description": "Generate instructions using model" + }, + "addTool": "Add Tool", + "saveAssistant": "Save Assistant", + "addCustomAssistant": "Add New Custom Assistant", + "addAssistant": "Add New Assistant", + "editAssistant": "Edit Assistant", + "editName": "Edit {{name}}", + "addVectorStore": "Add Vector Store", + "sync": "Sync", + "onlyFlowise": "Only Flowise", + "openAiFlowise": "OpenAI and Flowise", + "loadExisting": "Load Existing Assistant", + "apiEndpoint": "API Endpoint" + } + }, + "auth": { + "confirmEmailChange": { + "loading": "Confirming email change...", + "failed": "Confirmation failed.", + "success": { + "title": "Email updated successfully.", + "description": "Please sign in with your new email address." + } + }, + "expired": { + "expired": "Your enterprise license has expired", + "contact": " Please contact our support team to renew your license." + }, + "forgotPassword": { + "title": "Forgot Password?", + "resetCode": "Have a reset password code? Change your password here.", + "username": "Username", + "caption": "If you forgot the email you used for signing up, please contact your administrator.", + "messages": { + "success": "Password reset instructions sent to the email.", + "error": "Failed to send instructions, please contact your administrator." + } + }, + "rateLimit": { + "title": "429 Too Many Requests", + "description": "You have made too many requests in a short period of time. Please wait {{retryAfter}}s before trying again." + }, + "actions": { + "contact": "Contact Support", + "send": "Send Reset Password Instructions", + "back": "Back to Home", + "create": "Create Account", + "microsoft": "Sign In With Microsoft", + "google": "Sign In With Google", + "auth0": "Sign In With Auth0 by Okta", + "github": "Sign In With Github", + "update": "Update Password", + "resend": "Resend Verification Email", + "login": "Login", + "copyCallback": "Copy Callback URL", + "test": "Test {{providerName}} Configuration", + "backHome": "Back to Home", + "backLogin": "Back to Login" + }, + "loginActivity": { + "title": "Login Activity", + "showing": "Showing {{start}}-{{end}} of {{total}} Records", + "emailPassword": "Email/Password", + "badge": "Badge", + "inputs": { + "to": "To:", + "from": "From:", + "filterBy": "Filter By" + }, + "table": { + "title": "users table", + "activity": "Activity", + "date": "Date", + "method": "Method", + "message": "Message" + }, + "types": { + "loginSuccess": "Login Success", + "logoutSuccess": "Logout Success", + "unknownUser": "Unknown User", + "incorrectCredential": "Incorrect Credential", + "userDisabled": "User Disabled", + "noAssignedWorkspace": "No Assigned Workspace", + "unknownActivity": "Unknown Activity" + }, + "formats": { + "date": "MMMM Do, YYYY, HH:mm" + } + }, + "register": { + "signup": "Sign Up", + "signin": "Already have an account? Sign In.", + "messages": { + "success": { + "cloud": "To complete your registration, please click on the verification link we sent to your email address", + "enterprise": "Registration Successful. You will be redirected to the sign in page shortly." + }, + "error": { + "simple": "Error in registering user. Please try again.", + "enterprise": "Error in registering user. Please contact your administrator. ({{msg}})" + } + } + }, + "inputs": { + "fullName": { + "title": "Full Name", + "placeholder": "Display Name", + "caption": "Is used for display purposes only" + }, + "username": { + "title": "Username", + "placeholder": "John Doe" + }, + "password": { + "title": "Password", + "caption": "Password must be at least 8 characters long and contain at least one lowercase letter, one uppercase letter, one digit, and one special character." + }, + "confirm": { + "title": "Confirm Password", + "caption": "Confirm your password. Must match the password typed above." + }, + "email": { + "caption": "Kindly use a valid email address. Will be used as login id." + }, + "inviteCode": { + "title": "Invite Code", + "placeholder": "Paste in the invite code.", + "caption": "Please copy the token you would have received in your email." + }, + "reset": { + "title": "Reset Token", + "placeholder": "Paste in the reset token.", + "caption": "Please copy the token you received in your email." + }, + "newPassword": { + "title": "New Password" + }, + "enableSso": { + "title": "Enable SSO Login" + }, + "tenantId": { + "title": "Tenant ID" + }, + "clientId": { + "title": "Client ID" + }, + "clientSecret": { + "title": "Client Secret" + }, + "domain": { + "title": "Auth0 Domain" + } + }, + "resetPassword": { + "title": "Reset Password", + "back": "Back to Login.", + "messages": { + "reset": { + "success": "Password reset successful", + "error": "Failed to reset password!" + } + }, + "validation": { + "token": "Token cannot be left blank!", + "match": "New Password and Confirm Password do not match." + } + }, + "signin": { + "title": "Sign In", + "or": "OR", + "signup": { + "simple": "Don`t have an account? Sign up for free.", + "enterprise": "Have an invite code? Sign up for an account." + }, + "unverified": "User Email Unverified", + "messages": { + "success": "Verification email has been sent successfully.", + "error": "Failed to send verification email." + } + }, + "sso": { + "title": "Configure SSO", + "loading": { + "dashboard": "Loading dashboard...", + "data": "Loading data..." + }, + "validation": { + "azure": { + "tenantId": "Azure TenantID cannot be left blank!", + "clientId": "Azure ClientID cannot be left blank!", + "clientSecret": "Azure Client Secret cannot be left blank!" + }, + "google": { + "clientId": "Google ClientID cannot be left blank!", + "clientSecret": "Google Client Secret cannot be left blank!" + }, + "github": { + "clientId": "Github ClientID cannot be left blank!", + "clientSecret": "Github Client Secret cannot be left blank!" + }, + "auth0": { + "domain": "'Auth0 Domain cannot be left blank!'", + "clientId": "Auth0 ClientID cannot be left blank!", + "clientSecret": "Auth0 Client Secret cannot be left blank!" + } + }, + "messages": { + "update": { + "success": "SSO Configuration Updated!", + "error": "Failed to update SSO Configuration." + }, + "validation": { + "success": "{{providerName}} SSO Configuration is Valid!", + "error": "Failed to verify {{providerName}} SSO Configuration." + } + } + }, + "unauthorized": { + "forbidden": "403 Forbidden", + "permission": "You do not have permission to access this page." + }, + "verify": { + "loading": "Verifying Email...", + "failed": "Verification Failed.", + "success": "Email Verified Successfully." + } + }, + "canvas": { + "untitled": "Untitled {{title}}", + "unsaved": "You have unsaved changes! Do you want to navigate away?", + "outputFrom": "Output from {{id}}", + "valueFrom": "Value from {{key}}", + "variablesTooltip": "Type {{ to select variables", + "true": "True", + "false": "False", + "inputsLabel": "Inputs", + "actions": { + "denerateAgentflow": { + "title": "Generate Agentflow" + }, + "addNode": { + "title": "Add Node", + "label": "add" + }, + "apiEndpoint": "API Endpoint", + "saveByName": "Save {{name}}", + "setting": "Settings", + "duplicate": "Duplicate", + "info": "Info", + "additionalParameters": "Additional Parameters", + "toggleSnapping": "toggle snapping", + "toggleBackground": "toggle background", + "sync": { + "title": "Sync Nodes", + "label": "sync" + }, + "editTool": "Edit Tool", + "editAssistant": "Edit Assistant", + "addNewTool": "Add New Tool", + "addNewAssistant": "Add New Assistant", + "langchainHub": "Langchain Hub", + "setupNimLocally": "Setup NIM Locally", + "generateKnowledge": "Generate knowledge base description", + "generateInstructions": "Generate instructions", + "seeExample": "See Example" + }, + "addNodes": { + "title": "Add Nodes" + }, + "inputs": { + "search": { + "placeholder": "Search nodes" + } + }, + "dialogs": { + "delete": { + "description": "Delete {{title}} {{name}}?" + }, + "build": { + "title": "What would you like to build?", + "description": "Enter your prompt to generate an agentflow. Performance may vary with different models. Only nodes and edges are generated, you will need to fill in the input fields for each node." + }, + "viewMessages": "View Messages", + "viewLeads": "View Leads", + "exportTemplate": "Export As Template", + "viewUpsertHistory": "View Upsert History", + "configuration": "{{title}} Configuration", + "embed": "Embed in website or use as API", + "saveNew": "Save New {{name}}", + "addNewCredential": "Add New Credential", + "confirmChange": { + "title": "Confirm Change", + "description": "{{title}} {{name}} has changed since you have opened, overwrite changes?" + }, + "generateInstructions": { + "title": "Generate Instructions", + "description": "You can generate a prompt template by sharing basic details about your task.F" + } + }, + "messages": { + "saveChatflow": { + "success": "{{title}} saved", + "error": "Failed to retrieve {{title}}: {{msg}}" + }, + "saveAsTemplate": { + "error": "Please save the flow before exporting as template" + }, + "nodeVersion": { + "outdated": "Node version {{oldVersion}} outdated\nUpdate to latest version {{newVersion}}", + "versionEmpty": "Node outdated\nUpdate to latest version {{newVersion}}", + "deprecated": "This node will be deprecated in the next release. Change to a new node tagged with NEW" + }, + "warn": "Please fill in all mandatory fields.", + "docStoreTool": { + "success": "Document Store Tool Description generated successfully", + "error": "Please select a knowledge base" + }, + "generate": { + "error": "Error setting generated instruction" + } + } + }, + "chatbot": { + "invalid": { + "title": "Invalid Chatbot", + "description": "The chatbot you're looking for doesn't exist or requires API key authentication." + } + }, + "chatflows": { + "description": "Build single-agent systems, chatbots and simple LLM flows", + "searchPlaceholder": "Search Name or Category", + "notFound": "No Chatflows Yet", + "codes": { + "embed": { + "title": "Embed", + "warnApi": "You cannot use API key while embedding/sharing chatbot.", + "select": "Please select \"No Authorization\" from the dropdown at the top right corner." + }, + "python": "Python", + "js": "JavaScript", + "cUrl": "cURL", + "shareChatbot": "Share Chatbot", + "popupHtml": "Popup Html", + "fullpageHtml": "Fullpage Html", + "popupReact": "Popup React", + "fullpageReact": "Fullpage React" + }, + "config": { + "showOverrideConfig": "Show Override Config", + "override": "You can override existing input configuration of the chatflow with overrideConfig property.", + "secWarn": "For security reason, override config is disabled by default. You can change this by going into Chatflow Configuration -> Security tab, and enable the property you want to override.", + "refer": "Refer here for more details" + }, + "nodes": "Nodes", + "options": { + "noAuthorization": "No Authorization", + "addKey": "- Add New Key -" + }, + "specifyMultipleValues": "You can also specify multiple values for a config parameter by specifying the node id", + "streamInfo": "Read\u00A0here\u00A0on how to stream response back to application", + "choose": "Choose an API key", + "bodyTag": { + "prefix": "Paste this anywhere in the", + "suffix": "tag of your html file." + }, + "bodyTagAlso": "You can also specify a\u00A0version:", + "actions": { + "showChat": "Show Embed Chat Config", + "copyLink": "Copy Link", + "openNewTab": "Open New Tab", + "makePublic": { + "title": "Make Public", + "tooltip": "Making public will allow anyone to access the chatbot without authentication" + }, + "saveChanges": "Save Changes" + }, + "chat": { + "tooltipMessage": "Hi There 👋!", + "disclaimer": { + "title": "Disclaimer", + "message": "By using this chatbot, you agree to the Terms & Condition", + "buttonText": "Start Chatting" + }, + "chatWindow": { + "title": "Flowise Bot", + "welcomeMessage": "Hello! This is custom welcome message", + "errorMessage": "This is a custom error message", + "backgroundImage": "enter image path or link", + "sourceDocsTitle": "Sources:" + }, + "textInput": { + "placeholder": "Type your question", + "maxCharsWarningMessage": "You exceeded the characters limit. Please input less than 50 characters." + }, + "footer": { + "text": "Powered by", + "company": "Flowise" + } + }, + "messages": { + "save": { + "success": "Chatbot Configuration Saved", + "error": "Failed to save Chatbot Configuration: {{msg}}" + } + }, + "cards": { + "titleSettings": "Title Settings", + "generalSettings": "General Settings", + "botMessage": "Bot Message", + "userMessage": "User Message", + "textInput": "Text Input" + }, + "inputs": { + "title": { + "title": "Title", + "placeholder": "Flowise Assistant" + }, + "titleAvatarSrc": { + "title": "Title Avatar Link" + }, + "titleBackgroundColor": { + "title": "Title Background Color" + }, + "titleTextColor": { + "title": "Title TextColor" + }, + "welcomeMessage": { + "title": "Welcome Message", + "placeholder": "Hello! This is custom welcome message" + }, + "errorMessage": { + "title": "Error Message", + "placeholder": "This is custom error message" + }, + "backgroundColor": { + "title": "Background Color" + }, + "fontSize": { + "title": "Font Size" + }, + "poweredByTextColor": { + "title": "PoweredBy TextColor" + }, + "showAgentMessages": { + "title": "Show agent reasonings when using Agentflow" + }, + "renderHTML": { + "title": "Render HTML on the chat" + }, + "generateNewSession": { + "title": "Start new session when chatbot link is opened or refreshed" + }, + "textColor": { + "title": "Text Color" + }, + "avatarSrc": { + "title": "Avatar Link" + }, + "showAvatar": { + "title": "Show Avatar" + }, + "textInputPlaceholder": { + "title": "TextInput Placeholder", + "placeholder": "Type question.." + }, + "textInputSendButtonColor": { + "title": "TextIntput Send Button Color" + } + } + }, + "chatmessage": { + "notFound": "No data available for this item", + "iteration": "Iteration {{index}}", + "processFlow": "Process Flow", + "usedTools": "Used Tools", + "calledTools": "Called Tools", + "finished": "Finished", + "submitting": "Thank you for submitting your contact information.", + "fillForm": "Please Fill Out The Form", + "completeForm": "Complete all fields below to continue", + "submittingLoading": "Submitting...", + "savingLoading": "Saving...", + "sendingLoading": "Sending...", + "know": "Let us know where we can reach you:", + "tryThese": "Try these prompts", + "unsupproted": "To record audio, use modern browsers like Chrome or Firefox that support audio recording.", + "waitingResponse": "Waiting for response...", + "typeQuestion": "Type your question...", + "stopingLoading": "Stopping...", + "provideFeedback": "Provide Feedback", + "feedback": "Feedback", + "checklist": "Checklist ({{count}})", + "thinking": { + "loading": "Thinking...", + "done_one": "Thought for {{count}} second", + "done_other": "Thought for {{count}} seconds" + }, + "dragDrop": { + "here": "Drop here to upload", + "maxSize": "Max Allowed Size: {{size}} MB" + }, + "actions": { + "viewDetails": "View Details", + "clearConversation": "Clear Conversation", + "clearChat": "Clear Chat", + "removeAttachment": "Remove attachment", + "okay": "Okay", + "stop": "Stop", + "clearHistory": { + "title": "Clear Chat History", + "description": "Are you sure you want to clear all chat history?" + }, + "chat": "Chat", + "expandChat": "Expand Chat", + "validation": "validation", + "validateNodes": "Validate Nodes", + "validateFlow": { + "title": "Validate Flow", + "loading": "Validating..." + } + }, + "messages": { + "message": { + "success": "Message stopped" + }, + "tts": { + "error": "TTS failed: {{msg}}" + }, + "clear": { + "success": "Succesfully cleared all chat history" + }, + "validation": { + "success": "No issues found in your flow!", + "error": "Failed to validate flow" + } + }, + "errors": { + "cannotUpload": "Cannot upload file. Kindly check the allowed file types and maximum allowed size.", + "tryAgain": "Oops! There seems to be an error. Please try again.", + "unableUpload": "Unable to upload documents" + } + }, + "credentials": { + "title": "Credentials", + "description": "API keys, tokens, and secrets for 3rd party integrations", + "cannotShare": "Cannot edit shared credential.", + "notFound": "No Credentials Yet", + "inputs": { + "credentialName": "Credential Name", + "oAuthRedirectUrl": "OAuth Redirect URL", + "search": "Search Сredential" + }, + "table": { + "title": "Shared Credential", + "lastUpdated": "Last Updated", + "created": "Created" + }, + "actions": { + "authenticate": "Authenticate", + "addCredential": "Add Credential" + }, + "dialogs": { + "add": "Add New Credential", + "share": "Share Credential", + "delete": { + "description": "Delete credential {{name}}?" + } + }, + "messages": { + "add": { + "success": "New Credential added", + "error": "Failed to add new Credential: {{msg}}" + }, + "save": { + "success": "Credential saved", + "error": "Failed to save Credential: {{msg}}" + }, + "delete": { + "success": "Credential deleted", + "error": "Failed to delete Credential: {{msg}}" + }, + "setOAuth2": { + "success": "OAuth2 authorization completed successfully", + "errors": { + "simple": "OAuth2 authorization failed", + "withMsg": "OAuth2 authorization failed: {{msg}}" + } + } + } + }, + "datasets": { + "title": "Datasets", + "dataset": "Dataset: {{name}}", + "notFoundDataset": "No Datasets Yet", + "notFoundRows": "No Dataset Items Yet", + "datasetTooltip": "Use the drag icon at (extreme right) to reorder the dataset items", + "dialogs": { + "addDataset": "Add Dataset", + "editDataset": "Edit Dataset", + "addRow": "Add Item to {{name}} Dataset", + "editRow": "Edit Item in {{name}} Dataset", + "upload": "Upload Items to {{name}} Dataset", + "delete": { + "description": { + "items": "Delete {{count}} dataset items?", + "datasets": "Delete dataset {{name}}?" + } + } + }, + "table": { + "input": "Input", + "expectedOutput": "Expected Output", + "rows": "Rows", + "lastUpdated": "Last Updated" + }, + "actions": { + "upload": "Upload", + "uploadCsv": "Upload CSV", + "newItem": "New Item", + "deleteItem_one": " Delete {{count}} item", + "deleteItem_other": " Delete {{count}} items" + }, + "inputs": { + "uploadCsv": { + "title": "Upload CSV", + "tooltip": "Only the first 2 columns will be considered:\n----------------------------\n| Input | Output |\n----------------------------\n| test input | test output |\n----------------------------", + "label": "Treat First Row as headers in the upload file?" + }, + "input": "Input", + "anticipatedOutput": "Anticipated Output" + }, + "messages": { + "addDataset": { + "success": "New Dataset added", + "error": "Failed to add new Dataset: {{msg}}" + }, + "addRow": { + "success": "New Row added for the given Dataset", + "error": "Failed to add new row in the Dataset: {{msg}}" + }, + "saveDataset": { + "success": "Dataset saved", + "error": "Failed to save Dataset: {{msg}}" + }, + "saveRow": { + "success": "Dataset Row saved", + "error": "Failed to save Dataset Row: {{msg}}" + }, + "deleteRow": { + "success": "Dataset Items deleted", + "error": "Failed to delete dataset items: {{msg}}" + }, + "deleteDataset": { + "success": "Dataset deleted", + "error": "Failed to delete dataset: {{msg}}" + } + } + }, + "docstore": { + "title": "Document Store", + "description": "Store and upsert documents for LLM retrieval (RAG)", + "searchPlaceholder": "Search Name", + "empty": { + "documentStores": "No Document Stores Created Yet", + "documents": "No Document Added Yet", + "chunks": "No Chunks", + "history": "No Upsert History Yet" + }, + "common": { + "unknown": "Unknown", + "noSource": "No source" + }, + "labels": { + "chatflowsUsed": "Chatflows Used:", + "note": "Note:", + "override": "You can override existing configurations:", + "configuration": "Configuration", + "pendingRefresh": "Some files are pending processing. Please Refresh to get the latest status.", + "splitter": "Splitter" + }, + "api": { + "upsertNote": "With the Upsert API, you can choose an existing document and reuse the same configuration for upserting." + }, + "preview": { + "selectTextSplitter": "Select Text Splitter", + "showChunks": "Show Chunks in Preview", + "showing": "Showing {{start}}-{{end}} of {{total}} chunks", + "count": "{{current}} of {{total}} Chunks", + "characters": "{{count}} characters", + "chunkCharacters": "#{{index}}. Characters: {{total}}" + }, + "retrieval": { + "description": "Retrieval Playground - Test your vector store retrieval settings", + "results": { + "title": "Retrieved Documents", + "count": "Count: {{count}}. Time taken: {{time}} millis.", + "empty": "No Documents Retrieved" + } + }, + "vectorStore": { + "description": "Configure Embeddings, Vector Store and Record Manager", + "recordManagerUnavailable": "Record Manager is not applicable for selected Vector Store" + }, + "history": { + "titles": { + "added": "Added", + "updated": "Updated", + "skipped": "Skipped", + "deleted": "Deleted" + }, + "added": "Added: {{count}}", + "updated": "Updated: {{count}}", + "skipped": "Skipped: {{count}}", + "deleted": "Deleted: {{count}}" + }, + "table": { + "loader": "Loader", + "splitter": "Splitter", + "source": "Source(s)", + "chunks": "Chunks", + "chars": "Chars", + "actions": "Actions" + }, + "dialogs": { + "selectDocumentLoader": "Select Document Loader", + "confirm": { + "title": "Refresh all loaders and upsert all chunks?", + "description": "This will re-process all loaders and upsert all chunks. This action might take some time." + }, + "editDocumentStore": "Edit Document Store", + "addNewDocumentStore": "Add New Document Store", + "upsertApi": "Upsert API", + "renameDocumentStore": "Rename Document Store", + "selectEmbeddingsProvider": "Select Embeddings Provider", + "selectVectorStoreProvider": "Select a Vector Store Provider", + "selectRecordManager": "Select a Record Manager", + "delete": { + "note": "Note: Without a Record Manager configured, only the document chunks will be removed from the document store. The actual vector embeddings in your vector store database will remain unchanged. To enable automatic cleanup of vector store data, please configure a Record Manager. Learn more", + "description": { + "loader": { + "simple": "Delete {{name}}? This will delete all the associated document chunks from the document store.", + "withData": "Delete {{name}}? This will delete all the associated document chunks from the document store and remove the actual data from the vector store database." + }, + "store": { + "simple": "Delete Store {{name}}? This will delete all the associated loaders and document chunks from the document store.", + "withData": "Delete Store {{name}}? This will delete all the associated loaders and document chunks from the document store, and remove the actual data from the vector store database." + }, + "document": { + "simple": "Delete store [{{name}}]? This will remove this document store from the list.", + "withData": "Delete store [{{name}}]? This will remove this document store from the list and remove the actual data from the vector store database." + }, + "chunk": "Delete chunk {{id}}? This action cannot be undone." + } + } + }, + "inputs": { + "query": "Enter your Query", + "loaderName": { + "simple": "{{name}} name", + "loader": "{{name}} Loader Name" + } + }, + "actions": { + "update": "Update", + "refreshDocumentStore": "Refresh Document Store", + "addDocumentLoader": "Add Document Loader", + "moreActions": "More Actions", + "menu": { + "viewEditChunks": "View & Edit Chunks", + "upsertAllChunks": "Upsert All Chunks", + "retrievalQuery": "Retrieval Query", + "refresh": "Re-process all loaders and upsert all chunks", + "previewProcess": "Preview & Process", + "upsertChunks": "Upsert Chunks", + "viewApi": "View API" + }, + "deleteChunk": "Delete Chunk", + "documentStoreActions": "Document store actions", + "rename": "Rename", + "process": "Process", + "previewChunks": "Preview Chunks", + "preview": "Preview", + "details": "Details", + "reset": "Reset", + "saveConfig": "Save Config", + "upsert": "Upsert", + "selectEmbeddings": "Select Embeddings", + "selectVectorStore": "Select Vector Store", + "selectRecordManager": "Select Record Manager", + "upsertHistory": "Upsert History", + "editChunk": "Edit Chunk" + }, + "validation": { + "requiredFields": "Please fill in all mandatory fields.", + "requiredFieldsList": "Please fill in the following mandatory fields: {{fields}}" + }, + "messages": { + "deleteStore": { + "success": "Store, Loader and associated document chunks deleted", + "error": "Failed to delete Document Store: {{msg}}" + }, + "deleteLoader": { + "success": "Loader and associated document chunks deleted", + "error": "Failed to delete Document Loader: {{msg}}" + }, + "deleteDocument": { + "success": "Document Store deleted.", + "error": "Failed to delete Document Store: {{msg}}" + }, + "storeRefresh": { + "success": "Document store refresh successfully!", + "error": "Failed to refresh document store: {{msg}}" + }, + "create": { + "success": "New Document Store created.", + "error": "Failed to add new Document Store: {{msg}}" + }, + "update": { + "success": "Document Store Updated!", + "error": "Failed to update Document Store: {{msg}}" + }, + "previewChunks": { + "error": "Failed to preview chunks: {{msg}}" + }, + "save": { + "success": "File submitted for processing. Redirecting to Document Store..", + "error": "Failed to process chunking: {{msg}}" + }, + "edit": { + "success": "Document chunk successfully edited!", + "error": "Failed to edit chunk: {{msg}}" + }, + "deleteChunk": { + "success": "Document chunk successfully deleted!", + "error": "Failed to delete chunk: {{msg}}" + }, + "saveConfiguration": { + "success": "Configuration saved successfully" + }, + "vectorStoreUpdated": { + "success": "Vector Store Config Successfully Updated", + "error": "Failed to update vector store config: {{msg}}" + } + } + }, + "evaluations": { + "title": "Evaluations", + "input": "Input", + "latency": "Latency (ms)", + "customAssistant": "Custom Assistant", + "notFound": "No Evaluations Yet", + "totalRuns": "Total Runs: {{value}}", + "avgLatency": "Avg Latency: {{value}} ms", + "passRate": "Pass Rate: {{value}} %", + "notAvailable": "N/A", + "chatflowTable": "chatflow table", + "dialogs": { + "startNewEvaluation": { + "title": "Start New Evaluation", + "datasets": { + "title": "Datasets", + "select": "Select dataset to be tested on flows", + "useInput": "Uses the input column from the dataset to execute selected Chatflow(s), and compares the results with the output column.", + "computed": "The following metrics will be computed:", + "selectFlows": "Select your flows to Evaluate" + }, + "evaluators": { + "title": "Evaluators", + "unit": "Unit Test your flows by adding custom evaluators", + "execution": "Post execution, all the chosen evaluators will be executed on the results. Each evaluator will grade the results based on the criteria defined and return a pass/fail indicator." + }, + "metrics": { + "title": "LLM Graded Metrics", + "gradeFlows": "Grade flows using an LLM", + "execution": "Post execution, grades the answers by using an LLM. Used to generate comparative scores or reasoning or other custom defined criteria." + } + }, + "delete": { + "description_one": "Delete {{count}} evaluation? This will delete all versions of the evaluation.", + "description_other": "Delete {{count}} evaluation? This will delete all versions of the evaluation." + }, + "evalResult": { + "title": "Evaluation {{name}}", + "version": "Version {{number}}/{{count}}", + "errors": { + "cannotRerun": "This evaluation cannot be re-run, due to the following errors", + "outdated": "The following items are outdated, re-run the evaluation for the latest results." + }, + "dataset": "Dataset:", + "flows": "Flows:", + "passRate": "PASS RATE", + "tokensUsed": "TOKENS USED", + "latency": "LATENCY (ms)", + "evaluationDetails": "Evaluation Details", + "evaluationId": "Evaluation Id", + "flowsUsed": "Flows Used:", + "expectedOutput": "Expected Output", + "actualOutput": "Actual Output", + "evaluator": "Evaluator", + "LLMEvaluation": "LLM Evaluation", + "totalCost": "Total Cost: {{value}}", + "totalTokens": "Total Tokens: {{value}}", + "promptTokens": "Prompt Tokens: {{value}}", + "completionTokens": "Completion Tokens: {{value}}", + "promptCost": "Prompt Cost: {{value}}", + "completionCost": "Completion Cost: {{value}}", + "apiLatency": "API Latency: {{value}}", + "chainLatency": "Chain Latency: {{value}}", + "retrieverLatency": "Retriever Latency: {{value}}", + "toolLatency": "Tool Latency: {{value}}", + "LLMLatency": "LLM Latency: {{value}}", + "api": "API: {{value}}", + "chain": "Chain: {{value}}", + "retriever": "Retriever: {{value}}", + "llm": "LLM: {{value}}", + "node": "Node", + "providerModel": "Provider & Model", + "total": { + "title": "Total", + "value": "Total: {{value}}" + }, + "prompt": "Prompt: {{value}}", + "completion": "Completion: {{value}}", + "cost": "Cost", + "customEvaluators": "Custom Evaluators", + "llmGraded": "LLM Graded" + }, + "runAgain": { + "title": "Run Again", + "desceiption": "Initiate Rerun for Evaluation {{name}}?" + } + }, + "validations": { + "requiredFields": "Fill all the mandatory fields" + }, + "inputs": { + "name": { + "tooltip": "Friendly name to tag this run.", + "placeholder": "Evaluation" + }, + "datasetUse": { + "title": "Dataset to use", + "placeholder": "Select Dataset" + }, + "rowsOneConversation": "Treat all dataset rows as one conversation?", + "checkBox": { + "all": "All", + "agentflowV2": "Agentflow (v2)", + "customAssistants": "Custom Assistants" + }, + "selectEvaluators": "Select the Evaluators", + "useLLMgradeResults": "Use an LLM to grade the results?", + "enterModelName": { + "title": "Enter the Model Name", + "placeholder": "Model Name" + }, + "selectCredential": { + "title": "Select Credential", + "input": "Connect Credential" + }, + "selectEval": "Select Evaluators" + }, + "messages": { + "runAgain": { + "success": "Evaluation '{{name}}' is running. Redirecting to evaluations page." + }, + "delete": { + "success_one": "{{count}} evaluation deleted", + "success_other": "{{count}} evaluations deleted", + "error_one": "Failed to delete evaluation: {{msg}}", + "error_other": "Failed to delete evaluations: {{msg}}" + }, + "create": { + "error": "Failed to create new evaluation: {{msg}}" + } + }, + "actions": { + "skipEvaluators": "Skip Evaluators", + "skip": "Skip", + "startEvaluation": "Start Evaluation", + "minimize": "Minimize", + "versionHistory": "Version history", + "rerunEvaluation": "Re-run Evaluation", + "showCharts": "Show Charts", + "charts": "Charts", + "showCustomEvaluator": "Show Custom Evaluator", + "customEvaluator": "Custom Evaluator", + "showCostMetrics": "Show Cost Metrics", + "costMetrics": "Cost Metrics", + "showMetrics": "Show Metrics", + "tokenMetrics": "Token Metrics", + "showLatencyMetrics": "Show Latency Metrics", + "latencyMetrics": "Latency Metrics", + "version": "Version {{value}}", + "startNewEvaluation": "Start New Evaluation", + "disableAutoRefresh": "Disable auto-refresh", + "enableAutoRefresh": "Enable auto-refresh (every 5s)", + "newEvaluation": "New Evaluation", + "deleteEvaluation_one": "Delete evaluation", + "deleteEvaluation_other": "Delete evaluations", + "viewResults": "View Results" + }, + "table": { + "latestVersion": "Latest Version", + "averageMetrics": "Average Metrics", + "lastEvaluated": "Last Evaluated", + "flow": "Flow(s)", + "dataset": "Dataset", + "version": "Version", + "lastRun": "Last Run" + } + }, + "evaluators": { + "title": "Evaluators", + "notFound": "No Evaluators Yet", + "dialogs": { + "add": "Add Evaluator", + "edit": "Edit Evaluator", + "evaluationPrompt": "Evaluation Prompt", + "samplePrompts": "Sample Prompts", + "delete": { + "description": "Delete Evaluator {{name}}?" + } + }, + "inputs": { + "evaluatorType": { + "title": "Evaluator Type", + "placeholder": "Select Type", + "options": { + "text": { + "title": "Evaluate Result (Text Based)", + "description": "Set of Evaluators to evaluate the result of a Chatflow." + }, + "json": { + "title": "Evaluate Result (JSON)", + "description": "Set of Evaluators to evaluate the JSON response of a Chatflow." + }, + "numeric": { + "title": "Evaluate Metrics (Numeric)", + "description": "Set of Evaluators that evaluate the metrics (latency, tokens, cost, length of response) of a Chatflow." + }, + "llm": { + "title": "LLM based Grading (JSON)", + "description": "Post execution, grades the answers by using an LLM." + } + } + }, + "availableEvaluators": { + "title": "Available Evaluators", + "placeholder": "Select Dataset", + "options": { + "containsAny": { + "title": "Contains Any", + "description": "Returns true if any of the specified comma separated values are present in the response." + }, + "containsAll": { + "title": "Contains All", + "description": "Returns true if ALL of the specified comma separated values are present in the response." + }, + "doesNotContainAny": { + "title": "Does Not Contains Any", + "description": "Returns true if any of the specified comma separated values are present in the response." + }, + "doesNotContainAll": { + "title": "Does Not Contains All", + "description": "Returns true if ALL of the specified comma separated values are present in the response." + }, + "startsWith": { + "title": "Starts With", + "description": "Returns true if the response starts with the specified value." + }, + "notStartsWith": { + "title": "Does Not Start With", + "description": "Returns true if the response does not start with the specified value." + }, + "isValidJSON": { + "title": "Is Valid JSON", + "description": "Returns true if the response is a valid JSON." + }, + "isNotValidJSON": { + "title": "Is Not a Valid JSON", + "description": "Returns true if the response is a not a valid JSON." + }, + "totalTokens": { + "title": "Total Tokens", + "description": "Sum of Prompt Tokens and Completion Tokens." + }, + "promptTokens": { + "title": "Prompt Tokens", + "description": "This is the number of tokens in your prompt." + }, + "completionTokens": { + "title": "Completion Tokens", + "description": "Completion tokens are any tokens that the model generates in response to your input." + }, + "apiLatency": { + "title": "Total API Latency", + "description": "Total time taken for the Flowise Prediction API call (milliseconds)." + }, + "llm": { + "title": "LLM Latency", + "description": "Actual LLM invocation time (milliseconds)." + }, + "chain": { + "title": "Chatflow Latency", + "description": "Actual time spent in executing the chatflow (milliseconds)." + }, + "responseLength": { + "title": "Output Chars Length", + "description": "Number of characters in the response." + } + } + }, + "selectOperator": { + "title": "Select Operator", + "operators": { + "equals": "Equals", + "notEquals": "Not Equals", + "greaterThan": "Greater Than", + "lessThan": "Less Than", + "greaterThanOrEquals": "Greater Than or Equals", + "lessThanOrEquals": "Less Than or Equals" + } + }, + "availablePrompts": { + "title": "Available Prompts", + "placeholder": "Select Prompt" + }, + "value": "Value" + }, + "columns": { + "property": "Property", + "required": "Required" + }, + "actions": { + "loadSamples": "Load from Pre defined Samples", + "addItem": "Add Item", + "newEvaluator": "New Evaluator", + "selectPrompt": "Select Prompt" + }, + "messages": { + "update": { + "success": "Evaluator {{name}} updated", + "error": "Failed to update Evaluator {{name}}: {{msg}}" + }, + "add": { + "success": "New Evaluator added", + "error": "Failed to add new Evaluator: {{msg}}" + }, + "delete": { + "success": "Evaluator deleted", + "error": "Failed to delete Evaluator: {{mag}}" + } + }, + "outputSchema": { + "title": "Output Schema", + "tooltip1": "What is the output format in JSON?", + "tooltip2": "Instruct the LLM to give formatted JSON output" + }, + "evaluationPrompts": { + "correctness": "Correctness", + "hallucination": "Hallucination" + }, + "table": { + "details": "Details", + "lastUpdated": "Last Updated" + }, + "type": { + "numeric": "Numeric", + "textBased": "Text Based", + "jsonBased": "JSON Based", + "llmBased": "LLM Based", + "measure": "Measure", + "operator": "Operator", + "value": "Value", + "outputSchemaElements": "Output Schema Elements" + }, + "note": "You can use {question} {actualOutput} {expectedOutput} to inject runtime values into your prompt." + }, + "files": { + "title": "Files", + "searchPlaceholder": "Search File", + "notFound": "No Files Yet", + "dialogs": { + "delete": { + "description": "Delete {{name}}? This process cannot be undone." + } + }, + "messages": { + "success": "File deleted" + } + }, + "marketplaces": { + "title": "Marketplace", + "description": "Explore and use pre-built templates", + "searchPlaceholder": "Search Name/Description/Node", + "notFound": "No Marketplace Yet", + "notFoundCustomTemplate": "No Saved Custom Templatess", + "actions": { + "toggleSnapping": "toggle snapping", + "toggleBackground": "toggle background", + "useChatflow": "Use Chatflow", + "useTemplate": "Use Template", + "additionalParameters": "Additional Parameters" + }, + "dialogs": { + "shareCustomTemplate": "Share Custom Template", + "addNewTool": "Add New Tool", + "delete": { + "description": "Delete Custom Template {{name}}?" + } + }, + "messages": { + "delete": { + "success": "Custom Template deleted successfully!", + "error": "Failed to delete custom template: {{msg}}" + } + }, + "inputs": { + "inputs": "Inputs", + "tag": "Tag", + "framework": "Framework", + "usecases": "Usecases" + }, + "tabs": { + "communityTemplates": "Community Templates", + "myTemplates": "My Templates" + } + }, + "organization": { + "or": "OR", + "setup": { + "title": "Setup Account", + "caption": "Account setup does not make any external connections, your data stays securely on your locally hosted server." + }, + "inputs": { + "organization": { + "title": "Organization", + "placeholder": "Acme" + }, + "username": { + "title": "Username", + "placeholder": "John Doe" + }, + "password": { + "title": "Password", + "caption": "Password must be at least 8 characters long and contain at least one lowercase letter, one uppercase letter, one digit, and one special character." + }, + "confirmPassword": { + "title": "Confirm Password", + "caption": "Reconfirm your password. Must match the password typed above." + }, + "organizationName": "Organization Name", + "administratorName": { + "title": "Administrator Name", + "caption": "Is used for display purposes only.", + "placeholder": "Display Name" + }, + "administratorEmail": { + "title": "Administrator Email", + "caption": "Kindly use a valid email address. Will be used as login id." + } + }, + "errors": { + "registration": { + "base": "Error in registering account: {{msg}}", + "enterprise": "Error in registering organization. Please contact your administrator. ({{msg}})" + } + }, + "actions": { + "accountAdministrator": "Account Administrator", + "signup": { + "title": "Sign Up", + "microsoft": "Sign Up With Microsoft", + "google": "Sign Up With Google", + "auth0": "Sign Up With Auth0 by Okta" + } + } + }, + "roles": { + "title": "Roles", + "searchPlaceholder": "Search Roles", + "notFound": "No Roles Yet", + "dialogs": { + "edit": "Edit Role", + "view": "View Role", + "create": "Create New Role", + "delete": { + "description": "Delete Role {{name}}?" + } + }, + "messages": { + "create": { + "success": "New Role Created!", + "error": "Role Name cannot contain spaces." + }, + "update": { + "success": "Role Updated Successfully", + "error": "Failed: {{msg}}" + }, + "delete": { + "success": "Role deleted", + "error": "Failed to delete Role: {{msg}}" + } + }, + "inputs": { + "roleName": { + "title": "Role Name", + "placeholder": "Enter role name" + }, + "roleDescription": { + "title": "Role Description", + "placeholder": "Description of the role" + } + }, + "actions": { + "createRole": "Create Role", + "updateRole": "Update Role", + "view": "View", + "invite": "Invite", + "addRole": "Add Role" + }, + "removeUsersFirst": "Remove users with the role from Workspace first", + "table": { + "assignedUsersTable": "assigned users table", + "usersTable": "users table", + "permissions": "Permissions", + "assignedUsers": "Assigned Users" + } + }, + "logs": { + "title": "Logs", + "notFound": "No Logs Yet", + "timeRanges": { + "lastHour": "Last hour", + "last4Hours": "Last 4 hours", + "last24hours": "Last 24 hours", + "last2Days": "Last 2 days", + "last7Days": "Last 7 days", + "last14Days": "Last 14 days", + "last1Month": "Last 1 month", + "last2Months": "Last 2 months", + "last3Months": "Last 3 months", + "custom": "Custom" + }, + "formats": { + "time": "HH:mm", + "date": "yyyy MMMM d, h aa" + } + }, + "tools": { + "description": "External functions or APIs the agent can use to take action", + "searchPlaceholder": "Search Tools", + "notFound": "No Tools Created Yet", + "dialogs": { + "addNewTool": "Add New Tool", + "editTool": "Edit Tool", + "howToUse": { + "title": "How To Use Function", + "youCan": "You can use any libraries imported in Flowise", + "propUse": "You can use properties specified in Input Schema as variables with prefix $:", + "deafultConfig": "You can get default flow config:", + "variables": "You can get custom variables:", + "return": "Must return a string value at the end of function" + }, + "jsonSchema": { + "title": "Paste JSON Schema" + }, + "exportAsTemplate": "Export As Template", + "deleteTool": { + "title": "Delete Tool", + "description": "Delete tool {{name}}?" + } + }, + "actions": { + "create": "Create", + "outlinedPrimaryButtonGroup": "outlined primary button group", + "seeExample": "See Example", + "pasteJson": "Paste JSON", + "addItem": "Add Item", + "howUseFunction": "How to use Function", + "useTemplate": "Use Template" + }, + "messages": { + "export": { + "error": "Failed to export Tool: {{msg}}" + }, + "add": { + "success": "New Tool added", + "error": "Failed to add new Tool: {{msg}}" + }, + "save": { + "success": "Tool saved", + "error": "Failed to save Tool: {{msg}}" + }, + "delete": { + "success": "Tool deleted", + "error": "Failed to delete Tool: {{msg}}" + } + }, + "error": "Invalid JSON format. Please check your input.", + "columns": { + "property": "Property", + "required": "Required" + }, + "inputs": { + "toolName": { + "title": "Tool Name", + "tooltip": "Tool name must be small capital letter with underscore. Ex: my_tool", + "placeholder": "My New Tool" + }, + "toolDescription": { + "title": "Tool description", + "tooltip": "Description of what the tool does. This is for ChatGPT to determine when to use this tool." + }, + "toolIconSource": "Tool Icon Source", + "inputSchema": { + "title": "Input Schema", + "tooltip": "What is the input format in JSON?" + }, + "jsFunction": { + "title": "Javascript Function", + "tooltip": "Function to execute when tool is being used. You can use properties specified in Input Schema as variables. For example, if the property is userid, you can use as $userid. Return value must be a string. You can also override the code from API by following this guide" + } + } + }, + "users": { + "title": "User Management", + "searchPlaceholder": "Search Users", + "notFound": "No Users Yet", + "organizationOwner": "ORGANIZATION OWNER", + "assignedRoles": "Assigned Roles", + "statuses": { + "active": "Active", + "inactive": "Inactive" + }, + "actions": { + "sendInvite": "Send Invite", + "updateInvite": "Update Invite", + "inviteUser": "Invite User" + }, + "tables": { + "assignedRolesTable": "assigned roles table", + "role": "Role", + "emailName": "Email/Name", + "assignedRoles": "Assigned Roles", + "lastLogin": "Last Login" + }, + "dialogs": { + "delete": { + "description": "Remove {{name}} from organization?" + }, + "edit": { + "title": "Edit User" + } + }, + "messages": { + "delete": { + "success": "User removed from organization successfully", + "error": "Failed to delete User: {{msg}}" + }, + "update": { + "success": "User Details Updated", + "error": "Failed to update User: {{msg}}" + } + }, + "inputs": { + "accountStatus": "Account Status" + }, + "error": "Cannot change status of the organization owner!" + }, + "variables": { + "description": "Create and manage global variables", + "searchPlaceholder": "Search Variables", + "notFound": "No Variables Yet", + "actions": { + "howToUse": "How To Use", + "addVariable": "Add Variable" + }, + "dialogs": { + "delete": { + "description": "Delete variable {{name}}?" + }, + "add": "Add Variable", + "edit": "Edit Variable" + }, + "messages": { + "delete": { + "success": "Variable deleted", + "error": "Failed to delete Variable: {{msg}}" + }, + "add": { + "success": "New Variable added", + "error": "Failed to add new Variable: {{msg}}" + }, + "save": { + "success": "Variable saved", + "error": "Failed to add new Variable: {{msg}}" + } + }, + "inputs": { + "variableName": "Variable Name", + "value": "Value" + }, + "tables": { + "value": "Value", + "lastUpdated": "Last Updated", + "created": "Created" + }, + "types": { + "static": { + "title": "Static", + "description": "Variable value will be read from the value entered below" + }, + "runtime": { + "title": "Runtime", + "description": "Variable value will be read from .env file" + } + }, + "help": { + "title": "How To Use Variables", + "ifElse": "Variables can be used in Custom Tool, Custom Function, Custom Loader, If Else Function with the $ prefix.", + "sysmessage": "Variables can also be used in Text Field parameter of any node. For example, in System Message of Agent:", + "static": "If variable type is Static, the value will be retrieved as it is. If variable type is Runtime, the value will be retrieved from .env file.", + "override": "You can also override variable values in API overrideConfig using vars:", + "readmore": "Read more from docs" + } + }, + "vectorStore": { + "notFound": "No Upsert History Yet", + "dialogs": { + "upsertRecord": "Upsert Record", + "upsertVectorStore": "Upsert Vector Store" + }, + "addedDocuments": "{{count}} Added Documents", + "note": "For security reason, override config is disabled by default. You can change this by going into Chatflow Configuration -> Security tab, and enable the property you want to override.", + "referTo": "Refer here for more details", + "specify": "You can also specify multiple values for a config parameter by specifying the node id", + "upsertVectorDatabase": "Upsert Vector Database", + "tables": { + "date": "Date", + "added": { + "title": "Added", + "tooltip": "Number of vector embeddings added to Vector Store" + }, + "updated": { + "title": "Updated", + "tooltip": "Updated existing vector embeddings. Only works when a Record Manager is connected to the Vector Store" + }, + "skipped": { + "title": "Skipped", + "tooltip": "Number of same vector embeddings that exists, and were skipped re-upserting again. Only works when a Record Manager is connected to the Vector Store" + }, + "deleted": { + "title": "Deleted", + "tooltip": "Deleted vector embeddings. Only works when a Record Manager with a Cleanup method is connected to the Vector Store" + }, + "details": "Details" + }, + "actions": { + "delete_one": "Delete {{count}} row", + "delete_other": "Delete {{count}} rows", + "testRetrieval": "Test Retrieval", + "upsert": "Upsert" + }, + "messages": { + "remove": { + "success": "Succesfully deleted upsert history", + "error": "Failed to delete Upsert History: {{msg}}" + }, + "upsertClicked": { + "success": "Succesfully upserted vector store. You can start chatting now!" + } + }, + "inputs": { + "fromDate": "From Date", + "toDate": "To Date", + "showApi": "Show API" + } + }, + "workspace": { + "title": "Workspaces", + "searchPlaceholder": "Search Workspaces", + "users": "Users", + "change": "Change Workspace Role - {{email}} {{name}}", + "orgOwner": "ORGANIZATION OWNER", + "personalWorkspace": "PERSONAL WORKSPACE", + "notFound": "No Workspaces Yet", + "loading": "Switching workspace...", + "deleting": "Deleting workspace...", + "inputs": { + "assign": { + "title": "New Role to Assign", + "placeholder": "Select Role" + } + }, + "tables": { + "workspaceUsersTable": "workspace users table", + "user_other": "Users", + "role": "Role", + "lastUpdated": "Last Updated", + "emailName": "Email/Name", + "lastLogin": "Last Login" + }, + "dialogs": { + "add": "Add Workspace", + "edit": "Edit Workspace", + "delete": { + "title": "Delete Workspace {{name}}", + "description": "This is irreversible and will remove all associated data inside the workspace. Are you sure you want to delete?" + }, + "remove": { + "title": "Remove Users", + "description": "Remove the following users from the workspace?\n{{users}}" + }, + "workspaceUsers": { + "title": "{{name}}: Workspace Users", + "description": "Manage workspace users and permissions.", + "searchPlaceholder": "Search Users", + "notFound": "No Assigned Users Yet" + } + }, + "actions": { + "workspaceUsers": "Workspace Users", + "sendInvite": "Send Invite", + "updateInvite": "Update Invite", + "updateRole": "Update Role", + "remove": "Remove", + "removeUsers": "Remove Users", + "addUser": "Add User", + "changeRole": "Change Role" + }, + "messages": { + "add": { + "success": "New Workspace added", + "errors": { + "failed": "Failed to add new Workspace: {{msg}}", + "reserved": "Workspace name cannot be Default Workspace or Personal Workspace - this is a reserved name" + } + }, + "save": { + "success": "Workspace saved", + "error": "Failed to save Workspace: {{msg}}" + }, + "update": { + "success": "WorkspaceUser Details Updated", + "error": "Failed to update WorkspaceUser: {{msg}}" + }, + "delete": { + "success": "Workspace deleted", + "error": "Failed to delete workspace: {{msg}}" + }, + "remove": { + "success_one": "{{count}} User removed from workspace.", + "success_other": "{{count}} Users removed from workspace.", + "errors": { + "failed": "Organization owner cannot be removed from workspace.", + "link": "Failed to unlink users: {{msg}}" + } + } + } + }, + "scaleInitials": ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"], + "badges": { + "popular": "POPULAR", + "new": "NEW" + } +} diff --git a/packages/ui/src/ErrorBoundary.jsx b/packages/ui/src/ErrorBoundary.jsx index 25e95550883..806b0941bd2 100644 --- a/packages/ui/src/ErrorBoundary.jsx +++ b/packages/ui/src/ErrorBoundary.jsx @@ -3,11 +3,14 @@ import PropTypes from 'prop-types' import { Box, Card, IconButton, Stack, Typography, useTheme } from '@mui/material' import { IconCopy } from '@tabler/icons-react' +import { useTranslation } from 'react-i18next' + const ErrorBoundary = ({ error }) => { const theme = useTheme() + const { t } = useTranslation() const copyToClipboard = () => { - const errorMessage = `Status: ${error.response.status}\n${error.response.data.message}` + const errorMessage = t('errorBoundary.statusLabel', { status: `${error.response.status}\n${error.response.data.message}` }) navigator.clipboard.writeText(errorMessage) } @@ -15,8 +18,8 @@ const ErrorBoundary = ({ error }) => { - Oh snap! - The following error occurred when loading this page. + {t('errorBoundary.title')} + {t('errorBoundary.loadErrorMessage')} @@ -28,16 +31,16 @@ const ErrorBoundary = ({ error }) => {
-                            {`Status: ${error.response.status}`}
+                            {t('errorBoundary.statusLabel', { status: error.response?.status })}
                             
{error.response?.data?.message}
- Please retry after some time. If the issue persists, reach out to us on our Discord server. + {t('errorBoundary.retryMessage')}
- Alternatively, you can raise an issue on Github. + {t('errorBoundary.githubIssueMessage')}
diff --git a/packages/ui/src/hooks/useApi.jsx b/packages/ui/src/hooks/useApi.jsx index 58f8bf09cd9..1d817ab9fab 100644 --- a/packages/ui/src/hooks/useApi.jsx +++ b/packages/ui/src/hooks/useApi.jsx @@ -1,7 +1,10 @@ import { useState } from 'react' import { useError } from '@/store/context/ErrorContext' +// i18n +import { useTranslation } from 'react-i18next' export default (apiFunc) => { + const { t } = useTranslation() const [data, setData] = useState(null) const [loading, setLoading] = useState(false) const [error, setApiError] = useState(null) @@ -15,8 +18,8 @@ export default (apiFunc) => { setError(null) setApiError(null) } catch (err) { - handleError(err || 'Unexpected Error!') - setApiError(err || 'Unexpected Error!') + handleError(err || t('errors.unexpectedError')) + setApiError(err || t('errors.unexpectedError')) } finally { setLoading(false) } diff --git a/packages/ui/src/i18n.js b/packages/ui/src/i18n.js new file mode 100644 index 00000000000..a90c75dd34b --- /dev/null +++ b/packages/ui/src/i18n.js @@ -0,0 +1,18 @@ +import i18n from 'i18next' +import Backend from 'i18next-http-backend' +import LanguageDetector from 'i18next-browser-languagedetector' +import { initReactI18next } from 'react-i18next' + +i18n.use(Backend) + .use(LanguageDetector) + .use(initReactI18next) // passes i18n down to react-i18next + .init({ + backend: { + loadPath: '/locales/{{lng}}.json' + }, + fallbackLng: 'en', + saveMissing: true, + supportedLngs: ['en'] + }) + +export default i18n diff --git a/packages/ui/src/index.jsx b/packages/ui/src/index.jsx index d8833eed413..0f8d537f133 100644 --- a/packages/ui/src/index.jsx +++ b/packages/ui/src/index.jsx @@ -14,6 +14,8 @@ import ConfirmContextProvider from '@/store/context/ConfirmContextProvider' import { ReactFlowContext } from '@/store/context/ReactFlowContext' import { ConfigProvider } from '@/store/context/ConfigContext' import { ErrorProvider } from '@/store/context/ErrorContext' +// language +import '@/i18n' const container = document.getElementById('root') const root = createRoot(container) diff --git a/packages/ui/src/layout/MainLayout/Header/OrgWorkspaceBreadcrumbs/index.jsx b/packages/ui/src/layout/MainLayout/Header/OrgWorkspaceBreadcrumbs/index.jsx index 7c1cdc7c512..90bd71de28b 100644 --- a/packages/ui/src/layout/MainLayout/Header/OrgWorkspaceBreadcrumbs/index.jsx +++ b/packages/ui/src/layout/MainLayout/Header/OrgWorkspaceBreadcrumbs/index.jsx @@ -33,6 +33,9 @@ import useApi from '@/hooks/useApi' import { store } from '@/store' import { workspaceSwitchSuccess } from '@/store/reducers/authSlice' +// i18n +import { useTranslation } from 'react-i18next' + // ==============================|| OrgWorkspaceBreadcrumbs ||============================== // const StyledMenu = styled((props) => ( @@ -89,6 +92,7 @@ const StyledBreadcrumb = styled(Chip)(({ theme, isDarkMode }) => { }) const OrgWorkspaceBreadcrumbs = () => { + const { t } = useTranslation() const navigate = useNavigate() const user = useSelector((state) => state.auth.user) @@ -223,7 +227,9 @@ const OrgWorkspaceBreadcrumbs = () => { if (getOrganizationsByUserIdApi.data) { const formattedAssignedOrgs = getOrganizationsByUserIdApi.data.map((organization) => ({ id: organization.organizationId, - name: `${organization.user.name || organization.user.email}'s Organization` + name: t('profile.organizations.organizationName', { + name: organization.user.name || organization.user.email + }) })) const sortedOrgs = [...formattedAssignedOrgs].sort((a, b) => a.name.localeCompare(b.name)) @@ -241,7 +247,7 @@ const OrgWorkspaceBreadcrumbs = () => { } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [getOrganizationsByUserIdApi.data]) + }, [getOrganizationsByUserIdApi.data, t]) useEffect(() => { if (getOrganizationsByUserIdApi.error) { @@ -308,14 +314,17 @@ const OrgWorkspaceBreadcrumbs = () => { org.id === activeOrganizationId)?.name || 'Organization'} + label={ + assignedOrganizations.find((org) => org.id === activeOrganizationId)?.name || + t('profile.organizations.organization') + } deleteIcon={} onDelete={handleOrgClick} onClick={handleOrgClick} /> ws.id === activeWorkspaceId)?.name || 'Workspace'} + label={assignedWorkspaces.find((ws) => ws.id === activeWorkspaceId)?.name || t('common.labels.workspace')} deleteIcon={} onDelete={handleWorkspaceClick} onClick={handleWorkspaceClick} @@ -328,7 +337,7 @@ const OrgWorkspaceBreadcrumbs = () => { - Switching organization... + {t('profile.organizations.switching')} @@ -338,7 +347,7 @@ const OrgWorkspaceBreadcrumbs = () => { - Switching workspace... + {t('profile.workspaces.switching')} @@ -356,12 +365,10 @@ const OrgWorkspaceBreadcrumbs = () => { > - Workspace Unavailable + {t('profile.workspaces.unavailable')} {assignedWorkspaces.length > 0 && !activeOrganizationId ? ( <> - - Your current workspace is no longer available. Please select another workspace to continue. - + {t('profile.workspaces.unavailableContinue')} { displayEmpty > - Select Organization + {t('profile.organizations.select')} {assignedOrganizations.map((org, index) => ( @@ -415,7 +420,7 @@ const OrgWorkspaceBreadcrumbs = () => { sx={{ mt: 2 }} > - Select Workspace + {t('profile.workspaces.select')} {assignedWorkspaces.map((workspace, index) => ( diff --git a/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.jsx b/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.jsx index 7b75dc071e7..8e16d11344c 100644 --- a/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.jsx +++ b/packages/ui/src/layout/MainLayout/Header/ProfileSection/index.jsx @@ -54,23 +54,27 @@ import exportImportApi from '@/api/exportimport' import useApi from '@/hooks/useApi' import { getErrorMessage } from '@/utils/errorHandler' +// i18n +import { useTranslation } from 'react-i18next' + const dataToExport = [ - 'Agentflows', - 'Agentflows V2', - 'Assistants Custom', - 'Assistants OpenAI', - 'Assistants Azure', - 'Chatflows', - 'Chat Messages', - 'Chat Feedbacks', - 'Custom Templates', - 'Document Stores', - 'Executions', - 'Tools', - 'Variables' + { id: 'Agentflows', title: 'menu.agentflows' }, + { id: 'Agentflows V2', title: 'menu.agentflowsV2' }, + { id: 'Assistants Custom', title: 'menu.assistants.custom' }, + { id: 'Assistants OpenAI', title: 'menu.assistants.openAi' }, + { id: 'Assistants Azure', title: 'menu.assistants.azure' }, + { id: 'Chatflows', title: 'common.labels.chatflows' }, + { id: 'Chat Messages', title: 'menu.chatMessages' }, + { id: 'Chat Feedbacks', title: 'menu.chatFeedbacks' }, + { id: 'Custom Templates', title: 'menu.customTemplates' }, + { id: 'Document Stores', title: 'menu.documentStores' }, + { id: 'Executions', title: 'menu.executions' }, + { id: 'Tools', title: 'common.labels.tools' }, + { id: 'Variables', title: 'common.labels.variables' } ] const ExportDialog = ({ show, onCancel, onExport }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const [selectedData, setSelectedData] = useState(dataToExport) @@ -96,7 +100,7 @@ const ExportDialog = ({ show, onCancel, onExport }) => { aria-describedby='export-dialog-description' > - {!isExporting ? 'Select Data to Export' : 'Exporting..'} + {!isExporting ? t('profile.export.selectData') : t('profile.export.exporting')} {!isExporting && ( @@ -115,17 +119,17 @@ const ExportDialog = ({ show, onCancel, onExport }) => { control={ x.id === data.id)} onChange={(event) => { setSelectedData( event.target.checked ? [...selectedData, data] - : selectedData.filter((item) => item !== data) + : selectedData.filter((item) => item.id !== data.id) ) }} /> } - label={data} + label={t(data.title)} /> ))} @@ -142,7 +146,7 @@ const ExportDialog = ({ show, onCancel, onExport }) => { src={ExportingGIF} alt='ExportingGIF' /> - Exporting data might takes a while + {t('profile.export.exportingWhile')} )} @@ -155,7 +159,7 @@ const ExportDialog = ({ show, onCancel, onExport }) => { variant='contained' onClick={() => { setIsExporting(true) - onExport(selectedData) + onExport(selectedData.map((x) => x.id)) }} > Export @@ -175,12 +179,13 @@ ExportDialog.propTypes = { } const ImportDialog = ({ show }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const component = show ? ( - Importing... + {t('profile.import.importing')} @@ -194,7 +199,7 @@ const ImportDialog = ({ show }) => { src={ExportingGIF} alt='ImportingGIF' /> - Importing data might takes a while + {t('profile.import.importingWhile')} @@ -211,6 +216,7 @@ ImportDialog.propTypes = { // ==============================|| PROFILE MENU ||============================== // const ProfileSection = ({ handleLogout }) => { + const { t } = useTranslation() const theme = useTheme() const customization = useSelector((state) => state.customization) @@ -287,7 +293,7 @@ const ProfileSection = ({ handleLogout }) => { setImportDialogOpen(false) dispatch({ type: REMOVE_DIRTY }) enqueueSnackbar({ - message: `Import All successful`, + message: t('profile.import.importSuccess'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -334,15 +340,15 @@ const ProfileSection = ({ handleLogout }) => { useEffect(() => { if (importAllApi.error) { setImportDialogOpen(false) - let errMsg = 'Invalid Imported File' + let errMsg = t('profile.import.invalidImport') let error = importAllApi.error if (error?.response?.data) { errMsg = typeof error.response.data === 'object' ? error.response.data.message : error.response.data } - errorFailed(`Failed to import: ${errMsg}`) + errorFailed(t('profile.import.failToImport', { msg: errMsg })) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [importAllApi.error]) + }, [importAllApi.error, t]) useEffect(() => { if (exportAllApi.data) { @@ -358,24 +364,24 @@ const ProfileSection = ({ handleLogout }) => { linkElement.setAttribute('download', exportAllApi.data.FileDefaultName) linkElement.click() } catch (error) { - errorFailed(`Failed to export all: ${getErrorMessage(error)}`) + errorFailed(t('profile.export.failToExport', { msg: getErrorMessage(error) })) } } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [exportAllApi.data]) + }, [exportAllApi.data, t]) useEffect(() => { if (exportAllApi.error) { setExportDialogOpen(false) - let errMsg = 'Internal Server Error' + let errMsg = t('common.errors.internalServerError') let error = exportAllApi.error if (error?.response?.data) { errMsg = typeof error.response.data === 'object' ? error.response.data.message : error.response.data } - errorFailed(`Failed to export: ${errMsg}`) + errorFailed(t('profile.export.failToExport', { msg: errMsg })) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [exportAllApi.error]) + }, [exportAllApi.error, t]) useEffect(() => { if (prevOpen.current === true && open === false) { @@ -438,7 +444,7 @@ const ProfileSection = ({ handleLogout }) => { ) : ( - User + {t('common.labels.user')} )} @@ -471,7 +477,9 @@ const ProfileSection = ({ handleLogout }) => { - Export} /> + {t('common.actions.export')}} + /> { - Import} /> + {t('profile.actions.import')}} + /> { - Version} /> + {t('profile.version')}} + /> {isAuthenticated && !currentUser.isSSO && ( { - Account Settings} /> + {t('profile.accountSetting')}} + /> )} { - Logout} /> + {t('profile.logout')}} + /> diff --git a/packages/ui/src/layout/MainLayout/Header/WorkspaceSwitcher/index.jsx b/packages/ui/src/layout/MainLayout/Header/WorkspaceSwitcher/index.jsx index 54124000f37..54809adb901 100644 --- a/packages/ui/src/layout/MainLayout/Header/WorkspaceSwitcher/index.jsx +++ b/packages/ui/src/layout/MainLayout/Header/WorkspaceSwitcher/index.jsx @@ -34,6 +34,9 @@ import { useConfig } from '@/store/context/ConfigContext' import { store } from '@/store' import { logoutSuccess, workspaceSwitchSuccess } from '@/store/reducers/authSlice' +// i18n +import { useTranslation } from 'react-i18next' + // ==============================|| WORKSPACE SWITCHER ||============================== // const StyledMenu = styled((props) => ( @@ -73,6 +76,7 @@ const StyledMenu = styled((props) => ( })) const WorkspaceSwitcher = () => { + const { t } = useTranslation() const navigate = useNavigate() const user = useSelector((state) => state.auth.user) @@ -200,10 +204,11 @@ const WorkspaceSwitcher = () => { setShowWorkspaceUnavailableDialog(false) // Set error message and show error dialog - setErrorMessage(switchWorkspaceApi.error.message || 'Failed to switch workspace') + setErrorMessage(switchWorkspaceApi.error.message || t('profile.workspaces.failedToSwitch')) setShowErrorDialog(true) } - }, [switchWorkspaceApi.error]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [switchWorkspaceApi.error, t]) useEffect(() => { try { @@ -299,7 +304,7 @@ const WorkspaceSwitcher = () => { - Switching workspace... + {t('profile.workspaces.switching')} @@ -318,10 +323,8 @@ const WorkspaceSwitcher = () => { > - Workspace Unavailable - - Your current workspace is no longer available. Please select another workspace to continue. - + {t('profile.workspaces.unavailable')} + {t('profile.workspaces.unavailableContinue')} setImageTag(e.target.value)}> + {t('common.labels.model')} + setNimRelaxMemConstraints(e.target.value)} > - Yes - No + {t('common.actions.yes')} + {t('common.actions.no')} setHostPort(e.target.value)} inputProps={{ min: 1, max: 65535 }} sx={{ mt: 2 }} /> -

Click Next to start the container.

+

{t('components.dialogs.nvidiaNim.steps.start.tooltip')}

)} @@ -410,37 +416,49 @@ const NvidiaNIMDialog = ({ open, onClose, onComplete }) => {
{activeStep === 0 && ( )} setShowContainerConfirm(false)}> - Container Already Exists + {t('components.dialogs.nvidiaNim.exists.title')} -

A container for this image already exists:

+

{t('components.dialogs.nvidiaNim.exists.containerAlreadyExists')}

- Name: {existingContainer?.name || 'N/A'} + + }} + />

- Status: {existingContainer?.status || 'N/A'} + + }} + />

-

You can:

+

{t('components.dialogs.nvidiaNim.exists.youCan')}

    -
  • Use the existing container (recommended)
  • -
  • Change the port and try again
  • +
  • {t('components.dialogs.nvidiaNim.exists.useExisting')}
  • +
  • {t('components.dialogs.nvidiaNim.exists.changePort')}
@@ -450,7 +468,7 @@ const NvidiaNIMDialog = ({ open, onClose, onComplete }) => { setExistingContainer(null) }} > - Cancel + {t('common.actions.cancel')}
diff --git a/packages/ui/src/ui-component/dialog/PromptGeneratorDialog.jsx b/packages/ui/src/ui-component/dialog/PromptGeneratorDialog.jsx index 1b0e3e48397..026f99b5138 100644 --- a/packages/ui/src/ui-component/dialog/PromptGeneratorDialog.jsx +++ b/packages/ui/src/ui-component/dialog/PromptGeneratorDialog.jsx @@ -10,6 +10,8 @@ import { IconX, IconWand, IconArrowLeft, IconNotebook, IconLanguage, IconMail, I import useNotifier from '@/utils/useNotifier' import { LoadingButton } from '@mui/lab' +// i18n + const defaultInstructions = [ { text: 'Summarize a document', @@ -143,7 +145,7 @@ const AssistantPromptGenerator = ({ show, dialogProps, onCancel, onConfirm }) => rows={12} disabled={loading} value={customAssistantInstruction} - placeholder={'Describe your task here'} + placeholder={t('components.dialogs.promptGenerator.describeTaskPlaceholder')} onChange={(event) => setCustomAssistantInstruction(event.target.value)} /> )} @@ -168,7 +170,7 @@ const AssistantPromptGenerator = ({ show, dialogProps, onCancel, onConfirm }) => }} startIcon={} > - Generate + {t('common.actions.generate')} )} {generatedInstruction && ( @@ -179,12 +181,12 @@ const AssistantPromptGenerator = ({ show, dialogProps, onCancel, onConfirm }) => setGeneratedInstruction('') }} > - Back + {t('common.actions.back')} )} {generatedInstruction && ( onConfirm(generatedInstruction)}> - Apply + {t('common.actions.apply')} )} diff --git a/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.jsx b/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.jsx index 54ce99e13ec..36c509d44ff 100644 --- a/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.jsx +++ b/packages/ui/src/ui-component/dialog/PromptLangsmithHubDialog.jsx @@ -44,6 +44,9 @@ import useApi from '@/hooks/useApi' import promptApi from '@/api/prompt' import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions' +// i18n +import { useTranslation } from 'react-i18next' + const NewLineToBr = ({ children = '' }) => { return children.split('\n').reduce(function (arr, line) { return arr.concat(line,
) @@ -79,6 +82,7 @@ const AccordionDetails = styled(MuiAccordionDetails)(({ theme }) => ({ })) const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const dispatch = useDispatch() const customization = useSelector((state) => state.customization) @@ -138,32 +142,32 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => { const [modelName, setModelName] = useState([]) const usecases = [ - { id: 201, name: 'Agents' }, - { id: 202, name: 'Agent Stimulation' }, - { id: 203, name: 'Autonomous agents' }, - { id: 204, name: 'Classification' }, - { id: 205, name: 'Chatbots' }, - { id: 206, name: 'Code understanding' }, - { id: 207, name: 'Code writing' }, - { id: 208, name: 'Evaluation' }, - { id: 209, name: 'Extraction' }, - { id: 210, name: 'Interacting with APIs' }, - { id: 211, name: 'Multi-modal' }, - { id: 212, name: 'QA over documents' }, - { id: 213, name: 'Self-checking' }, - { id: 214, name: 'SQL' }, - { id: 215, name: 'Summarization' }, - { id: 216, name: 'Tagging' } + { id: 201, name: 'common.labels.agents' }, + { id: 202, name: 'components.dialogs.promptLangsmithHub.cases.agentStimulation' }, + { id: 203, name: 'components.dialogs.promptLangsmithHub.cases.autonomousAgents' }, + { id: 204, name: 'components.dialogs.promptLangsmithHub.cases.classification' }, + { id: 205, name: 'components.dialogs.promptLangsmithHub.cases.chatbots' }, + { id: 206, name: 'components.dialogs.promptLangsmithHub.cases.codeUnderstanding' }, + { id: 207, name: 'components.dialogs.promptLangsmithHub.cases.codeWriting' }, + { id: 208, name: 'components.dialogs.promptLangsmithHub.cases.evaluation' }, + { id: 209, name: 'components.dialogs.promptLangsmithHub.cases.extraction' }, + { id: 210, name: 'components.dialogs.promptLangsmithHub.cases.interactingApi' }, + { id: 211, name: 'components.dialogs.promptLangsmithHub.cases.multiModal' }, + { id: 212, name: 'components.dialogs.promptLangsmithHub.cases.qaDocuments' }, + { id: 213, name: 'components.dialogs.promptLangsmithHub.cases.selfChecking' }, + { id: 214, name: 'components.dialogs.promptLangsmithHub.cases.sql' }, + { id: 215, name: 'components.dialogs.promptLangsmithHub.cases.summarization' }, + { id: 216, name: 'components.dialogs.promptLangsmithHub.cases.tagging' } ] const [usecase, setUsecase] = useState([]) const languages = [ - { id: 301, name: 'Chinese' }, - { id: 302, name: 'English' }, - { id: 303, name: 'French' }, - { id: 304, name: 'German' }, - { id: 305, name: 'Russian' }, - { id: 306, name: 'Spanish' } + { id: 301, name: 'components.dialogs.promptLangsmithHub.langs.chinese' }, + { id: 302, name: 'components.dialogs.promptLangsmithHub.langs.english' }, + { id: 303, name: 'components.dialogs.promptLangsmithHub.langs.french' }, + { id: 304, name: 'components.dialogs.promptLangsmithHub.langs.german' }, + { id: 305, name: 'components.dialogs.promptLangsmithHub.langs.russian' }, + { id: 306, name: 'components.dialogs.promptLangsmithHub.langs.spanish' } ] const [language, setLanguage] = useState([]) const [availablePrompNameList, setAvailablePrompNameList] = useState([]) @@ -260,7 +264,7 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => { - Model + {t('common.labels.model')} { size='small' value={usecase} onChange={handleUsecaseChange} - input={} + input={} renderValue={(selected) => selected.map((x) => x.name).join(', ')} endAdornment={ usecase.length ? ( @@ -328,14 +332,14 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => { {usecases.map((variant) => ( item.id === variant.id) >= 0} /> - + ))} - Language + {t('common.labels.language')} @@ -382,7 +386,7 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => { promptEmptySVG -
Please wait....loading Prompts
+
{t('components.dialogs.promptLangsmithHub.loading.wait')}
)} {!loading && availablePrompNameList && availablePrompNameList.length === 0 && ( @@ -390,7 +394,7 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => { promptEmptySVG -
No Available Prompts
+
{t('components.dialogs.promptLangsmithHub.loading.notFound')}
)} {!loading && availablePrompNameList && availablePrompNameList.length > 0 && ( @@ -402,9 +406,9 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => { - Available Prompts + {t('components.dialogs.promptLangsmithHub.loading.found')} - + {availablePrompNameList.map((item, index) => ( { expandIcon={} id='panel2d-header' > - Prompt + {t('common.labels.prompt')} @@ -488,7 +492,7 @@ const PromptLangsmithHubDialog = ({ promptType, show, onCancel, onSubmit }) => { expandIcon={} id='panel1d-header' > - Description + {t('common.labels.description')} { aria-controls='panel3d-content' id='panel3d-header' > - Readme + {t('components.dialogs.promptLangsmithHub.readme')}
{ {availablePrompNameList && availablePrompNameList.length > 0 && ( - + onSubmit(selectedPrompt.detailed)} variant='contained' > - Load + {t('common.actions.load')} )} diff --git a/packages/ui/src/ui-component/dialog/SaveChatflowDialog.jsx b/packages/ui/src/ui-component/dialog/SaveChatflowDialog.jsx index e567132899f..01462d91277 100644 --- a/packages/ui/src/ui-component/dialog/SaveChatflowDialog.jsx +++ b/packages/ui/src/ui-component/dialog/SaveChatflowDialog.jsx @@ -5,7 +5,11 @@ import PropTypes from 'prop-types' import { Button, Dialog, DialogActions, DialogContent, OutlinedInput, DialogTitle } from '@mui/material' import { StyledButton } from '@/ui-component/button/StyledButton' +// i18n +import { useTranslation } from 'react-i18next' + const SaveChatflowDialog = ({ show, dialogProps, onCancel, onConfirm }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const [chatflowName, setChatflowName] = useState('') @@ -37,7 +41,7 @@ const SaveChatflowDialog = ({ show, dialogProps, onCancel, onConfirm }) => { id='chatflow-name' type='text' fullWidth - placeholder='My New Chatflow' + placeholder={t('components.dialogs.saveChatFlow.placeholder')} value={chatflowName} onChange={(e) => setChatflowName(e.target.value)} onKeyDown={(e) => { diff --git a/packages/ui/src/ui-component/dialog/ShareWithWorkspaceDialog.jsx b/packages/ui/src/ui-component/dialog/ShareWithWorkspaceDialog.jsx index f9ba47e73fd..2ea84c86bb4 100644 --- a/packages/ui/src/ui-component/dialog/ShareWithWorkspaceDialog.jsx +++ b/packages/ui/src/ui-component/dialog/ShareWithWorkspaceDialog.jsx @@ -29,7 +29,11 @@ import useNotifier from '@/utils/useNotifier' // const import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions' +// i18n +import { useTranslation } from 'react-i18next' + const ShareWithWorkspaceDialog = ({ show, dialogProps, onCancel, setError }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const dispatch = useDispatch() @@ -63,9 +67,16 @@ const ShareWithWorkspaceDialog = ({ show, dialogProps, onCancel, setError }) => const columns = useMemo( () => [ - { field: 'workspaceName', headerName: 'Workspace', editable: false, flex: 1 }, - { field: 'shared', headerName: 'Share', type: 'boolean', editable: true, width: 180 } + { field: 'workspaceName', headerName: t('common.labels.workspace'), editable: false, flex: 1 }, + { + field: 'shared', + headerName: t('common.actions.share'), + type: 'boolean', + editable: true, + width: 180 + } ], + // eslint-disable-next-line react-hooks/exhaustive-deps [] ) @@ -130,7 +141,7 @@ const ShareWithWorkspaceDialog = ({ show, dialogProps, onCancel, setError }) => const sharedResp = await workspaceApi.setSharedWorkspacesForItem(dialogProps.data.id, obj) if (sharedResp.data) { enqueueSnackbar({ - message: 'Items Shared Successfully', + message: t('components.dialogs.shareWithWorkspace.messages.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -146,9 +157,9 @@ const ShareWithWorkspaceDialog = ({ show, dialogProps, onCancel, setError }) => } catch (error) { if (setError) setError(error) enqueueSnackbar({ - message: `Failed to share Item: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('components.dialogs.shareWithWorkspace.messages.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -182,7 +193,7 @@ const ShareWithWorkspaceDialog = ({ show, dialogProps, onCancel, setError }) => - Name + {t('common.labels.name')} diff --git a/packages/ui/src/ui-component/dialog/SourceDocDialog.jsx b/packages/ui/src/ui-component/dialog/SourceDocDialog.jsx index 02589a523ab..9cac8666453 100644 --- a/packages/ui/src/ui-component/dialog/SourceDocDialog.jsx +++ b/packages/ui/src/ui-component/dialog/SourceDocDialog.jsx @@ -5,7 +5,11 @@ import PropTypes from 'prop-types' import { Box, Dialog, DialogContent, DialogTitle, Typography } from '@mui/material' import ReactJson from 'flowise-react-json-view' +// i18n +import { useTranslation } from 'react-i18next' + const SourceDocDialog = ({ show, dialogProps, onCancel }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const customization = useSelector((state) => state.customization) @@ -29,7 +33,7 @@ const SourceDocDialog = ({ show, dialogProps, onCancel }) => { aria-describedby='alert-dialog-description' > - {dialogProps.title ?? 'Source Documents'} + {dialogProps.title ?? t('components.dialogs.sourceDoc.title')} {data.error && ( @@ -44,7 +48,7 @@ const SourceDocDialog = ({ show, dialogProps, onCancel }) => { }} > - Error: + {t('components.dialogs.sourceDoc.errorLabel')} {data.error} diff --git a/packages/ui/src/ui-component/dialog/SpeechToTextDialog.jsx b/packages/ui/src/ui-component/dialog/SpeechToTextDialog.jsx index 918313e3d3f..310099e284b 100644 --- a/packages/ui/src/ui-component/dialog/SpeechToTextDialog.jsx +++ b/packages/ui/src/ui-component/dialog/SpeechToTextDialog.jsx @@ -13,7 +13,11 @@ import useNotifier from '@/utils/useNotifier' // Project imports import SpeechToText from '@/ui-component/extended/SpeechToText' +// i18n +import { useTranslation } from 'react-i18next' + const SpeechToTextDialog = ({ show, dialogProps, onCancel, onConfirm }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const dispatch = useDispatch() @@ -35,7 +39,7 @@ const SpeechToTextDialog = ({ show, dialogProps, onCancel, onConfirm }) => { aria-describedby='alert-dialog-description' > - {dialogProps.title || 'Allowed Domains'} + {dialogProps.title || t('components.speechToText.fallBackTitle')} diff --git a/packages/ui/src/ui-component/dialog/StarterPromptsDialog.jsx b/packages/ui/src/ui-component/dialog/StarterPromptsDialog.jsx index 58ebc5be379..ae794b3c2db 100644 --- a/packages/ui/src/ui-component/dialog/StarterPromptsDialog.jsx +++ b/packages/ui/src/ui-component/dialog/StarterPromptsDialog.jsx @@ -13,7 +13,11 @@ import useNotifier from '@/utils/useNotifier' // Project imports import StarterPrompts from '@/ui-component/extended/StarterPrompts' +// i18n +import { useTranslation } from 'react-i18next' + const StarterPromptsDialog = ({ show, dialogProps, onCancel, onConfirm }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const dispatch = useDispatch() @@ -35,7 +39,7 @@ const StarterPromptsDialog = ({ show, dialogProps, onCancel, onConfirm }) => { aria-describedby='alert-dialog-description' > - {dialogProps.title || 'Conversation Starter Prompts'} + {dialogProps.title || t('components.dialogs.starterPrompts.title')} diff --git a/packages/ui/src/ui-component/dialog/TagDialog.jsx b/packages/ui/src/ui-component/dialog/TagDialog.jsx index 82c35dde62b..8bd1e7da9ef 100644 --- a/packages/ui/src/ui-component/dialog/TagDialog.jsx +++ b/packages/ui/src/ui-component/dialog/TagDialog.jsx @@ -7,7 +7,11 @@ import Chip from '@mui/material/Chip' import PropTypes from 'prop-types' import { DialogActions, DialogContent, DialogTitle, Typography } from '@mui/material' +// i18n +import { useTranslation } from 'react-i18next' + const TagDialog = ({ isOpen, dialogProps, onClose, onSubmit }) => { + const { t } = useTranslation() const [inputValue, setInputValue] = useState('') const [categoryValues, setCategoryValues] = useState([]) @@ -58,7 +62,7 @@ const TagDialog = ({ isOpen, dialogProps, onClose, onSubmit }) => { aria-describedby='category-dialog-description' > - Set Chatflow Category Tags + {t('components.dialogs.tag.title')} @@ -81,19 +85,19 @@ const TagDialog = ({ isOpen, dialogProps, onClose, onSubmit }) => { value={inputValue} onChange={handleInputChange} onKeyDown={handleInputKeyDown} - label='Add a tag' + label={t('components.dialogs.tag.addLabel')} variant='outlined' /> - Enter a tag and press enter to add it to the list. You can add as many tags as you want. + {t('components.dialogs.tag.tooltip')} - + diff --git a/packages/ui/src/ui-component/dialog/ViewLeadsDialog.jsx b/packages/ui/src/ui-component/dialog/ViewLeadsDialog.jsx index fed7591d107..5f8b721b9b6 100644 --- a/packages/ui/src/ui-component/dialog/ViewLeadsDialog.jsx +++ b/packages/ui/src/ui-component/dialog/ViewLeadsDialog.jsx @@ -36,6 +36,9 @@ import leadsApi from '@/api/lead' import '@/views/chatmessage/ChatMessage.css' import 'react-datepicker/dist/react-datepicker.css' +// i18n +import { useTranslation } from 'react-i18next' + const DatePickerCustomInput = forwardRef(function DatePickerCustomInput({ value, onClick }, ref) { return ( @@ -50,6 +53,7 @@ DatePickerCustomInput.propTypes = { } const ViewLeadsDialog = ({ show, dialogProps, onCancel }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const dispatch = useDispatch() const theme = useTheme() @@ -136,7 +140,7 @@ const ViewLeadsDialog = ({ show, dialogProps, onCancel }) => { } }} variant='outlined' - placeholder='Search Name or Email or Phone' + placeholder={t('components.dialogs.viewLeads.placeholder')} onChange={onSearchChange} startAdornment={ {
{leads && leads.length > 0 && ( )}
@@ -167,7 +171,7 @@ const ViewLeadsDialog = ({ show, dialogProps, onCancel }) => { msgEmptySVG -
No Leads
+
{t('components.dialogs.viewLeads.noLeads')}
)} {leads && leads.length > 0 && ( @@ -175,10 +179,10 @@ const ViewLeadsDialog = ({ show, dialogProps, onCancel }) => { - Name - Email Address - Phone - Created Date + {t('common.labels.name')} + {t('common.labels.emailAddress')} + {t('common.labels.phone')} + {t('components.dialogs.viewLeads.leadsTable.created')} @@ -187,7 +191,7 @@ const ViewLeadsDialog = ({ show, dialogProps, onCancel }) => { {lead.name} {lead.email} {lead.phone} - {moment(lead.createdDate).format('MMMM Do, YYYY')} + {moment(lead.createdDate).format(t('common.formats.dateMonthDayYear'))} ))} diff --git a/packages/ui/src/ui-component/dialog/ViewMessagesDialog.jsx b/packages/ui/src/ui-component/dialog/ViewMessagesDialog.jsx index 091640c50fc..be48ad393a6 100644 --- a/packages/ui/src/ui-component/dialog/ViewMessagesDialog.jsx +++ b/packages/ui/src/ui-component/dialog/ViewMessagesDialog.jsx @@ -72,6 +72,9 @@ import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackba import '@/views/chatmessage/ChatMessage.css' import 'react-datepicker/dist/react-datepicker.css' +// i18n +import { useTranslation, Trans } from 'react-i18next' + const StyledMenu = styled((props) => ( { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const [hardDelete, setHardDelete] = useState(false) @@ -152,7 +156,7 @@ const ConfirmDeleteMessageDialog = ({ show, dialogProps, onCancel, onConfirm }) {dialogProps.isChatflow && ( setHardDelete(event.target.checked)} />} - label='Remove messages from 3rd party Memory Node' + label={t('components.dialogs.viewMessages.removeMegLabel')} /> )} @@ -176,6 +180,7 @@ ConfirmDeleteMessageDialog.propTypes = { } const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const dispatch = useDispatch() const theme = useTheme() @@ -275,10 +280,10 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { const onDeleteMessages = () => { setHardDeleteDialogProps({ - title: 'Delete Messages', - description: 'Are you sure you want to delete messages? This action cannot be undone.', - confirmButtonName: 'Delete', - cancelButtonName: 'Cancel', + title: t('components.dialogs.viewMessages.delete.title'), + description: t('components.dialogs.viewMessages.delete.description'), + confirmButtonName: t('common.actions.delete'), + cancelButtonName: t('common.actions.cancel'), isChatflow: dialogProps.isChatflow }) setHardDeleteDialogOpen(true) @@ -312,7 +317,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { await chatmessageApi.deleteChatmessage(chatflowid, obj) enqueueSnackbar({ - message: 'Succesfully deleted messages', + message: t('components.dialogs.viewMessages.delete.messages.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -344,13 +349,13 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { const getChatType = (chatType) => { if (chatType === 'INTERNAL') { - return 'UI' + return t('components.dialogs.viewMessages.labels.ui') } else if (chatType === 'EVALUATION') { - return 'Evaluation' + return t('components.dialogs.viewMessages.labels.evaluations') } else if (chatType === 'MCP') { - return 'MCP' + return t('components.dialogs.viewMessages.labels.mcp') } - return 'API/Embed' + return t('components.dialogs.viewMessages.labels.apiEmbed') } const exportMessages = async () => { @@ -376,7 +381,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { linkElement.click() enqueueSnackbar({ - message: 'Messages exported successfully', + message: t('components.dialogs.viewMessages.export.messages.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -390,7 +395,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { } catch (error) { console.error('Error exporting messages:', error) enqueueSnackbar({ - message: 'Failed to export messages', + message: t('components.dialogs.viewMessages.export.messages.error'), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -408,13 +413,16 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { const clearChat = async (chatmsg) => { const description = chatmsg.sessionId && chatmsg.memoryType - ? `Are you sure you want to clear session id: ${chatmsg.sessionId} from ${chatmsg.memoryType}?` - : `Are you sure you want to clear messages?` + ? t('components.dialogs.viewMessages.clearChat.descriptionSession', { + sessionId: chatmsg.sessionId, + memoryType: chatmsg.memoryType + }) + : t('components.dialogs.viewMessages.clearChat.description') const confirmPayload = { - title: `Clear Session`, + title: t('components.dialogs.viewMessages.clearChat.title'), description, - confirmButtonName: 'Clear', - cancelButtonName: 'Cancel' + confirmButtonName: t('common.actions.clear'), + cancelButtonName: t('common.actions.cancel') } const isConfirmed = await confirm(confirmPayload) @@ -430,8 +438,11 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { await chatmessageApi.deleteChatmessage(chatflowid, obj) const description = chatmsg.sessionId && chatmsg.memoryType - ? `Succesfully cleared session id: ${chatmsg.sessionId} from ${chatmsg.memoryType}` - : `Succesfully cleared messages` + ? t('components.dialogs.viewMessages.clearChat.messages.success.session', { + sessionId: chatmsg.sessionId, + memoryType: chatmsg.memoryType + }) + : t('components.dialogs.viewMessages.clearChat.messages.success.simple') enqueueSnackbar({ message: description, options: { @@ -573,15 +584,15 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { // Check both messages and assign based on role, not order if (firstMessage.role === 'userMessage') { - userContent = `User: ${firstMessage.content}` + userContent = t('components.dialogs.viewMessages.logs.user', { msg: firstMessage.content }) } else if (firstMessage.role === 'apiMessage') { - apiContent = `Bot: ${firstMessage.content}` + apiContent = t('components.dialogs.viewMessages.logs.bot', { msg: firstMessage.content }) } if (secondMessage.role === 'userMessage') { - userContent = `User: ${secondMessage.content}` + userContent = t('components.dialogs.viewMessages.logs.user', { msg: secondMessage.content }) } else if (secondMessage.role === 'apiMessage') { - apiContent = `Bot: ${secondMessage.content}` + apiContent = t('components.dialogs.viewMessages.logs.bot', { msg: secondMessage.content }) } seen[PK] = { @@ -672,7 +683,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { return ( /* eslint-disable jsx-a11y/media-has-caption */ ) @@ -863,7 +874,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { }} >
- From Date + {t('components.dialogs.viewMessages.inputs.fromDate')} onStartDateSelected(date)} @@ -874,7 +885,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { />
- To Date + {t('components.dialogs.viewMessages.inputs.toDate')} onEndDateSelected(date)} @@ -901,19 +912,19 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { name='chatType' options={[ { - label: 'UI', + label: t('components.dialogs.viewMessages.labels.ui'), name: 'INTERNAL' }, { - label: 'API/Embed', + label: t('components.dialogs.viewMessages.labels.apiEmbed'), name: 'EXTERNAL' }, { - label: 'MCP', + label: t('components.dialogs.viewMessages.labels.apiEmbed'), name: 'MCP' }, { - label: 'Evaluations', + label: t('components.dialogs.viewMessages.labels.mcp'), name: 'EVALUATION' } ]} @@ -937,11 +948,11 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { name='feedbackType' options={[ { - label: 'Positive', + label: t('components.dialogs.viewMessages.labels.positive'), name: 'THUMBS_UP' }, { - label: 'Negative', + label: t('components.dialogs.viewMessages.labels.negative'), name: 'THUMBS_DOWN' } ]} @@ -970,7 +981,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { } > - More Actions + {t('components.dialogs.viewMessages.actions.moreActions')} { disableRipple > - Export to JSON + {t('components.dialogs.viewMessages.actions.exportJSON')} {(stats.totalMessages ?? 0) > 0 && ( { disableRipple > - Delete All + {t('components.dialogs.viewMessages.actions.deleteAll')} )} @@ -1016,11 +1027,20 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { marginTop: 20 }} > - - - + + +
@@ -1034,7 +1054,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { alt='msgEmptySVG' /> -
No Messages
+
{t('components.dialogs.viewMessages.labels.noMessages')}
)} {chatlogs && chatlogs.length > 0 && ( @@ -1059,8 +1079,11 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { }} > - Sessions {pageLimit * (currentPage - 1) + 1} - {Math.min(pageLimit * currentPage, total)} of{' '} - {total} + {t('components.dialogs.viewMessages.labels.totalSessions', { + current: pageLimit * (currentPage - 1) + 1, + limit: Math.min(pageLimit * currentPage, total), + total: total + })} { } - secondary={moment(chatmsg.createdDate).format('MMMM Do YYYY, h:mm:ss a')} + secondary={moment(chatmsg.createdDate).format( + t('common.formats.dateMonthDayYearTime12Seconds') + )} /> @@ -1118,22 +1143,46 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => {
{chatMessages[1].sessionId && (
- Session Id: {chatMessages[1].sessionId} + + }} + />
)} {chatMessages[1].chatType && (
- Source: {getChatType(chatMessages[1].chatType)} + + }} + />
)} {chatMessages[1].memoryType && (
- Memory: {chatMessages[1].memoryType} + + }} + />
)} {leadEmail && (
- Email: {leadEmail} + + }} + />
)}
@@ -1145,18 +1194,13 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { alignItems: 'end' }} > - + clearChat(chatMessages[1])}> {chatMessages[1].sessionId && ( - + @@ -1325,7 +1369,9 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { onClick={() => onSourceDialogClick( tool, - 'Used Tools' + t( + 'components.dialogs.viewMessages.labels.usedTools' + ) ) } /> @@ -1344,7 +1390,7 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { > { onClick={() => onSourceDialogClick( agent.state, - 'State' + t('common.labels.state') ) } /> @@ -1397,7 +1443,13 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { )} {agent.instructions &&

{agent.instructions}

} {agent.messages.length === 0 && - !agent.instructions &&

Finished

} + !agent.instructions && ( +

+ {t( + 'components.dialogs.viewMessages.labels.finished' + )} +

+ )} {agent.sourceDocuments && agent.sourceDocuments.length > 0 && (
{ } /> } - onClick={() => onSourceDialogClick(tool, 'Used Tools')} + onClick={() => + onSourceDialogClick( + tool, + t( + 'components.dialogs.viewMessages.labels.usedTools' + ) + ) + } /> ) })} @@ -1599,7 +1658,9 @@ const ViewMessagesDialog = ({ show, dialogProps, onCancel }) => { key={index} style={{ display: 'flex', justifyContent: 'center', alignContent: 'center' }} > - {moment(message.message).format('MMMM Do YYYY, h:mm:ss a')} + {moment(message.message).format( + t('common.formats.dateMonthDayYearTime12Seconds') + )} ) } diff --git a/packages/ui/src/ui-component/dropdown/AsyncDropdown.jsx b/packages/ui/src/ui-component/dropdown/AsyncDropdown.jsx index 5534d6526fb..ea36383d3ff 100644 --- a/packages/ui/src/ui-component/dropdown/AsyncDropdown.jsx +++ b/packages/ui/src/ui-component/dropdown/AsyncDropdown.jsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useContext, Fragment } from 'react' +import { useState, useEffect, useContext, Fragment, useMemo } from 'react' import { useSelector } from 'react-redux' import PropTypes from 'prop-types' import axios from 'axios' @@ -16,6 +16,9 @@ import { baseURL } from '@/store/constant' import { flowContext } from '@/store/context/ReactFlowContext' import { getAvailableNodesForVariable } from '@/utils/genericHelper' +// i18n +import { useTranslation } from 'react-i18next' + const StyledPopper = styled(Popper)({ boxShadow: '0px 8px 10px -5px rgb(0 0 0 / 20%), 0px 16px 24px 2px rgb(0 0 0 / 14%), 0px 6px 30px 5px rgb(0 0 0 / 12%)', borderRadius: '10px', @@ -74,16 +77,19 @@ export const AsyncDropdown = ({ multiple = false, fullWidth = false }) => { + const { t } = useTranslation() const customization = useSelector((state) => state.customization) const theme = useTheme() + const chooseOption = useMemo(() => t('components.dropdown.chooseOption'), [t]) + const [open, setOpen] = useState(false) const [options, setOptions] = useState([]) const [loading, setLoading] = useState(false) const findMatchingOptions = (options = [], value) => { if (multiple) { let values = [] - if ('choose an option' !== value && value && typeof value === 'string') { + if (chooseOption !== value && value && typeof value === 'string') { values = JSON.parse(value) } else { values = value @@ -93,8 +99,9 @@ export const AsyncDropdown = ({ return options.find((option) => option.name === value) } const getDefaultOptionValue = () => (multiple ? [] : '') - const addNewOption = [{ label: '- Create New -', name: '-create-' }] - let [internalValue, setInternalValue] = useState(value ?? 'choose an option') + // [WARN]: In this place can be possible error after translation + const addNewOption = [{ label: t('components.dropdown.createNewItemLabel'), name: '-create-' }] + let [internalValue, setInternalValue] = useState(value ?? chooseOption) const { reactFlowInstance } = useContext(flowContext) const fetchCredentialList = async () => { @@ -244,7 +251,7 @@ export const AsyncDropdown = ({ key={option.name} component='img' src={option.imageSrc} - alt={option.label || 'Selected Option'} + alt={option.label || t('components.dropdown.selected')} sx={{ width: 32, height: 32, diff --git a/packages/ui/src/ui-component/dropdown/Dropdown.jsx b/packages/ui/src/ui-component/dropdown/Dropdown.jsx index c18f52c944e..dc3f7f0a680 100644 --- a/packages/ui/src/ui-component/dropdown/Dropdown.jsx +++ b/packages/ui/src/ui-component/dropdown/Dropdown.jsx @@ -6,6 +6,9 @@ import Autocomplete, { autocompleteClasses } from '@mui/material/Autocomplete' import { useTheme, styled } from '@mui/material/styles' import PropTypes from 'prop-types' +// i18n +import { useTranslation } from 'react-i18next' + const StyledPopper = styled(Popper)({ boxShadow: '0px 8px 10px -5px rgb(0 0 0 / 20%), 0px 16px 24px 2px rgb(0 0 0 / 14%), 0px 6px 30px 5px rgb(0 0 0 / 12%)', borderRadius: '10px', @@ -19,10 +22,11 @@ const StyledPopper = styled(Popper)({ }) export const Dropdown = ({ name, value, loading, options, onSelect, disabled = false, freeSolo = false, disableClearable = false }) => { + const { t } = useTranslation() const customization = useSelector((state) => state.customization) const findMatchingOptions = (options = [], value) => options.find((option) => option.name === value) const getDefaultOptionValue = () => '' - let [internalValue, setInternalValue] = useState(value ?? 'choose an option') + let [internalValue, setInternalValue] = useState(value ?? t('components.dropdown.chooseOption')) const theme = useTheme() return ( @@ -63,7 +67,7 @@ export const Dropdown = ({ name, value, loading, options, onSelect, disabled = f { + const { t } = useTranslation() + + const chooseOption = useMemo(() => t('components.dropdown.chooseOption'), [t]) + const customization = useSelector((state) => state.customization) const findMatchingOptions = (options = [], internalValue) => { let values = [] - if ('choose an option' !== internalValue && internalValue && typeof internalValue === 'string') values = JSON.parse(internalValue) + if (chooseOption !== internalValue && internalValue && typeof internalValue === 'string') values = JSON.parse(internalValue) else values = internalValue return options.filter((option) => values.includes(option.name)) } diff --git a/packages/ui/src/ui-component/extended/AllowedDomains.jsx b/packages/ui/src/ui-component/extended/AllowedDomains.jsx index 29a44a5792e..18b832fe54b 100644 --- a/packages/ui/src/ui-component/extended/AllowedDomains.jsx +++ b/packages/ui/src/ui-component/extended/AllowedDomains.jsx @@ -17,7 +17,11 @@ import useNotifier from '@/utils/useNotifier' // API import chatflowsApi from '@/api/chatflows' +// i18n +import { useTranslation } from 'react-i18next' + const AllowedDomains = ({ dialogProps, onConfirm, hideTitle = false }) => { + const { t } = useTranslation() const dispatch = useDispatch() useNotifier() @@ -60,7 +64,7 @@ const AllowedDomains = ({ dialogProps, onConfirm, hideTitle = false }) => { }) if (saveResp.data) { enqueueSnackbar({ - message: 'Allowed Origins Saved', + message: t('components.allowedDomains.messages.saved'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -76,9 +80,9 @@ const AllowedDomains = ({ dialogProps, onConfirm, hideTitle = false }) => { } } catch (error) { enqueueSnackbar({ - message: `Failed to save Allowed Origins: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('components.allowedDomains.messages.failed', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -122,16 +126,13 @@ const AllowedDomains = ({ dialogProps, onConfirm, hideTitle = false }) => { {!hideTitle && ( - Allowed Domains - + {t('components.allowedDomains.title')} + )} - Domains + {t('components.allowedDomains.shortTitle')} {inputFields.map((origin, index) => { return (
@@ -176,18 +177,15 @@ const AllowedDomains = ({ dialogProps, onConfirm, hideTitle = false }) => { - Error Message - + {t('components.allowedDomains.error.title')} + { setErrorMessage(e.target.value) @@ -197,7 +195,7 @@ const AllowedDomains = ({ dialogProps, onConfirm, hideTitle = false }) => { - Save + {t('common.actions.save')} diff --git a/packages/ui/src/ui-component/extended/AnalyseFlow.jsx b/packages/ui/src/ui-component/extended/AnalyseFlow.jsx index 84038f4c69c..d732fa02bec 100644 --- a/packages/ui/src/ui-component/extended/AnalyseFlow.jsx +++ b/packages/ui/src/ui-component/extended/AnalyseFlow.jsx @@ -39,29 +39,32 @@ import useNotifier from '@/utils/useNotifier' // API import chatflowsApi from '@/api/chatflows' +// i18n +import { useTranslation } from 'react-i18next' + const analyticProviders = [ { - label: 'LangSmith', + label: 'common.providers.langSmith', name: 'langSmith', icon: langsmithPNG, url: 'https://smith.langchain.com', inputs: [ { - label: 'Connect Credential', + label: 'components.analyzeFlow.inputs.credential', name: 'credential', type: 'credential', credentialNames: ['langsmithApi'] }, { - label: 'Project Name', + label: 'components.analyzeFlow.inputs.projectName.title', name: 'projectName', type: 'string', optional: true, - description: 'If not provided, default will be used', + description: 'components.analyzeFlow.inputs.projectName.descriptions.langSmith', placeholder: 'default' }, { - label: 'On/Off', + label: 'components.analyzeFlow.inputs.status', name: 'status', type: 'boolean', optional: true @@ -69,26 +72,26 @@ const analyticProviders = [ ] }, { - label: 'LangFuse', + label: 'common.providers.langFuse', name: 'langFuse', icon: langfuseSVG, url: 'https://langfuse.com', inputs: [ { - label: 'Connect Credential', + label: 'components.analyzeFlow.inputs.credential', name: 'credential', type: 'credential', credentialNames: ['langfuseApi'] }, { - label: 'Release', + label: 'components.analyzeFlow.inputs.release.title', name: 'release', type: 'string', optional: true, - description: 'The release number/hash of the application to provide analytics grouped by release' + description: 'components.analyzeFlow.inputs.release.description' }, { - label: 'On/Off', + label: 'components.analyzeFlow.inputs.status', name: 'status', type: 'boolean', optional: true @@ -96,19 +99,19 @@ const analyticProviders = [ ] }, { - label: 'Lunary', + label: 'common.providers.lunary', name: 'lunary', icon: lunarySVG, url: 'https://lunary.ai', inputs: [ { - label: 'Connect Credential', + label: 'components.analyzeFlow.inputs.credential', name: 'credential', type: 'credential', credentialNames: ['lunaryApi'] }, { - label: 'On/Off', + label: 'components.analyzeFlow.inputs.status', name: 'status', type: 'boolean', optional: true @@ -116,19 +119,19 @@ const analyticProviders = [ ] }, { - label: 'LangWatch', + label: 'common.providers.langWatch', name: 'langWatch', icon: langwatchSVG, url: 'https://langwatch.ai', inputs: [ { - label: 'Connect Credential', + label: 'components.analyzeFlow.inputs.credential', name: 'credential', type: 'credential', credentialNames: ['langwatchApi'] }, { - label: 'On/Off', + label: 'components.analyzeFlow.inputs.status', name: 'status', type: 'boolean', optional: true @@ -136,27 +139,27 @@ const analyticProviders = [ ] }, { - label: 'Arize', + label: 'common.providers.arize', name: 'arize', icon: arizePNG, url: 'https://arize.com', inputs: [ { - label: 'Connect Credential', + label: 'components.analyzeFlow.inputs.credential', name: 'credential', type: 'credential', credentialNames: ['arizeApi'] }, { - label: 'Project Name', + label: 'components.analyzeFlow.inputs.projectName.title', name: 'projectName', type: 'string', optional: true, - description: 'If not provided, default will be used.', + description: 'components.analyzeFlow.inputs.projectName.descriptions.arize', placeholder: 'default' }, { - label: 'On/Off', + label: 'components.analyzeFlow.inputs.status', name: 'status', type: 'boolean', optional: true @@ -164,27 +167,27 @@ const analyticProviders = [ ] }, { - label: 'Phoenix', + label: 'common.providers.phoenix', name: 'phoenix', icon: phoenixPNG, url: 'https://phoenix.arize.com', inputs: [ { - label: 'Connect Credential', + label: 'components.analyzeFlow.inputs.credential', name: 'credential', type: 'credential', credentialNames: ['phoenixApi'] }, { - label: 'Project Name', + label: 'components.analyzeFlow.inputs.projectName.title', name: 'projectName', type: 'string', optional: true, - description: 'If not provided, default will be used.', + description: 'components.analyzeFlow.inputs.projectName.descriptions.phoenix', placeholder: 'default' }, { - label: 'On/Off', + label: 'components.analyzeFlow.inputs.status', name: 'status', type: 'boolean', optional: true @@ -192,26 +195,26 @@ const analyticProviders = [ ] }, { - label: 'Opik', + label: 'common.providers.opik', name: 'opik', icon: opikPNG, url: 'https://www.comet.com/opik', inputs: [ { - label: 'Connect Credential', + label: 'components.analyzeFlow.inputs.credential', name: 'credential', type: 'credential', credentialNames: ['opikApi'] }, { - label: 'Project Name', + label: 'components.analyzeFlow.inputs.projectName.title', name: 'opikProjectName', type: 'string', - description: 'Name of your Opik project', + description: 'components.analyzeFlow.inputs.projectName.descriptions.opik', placeholder: 'default' }, { - label: 'On/Off', + label: 'components.analyzeFlow.inputs.status', name: 'status', type: 'boolean', optional: true @@ -221,6 +224,7 @@ const analyticProviders = [ ] const AnalyseFlow = ({ dialogProps }) => { + const { t } = useTranslation() const dispatch = useDispatch() const theme = useTheme() @@ -239,7 +243,7 @@ const AnalyseFlow = ({ dialogProps }) => { }) if (saveResp.data) { enqueueSnackbar({ - message: 'Analytic Configuration Saved', + message: t('components.analyzeFlow.messages.save.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -254,9 +258,9 @@ const AnalyseFlow = ({ dialogProps }) => { } } catch (error) { enqueueSnackbar({ - message: `Failed to save Analytic Configuration: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('components.analyzeFlow.messages.save.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -339,7 +343,7 @@ const AnalyseFlow = ({ dialogProps }) => { { backgroundColor: '#70e000' }} /> - ON + {t('components.analyzeFlow.on')}
)} @@ -384,10 +388,10 @@ const AnalyseFlow = ({ dialogProps }) => {
- {inputParam.label} + {t(inputParam.label)} {!inputParam.optional &&  *} {inputParam.description && ( - + )}
@@ -425,7 +429,7 @@ const AnalyseFlow = ({ dialogProps }) => { ))} - Save + {t('common.actions.save')} diff --git a/packages/ui/src/ui-component/extended/ChatFeedback.jsx b/packages/ui/src/ui-component/extended/ChatFeedback.jsx index ca55dc5b718..722243c4185 100644 --- a/packages/ui/src/ui-component/extended/ChatFeedback.jsx +++ b/packages/ui/src/ui-component/extended/ChatFeedback.jsx @@ -17,7 +17,11 @@ import useNotifier from '@/utils/useNotifier' // API import chatflowsApi from '@/api/chatflows' +// i18n +import { useTranslation } from 'react-i18next' + const ChatFeedback = ({ dialogProps, onConfirm }) => { + const { t } = useTranslation() const dispatch = useDispatch() useNotifier() @@ -45,7 +49,7 @@ const ChatFeedback = ({ dialogProps, onConfirm }) => { }) if (saveResp.data) { enqueueSnackbar({ - message: 'Chat Feedback Settings Saved', + message: t('components.chatFeedback.messages.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -61,9 +65,9 @@ const ChatFeedback = ({ dialogProps, onConfirm }) => { } } catch (error) { enqueueSnackbar({ - message: `Failed to save Chat Feedback Settings: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('components.chatFeedback.messages.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -92,10 +96,14 @@ const ChatFeedback = ({ dialogProps, onConfirm }) => { return ( - + - Save + {t('common.actions.save')} diff --git a/packages/ui/src/ui-component/extended/FileUpload.jsx b/packages/ui/src/ui-component/extended/FileUpload.jsx index fc561359088..532812b0e05 100644 --- a/packages/ui/src/ui-component/extended/FileUpload.jsx +++ b/packages/ui/src/ui-component/extended/FileUpload.jsx @@ -2,7 +2,6 @@ import { useDispatch, useSelector } from 'react-redux' import { useState, useEffect } from 'react' import PropTypes from 'prop-types' import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from '@/store/actions' -import parser from 'html-react-parser' // material-ui import { @@ -30,9 +29,8 @@ import useNotifier from '@/utils/useNotifier' // API import chatflowsApi from '@/api/chatflows' -const message = `The full contents of uploaded files will be converted to text and sent to the Agent. -
-Refer docs for more details.` +// i18n +import { useTranslation, Trans } from 'react-i18next' const availableFileTypes = [ { name: 'CSS', ext: 'text/css', extension: '.css' }, @@ -52,6 +50,7 @@ const availableFileTypes = [ ] const FileUpload = ({ dialogProps }) => { + const { t } = useTranslation() const dispatch = useDispatch() const customization = useSelector((state) => state.customization) @@ -97,7 +96,7 @@ const FileUpload = ({ dialogProps }) => { }) if (saveResp.data) { enqueueSnackbar({ - message: 'File Upload Configuration Saved', + message: t('components.fileUpload.messages.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -112,9 +111,9 @@ const FileUpload = ({ dialogProps }) => { } } catch (error) { enqueueSnackbar({ - message: `Failed to save File Upload Configuration: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('components.fileUpload.messages.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -185,12 +184,21 @@ const FileUpload = ({ dialogProps }) => { }} > - {parser(message)} + + , + br:
+ }} + /> +
- +
- Allow Uploads of Type + {t('components.fileUpload.allowUploadsType')}
{ expandIcon={} sx={{ minHeight: 40, px: 2, '& .MuiAccordionSummary-content': { my: 0.75 } }} > - Advanced Settings + + {t('components.fileUpload.configuration.title')} + {/* PDF Processing */} {allowedFileTypes.includes('application/pdf') && ( - PDF Processing + {t('components.fileUpload.configuration.usage.title')} } - label={One document per page} + label={ + + {t('components.fileUpload.configuration.usage.oneDocPerPage')} + + } /> } - label={One document per file} + label={ + + {t('components.fileUpload.configuration.usage.oneDocPerFile')} + + } /> @@ -276,7 +294,7 @@ const FileUpload = ({ dialogProps }) => { - Save + {t('common.actions.save')} diff --git a/packages/ui/src/ui-component/extended/FollowUpPrompts.jsx b/packages/ui/src/ui-component/extended/FollowUpPrompts.jsx index 8a7a711b88e..15253900eb2 100644 --- a/packages/ui/src/ui-component/extended/FollowUpPrompts.jsx +++ b/packages/ui/src/ui-component/extended/FollowUpPrompts.jsx @@ -26,10 +26,11 @@ import { AsyncDropdown } from '@/ui-component/dropdown/AsyncDropdown' import { IconX } from '@tabler/icons-react' import { Dropdown } from '@/ui-component/dropdown/Dropdown' -const promptDescription = - 'Prompt to generate questions based on the conversation history. You can use variable {history} to refer to the conversation history.' -const defaultPrompt = - 'Given the following conversations: {history}. Please help me predict the three most likely questions that human would ask and keeping each question short and concise.' +// i18n +import { useTranslation } from 'react-i18next' + +const promptDescription = 'components.followUpPrompts.inputs.prompt.descriptions.prompt' +const defaultPrompt = 'components.followUpPrompts.inputs.prompt.descriptions.default' // update when adding new providers const FollowUpPromptProviders = { @@ -44,24 +45,24 @@ const FollowUpPromptProviders = { const followUpPromptsOptions = { [FollowUpPromptProviders.ANTHROPIC]: { - label: 'Anthropic Claude', + label: 'common.providers.anthropic', name: FollowUpPromptProviders.ANTHROPIC, icon: anthropicIcon, inputs: [ { - label: 'Connect Credential', + label: 'components.followUpPrompts.inputs.connectCredential', name: 'credential', type: 'credential', credentialNames: ['anthropicApi'] }, { - label: 'Model Name', + label: 'components.followUpPrompts.inputs.modelName.title', name: 'modelName', type: 'asyncOptions', loadMethod: 'listModels' }, { - label: 'Prompt', + label: 'common.labels.prompt', name: 'prompt', type: 'string', rows: 4, @@ -70,7 +71,7 @@ const followUpPromptsOptions = { default: defaultPrompt }, { - label: 'Temperature', + label: 'components.followUpPrompts.inputs.temperature', name: 'temperature', type: 'number', step: 0.1, @@ -80,24 +81,24 @@ const followUpPromptsOptions = { ] }, [FollowUpPromptProviders.AZURE_OPENAI]: { - label: 'Azure ChatOpenAI', + label: 'common.providers.azureOpenAI', name: FollowUpPromptProviders.AZURE_OPENAI, icon: azureOpenAiIcon, inputs: [ { - label: 'Connect Credential', + label: 'components.followUpPrompts.inputs.connectCredential', name: 'credential', type: 'credential', credentialNames: ['azureOpenAIApi'] }, { - label: 'Model Name', + label: 'components.followUpPrompts.inputs.modelName.title', name: 'modelName', type: 'asyncOptions', loadMethod: 'listModels' }, { - label: 'Prompt', + label: 'common.labels.prompt', name: 'prompt', type: 'string', rows: 4, @@ -106,7 +107,7 @@ const followUpPromptsOptions = { default: defaultPrompt }, { - label: 'Temperature', + label: 'components.followUpPrompts.inputs.temperature', name: 'temperature', type: 'number', step: 0.1, @@ -116,24 +117,24 @@ const followUpPromptsOptions = { ] }, [FollowUpPromptProviders.GOOGLE_GENAI]: { - label: 'Google Gemini', + label: 'common.providers.googleGemini', name: FollowUpPromptProviders.GOOGLE_GENAI, icon: geminiIcon, inputs: [ { - label: 'Connect Credential', + label: 'components.followUpPrompts.inputs.connectCredential', name: 'credential', type: 'credential', credentialNames: ['googleGenerativeAI'] }, { - label: 'Model Name', + label: 'components.followUpPrompts.inputs.modelName.title', name: 'modelName', type: 'asyncOptions', loadMethod: 'listModels' }, { - label: 'Prompt', + label: 'common.labels.prompt', name: 'prompt', type: 'string', rows: 4, @@ -142,7 +143,7 @@ const followUpPromptsOptions = { default: defaultPrompt }, { - label: 'Temperature', + label: 'components.followUpPrompts.inputs.temperature', name: 'temperature', type: 'number', step: 0.1, @@ -152,24 +153,24 @@ const followUpPromptsOptions = { ] }, [FollowUpPromptProviders.GROQ]: { - label: 'Groq', + label: 'common.providers.groq', name: FollowUpPromptProviders.GROQ, icon: groqIcon, inputs: [ { - label: 'Connect Credential', + label: 'components.followUpPrompts.inputs.connectCredential', name: 'credential', type: 'credential', credentialNames: ['groqApi'] }, { - label: 'Model Name', + label: 'components.followUpPrompts.inputs.modelName.title', name: 'modelName', type: 'asyncOptions', loadMethod: 'listModels' }, { - label: 'Prompt', + label: 'common.labels.prompt', name: 'prompt', type: 'string', rows: 4, @@ -178,7 +179,7 @@ const followUpPromptsOptions = { default: defaultPrompt }, { - label: 'Temperature', + label: 'components.followUpPrompts.inputs.temperature', name: 'temperature', type: 'number', step: 0.1, @@ -188,24 +189,24 @@ const followUpPromptsOptions = { ] }, [FollowUpPromptProviders.MISTRALAI]: { - label: 'Mistral AI', + label: 'common.providers.mistralAI', name: FollowUpPromptProviders.MISTRALAI, icon: mistralAiIcon, inputs: [ { - label: 'Connect Credential', + label: 'components.followUpPrompts.inputs.connectCredential', name: 'credential', type: 'credential', credentialNames: ['mistralAIApi'] }, { - label: 'Model Name', + label: 'components.followUpPrompts.inputs.modelName.title', name: 'modelName', type: 'asyncOptions', loadMethod: 'listModels' }, { - label: 'Prompt', + label: 'common.labels.prompt', name: 'prompt', type: 'string', rows: 4, @@ -214,7 +215,7 @@ const followUpPromptsOptions = { default: defaultPrompt }, { - label: 'Temperature', + label: 'components.followUpPrompts.inputs.temperature', name: 'temperature', type: 'number', step: 0.1, @@ -224,24 +225,24 @@ const followUpPromptsOptions = { ] }, [FollowUpPromptProviders.OPENAI]: { - label: 'OpenAI', + label: 'common.providers.openAI', name: FollowUpPromptProviders.OPENAI, icon: openAiIcon, inputs: [ { - label: 'Connect Credential', + label: 'components.followUpPrompts.inputs.connectCredential', name: 'credential', type: 'credential', credentialNames: ['openAIApi'] }, { - label: 'Model Name', + label: 'components.followUpPrompts.inputs.modelName.title', name: 'modelName', type: 'asyncOptions', loadMethod: 'listModels' }, { - label: 'Prompt', + label: 'common.labels.prompt', name: 'prompt', type: 'string', rows: 4, @@ -250,7 +251,7 @@ const followUpPromptsOptions = { default: defaultPrompt }, { - label: 'Temperature', + label: 'components.followUpPrompts.inputs.temperature', name: 'temperature', type: 'number', step: 0.1, @@ -260,28 +261,28 @@ const followUpPromptsOptions = { ] }, [FollowUpPromptProviders.OLLAMA]: { - label: 'Ollama', + label: 'common.providers.ollama', name: FollowUpPromptProviders.OLLAMA, icon: ollamaIcon, inputs: [ { - label: 'Base URL', + label: 'components.followUpPrompts.inputs.baseUrl.title', name: 'baseUrl', type: 'string', placeholder: 'http://127.0.0.1:11434', - description: 'Base URL of your Ollama instance', + description: 'components.followUpPrompts.inputs.baseUrl.description', default: 'http://127.0.0.1:11434' }, { - label: 'Model Name', + label: 'components.followUpPrompts.inputs.modelName.title', name: 'modelName', type: 'string', placeholder: 'llama2', - description: 'Name of the Ollama model to use', + description: 'components.followUpPrompts.inputs.modelName.description', default: 'llama3.2-vision:latest' }, { - label: 'Prompt', + label: 'common.labels.prompt', name: 'prompt', type: 'string', rows: 4, @@ -290,7 +291,7 @@ const followUpPromptsOptions = { default: defaultPrompt }, { - label: 'Temperature', + label: 'components.followUpPrompts.inputs.temperature', name: 'temperature', type: 'number', step: 0.1, @@ -302,6 +303,7 @@ const followUpPromptsOptions = { } const FollowUpPrompts = ({ dialogProps }) => { + const { t } = useTranslation() const dispatch = useDispatch() useNotifier() @@ -380,7 +382,7 @@ const FollowUpPrompts = ({ dialogProps }) => { }) if (saveResp.data) { enqueueSnackbar({ - message: 'Follow-up Prompts configuration saved', + message: t('components.followUpPrompts.messages.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -396,7 +398,7 @@ const FollowUpPrompts = ({ dialogProps }) => { } catch (error) { const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` enqueueSnackbar({ - message: `Failed to save follow-up prompts configuration: ${errorData}`, + message: t('components.followUpPrompts.messages.error', { msg: errorData }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -461,13 +463,13 @@ const FollowUpPrompts = ({ dialogProps }) => { }} > handleChange('status', value)} value={followUpPromptsConfig.status} /> {followUpPromptsConfig && followUpPromptsConfig.status && ( <> - Providers + {t('components.followUpPrompts.providersLabel')} @@ -511,7 +513,7 @@ const FollowUpPrompts = ({ dialogProps }) => {
{followUpPromptsOptions[selectedProvider].url} @@ -523,10 +525,10 @@ const FollowUpPrompts = ({ dialogProps }) => {
- {inputParam.label} + {t(inputParam.label)} {!inputParam.optional &&  *} {inputParam.description && ( - + )}
@@ -573,7 +575,7 @@ const FollowUpPrompts = ({ dialogProps }) => { followUpPromptsConfig[selectedProvider] && followUpPromptsConfig[selectedProvider][inputParam.name] ? followUpPromptsConfig[selectedProvider][inputParam.name] - : inputParam.default ?? 'choose an option' + : inputParam.default ?? t('components.dropdown.chooseOption') } onSelect={(newValue) => setValue(newValue, selectedProvider, inputParam.name)} /> @@ -590,7 +592,7 @@ const FollowUpPrompts = ({ dialogProps }) => { followUpPromptsConfig[selectedProvider] && followUpPromptsConfig[selectedProvider][inputParam.name] ? followUpPromptsConfig[selectedProvider][inputParam] - : inputParam.default ?? 'choose an option' + : inputParam.default ?? t('components.dropdown.chooseOption') } /> )} @@ -603,7 +605,7 @@ const FollowUpPrompts = ({ dialogProps }) => {
- Save + {t('common.actions.save')} diff --git a/packages/ui/src/ui-component/extended/Leads.jsx b/packages/ui/src/ui-component/extended/Leads.jsx index 58fa8169bb1..5611f74ea3c 100644 --- a/packages/ui/src/ui-component/extended/Leads.jsx +++ b/packages/ui/src/ui-component/extended/Leads.jsx @@ -17,14 +17,12 @@ import useNotifier from '@/utils/useNotifier' // API import chatflowsApi from '@/api/chatflows' -const formTitle = `Hey 👋 thanks for your interest! -Let us know where we can reach you` - -const endTitle = `Thank you! -What can I do for you?` +// i18n +import { useTranslation } from 'react-i18next' const Leads = ({ dialogProps }) => { const dispatch = useDispatch() + const { t } = useTranslation() useNotifier() @@ -52,7 +50,7 @@ const Leads = ({ dialogProps }) => { }) if (saveResp.data) { enqueueSnackbar({ - message: 'Leads configuration Saved', + message: t('components.leads.messages.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -68,7 +66,7 @@ const Leads = ({ dialogProps }) => { } catch (error) { const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}` enqueueSnackbar({ - message: `Failed to save Leads configuration: ${errorData}`, + message: t('components.leads.messages.error', { msg: errorData }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -108,11 +106,15 @@ const Leads = ({ dialogProps }) => { mb: 2 }} > - handleChange('status', value)} value={leadsConfig.status} /> + handleChange('status', value)} + value={leadsConfig.status} + /> {leadsConfig && leadsConfig['status'] && ( <> - Form Title + {t('components.leads.form.title')} { multiline={true} minRows={4} value={leadsConfig.title} - placeholder={formTitle} + placeholder={t('components.leads.form.placeholder')} name='form-title' size='small' onChange={(e) => { @@ -129,7 +131,7 @@ const Leads = ({ dialogProps }) => { /> - Message after lead captured + {t('components.leads.messageAfterLeadCaptured.title')} { multiline={true} minRows={4} value={leadsConfig.successMessage} - placeholder={endTitle} + placeholder={t('components.leads.messageAfterLeadCaptured.placeholder')} name='form-title' size='small' onChange={(e) => { @@ -145,16 +147,24 @@ const Leads = ({ dialogProps }) => { }} /> - Form fields + {t('components.leads.formFields')} - handleChange('name', value)} value={leadsConfig.name} /> handleChange('name', value)} + value={leadsConfig.name} + /> + handleChange('email', value)} value={leadsConfig.email} /> - handleChange('phone', value)} value={leadsConfig.phone} /> + handleChange('phone', value)} + value={leadsConfig.phone} + /> @@ -167,7 +177,7 @@ const Leads = ({ dialogProps }) => { onClick={onSave} sx={{ minWidth: 100 }} > - Save + {t('common.actions.save')} diff --git a/packages/ui/src/ui-component/extended/McpServer.jsx b/packages/ui/src/ui-component/extended/McpServer.jsx index b712130a9d7..694e79461a9 100644 --- a/packages/ui/src/ui-component/extended/McpServer.jsx +++ b/packages/ui/src/ui-component/extended/McpServer.jsx @@ -23,7 +23,11 @@ import useNotifier from '@/utils/useNotifier' import mcpServerApi from '@/api/mcpserver' import chatflowsApi from '@/api/chatflows' +// i18n +import { useTranslation } from 'react-i18next' + const McpServer = ({ dialogProps, onStatusChange }) => { + const { t } = useTranslation() const dispatch = useDispatch() const theme = useTheme() const customization = useSelector((state) => state.customization) @@ -48,9 +52,9 @@ const McpServer = ({ dialogProps, onStatusChange }) => { const endpointUrl = chatflowId ? `${window.location.origin}/api/v1/mcp/${chatflowId}` : '' const validateToolName = (name) => { - if (!name) return 'Tool name is required' - if (name.length > 64) return 'Tool name must be 64 characters or less' - if (!/^[A-Za-z0-9_-]+$/.test(name)) return 'Only letters, numbers, underscores, and hyphens allowed' + if (!name) return t('components.mcpServer.validation.toolRequired') + if (name.length > 64) return t('components.mcpServer.validation.length') + if (!/^[A-Za-z0-9_-]+$/.test(name)) return t('components.mcpServer.validation.only') return '' } @@ -124,7 +128,7 @@ const McpServer = ({ dialogProps, onStatusChange }) => { setToolName(resp.data.toolName || '') setDescription(resp.data.description || '') onStatusChange?.(resp.data.enabled) - showSuccess('MCP Server settings saved') + showSuccess(t('components.mcpServer.messages.save.success')) } } else { const resp = await mcpServerApi.createMcpServerConfig(dialogProps.chatflow.id, { @@ -138,21 +142,21 @@ const McpServer = ({ dialogProps, onStatusChange }) => { setDescription(resp.data.description || '') setHasExistingConfig(true) onStatusChange?.(resp.data.enabled) - showSuccess('MCP Server settings saved') + showSuccess(t('components.mcpServer.messages.save.success')) } } } else { await mcpServerApi.deleteMcpServerConfig(dialogProps.chatflow.id) setMcpEnabled(false) onStatusChange?.(false) - showSuccess('MCP Server disabled') + showSuccess(t('components.mcpServer.messages.save.disabled')) } await refreshChatflowStore() } catch (error) { showError( - `Failed to save MCP Server settings: ${ - typeof error.response?.data === 'object' ? error.response.data.message : error.response?.data || error.message - }` + t('components.mcpServer.messages.save.error', { + msg: typeof error.response?.data === 'object' ? error.response.data.message : error.response?.data || error.message + }) ) } finally { setLoading(false) @@ -162,16 +166,15 @@ const McpServer = ({ dialogProps, onStatusChange }) => { const handleCopyUrl = (url) => { if (!url) return navigator.clipboard.writeText(url) - showSuccess('URL copied to clipboard') + showSuccess(t('components.mcpServer.messages.copyUrl.success')) } const handleRefreshCode = async () => { const confirmPayload = { - title: 'Rotate Token', - description: - 'This will invalidate the existing token. Any clients using the old token will need to be updated with the new one. Are you sure?', - confirmButtonName: 'Rotate', - cancelButtonName: 'Cancel' + title: t('components.mcpServer.dialogs.rotate.title'), + description: t('components.mcpServer.dialogs.rotate.description'), + confirmButtonName: t('components.mcpServer.actions.rotate'), + cancelButtonName: t('common.actions.cancel') } const isConfirmed = await confirm(confirmPayload) if (!isConfirmed) return @@ -182,14 +185,14 @@ const McpServer = ({ dialogProps, onStatusChange }) => { const resp = await mcpServerApi.refreshMcpToken(dialogProps.chatflow.id) if (resp.data) { setToken(resp.data.token || '') - showSuccess('Token rotated successfully') + showSuccess(t('components.mcpServer.messages.rotate.success')) } await refreshChatflowStore() } catch (error) { showError( - `Failed to rotate token: ${ - typeof error.response?.data === 'object' ? error.response.data.message : error.response?.data || error.message - }` + t('components.mcpServer.messages.rotate.error', { + msg: typeof error.response?.data === 'object' ? error.response.data.message : error.response?.data || error.message + }) ) } finally { setLoading(false) @@ -206,15 +209,16 @@ const McpServer = ({ dialogProps, onStatusChange }) => { useEffect(() => { if (getMcpServerConfigApi.error) { showError( - `Failed to load MCP Server configuration: ${ - typeof getMcpServerConfigApi.error.response?.data === 'object' - ? getMcpServerConfigApi.error.response.data.message - : getMcpServerConfigApi.error.response?.data || getMcpServerConfigApi.error.message - }` + t('components.mcpServer.messages.load.error', { + msg: + typeof getMcpServerConfigApi.error.response?.data === 'object' + ? getMcpServerConfigApi.error.response.data.message + : getMcpServerConfigApi.error.response?.data || getMcpServerConfigApi.error.message + }) ) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [getMcpServerConfigApi.error]) + }, [getMcpServerConfigApi.error, t]) useEffect(() => { if (getMcpServerConfigApi.data) { @@ -231,7 +235,7 @@ const McpServer = ({ dialogProps, onStatusChange }) => { if (getMcpServerConfigApi.loading) { return ( - Loading MCP Server configuration... + {t('components.mcpServer.loading')} ) } @@ -239,7 +243,12 @@ const McpServer = ({ dialogProps, onStatusChange }) => { return ( <> - + {mcpEnabled && ( @@ -247,14 +256,14 @@ const McpServer = ({ dialogProps, onStatusChange }) => { {/* Tool Name (required) */} - Tool Name * + {t('components.mcpServer.inputs.toolName.title')} * handleToolNameChange(e.target.value)} - placeholder='e.g. product_qa' + placeholder={t('components.mcpServer.inputs.toolName.placeholder')} error={!!toolNameError} disabled={loading} /> @@ -267,14 +276,14 @@ const McpServer = ({ dialogProps, onStatusChange }) => { variant='caption' sx={{ mt: 0.5, display: 'block', color: customization.isDarkMode ? theme.palette.grey[400] : 'text.secondary' }} > - Used as the MCP tool identifier by LLM clients. + {t('components.mcpServer.inputs.toolName.caption')} {/* Description (required) */} - Description * + {t('common.labels.description')} * { rows={3} value={description} onChange={(e) => setDescription(e.target.value)} - placeholder='e.g. Answers product catalog questions' + placeholder={t('components.mcpServer.inputs.description.placeholder')} disabled={loading} /> - Helps LLMs understand when to route queries to this tool. Good descriptions improve tool selection accuracy. + {t('components.mcpServer.inputs.description.caption')} {/* MCP Endpoint URL — visible only when has token */} {token && ( - Streamable HTTP Endpoint + {t('components.mcpServer.inputs.endpoint.title')} { handleCopyUrl(endpointUrl)} - title='Copy URL to clipboard' + title={t('components.mcpServer.actions.copyUrl')} sx={{ color: customization.isDarkMode ? theme.palette.grey[300] : 'inherit' }} > @@ -328,10 +337,10 @@ const McpServer = ({ dialogProps, onStatusChange }) => { color: customization.isDarkMode ? theme.palette.grey[400] : 'text.secondary' }} > - For clients that support the Streamable HTTP transport + {t('components.mcpServer.inputs.endpoint.caption')} - Token (Bearer Token) + {t('components.mcpServer.inputs.token.title')} { size='small' onClick={() => { navigator.clipboard.writeText(token) - showSuccess('Token copied to clipboard') + showSuccess(t('components.mcpServer.messages.copyToken.success')) }} - title='Copy token' + title={t('components.mcpServer.actions.copyToken')} sx={{ color: customization.isDarkMode ? theme.palette.grey[300] : 'inherit' }} > @@ -358,7 +367,7 @@ const McpServer = ({ dialogProps, onStatusChange }) => { @@ -380,15 +389,14 @@ const McpServer = ({ dialogProps, onStatusChange }) => { }) }} > - Use the URL above as the MCP endpoint and pass the token as a Bearer token in the Authorization header. - Configure your MCP client with:{' '} + {t('components.mcpServer.inputs.token.alert.prefix')}{' '} - Authorization: Bearer {''} + {t('components.mcpServer.inputs.token.alert.suffix')} {''} @@ -403,7 +411,7 @@ const McpServer = ({ dialogProps, onStatusChange }) => { onClick={onSave} sx={{ minWidth: 100 }} > - {loading ? 'Saving...' : 'Save'} + {t(loading ? 'components.mcpServer.saving' : 'common.actions.save')} diff --git a/packages/ui/src/ui-component/extended/OverrideConfig.jsx b/packages/ui/src/ui-component/extended/OverrideConfig.jsx index 0ca1b4c85fd..8c7a7e835fd 100644 --- a/packages/ui/src/ui-component/extended/OverrideConfig.jsx +++ b/packages/ui/src/ui-component/extended/OverrideConfig.jsx @@ -36,9 +36,13 @@ import chatflowsApi from '@/api/chatflows' import configApi from '@/api/config' import variablesApi from '@/api/variables' +// i18n +import { useTranslation, Trans } from 'react-i18next' + // utils const OverrideConfigTable = ({ columns, onToggle, rows, sx }) => { + const { t } = useTranslation() const customization = useSelector((state) => state.customization) const isDark = customization?.isDarkMode @@ -70,13 +74,13 @@ const OverrideConfigTable = ({ columns, onToggle, rows, sx }) => { } else if (typeof row.schema === 'object' && row.schema !== null) { schemaContent = JSON.stringify(row.schema, null, 2).replace(/\n/g, '
').replace(/ /g, ' ') } else { - schemaContent = 'No schema available' + schemaContent = t('components.overrideConfig.schemaUnavailable') } return ( {row[key]} - Schema:
${schemaContent}
`} /> + ${t('components.overrideConfig.schema')}:
${schemaContent}`} /> ) } else { @@ -154,6 +158,7 @@ OverrideConfigTable.propTypes = { } const OverrideConfig = ({ dialogProps, hideTitle = false }) => { + const { t } = useTranslation() const dispatch = useDispatch() const customization = useSelector((state) => state.customization) const chatflow = useSelector((state) => state.canvas.chatflow) @@ -338,7 +343,7 @@ const OverrideConfig = ({ dialogProps, hideTitle = false }) => { }) if (saveResp.data) { enqueueSnackbar({ - message: 'Override Configuration Saved', + message: t('components.overrideConfig.messages.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -353,9 +358,9 @@ const OverrideConfig = ({ dialogProps, hideTitle = false }) => { } } catch (error) { enqueueSnackbar({ - message: `Failed to save Override Configuration: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('components.overrideConfig.messages.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -398,17 +403,33 @@ const OverrideConfig = ({ dialogProps, hideTitle = false }) => { {!hideTitle && ( - Override Configuration + {t('components.overrideConfig.title')} documentation for more information.' + + ) + }} + /> } /> )} - + {overrideConfigStatus && ( <> {nodeOverrides && nodeConfig && ( @@ -423,7 +444,7 @@ const OverrideConfig = ({ dialogProps, hideTitle = false }) => { > - Nodes + {t('components.overrideConfig.nodes')} {Object.keys(nodeOverrides) @@ -506,7 +527,7 @@ const OverrideConfig = ({ dialogProps, hideTitle = false }) => { > - Variables + {t('common.labels.variables')} { - Save + {t('common.actions.save')} diff --git a/packages/ui/src/ui-component/extended/PostProcessing.jsx b/packages/ui/src/ui-component/extended/PostProcessing.jsx index e6cab9400df..bfd36cb746e 100644 --- a/packages/ui/src/ui-component/extended/PostProcessing.jsx +++ b/packages/ui/src/ui-component/extended/PostProcessing.jsx @@ -35,6 +35,9 @@ import ExpandTextDialog from '@/ui-component/dialog/ExpandTextDialog' import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction, SET_CHATFLOW } from '@/store/actions' import useNotifier from '@/utils/useNotifier' +// i18n +import { useTranslation } from 'react-i18next' + // API import chatflowsApi from '@/api/chatflows' @@ -46,6 +49,7 @@ return $flow.rawOutput + " This is a post processed response!";` const PostProcessing = ({ dialogProps }) => { const dispatch = useDispatch() + const { t } = useTranslation() useNotifier() const theme = useTheme() @@ -68,15 +72,15 @@ const PostProcessing = ({ dialogProps }) => { const dialogProps = { value, inputParam: { - label: 'Post Processing Function', + label: t('components.postProcessing.postProcessionLabel'), name: 'postProcessingFunction', type: 'code', placeholder: sampleFunction, hideCodeExecute: true }, languageType: 'js', - confirmButtonName: 'Save', - cancelButtonName: 'Cancel' + confirmButtonName: t('common.actions.save'), + cancelButtonName: t('common.actions.cancel') } setExpandDialogProps(dialogProps) setShowExpandDialog(true) @@ -96,7 +100,7 @@ const PostProcessing = ({ dialogProps }) => { }) if (saveResp.data) { enqueueSnackbar({ - message: 'Post Processing Settings Saved', + message: t('components.postProcessing.messages.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -111,9 +115,9 @@ const PostProcessing = ({ dialogProps }) => { } } catch (error) { enqueueSnackbar({ - message: `Failed to save Post Processing Settings: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('components.postProcessing.messages.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -146,11 +150,11 @@ const PostProcessing = ({ dialogProps }) => { return ( <> - + - JS Function + {t('components.postProcessing.functionLabel')}
{ }} > }> - Available Variables + {t('components.postProcessing.availableVariables')} @@ -232,7 +236,7 @@ const PostProcessing = ({ dialogProps }) => { borderColor: customization.isDarkMode ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.08)' }} > - Variable + {t('components.postProcessing.variablesTable.variable')} { borderColor: customization.isDarkMode ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.08)' }} > - Type + {t('common.labels.type')} { borderColor: customization.isDarkMode ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.08)' }} > - Description + {t('common.labels.description')} @@ -274,71 +278,71 @@ const PostProcessing = ({ dialogProps }) => { $flow.rawOutput - string - The raw output response from the flow + {t('components.postProcessing.types.string')} + {t('components.postProcessing.variables.rawOutput.description')} $flow.input - string - The user input message + {t('components.postProcessing.types.string')} + {t('components.postProcessing.variables.input.description')} $flow.chatHistory - array - Array of previous messages in the conversation + {t('components.postProcessing.types.array')} + {t('components.postProcessing.variables.chatHistory.description')} $flow.chatflowId - string - Unique identifier for the chatflow + {t('components.postProcessing.types.string')} + {t('components.postProcessing.variables.chatflowId.description')} $flow.sessionId - string - Current session identifier + {t('components.postProcessing.types.string')} + {t('components.postProcessing.variables.sessionId.description')} $flow.chatId - string - Current chat identifier + {t('components.postProcessing.types.string')} + {t('components.postProcessing.variables.chatId.description')} $flow.sourceDocuments - array - Source documents used in retrieval (if applicable) + {t('components.postProcessing.types.array')} + {t('components.postProcessing.variables.sourceDocuments.description')} $flow.usedTools - array - List of tools used during execution + {t('components.postProcessing.types.array')} + {t('components.postProcessing.variables.usedTools.description')} $flow.artifacts - array - List of artifacts generated during execution + {t('components.postProcessing.types.array')} + {t('components.postProcessing.variables.artifacts.description')} $flow.fileAnnotations - array - File annotations associated with the response + {t('components.postProcessing.types.array')} + {t('components.postProcessing.variables.fileAnnotations.description')}
@@ -353,7 +357,7 @@ const PostProcessing = ({ dialogProps }) => { onClick={onSave} sx={{ minWidth: 100 }} > - Save + {t('common.actions.save')}
{ const dispatch = useDispatch() + const { t } = useTranslation() const chatflow = useSelector((state) => state.canvas.chatflow) const chatflowid = chatflow.id const apiConfig = chatflow.apiConfig ? JSON.parse(chatflow.apiConfig) : {} @@ -80,7 +84,7 @@ const RateLimit = ({ dialogProps, hideTitle = false }) => { }) if (saveResp.data) { enqueueSnackbar({ - message: 'Rate Limit Configuration Saved', + message: t('components.rateLimit.messages.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -95,9 +99,9 @@ const RateLimit = ({ dialogProps, hideTitle = false }) => { } } catch (error) { enqueueSnackbar({ - message: `Failed to save Rate Limit Configuration: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('components.rateLimit.messages.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -150,26 +154,38 @@ const RateLimit = ({ dialogProps, hideTitle = false }) => { {!hideTitle && ( - Rate Limit{' '} + {t('components.rateLimit.title')} Rate Limit Setup Guide to set up Rate Limit correctly in your hosting environment.' + + }} + /> } /> )} - + {rateLimitStatus && ( - {textField(limitMax, 'limitMax', 'Message Limit per Duration', 'number', '5')} - {textField(limitDuration, 'limitDuration', 'Duration in Second', 'number', '60')} - {textField(limitMsg, 'limitMsg', 'Limit Message', 'string', 'You have reached the quota')} + {textField(limitMax, 'limitMax', t('components.rateLimit.limitMax'), 'number', '5')} + {textField(limitDuration, 'limitDuration', t('components.rateLimit.limitDuration'), 'number', '60')} + {textField( + limitMsg, + 'limitMsg', + t('components.rateLimit.limitMsg.title'), + 'string', + t('components.rateLimit.limitMsg.placeholder') + )} )} onSave()} sx={{ minWidth: 100 }}> - Save + {t('common.actions.save')} diff --git a/packages/ui/src/ui-component/extended/SpeechToText.jsx b/packages/ui/src/ui-component/extended/SpeechToText.jsx index 2ca7fd95c28..248ef01d83b 100644 --- a/packages/ui/src/ui-component/extended/SpeechToText.jsx +++ b/packages/ui/src/ui-component/extended/SpeechToText.jsx @@ -24,6 +24,9 @@ import groqPng from '@/assets/images/groq.png' // store import useNotifier from '@/utils/useNotifier' +// i18n +import { useTranslation } from 'react-i18next' + // API import chatflowsApi from '@/api/chatflows' @@ -40,52 +43,51 @@ const SpeechToTextType = { // Weird quirk - the key must match the name property value. const speechToTextProviders = { [SpeechToTextType.OPENAI_WHISPER]: { - label: 'OpenAI Whisper', + label: 'common.providers.openAIWhisper', name: SpeechToTextType.OPENAI_WHISPER, icon: openAISVG, url: 'https://platform.openai.com/docs/guides/speech-to-text', inputs: [ { - label: 'Connect Credential', + label: 'components.speechToText.inputs.connectCredential', name: 'credential', type: 'credential', credentialNames: ['openAIApi'] }, { - label: 'Language', + label: 'components.speechToText.inputs.language.tit;e', name: 'language', type: 'string', - description: - 'The language of the input audio. Supplying the input language in ISO-639-1 format will improve accuracy and latency.', + description: 'components.speechToText.inputs.language.description', placeholder: 'en', optional: true }, { - label: 'Prompt', + label: 'common.labels.prompt', name: 'prompt', type: 'string', rows: 4, - description: `An optional text to guide the model's style or continue a previous audio segment. The prompt should match the audio language.`, + description: 'components.speechToText.inputs.prompt.description', optional: true }, { - label: 'Temperature', + label: 'components.speechToText.inputs.temperature.title', name: 'temperature', type: 'number', step: 0.1, - description: `The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.`, + description: 'components.speechToText.inputs.temperature.description', optional: true } ] }, [SpeechToTextType.ASSEMBLYAI_TRANSCRIBE]: { - label: 'Assembly AI', + label: 'common.providers.assemblyAi', name: SpeechToTextType.ASSEMBLYAI_TRANSCRIBE, icon: assemblyAIPng, url: 'https://www.assemblyai.com/', inputs: [ { - label: 'Connect Credential', + label: 'components.speechToText.inputs.connectCredential', name: 'credential', type: 'credential', credentialNames: ['assemblyAIApi'] @@ -93,94 +95,93 @@ const speechToTextProviders = { ] }, [SpeechToTextType.LOCALAI_STT]: { - label: 'LocalAi STT', + label: 'common.providers.localAiSTT', name: SpeechToTextType.LOCALAI_STT, icon: localAiPng, url: 'https://localai.io/features/audio-to-text/', inputs: [ { - label: 'Connect Credential', + label: 'components.speechToText.inputs.connectCredential', name: 'credential', type: 'credential', credentialNames: ['localAIApi'] }, { - label: 'Base URL', + label: 'components.speechToText.inputs.baseUrl.title', name: 'baseUrl', type: 'string', - description: 'The base URL of the local AI server' + description: 'components.speechToText.inputs.baseUrl.description' }, { - label: 'Language', + label: 'common.labels.language', name: 'language', type: 'string', - description: - 'The language of the input audio. Supplying the input language in ISO-639-1 format will improve accuracy and latency.', + description: 'components.speechToText.inputs.language.description', placeholder: 'en', optional: true }, { - label: 'Model', + label: 'common.labels.model', name: 'model', type: 'string', - description: `The STT model to load. Defaults to whisper-1 if left blank.`, + description: 'components.speechToText.inputs.model.description.localAiSTT', placeholder: 'whisper-1', optional: true }, { - label: 'Prompt', + label: 'common.labels.prompt', name: 'prompt', type: 'string', rows: 4, - description: `An optional text to guide the model's style or continue a previous audio segment. The prompt should match the audio language.`, + description: 'components.speechToText.inputs.prompt.description', optional: true }, { - label: 'Temperature', + label: 'components.speechToText.inputs.temperature.title', name: 'temperature', type: 'number', step: 0.1, - description: `The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.`, + description: 'components.speechToText.inputs.temperature.description', optional: true } ] }, [SpeechToTextType.AZURE_COGNITIVE]: { - label: 'Azure Cognitive Services', + label: 'common.providers.azureCognitive', name: SpeechToTextType.AZURE_COGNITIVE, icon: azureSvg, url: 'https://azure.microsoft.com/en-us/products/cognitive-services/speech-services', inputs: [ { - label: 'Connect Credential', + label: 'components.speechToText.inputs.connectCredential', name: 'credential', type: 'credential', credentialNames: ['azureCognitiveServices'] }, { - label: 'Language', + label: 'common.labels.language', name: 'language', type: 'string', - description: 'The recognition language (e.g., "en-US", "es-ES")', + description: 'components.speechToText.inputs.language.description', placeholder: 'en-US', optional: true }, { - label: 'Profanity Filter Mode', + label: 'components.speechToText.inputs.profanityFilterMode.title', name: 'profanityFilterMode', type: 'options', - description: 'How to handle profanity in the transcription', + description: 'components.speechToText.inputs.profanityFilterMode.description', options: [ { - label: 'None', + label: 'common.providers.none', name: 'None' }, { - label: 'Masked', + label: 'components.speechToText.inputs.profanityFilterMode.options.masked', name: 'Masked' }, { - label: 'Removed', + label: 'components.speechToText.inputs.profanityFilterMode.options.removed', name: 'Removed' } ], @@ -188,51 +189,49 @@ const speechToTextProviders = { optional: true }, { - label: 'Audio Channels', + label: 'components.speechToText.inputs.audioChannels.title', name: 'channels', type: 'string', - description: 'Comma-separated list of audio channels to process (e.g., "0,1")', + description: 'components.speechToText.inputs.audioChannels.description', placeholder: '0,1', default: '0,1' } ] }, [SpeechToTextType.GROQ_WHISPER]: { - label: 'Groq Whisper', + label: 'common.providers.groqWhisper', name: SpeechToTextType.GROQ_WHISPER, icon: groqPng, url: 'https://console.groq.com/', inputs: [ { - label: 'Model', + label: 'common.labels.model', name: 'model', type: 'string', - description: `The STT model to load. Defaults to whisper-large-v3 if left blank.`, + description: 'components.speechToText.inputs.model.description.groq', placeholder: 'whisper-large-v3', optional: true }, { - label: 'Connect Credential', + label: 'components.speechToText.inputs.connectCredential', name: 'credential', type: 'credential', credentialNames: ['groqApi'] }, { - label: 'Language', + label: 'common.labels.language', name: 'language', type: 'string', - description: - 'The language of the input audio. Supplying the input language in ISO-639-1 format will improve accuracy and latency.', + description: 'components.speechToText.inputs.language.description', placeholder: 'en', optional: true }, { - label: 'Temperature', + label: 'components.speechToText.inputs.temperature.title', name: 'temperature', type: 'number', step: 0.1, - description: - 'The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.', + description: 'components.speechToText.inputs.temperature.description', optional: true } ] @@ -241,6 +240,7 @@ const speechToTextProviders = { const SpeechToText = ({ dialogProps, onConfirm }) => { const dispatch = useDispatch() + const { t } = useTranslation() useNotifier() const theme = useTheme() @@ -259,7 +259,7 @@ const SpeechToText = ({ dialogProps, onConfirm }) => { }) if (saveResp.data) { enqueueSnackbar({ - message: 'Speech To Text Configuration Saved', + message: t('components.speechToText.messages.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -275,9 +275,9 @@ const SpeechToText = ({ dialogProps, onConfirm }) => { } } catch (error) { enqueueSnackbar({ - message: `Failed to save Speech To Text Configuration: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('components.speechToText.messages.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -350,7 +350,7 @@ const SpeechToText = ({ dialogProps, onConfirm }) => { return ( <> - Providers + {t('components.speechToText.providersLabel')} @@ -401,7 +401,7 @@ const SpeechToText = ({ dialogProps, onConfirm }) => { {
- {inputParam.label} + {t(inputParam.label)} {!inputParam.optional &&  *} {inputParam.description && ( - + )}
@@ -465,12 +465,15 @@ const SpeechToText = ({ dialogProps, onConfirm }) => { {inputParam.type === 'options' && ( ({ + label: t(opt.label), + name: opt.name + }))} onSelect={(newValue) => setValue(newValue, selectedProvider, inputParam.name)} value={ speechToText[selectedProvider] ? speechToText[selectedProvider][inputParam.name] - : inputParam.default ?? 'choose an option' + : inputParam.default ?? t('components.dropdown.chooseOption') } /> )} @@ -485,7 +488,7 @@ const SpeechToText = ({ dialogProps, onConfirm }) => { onClick={onSave} sx={{ minWidth: 100 }} > - Save + {t('common.actions.save')}
diff --git a/packages/ui/src/ui-component/extended/StarterPrompts.jsx b/packages/ui/src/ui-component/extended/StarterPrompts.jsx index 861bea1e125..39951b00302 100644 --- a/packages/ui/src/ui-component/extended/StarterPrompts.jsx +++ b/packages/ui/src/ui-component/extended/StarterPrompts.jsx @@ -13,10 +13,14 @@ import { StyledButton } from '@/ui-component/button/StyledButton' // store import useNotifier from '@/utils/useNotifier' +// i18n +import { useTranslation } from 'react-i18next' + // API import chatflowsApi from '@/api/chatflows' const StarterPrompts = ({ dialogProps, onConfirm }) => { + const { t } = useTranslation() const dispatch = useDispatch() useNotifier() @@ -66,7 +70,7 @@ const StarterPrompts = ({ dialogProps, onConfirm }) => { }) if (saveResp.data) { enqueueSnackbar({ - message: 'Conversation Starter Prompts Saved', + message: t('components.starterPrompts.messages.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -82,9 +86,9 @@ const StarterPrompts = ({ dialogProps, onConfirm }) => { } } catch (error) { enqueueSnackbar({ - message: `Failed to save Conversation Starter Prompts: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('components.starterPrompts.messages.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -148,7 +152,7 @@ const StarterPrompts = ({ dialogProps, onConfirm }) => { > - Starter prompts will only be shown when there are no messages on the chat + S{t('components.starterPrompts.info')}
:not(style)': { m: 1 }, pt: 2 }}> @@ -197,7 +201,7 @@ const StarterPrompts = ({ dialogProps, onConfirm }) => { - Save + {t('common.actions.save')} diff --git a/packages/ui/src/ui-component/extended/TextToSpeech.jsx b/packages/ui/src/ui-component/extended/TextToSpeech.jsx index 8ce171615a4..15d7d42b84c 100644 --- a/packages/ui/src/ui-component/extended/TextToSpeech.jsx +++ b/packages/ui/src/ui-component/extended/TextToSpeech.jsx @@ -35,6 +35,9 @@ import elevenLabsSVG from '@/assets/images/elevenlabs.svg' // store import useNotifier from '@/utils/useNotifier' +// i18n +import { useTranslation } from 'react-i18next' + // API import chatflowsApi from '@/api/chatflows' import ttsApi from '@/api/tts' @@ -47,44 +50,44 @@ const TextToSpeechType = { // Weird quirk - the key must match the name property value. const textToSpeechProviders = { [TextToSpeechType.OPENAI_TTS]: { - label: 'OpenAI TTS', + label: 'common.providers.openAiTts', name: TextToSpeechType.OPENAI_TTS, icon: openAISVG, url: 'https://platform.openai.com/docs/guides/text-to-speech', inputs: [ { - label: 'Connect Credential', + label: 'components.textToSpeech.inputs.connectCredential', name: 'credential', type: 'credential', credentialNames: ['openAIApi'] }, { - label: 'Voice', + label: 'components.textToSpeech.inputs.voice.title', name: 'voice', type: 'voice_select', - description: 'The voice to use when generating the audio', + description: 'components.textToSpeech.inputs.voice.description.openai', default: 'alloy', optional: true } ] }, [TextToSpeechType.ELEVEN_LABS_TTS]: { - label: 'Eleven Labs TTS', + label: 'common.providers.elevenlabs', name: TextToSpeechType.ELEVEN_LABS_TTS, icon: elevenLabsSVG, url: 'https://elevenlabs.io/', inputs: [ { - label: 'Connect Credential', + label: 'components.textToSpeech.inputs.connectCredential', name: 'credential', type: 'credential', credentialNames: ['elevenLabsApi'] }, { - label: 'Voice', + label: 'components.textToSpeech.inputs.voice.title', name: 'voice', type: 'voice_select', - description: 'The voice to use for text-to-speech', + description: 'components.textToSpeech.inputs.voice.description.elevenlabs', default: '21m00Tcm4TlvDq8ikWAM', optional: true } @@ -94,6 +97,7 @@ const textToSpeechProviders = { const TextToSpeech = ({ dialogProps }) => { const dispatch = useDispatch() + const { t } = useTranslation() useNotifier() const theme = useTheme() @@ -129,7 +133,7 @@ const TextToSpeech = ({ dialogProps }) => { }) if (saveResp.data) { enqueueSnackbar({ - message: 'Text To Speech Configuration Saved', + message: t('components.textToSpeech.messages.onSave.success'), options: { key: Date.now() + Math.random(), variant: 'success', @@ -144,9 +148,9 @@ const TextToSpeech = ({ dialogProps }) => { } } catch (error) { enqueueSnackbar({ - message: `Failed to save Text To Speech Configuration: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('components.textToSpeech.messages.onSave.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: Date.now() + Math.random(), variant: 'error', @@ -233,7 +237,7 @@ const TextToSpeech = ({ dialogProps }) => { const testTTS = async () => { if (selectedProvider === 'none' || !textToSpeech?.[selectedProvider]?.credentialId) { enqueueSnackbar({ - message: 'Please select a provider and configure credentials first', + message: t('components.textToSpeech.messages.testTTS.error.provider'), options: { variant: 'warning' } }) return @@ -317,7 +321,7 @@ const TextToSpeech = ({ dialogProps }) => { } catch (error) { console.error('Error testing TTS:', error) enqueueSnackbar({ - message: `TTS test failed: ${error.message}`, + message: t('components.textToSpeech.messages.testTTS.error.failed', { msg: error.message }), options: { variant: 'error' } }) } finally { @@ -419,7 +423,7 @@ const TextToSpeech = ({ dialogProps }) => { return ( <> - Providers + {t('components.textToSpeech.providersLabel')} @@ -470,7 +474,7 @@ const TextToSpeech = ({ dialogProps }) => { {
- {inputParam.label} + {t(inputParam.label)} {!inputParam.optional &&  *} {inputParam.description && ( - + )}
@@ -544,7 +548,7 @@ const TextToSpeech = ({ dialogProps }) => { value={ textToSpeech?.[selectedProvider] ? textToSpeech[selectedProvider][inputParam.name] - : inputParam.default ?? 'choose an option' + : inputParam.default ?? t('components.dropdown.chooseOption') } /> )} @@ -567,7 +571,11 @@ const TextToSpeech = ({ dialogProps }) => { renderInput={(params) => ( {
- Automatically play audio - + {t('components.textToSpeech.autoPlay.title')} +
{ - Test Voice + {t('components.textToSpeech.testAudio.title')} - Test text: "Today is a wonderful day to build something with Flowise!" + {t('components.textToSpeech.testAudio.text', { + text: 'Today is a wonderful day to build something with Flowise!' + })} { onClick={onSave} sx={{ minWidth: 100 }} > - Save + {t('common.actions.save')} diff --git a/packages/ui/src/ui-component/file/File.jsx b/packages/ui/src/ui-component/file/File.jsx index 0c81a79fdd3..a35e0a69efa 100644 --- a/packages/ui/src/ui-component/file/File.jsx +++ b/packages/ui/src/ui-component/file/File.jsx @@ -5,7 +5,11 @@ import { FormControl, Button } from '@mui/material' import { IconUpload } from '@tabler/icons-react' import { getFileName } from '@/utils/genericHelper' +// i18n +import { useTranslation } from 'react-i18next' + export const File = ({ value, formDataUpload, fileType, onChange, onFormDataChange, disabled = false }) => { + const { t } = useTranslation() const theme = useTheme() const [myValue, setMyValue] = useState(value ?? '') @@ -88,7 +92,7 @@ export const File = ({ value, formDataUpload, fileType, onChange, onFormDataChan marginBottom: '1rem' }} > - {myValue ? getFileName(myValue) : 'Choose a file to upload'} + {myValue ? getFileName(myValue) : t('components.file.chooseFile')} )} )} diff --git a/packages/ui/src/ui-component/input/Input.jsx b/packages/ui/src/ui-component/input/Input.jsx index d827c5b4816..9f1611331ac 100644 --- a/packages/ui/src/ui-component/input/Input.jsx +++ b/packages/ui/src/ui-component/input/Input.jsx @@ -7,7 +7,11 @@ import VisibilityOff from '@mui/icons-material/VisibilityOff' import SelectVariable from '@/ui-component/json/SelectVariable' import { getAvailableNodesForVariable } from '@/utils/genericHelper' +// i18n +import { useTranslation } from 'react-i18next' + export const Input = ({ inputParam, value, nodes, edges, nodeId, onChange, onBlur, disabled = false }) => { + const { t } = useTranslation() const theme = useTheme() const [myValue, setMyValue] = useState(value ?? '') const [anchorEl, setAnchorEl] = useState(null) @@ -156,7 +160,9 @@ export const Input = ({ inputParam, value, nodes, edges, nodeId, onChange, onBlu edge='end' onClick={handleTogglePasswordVisibility} onMouseDown={(e) => e.preventDefault()} - aria-label={isPasswordVisible ? 'Hide password' : 'Show password'} + aria-label={t( + isPasswordVisible ? 'components.input.hidePassword' : 'components.input.showPassword' + )} > {isPasswordVisible ? : } diff --git a/packages/ui/src/ui-component/input/RichInput.jsx b/packages/ui/src/ui-component/input/RichInput.jsx index 69060ca0a2f..65d23a12347 100644 --- a/packages/ui/src/ui-component/input/RichInput.jsx +++ b/packages/ui/src/ui-component/input/RichInput.jsx @@ -15,6 +15,9 @@ import { getAvailableNodesForVariable } from '@/utils/genericHelper' import { CustomMention } from '@/utils/customMention' import { isHtmlContent, escapeXmlTags, unescapeXmlEntities, unescapeXmlTags } from '@/utils/xmlTagUtils' +// i18n +import { useTranslation } from 'react-i18next' + const lowlight = createLowlight(common) // define your extension array @@ -25,7 +28,8 @@ const extensions = ( nodes, nodeData, isNodeInsideInteration, - useMarkdown + useMarkdown, + t ) => [ Markdown, StarterKit.configure({ @@ -49,7 +53,8 @@ const extensions = ( acceptNodeOutputAsVariable, nodes, nodeData, - isNodeInsideInteration + isNodeInsideInteration, + t ), deleteTriggerWithBackspace: true }), @@ -107,6 +112,7 @@ const StyledEditorContent = styled(EditorContent)(({ theme, rows, disabled, isDa })) export const RichInput = ({ inputParam, value, nodes, edges, nodeId, onChange, disabled = false }) => { + const { t } = useTranslation() const useMarkdown = !!inputParam?.rows const customization = useSelector((state) => state.customization) const isDarkMode = customization.isDarkMode @@ -141,7 +147,8 @@ export const RichInput = ({ inputParam, value, nodes, edges, nodeId, onChange, d nodes, nodeData, isNodeInsideInteration, - useMarkdown + useMarkdown, + t ), Placeholder.configure({ placeholder: inputParam?.placeholder }) ], diff --git a/packages/ui/src/ui-component/input/suggestionOption.js b/packages/ui/src/ui-component/input/suggestionOption.js index 0247c8a059e..431d6f82427 100644 --- a/packages/ui/src/ui-component/input/suggestionOption.js +++ b/packages/ui/src/ui-component/input/suggestionOption.js @@ -47,45 +47,66 @@ export const suggestionOptions = ( acceptNodeOutputAsVariable, nodes, nodeData, - isNodeInsideInteration + isNodeInsideInteration, + t ) => ({ char: '{{', items: async ({ query }) => { const defaultItems = [ - { id: 'question', mentionLabel: 'question', description: "User's question from chatbox", category: 'Chat Context' }, + { + id: 'question', + mentionLabel: 'question', + description: t('suggestOptions.description.question'), + category: 'Chat Context' + }, { id: 'chat_history', mentionLabel: 'chat_history', - description: 'Past conversation history between user and AI', + description: t('suggestOptions.description.chatHistory'), category: 'Chat Context' }, { id: 'current_date_time', mentionLabel: 'current_date_time', - description: 'Current date and time', + description: t('suggestOptions.description.currentDateTime'), category: 'Chat Context' }, { id: 'runtime_messages_length', mentionLabel: 'runtime_messages_length', - description: 'Total messages between LLM and Agent', + description: t('suggestOptions.description.runtimeMessagesLength'), category: 'Chat Context' }, { id: 'loop_count', mentionLabel: 'loop_count', - description: 'Current loop count', + description: t('suggestOptions.description.loopCount'), category: 'Chat Context' }, { id: 'file_attachment', mentionLabel: 'file_attachment', - description: 'Files uploaded from the chat', + description: t('suggestOptions.description.fileAttachment'), category: 'Chat Context' }, - { id: '$flow.sessionId', mentionLabel: '$flow.sessionId', description: 'Current session ID', category: 'Flow Variables' }, - { id: '$flow.chatId', mentionLabel: '$flow.chatId', description: 'Current chat ID', category: 'Flow Variables' }, - { id: '$flow.chatflowId', mentionLabel: '$flow.chatflowId', description: 'Current chatflow ID', category: 'Flow Variables' } + { + id: '$flow.sessionId', + mentionLabel: '$flow.sessionId', + description: t('suggestOptions.description.sessionId'), + category: 'Flow Variables' + }, + { + id: '$flow.chatId', + mentionLabel: '$flow.chatId', + description: t('suggestOptions.description.chatId'), + category: 'Flow Variables' + }, + { + id: '$flow.chatflowId', + mentionLabel: '$flow.chatflowId', + description: t('suggestOptions.description.chatflowId'), + category: 'Flow Variables' + } ] const stateItems = (availableState || []).map((state) => ({ @@ -98,7 +119,7 @@ export const suggestionOptions = ( defaultItems.unshift({ id: '$iteration', mentionLabel: '$iteration', - description: 'Iteration item. For JSON, use dot notation: $iteration.name', + description: t('suggestOptions.description.iteration'), category: 'Iteration' }) } @@ -108,7 +129,7 @@ export const suggestionOptions = ( defaultItems.unshift({ id: 'output', mentionLabel: 'output', - description: 'Output from the current node', + description: t('suggestOptions.description.output'), category: 'Node Outputs' }) @@ -133,7 +154,7 @@ export const suggestionOptions = ( const variableItems = cachedVariables.map((variable) => ({ id: `$vars.${variable.name}`, mentionLabel: `$vars.${variable.name}`, - description: `Variable: ${variable.value} (${variable.type})`, + description: t('suggestOptions.description.variable', { value: variable.value, type: variable.type }), category: 'Custom Variables' })) @@ -145,7 +166,7 @@ export const suggestionOptions = ( formItems = (formInputTypes || []).map((input) => ({ id: `$form.${input.name}`, mentionLabel: `$form.${input.name}`, - description: `Form Input: ${input.label}`, + description: t('suggestOptions.description.formInput', { label: input.label }), category: 'Form Inputs' })) } @@ -159,7 +180,10 @@ export const suggestionOptions = ( description: node.data.name === 'ifElseFunction' ? node.data.description - : `${selectedOutputAnchor?.label ?? 'Output'} from ${node.data.label}`, + : t('suggestOptions.description.nodeOutput', { + output: selectedOutputAnchor?.label ?? 'Output', + node: node.data.label + }), category: 'Node Outputs' } }) diff --git a/packages/ui/src/ui-component/json/SelectVariable.jsx b/packages/ui/src/ui-component/json/SelectVariable.jsx index 55fdce061d6..617ab9b2caa 100644 --- a/packages/ui/src/ui-component/json/SelectVariable.jsx +++ b/packages/ui/src/ui-component/json/SelectVariable.jsx @@ -8,26 +8,30 @@ import diskPNG from '@/assets/images/floppy-disc.png' import fileAttachmentPNG from '@/assets/images/fileAttachment.png' import { baseURL } from '@/store/constant' +// i18n +import { useTranslation } from 'react-i18next' + const sequentialStateMessagesSelection = [ { primary: '$flow.state.messages', - secondary: `All messages from the start of the conversation till now` + secondary: 'components.selectVariable.sequentialState.messages' }, { primary: '$flow.state.', - secondary: `Current value of the state variable with specified key` + secondary: 'components.selectVariable.sequentialState.key' }, { primary: '$flow.state.messages[0].content', - secondary: `First message content` + secondary: 'components.selectVariable.sequentialState.firstMessage' }, { primary: '$flow.state.messages[-1].content', - secondary: `Last message content` + secondary: 'components.selectVariable.sequentialState.lastMessage' } ] const SelectVariable = ({ availableNodesForVariable, disabled = false, onSelectAndReturnVal, isSequentialAgent }) => { + const { t } = useTranslation() const customization = useSelector((state) => state.customization) const onSelectOutputResponseClick = (node, prefix) => { @@ -41,7 +45,7 @@ const SelectVariable = ({ availableNodesForVariable, disabled = false, onSelectA {!disabled && (
- Select Variable + {t('components.selectVariable.title')} @@ -78,7 +82,11 @@ const SelectVariable = ({ availableNodesForVariable, disabled = false, onSelectA />
- +
@@ -155,7 +163,7 @@ const SelectVariable = ({ availableNodesForVariable, disabled = false, onSelectA @@ -211,7 +219,10 @@ const SelectVariable = ({ availableNodesForVariable, disabled = false, onSelectA secondary={ node.data.name === 'ifElseFunction' ? `${node.data.description}` - : `${selectedOutputAnchor?.label ?? 'output'} from ${node.data.label}` + : t('components.selectVariable.output', { + output: selectedOutputAnchor?.label ?? 'output', + node: node.data.label + }) } /> @@ -253,7 +264,7 @@ const SelectVariable = ({ availableNodesForVariable, disabled = false, onSelectA />
- +
))} diff --git a/packages/ui/src/ui-component/language/LanguageSwitcher.jsx b/packages/ui/src/ui-component/language/LanguageSwitcher.jsx new file mode 100644 index 00000000000..fd96cf19209 --- /dev/null +++ b/packages/ui/src/ui-component/language/LanguageSwitcher.jsx @@ -0,0 +1,67 @@ +import { useMemo, useState } from 'react' +import PropTypes from 'prop-types' + +// material-ui +import { IconButton, Menu, MenuItem, Tooltip, Typography } from '@mui/material' + +// icons +import { IconLanguage } from '@tabler/icons-react' + +import { getI18nLanguages } from '@/utils/language' + +// i18n +import { useTranslation } from 'react-i18next' + +const LanguageSwitcher = ({ persist = false, size = 'small' }) => { + const { t, i18n } = useTranslation() + const [anchorEl, setAnchorEl] = useState(null) + + const languages = useMemo(() => getI18nLanguages(i18n), [i18n]) + const currentLanguage = i18n.resolvedLanguage || i18n.language + + const open = Boolean(anchorEl) + + const handleClick = (event) => { + setAnchorEl(event.currentTarget) + } + + const handleClose = () => { + setAnchorEl(null) + } + + const handleLanguageChange = async (language) => { + await i18n.changeLanguage(language) + if (persist) { + localStorage.setItem('i18nextLng', language) + } + handleClose() + } + + return ( + <> + + + + + + + {languages.map((language) => ( + handleLanguageChange(language)} + > + {language.toUpperCase()} + + ))} + + + ) +} + +LanguageSwitcher.propTypes = { + persist: PropTypes.bool, + size: PropTypes.oneOf(['small', 'medium', 'large']) +} + +export default LanguageSwitcher diff --git a/packages/ui/src/ui-component/markdown/CodeBlock.jsx b/packages/ui/src/ui-component/markdown/CodeBlock.jsx index 670a8ebe063..fd21775fc12 100644 --- a/packages/ui/src/ui-component/markdown/CodeBlock.jsx +++ b/packages/ui/src/ui-component/markdown/CodeBlock.jsx @@ -5,6 +5,7 @@ import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism' import PropTypes from 'prop-types' import { Box, IconButton, Popover, Typography } from '@mui/material' import { useTheme } from '@mui/material/styles' +import { useTranslation } from 'react-i18next' const programmingLanguages = { javascript: '.js', @@ -33,6 +34,7 @@ const programmingLanguages = { } export const CodeBlock = memo(({ language, chatflowid, isFullWidth, value }) => { + const { t } = useTranslation() const theme = useTheme() const [anchorEl, setAnchorEl] = useState(null) const openPopOver = Boolean(anchorEl) @@ -81,7 +83,7 @@ export const CodeBlock = memo(({ language, chatflowid, isFullWidth, value }) =>
{language}
- + }} > - Copied! + {t('common.messages.copied')} - +
diff --git a/packages/ui/src/ui-component/subscription/PricingDialog.jsx b/packages/ui/src/ui-component/subscription/PricingDialog.jsx index 7e97f679b9c..d55d6d0a920 100644 --- a/packages/ui/src/ui-component/subscription/PricingDialog.jsx +++ b/packages/ui/src/ui-component/subscription/PricingDialog.jsx @@ -25,7 +25,11 @@ import PropTypes from 'prop-types' import { useEffect, useMemo, useState } from 'react' import { useSelector } from 'react-redux' +// i18n +import { useTranslation } from 'react-i18next' + const PricingDialog = ({ open, onClose }) => { + const { t } = useTranslation() const customization = useSelector((state) => state.customization) const currentUser = useSelector((state) => state.auth.user) const theme = useTheme() @@ -93,16 +97,16 @@ const PricingDialog = ({ open, onClose }) => { if (response.data.status === 'success') { // Subscription updated successfully store.dispatch(upgradePlanSuccess(response.data.user)) - enqueueSnackbar('Subscription updated successfully!', { variant: 'success' }) + enqueueSnackbar(t('components.dialogs.pricing.messages.updatePlan.success'), { variant: 'success' }) onClose(true) } else { - const errorMessage = response.data.message || 'Subscription failed to update' + const errorMessage = response.data.message || t('components.dialogs.pricing.messages.updatePlan.errors.failedUpdate') enqueueSnackbar(errorMessage, { variant: 'error' }) onClose() } } catch (error) { console.error('Error updating plan:', error) - const errorMessage = err.response?.data?.message || 'Failed to verify subscription' + const errorMessage = err.response?.data?.message || t('components.dialogs.pricing.messages.updatePlan.errors.failedVerify') enqueueSnackbar(errorMessage, { variant: 'error' }) onClose() } finally { @@ -234,7 +238,7 @@ const PricingDialog = ({ open, onClose }) => { position: 'relative' }} > - Pricing Plans + {t('components.dialogs.pricing.title')} { }} > - Current Plan + {t('components.dialogs.pricing.currentPlan')} )} @@ -304,7 +308,7 @@ const PricingDialog = ({ open, onClose }) => { }} > - Most Popular + {t('components.dialogs.pricing.mostPopular')} )} @@ -384,7 +388,7 @@ const PricingDialog = ({ open, onClose }) => { position: 'relative' }} > - First Month Free + {t('components.dialogs.pricing.firstMonthFree')} )} @@ -395,7 +399,7 @@ const PricingDialog = ({ open, onClose }) => { onClick={plan.buttonAction} disabled={plan.disabled} > - {plan.currentPlan ? 'Current Plan' : plan.buttonText} + {plan.currentPlan ? t('components.dialogs.pricing.currentPlan') : plan.buttonText} @@ -405,7 +409,7 @@ const PricingDialog = ({ open, onClose }) => { - Confirm Plan Change + {t('components.dialogs.pricing.confirmPlanChange')} {purchasedSeats > 0 || occupiedSeats > 1 ? ( @@ -420,7 +424,7 @@ const PricingDialog = ({ open, onClose }) => { }} > - You must remove additional seats and users before changing your plan. + {t('components.dialogs.pricing.additionalSeatsWarning')} ) : workspaceCount > 1 ? ( <> @@ -435,7 +439,7 @@ const PricingDialog = ({ open, onClose }) => { }} > - You must remove all workspaces except the default workspace before changing your plan. + {t('components.dialogs.pricing.workspacesWarning')} ) : proAPIKeysCount > 0 ? ( @@ -451,7 +455,7 @@ const PricingDialog = ({ open, onClose }) => { }} > - You must remove all API keys with sharing permissions before changing your plan. + {t('components.dialogs.pricing.apiKeysWarning')} ) : ( @@ -460,7 +464,7 @@ const PricingDialog = ({ open, onClose }) => { ) : getCustomerDefaultSourceApi.data?.invoice_settings?.default_payment_method ? ( - Payment Method + {t('components.dialogs.pricing.paymentMethod')} {getCustomerDefaultSourceApi.data.invoice_settings.default_payment_method.card && ( <> @@ -501,7 +505,7 @@ const PricingDialog = ({ open, onClose }) => { - No payment method found + {t('components.dialogs.pricing.noPaymentMethod')} @@ -567,7 +571,7 @@ const PricingDialog = ({ open, onClose }) => { }} > - {`You're eligible for your first month free!`} + {t('components.dialogs.pricing.eligibleFirstMonthFree')} )} @@ -582,7 +586,9 @@ const PricingDialog = ({ open, onClose }) => { {selectedPlan?.title === 'Starter' && prorationInfo.eligibleForFirstMonthFree && ( - First Month Discount + + {t('components.dialogs.pricing.firstMonthDiscount')} + -{prorationInfo.currency} {Math.max(0, prorationInfo.newPlanAmount).toFixed(2)} @@ -598,7 +604,9 @@ const PricingDialog = ({ open, onClose }) => { alignItems: 'center' }} > - Applied account balance + + {t('components.dialogs.pricing.appliedAccountBalance')} + { alignItems: 'center' }} > - Credit balance + {t('components.dialogs.pricing.creditBalance')} { borderTop: `1px solid ${theme.palette.divider}` }} > - Due today + {t('components.dialogs.pricing.dueToday')} {prorationInfo.currency}{' '} {Math.max(0, prorationInfo.prorationAmount + prorationInfo.creditBalance).toFixed(2)} @@ -652,7 +660,7 @@ const PricingDialog = ({ open, onClose }) => { fontStyle: 'italic' }} > - Your available credit will automatically apply to your next invoice. + {t('components.dialogs.pricing.creditNotice')} )} @@ -664,7 +672,7 @@ const PricingDialog = ({ open, onClose }) => { {getCustomerDefaultSourceApi.data?.invoice_settings?.default_payment_method && ( diff --git a/packages/ui/src/ui-component/table/DocumentStoreTable.jsx b/packages/ui/src/ui-component/table/DocumentStoreTable.jsx index 09ec419d614..4a77076247d 100644 --- a/packages/ui/src/ui-component/table/DocumentStoreTable.jsx +++ b/packages/ui/src/ui-component/table/DocumentStoreTable.jsx @@ -21,6 +21,9 @@ import { tableCellClasses } from '@mui/material/TableCell' import DocumentStoreStatus from '@/views/docstore/DocumentStoreStatus' import { IconDotsVertical } from '@tabler/icons-react' +// i18n +import { useTranslation } from 'react-i18next' + const StyledTableCell = styled(TableCell)(({ theme }) => ({ borderColor: theme.palette.grey[900] + 25, @@ -41,6 +44,7 @@ const StyledTableRow = styled(TableRow)(() => ({ })) export const DocumentStoreTable = ({ data, isLoading, onRowClick, images, showActions, onActionMenuClick, actionButtonSx }) => { + const { t } = useTranslation() const theme = useTheme() const customization = useSelector((state) => state.customization) @@ -82,14 +86,14 @@ export const DocumentStoreTable = ({ data, isLoading, onRowClick, images, showAc   handleRequestSort('name')}> - Name + {t('common.labels.name')} - Description - Connected flows - Total characters - Total chunks - Loader Types + {t('common.labels.description')} + {t('components.documentStoreTable.connectedFlows')} + {t('components.documentStoreTable.totalCharacters')} + {t('components.documentStoreTable.totalChunks')} + {t('components.documentStoreTable.loaderTypes')} {showActions && (   @@ -244,7 +248,7 @@ export const DocumentStoreTable = ({ data, isLoading, onRowClick, images, showAc fontWeight: 200 }} > - + {images.length - 3} More + {t('components.documentStoreTable.more', { count: images.length - 3 })} )} @@ -254,7 +258,7 @@ export const DocumentStoreTable = ({ data, isLoading, onRowClick, images, showAc { event.stopPropagation() diff --git a/packages/ui/src/ui-component/table/ExecutionsListTable.jsx b/packages/ui/src/ui-component/table/ExecutionsListTable.jsx index 1b7dd68f996..21e4129772e 100644 --- a/packages/ui/src/ui-component/table/ExecutionsListTable.jsx +++ b/packages/ui/src/ui-component/table/ExecutionsListTable.jsx @@ -23,6 +23,9 @@ import StopCircleIcon from '@mui/icons-material/StopCircle' import ErrorIcon from '@mui/icons-material/Error' import { IconLoader, IconCircleXFilled } from '@tabler/icons-react' +// i18n +import { useTranslation } from 'react-i18next' + const StyledTableCell = styled(TableCell)(({ theme }) => ({ borderColor: theme.palette.grey[900] + 25, @@ -87,6 +90,7 @@ const getIconColor = (state) => { } export const ExecutionsListTable = ({ data, isLoading, onExecutionRowClick, onSelectionChange }) => { + const { t } = useTranslation() const theme = useTheme() const customization = useSelector((state) => state.customization) @@ -169,33 +173,33 @@ export const ExecutionsListTable = ({ data, isLoading, onExecutionRowClick, onSe checked={data.length > 0 && selected.length === data.length} onChange={handleSelectAllClick} inputProps={{ - 'aria-label': 'select all executions' + 'aria-label': t('components.executionsListTable.selectAll') }} /> - Status + {t('common.labels.status')} handleRequestSort('updatedDate')} > - Last Updated + {t('components.executionsListTable.lastUpdated')} handleRequestSort('name')}> - Agentflow + {t('components.executionsListTable.agentflow')} - Session + {t('components.executionsListTable.session')} handleRequestSort('createdDate')} > - Created + {t('components.executionsListTable.created')} @@ -274,14 +278,14 @@ export const ExecutionsListTable = ({ data, isLoading, onExecutionRowClick, onSe /> onExecutionRowClick(row)}> - {moment(row.updatedDate).format('MMM D, YYYY h:mm A')} + {moment(row.updatedDate).format(t('common.formats.dateMonthShortDayYearTime12'))} onExecutionRowClick(row)}> {row.agentflow?.name} onExecutionRowClick(row)}>{row.sessionId} onExecutionRowClick(row)}> - {moment(row.createdDate).format('MMM D, YYYY h:mm A')} + {moment(row.createdDate).format(t('common.formats.dateMonthShortDayYearTime12'))} ) diff --git a/packages/ui/src/ui-component/table/FilesTable.jsx b/packages/ui/src/ui-component/table/FilesTable.jsx index 71734c6baef..d9c17d0b783 100644 --- a/packages/ui/src/ui-component/table/FilesTable.jsx +++ b/packages/ui/src/ui-component/table/FilesTable.jsx @@ -18,6 +18,9 @@ import { import { tableCellClasses } from '@mui/material/TableCell' import { IconTrash } from '@tabler/icons-react' +// i18n +import { useTranslation } from 'react-i18next' + const StyledTableCell = styled(TableCell)(({ theme }) => ({ borderColor: theme.palette.grey[900] + 25, @@ -38,6 +41,7 @@ const StyledTableRow = styled(TableRow)(() => ({ })) export const FilesTable = ({ data, isLoading, filterFunction, handleDelete }) => { + const { t } = useTranslation() const theme = useTheme() const customization = useSelector((state) => state.customization) @@ -53,16 +57,16 @@ export const FilesTable = ({ data, isLoading, filterFunction, handleDelete }) => > - Name + {t('common.labels.name')} - Path + {t('components.filesTable.path')} - Size + {t('components.filesTable.size')} - Actions + {t('components.filesTable.actions')} diff --git a/packages/ui/src/ui-component/table/FlowListTable.jsx b/packages/ui/src/ui-component/table/FlowListTable.jsx index 2d1e0570472..59ff55c439d 100644 --- a/packages/ui/src/ui-component/table/FlowListTable.jsx +++ b/packages/ui/src/ui-component/table/FlowListTable.jsx @@ -27,6 +27,9 @@ import { useAuth } from '@/hooks/useAuth' import MoreItemsTooltip from '../tooltip/MoreItemsTooltip' +// i18n +import { useTranslation } from 'react-i18next' + const StyledTableCell = styled(TableCell)(({ theme }) => ({ borderColor: theme.palette.grey[900] + 25, @@ -63,6 +66,7 @@ export const FlowListTable = ({ currentPage, pageLimit }) => { + const { t } = useTranslation() const { hasPermission } = useAuth() const isActionsAvailable = isAgentCanvas ? hasPermission('agentflows:update,agentflows:delete,agentflows:config,agentflows:domains,templates:flowexport,agentflows:export') @@ -119,14 +123,14 @@ export const FlowListTable = ({ handleRequestSort('name')}> - Name + {t('common.labels.name')} - Category + {t('components.flowListTable.category')} - Nodes + {t('components.flowListTable.nodes')} handleRequestSort('updatedDate')} > - Last Modified Date + {t('components.flowListTable.lastModifiedDate')} {isActionsAvailable && ( - Actions + {t('components.flowListTable.actions')} )} @@ -309,7 +313,9 @@ export const FlowListTable = ({ fontWeight: 200 }} > - + {(images[row.id]?.length || 0) + (icons[row.id]?.length || 0) - 5} More + {t('components.flowListTable.more', { + count: (images[row.id]?.length || 0) + (icons[row.id]?.length || 0) - 5 + })} )} @@ -317,7 +323,7 @@ export const FlowListTable = ({ )} - {moment(row.updatedDate).format('MMMM Do, YYYY HH:mm:ss')} + {moment(row.updatedDate).format(t('common.formats.dateMonthDayYearTime24Long'))} {isActionsAvailable && ( diff --git a/packages/ui/src/ui-component/table/MarketplaceTable.jsx b/packages/ui/src/ui-component/table/MarketplaceTable.jsx index 23a35c220cc..8a5671970dd 100644 --- a/packages/ui/src/ui-component/table/MarketplaceTable.jsx +++ b/packages/ui/src/ui-component/table/MarketplaceTable.jsx @@ -20,6 +20,9 @@ import { import { IconShare, IconTrash } from '@tabler/icons-react' import { PermissionIconButton } from '@/ui-component/button/RBACButtons' +// i18n +import { useTranslation } from 'react-i18next' + const StyledTableCell = styled(TableCell)(({ theme }) => ({ borderColor: theme.palette.grey[900] + 25, @@ -52,6 +55,7 @@ export const MarketplaceTable = ({ onDelete, onShare }) => { + const { t } = useTranslation() const theme = useTheme() const customization = useSelector((state) => state.customization) @@ -66,7 +70,7 @@ export const MarketplaceTable = ({ return ( <> - +
- Name + {t('common.labels.name')} - Type + {t('common.labels.type')} - Description + {t('common.labels.description')} - Framework + {t('components.marketplaceTable.framework')} - Use cases + {t('components.marketplaceTable.useCases')} - Badges + {t('components.marketplaceTable.badges')} @@ -230,14 +234,14 @@ export const MarketplaceTable = ({ {row.shared ? ( - Shared Template + {t('components.marketplaceTable.sharedTemplate')} ) : ( <> {onShare && ( onShare(row)} > @@ -247,7 +251,7 @@ export const MarketplaceTable = ({ {onDelete && ( onDelete(row)} > diff --git a/packages/ui/src/ui-component/table/Table.jsx b/packages/ui/src/ui-component/table/Table.jsx index 9189cc3241f..3b96f73a2f3 100644 --- a/packages/ui/src/ui-component/table/Table.jsx +++ b/packages/ui/src/ui-component/table/Table.jsx @@ -2,13 +2,21 @@ import PropTypes from 'prop-types' import { TableContainer, Table, TableHead, TableCell, TableRow, TableBody, Paper, Chip, Stack, Typography } from '@mui/material' import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser' +// i18n +import { useTranslation } from 'react-i18next' + export const TableViewOnly = ({ columns, rows, sx }) => { + const { t } = useTranslation() // Helper function to safely render cell content const renderCellContent = (key, row) => { if (row[key] === null || row[key] === undefined) { return '' } else if (key === 'enabled') { - return row[key] ? : + return row[key] ? ( + + ) : ( + + ) } else if (key === 'type' && row.schema) { // If there's schema information, add a tooltip let schemaContent @@ -33,13 +41,13 @@ export const TableViewOnly = ({ columns, rows, sx }) => { // Handle object format: { "field": "string", "field2": "number", ... } schemaContent = JSON.stringify(row.schema, null, 2).replace(/\n/g, '
').replace(/ /g, ' ') } else { - schemaContent = 'No schema available' + schemaContent = t('components.table.schemaUnavailable') } return ( {row[key]} - Schema:
${schemaContent}`} /> + ${t('components.table.schema')}:
${schemaContent}`} />
) } else if (typeof row[key] === 'object') { @@ -60,12 +68,10 @@ export const TableViewOnly = ({ columns, rows, sx }) => { {col === 'enabled' ? ( <> - Override + {t('components.table.override')} ) : ( diff --git a/packages/ui/src/ui-component/table/ToolsListTable.jsx b/packages/ui/src/ui-component/table/ToolsListTable.jsx index f02e6366d9b..c575246a9a0 100644 --- a/packages/ui/src/ui-component/table/ToolsListTable.jsx +++ b/packages/ui/src/ui-component/table/ToolsListTable.jsx @@ -16,6 +16,9 @@ import { useTheme } from '@mui/material' +// i18n +import { useTranslation } from 'react-i18next' + const StyledTableCell = styled(TableCell)(({ theme }) => ({ borderColor: theme.palette.grey[900] + 25, @@ -36,6 +39,7 @@ const StyledTableRow = styled(TableRow)(() => ({ })) export const ToolsTable = ({ data, isLoading, onSelect }) => { + const { t } = useTranslation() const theme = useTheme() const customization = useSelector((state) => state.customization) @@ -51,9 +55,9 @@ export const ToolsTable = ({ data, isLoading, onSelect }) => { > - Name + {t('common.labels.name')} - Description + {t('common.labels.description')}   diff --git a/packages/ui/src/utils/genericHelper.js b/packages/ui/src/utils/genericHelper.js index ac834c77f19..e859275d736 100644 --- a/packages/ui/src/utils/genericHelper.js +++ b/packages/ui/src/utils/genericHelper.js @@ -1,5 +1,6 @@ import { uniq, get, isEqual } from 'lodash' import moment from 'moment' +import i18next from 'i18next' export const getUniqueNodeId = (nodeData, nodes) => { let suffix = 0 @@ -1021,11 +1022,12 @@ export const getOS = () => { } export const formatBytes = (number) => { + var scaleInitials = i18next.t('scaleInitials') + if (number == null || number === undefined || number <= 0) { - return '0 Bytes' + return `0 ${scaleInitials[0]}` } var scaleCounter = 0 - var scaleInitials = [' Bytes', ' KB', ' MB', ' GB', ' TB', ' PB', ' EB', ' ZB', ' YB'] while (number >= 1024 && scaleCounter < scaleInitials.length - 1) { number /= 1024 scaleCounter++ @@ -1035,7 +1037,7 @@ export const formatBytes = (number) => { .toFixed(2) .replace(/\.?0+$/, '') .replace(/\B(?=(\d{3})+(?!\d))/g, ',') - compactNumber += scaleInitials[scaleCounter] + compactNumber += ' ' + scaleInitials[scaleCounter] return compactNumber.trim() } @@ -1103,15 +1105,15 @@ export const getCustomConditionOutputs = (value, nodeId, existingEdges, isDataGr if (parsedValue && parsedValue.length) { for (const item of parsedValue) { if (!item.variable) { - alert('Please specify a Variable. Try connecting Condition node to a previous node and select the variable') + alert(i18next.t('errors.specifyVariable')) return undefined } if (!item.output) { - alert('Please specify an Output Name') + alert(i18next.t('errors.specifyOutputName')) return undefined } if (!item.operation) { - alert('Please select an operation for the condition') + alert(i18next.t('errors.selectOperation')) return undefined } numberOfReturns.push(item.output) @@ -1124,11 +1126,8 @@ export const getCustomConditionOutputs = (value, nodeId, existingEdges, isDataGr } if (numberOfReturns.length === 0) { - if (isDataGrid) alert('Please add an item for the condition') - else - alert( - 'Please add a return statement in the condition code to define the output. You can refer to How to Use for more information.' - ) + if (isDataGrid) alert(i18next.t('errors.addItem')) + else alert(i18next.t('errors.addReturn')) return undefined } @@ -1161,7 +1160,7 @@ export const getCustomConditionOutputs = (value, nodeId, existingEdges, isDataGr } const newOutput = { name: 'output', - label: 'Output', + label: i18next.t('common.labels.output'), type: 'options', options } diff --git a/packages/ui/src/utils/language.js b/packages/ui/src/utils/language.js new file mode 100644 index 00000000000..6d6d02bef19 --- /dev/null +++ b/packages/ui/src/utils/language.js @@ -0,0 +1,33 @@ +import i18n from '@/i18n' + +export const getPersistedLanguage = () => { + return localStorage.getItem('i18nextLng') +} + +export const applyPersistedLanguage = async () => { + const persistedLanguage = getPersistedLanguage() + if (!persistedLanguage) return + if (i18n.resolvedLanguage === persistedLanguage) return + + await i18n.changeLanguage(persistedLanguage) +} + +export const getI18nLanguages = (instance) => { + const supportedLngs = instance.options?.supportedLngs?.filter((lng) => lng && lng !== 'cimode') + if (supportedLngs?.length) return supportedLngs + + const loadedLngs = Object.keys(instance.store?.data || {}) + if (loadedLngs.length) return loadedLngs + + if (instance.resolvedLanguage) return [instance.resolvedLanguage] + + if (Array.isArray(instance.options?.fallbackLng)) return instance.options.fallbackLng + if (typeof instance.options?.fallbackLng === 'string') return [instance.options.fallbackLng] + + return ['en'] +} + +export const setPersistedLanguage = (language) => { + if (!language) return + localStorage.setItem('i18nextLng', language) +} diff --git a/packages/ui/src/utils/validation.js b/packages/ui/src/utils/validation.js index a443d446dcf..c823f483bd3 100644 --- a/packages/ui/src/utils/validation.js +++ b/packages/ui/src/utils/validation.js @@ -1,13 +1,14 @@ import { z } from 'zod/v3' -export const passwordSchema = z - .string() - .min(8, 'Password must be at least 8 characters') - .max(128, 'Password must not be more than 128 characters') - .regex(/[a-z]/, 'Password must contain at least one lowercase letter') - .regex(/[A-Z]/, 'Password must contain at least one uppercase letter') - .regex(/\d/, 'Password must contain at least one digit') - .regex(/[^a-zA-Z0-9]/, 'Password must contain at least one special character') +export const passwordSchema = (t) => + z + .string() + .min(8, t('common.validation.password.atLeast8')) + .max(128, t('common.validation.password.notMoreThan128')) + .regex(/[a-z]/, t('common.validation.password.lowercase')) + .regex(/[A-Z]/, t('common.validation.password.uppercase')) + .regex(/\d/, t('common.validation.password.digit')) + .regex(/[^a-zA-Z0-9]/, t('common.validation.password.special')) export const validatePassword = (password) => { const result = passwordSchema.safeParse(password) diff --git a/packages/ui/src/views/account/index.jsx b/packages/ui/src/views/account/index.jsx index ae5bc501d18..c1867e446f3 100644 --- a/packages/ui/src/views/account/index.jsx +++ b/packages/ui/src/views/account/index.jsx @@ -49,6 +49,9 @@ import { gridSpacing } from '@/store/constant' import { useConfig } from '@/store/context/ConfigContext' import { logoutSuccess, userProfileUpdated } from '@/store/reducers/authSlice' +// i18n +import { useTranslation, Trans } from 'react-i18next' + // ==============================|| ACCOUNT SETTINGS ||============================== // const calculatePercentage = (count, total) => { @@ -56,6 +59,7 @@ const calculatePercentage = (count, total) => { } const AccountSettings = () => { + const { t } = useTranslation() const theme = useTheme() const dispatch = useDispatch() useNotifier() @@ -207,7 +211,7 @@ const AccountSettings = () => { } } catch (error) { enqueueSnackbar({ - message: 'Failed to access billing portal', + message: t('profile.messages.errors.failedAccessPortal'), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -236,9 +240,12 @@ const AccountSettings = () => { store.dispatch(userProfileUpdated(payload.user)) const pendingMsg = payload.emailChangePending && - `Check your current email (${payload.user.email}) to confirm the change to ${payload.pendingEmail}.` + t('profile.messages.profile.success.check', { + email: payload.user.email, + pendingEmail: payload.pendingEmail + }) enqueueSnackbar({ - message: pendingMsg || 'Profile updated', + message: pendingMsg || t('profile.messages.profile.success.simple'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -255,7 +262,7 @@ const AccountSettings = () => { } else if (payload) { store.dispatch(userProfileUpdated(payload)) enqueueSnackbar({ - message: 'Profile updated', + message: t('profile.messages.profile.success.simple'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -272,9 +279,9 @@ const AccountSettings = () => { } } catch (error) { enqueueSnackbar({ - message: `Failed to update profile: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('profile.messages.profile.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -293,10 +300,10 @@ const AccountSettings = () => { try { const validationErrors = [] if (!oldPassword) { - validationErrors.push('Old Password cannot be left blank') + validationErrors.push(t('profile.messages.password.errors.oldPassword')) } if (newPassword !== confirmPassword) { - validationErrors.push('New Password and Confirm Password do not match') + validationErrors.push(t('profile.messages.password.errors.passwordsNotMatch')) } const passwordErrors = validatePassword(newPassword) if (passwordErrors.length > 0) { @@ -335,7 +342,7 @@ const AccountSettings = () => { setConfirmPassword('') await logoutApi.request() enqueueSnackbar({ - message: 'Password updated', + message: t('profile.messages.password.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -349,9 +356,9 @@ const AccountSettings = () => { } } catch (error) { enqueueSnackbar({ - message: `Failed to update password: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('profile.messages.password.errors.failedUpdate', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -380,7 +387,7 @@ const AccountSettings = () => { prorationInfo.prorationDate ) enqueueSnackbar({ - message: 'Seats updated successfully', + message: t('profile.messages.seats.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -396,9 +403,9 @@ const AccountSettings = () => { } catch (error) { console.error('Error updating seats:', error) enqueueSnackbar({ - message: `Failed to update seats: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('profile.messages.seats.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -447,10 +454,12 @@ const AccountSettings = () => { // Calculate empty seats const emptySeats = Math.min(purchasedSeats, totalSeats - occupiedSeats) + const permanentlyDeleteText = t('profile.deleteAccount.permanentlyDelete') + return ( - + {isLoading && !getUserByIdApi.data ? ( @@ -471,7 +480,7 @@ const AccountSettings = () => { <> {isCloud && ( <> - + { > {currentPlanTitle && ( - Current Organization Plan: + {t('profile.subscription.currentPlan')} {currentPlanTitle.toUpperCase()} @@ -504,7 +513,7 @@ const AccountSettings = () => { variant='body2' color='text.secondary' > - Update your billing details and subscription + {t('profile.subscription.updatePlan')} { {isBillingLoading ? ( - Loading + {t('profile.subscription.loading')} ) : ( - 'Billing' + t('profile.subscription.actions.billing') )} - + { }} > - Seats Included in Plan: + {t('profile.seats.includedInPlan')} {getAdditionalSeatsQuantityApi.loading ? : includedSeats} - Additional Seats Purchased: + {t('profile.seats.additionalPurchased')} {getAdditionalSeatsQuantityApi.loading ? ( @@ -601,7 +610,7 @@ const AccountSettings = () => { - Occupied Seats: + {t('profile.seats.occupiedSeats')} {getAdditionalSeatsQuantityApi.loading ? ( @@ -635,7 +644,7 @@ const AccountSettings = () => { color='error' sx={{ borderRadius: 2, height: 40 }} > - Remove Seats + {t('profile.actions.removeSeats')} )} { setOpenPricingDialog(true) } }} - title='Add Seats is available only for PRO plan' + title={t('profile.actions.addSeats.tooltip')} sx={{ borderRadius: 2, height: 40 }} > - Add Seats + {t('profile.actions.addSeats.title')} - + { > - Predictions + {t('profile.usage.predictions')} {`${usage?.predictions?.usage || 0} / ${usage?.predictions?.limit || 0}`} @@ -697,11 +706,12 @@ const AccountSettings = () => { - Storage + {t('profile.usage.storage.title')} - {`${(usage?.storage?.usage || 0).toFixed(2)}MB / ${(usage?.storage?.limit || 0).toFixed( - 2 - )}MB`} + {t('profile.usage.storage.current', { + usage: (usage?.storage?.usage || 0).toFixed(2), + limit: (usage?.storage?.limit || 0).toFixed(2) + })} @@ -735,10 +745,10 @@ const AccountSettings = () => { - Save + {t('common.actions.save')} } - title='Profile' + title={t('profile.actions.save.tooltips.profile')} > { }} > - Name + {t('common.labels.name')} setProfileName(e.target.value)} value={profileName} /> - Email Address + {t('common.labels.emailAddress')} setEmail(e.target.value)} value={email} @@ -784,10 +794,10 @@ const AccountSettings = () => { sx={{ borderRadius: 2, height: 40 }} variant='contained' > - Save + {t('common.actions.save')} } - title='Security' + title={t('profile.actions.save.tooltips.security')} > { gap: 1 }} > - Old Password + {t('profile.inputs.oldPassword')} setOldPassword(e.target.value)} value={oldPassword} @@ -825,21 +835,18 @@ const AccountSettings = () => { gap: 1 }} > - New Password + {t('profile.inputs.newPassword.title')} setNewPassword(e.target.value)} value={newPassword} /> - - Password must be at least 8 characters long and contain at least one lowercase letter, one - uppercase letter, one digit, and one special character. - + {t('profile.inputs.newPassword.caption')} { gap: 1 }} > - Confirm New Password + {t('profile.inputs.confirmNewPassword')} setConfirmPassword(e.target.value)} value={confirmPassword} @@ -866,7 +873,7 @@ const AccountSettings = () => { )} {isCloud && ( <> - + { }} > - Permanently deletes all your data and cancels your subscription. This action cannot be - undone. + {t('profile.deleteAccount.description')} { {deleteAccountApi.loading ? ( - Deleting... + {t('profile.deleteAccount.loading')} ) : ( - 'Delete your account' + t('profile.actions.delete') )} @@ -939,7 +945,7 @@ const AccountSettings = () => { )} {/* Remove Seats Dialog */} - Remove Additional Seats + {t('profile.dialog.removeSeats')} {emptySeats === 0 ? ( @@ -954,7 +960,7 @@ const AccountSettings = () => { }} > - You must remove users from your organization before removing seats. + {t('profile.dialog.removeUsersBefore')} ) : ( { > {/* Occupied Seats */} - Occupied Seats + {t('profile.dialog.occupiedSeats')} {occupiedSeats} {/* Empty Seats */} - Empty Seats + {t('profile.dialog.emptySeats')} {emptySeats} - Number of Empty Seats to Remove + {t('profile.dialog.numberToRemove')} { borderTop: `1px solid ${theme.palette.divider}` }} > - New Total Seats + {t('profile.dialog.newTotalSeats')} {totalSeats - seatsQuantity} @@ -1032,7 +1038,7 @@ const AccountSettings = () => { ) : getCustomerDefaultSourceApi.data?.invoice_settings?.default_payment_method ? ( - Payment Method + {t('profile.dialog.paymentMethod')} {getCustomerDefaultSourceApi.data.invoice_settings.default_payment_method.card && ( <> @@ -1046,14 +1052,12 @@ const AccountSettings = () => { {getCustomerDefaultSourceApi.data.invoice_settings.default_payment_method.card.last4} - (expires{' '} - { - getCustomerDefaultSourceApi.data.invoice_settings.default_payment_method.card - .exp_month - } - / - {getCustomerDefaultSourceApi.data.invoice_settings.default_payment_method.card.exp_year} - ) + {t('profile.dialog.expires', { + month: getCustomerDefaultSourceApi.data.invoice_settings.default_payment_method.card + .exp_month, + year: getCustomerDefaultSourceApi.data.invoice_settings.default_payment_method.card + .exp_year + })} @@ -1064,7 +1068,7 @@ const AccountSettings = () => { - No payment method found + {t('profile.dialog.noPaymentMethod')} )} @@ -1093,12 +1097,12 @@ const AccountSettings = () => { > {/* Date Range */} - {new Date(prorationInfo.currentPeriodStart * 1000).toLocaleDateString('en-US', { + {new Date(prorationInfo.currentPeriodStart * 1000).toLocaleDateString(t('common.locale'), { month: 'short', day: 'numeric' })}{' '} -{' '} - {new Date(prorationInfo.currentPeriodEnd * 1000).toLocaleDateString('en-US', { + {new Date(prorationInfo.currentPeriodEnd * 1000).toLocaleDateString(t('common.locale'), { month: 'short', day: 'numeric', year: 'numeric' @@ -1128,9 +1132,11 @@ const AccountSettings = () => { }} > - Additional Seats Left (Prorated) + {t('profile.dialog.additionalSeats.title.left')} - Qty {purchasedSeats - seatsQuantity} + {t('profile.dialog.additionalSeats.qty', { + value: purchasedSeats - seatsQuantity + })} @@ -1138,7 +1144,10 @@ const AccountSettings = () => { {prorationInfo.currency} {Math.max(0, prorationInfo.additionalSeatsProratedAmount).toFixed(2)} - {prorationInfo.currency} {prorationInfo.seatPerUnitPrice.toFixed(2)} each + {t('profile.dialog.additionalSeats.each', { + currency: prorationInfo.currency, + seatPerUnitPrice: prorationInfo.seatPerUnitPrice.toFixed(2) + })} @@ -1151,7 +1160,7 @@ const AccountSettings = () => { alignItems: 'center' }} > - Credit balance + {t('profile.dialog.creditBalance')} { borderTop: `1px solid ${theme.palette.divider}` }} > - Due today + {t('profile.dialog.nextPayment.due')} {prorationInfo.currency} {Math.max(0, prorationInfo.prorationAmount).toFixed(2)} @@ -1186,7 +1195,7 @@ const AccountSettings = () => { fontStyle: 'italic' }} > - Your available credit will automatically apply to your next invoice. + {t('profile.dialog.nextPayment.availableCredit')} )} @@ -1196,7 +1205,7 @@ const AccountSettings = () => { {getCustomerDefaultSourceApi.data?.invoice_settings?.default_payment_method && ( @@ -1225,7 +1234,7 @@ const AccountSettings = () => { {/* Add Seats Dialog */} - Add Additional Seats + {t('profile.dialog.addSeats')} { > {/* Occupied Seats */} - Occupied Seats + {t('profile.dialog.occupiedSeats')} {occupiedSeats} {/* Included Seats */} - Seats Included with Plan + {t('profile.dialog.includedInPlan')} {includedSeats} {/* Additional Seats */} - Additional Seats Purchased + {t('profile.dialog.additionalPurchased')} {purchasedSeats} - Number of Additional Seats to Add + {t('profile.dialog.numberToAdd')} { borderTop: `1px solid ${theme.palette.divider}` }} > - New Total Seats + {t('profile.dialog.newTotalSeats')} {totalSeats + seatsQuantity} @@ -1306,7 +1315,7 @@ const AccountSettings = () => { ) : getCustomerDefaultSourceApi.data?.invoice_settings?.default_payment_method ? ( - Payment Method + {t('profile.dialog.paymentMethod')} {getCustomerDefaultSourceApi.data.invoice_settings.default_payment_method.card && ( <> @@ -1320,14 +1329,12 @@ const AccountSettings = () => { {getCustomerDefaultSourceApi.data.invoice_settings.default_payment_method.card.last4} - (expires{' '} - { - getCustomerDefaultSourceApi.data.invoice_settings.default_payment_method.card - .exp_month - } - / - {getCustomerDefaultSourceApi.data.invoice_settings.default_payment_method.card.exp_year} - ) + {t('profile.dialog.expires', { + month: getCustomerDefaultSourceApi.data.invoice_settings.default_payment_method.card + .exp_month, + year: getCustomerDefaultSourceApi.data.invoice_settings.default_payment_method.card + .exp_year + })} @@ -1338,7 +1345,7 @@ const AccountSettings = () => { - No payment method found + {t('profile.dialog.noPaymentMethod')} )} @@ -1367,12 +1374,12 @@ const AccountSettings = () => { > {/* Date Range */} - {new Date(prorationInfo.currentPeriodStart * 1000).toLocaleDateString('en-US', { + {new Date(prorationInfo.currentPeriodStart * 1000).toLocaleDateString(t('common.locale'), { month: 'short', day: 'numeric' })}{' '} -{' '} - {new Date(prorationInfo.currentPeriodEnd * 1000).toLocaleDateString('en-US', { + {new Date(prorationInfo.currentPeriodEnd * 1000).toLocaleDateString(t('common.locale'), { month: 'short', day: 'numeric', year: 'numeric' @@ -1390,9 +1397,11 @@ const AccountSettings = () => { {/* Additional Seats */} - Additional Seats (Prorated) + {t('profile.dialog.additionalSeats.title.current')} - Qty {seatsQuantity + purchasedSeats} + {t('profile.dialog.additionalSeats.qty', { + value: seatsQuantity + purchasedSeats + })} @@ -1400,7 +1409,10 @@ const AccountSettings = () => { {prorationInfo.currency} {prorationInfo.additionalSeatsProratedAmount.toFixed(2)} - {prorationInfo.currency} {prorationInfo.seatPerUnitPrice.toFixed(2)} each + {t('profile.dialog.additionalSeats.each', { + currency: prorationInfo.currency, + seatPerUnitPrice: prorationInfo.seatPerUnitPrice.toFixed(2) + })} @@ -1408,7 +1420,7 @@ const AccountSettings = () => { {/* Credit Balance */} {prorationInfo.creditBalance !== 0 && ( - Applied account balance + {t('profile.dialog.appliedAccountBalance')} {prorationInfo.currency} {prorationInfo.creditBalance.toFixed(2)} @@ -1425,7 +1437,7 @@ const AccountSettings = () => { borderTop: `1px solid ${theme.palette.divider}` }} > - Due today + {t('profile.dialog.nextPayment.due')} {prorationInfo.currency}{' '} {Math.max(0, prorationInfo.prorationAmount + prorationInfo.creditBalance).toFixed(2)} @@ -1440,7 +1452,7 @@ const AccountSettings = () => { fontStyle: 'italic' }} > - Your available credit will automatically apply to your next invoice. + {t('profile.dialog.nextPayment.availableCredit')} )} @@ -1450,7 +1462,7 @@ const AccountSettings = () => { {getCustomerDefaultSourceApi.data?.invoice_settings?.default_payment_method && ( @@ -1487,22 +1499,24 @@ const AccountSettings = () => { } }} > - Delete Account + {t('profile.deleteAccount.title')} - - This will permanently delete your account and all associated data. Your subscription will be cancelled - immediately and you will be logged out. This action cannot be undone and there is no way to recover your data. - + {t('profile.deleteAccount.warn')} - To confirm, please type permanently delete below: + + }} + /> setDeleteConfirmationText(e.target.value)} disabled={deleteAccountApi.loading} @@ -1518,15 +1532,16 @@ const AccountSettings = () => { }} disabled={deleteAccountApi.loading} > - Cancel + {t('common.actions.cancel')} + {/* Since the text of this value may vary, we also use its translated version */} diff --git a/packages/ui/src/views/agentexecutions/ExecutionDetails.jsx b/packages/ui/src/views/agentexecutions/ExecutionDetails.jsx index c89a1130f01..e41b47fccf2 100644 --- a/packages/ui/src/views/agentexecutions/ExecutionDetails.jsx +++ b/packages/ui/src/views/agentexecutions/ExecutionDetails.jsx @@ -49,6 +49,9 @@ import executionsApi from '@/api/executions' // Hooks import useApi from '@/hooks/useApi' +// i18n +import { useTranslation } from 'react-i18next' + const getIconColor = (status) => { switch (status) { case 'FINISHED': @@ -291,6 +294,7 @@ const DEFAULT_DRAWER_WIDTH = window.innerWidth - 400 const MAX_DRAWER_WIDTH = window.innerWidth export const ExecutionDetails = ({ open, isPublic, execution, metadata, onClose, onProceedSuccess, onUpdateSharing, onRefresh }) => { + const { t } = useTranslation() const [drawerWidth, setDrawerWidth] = useState(Math.min(DEFAULT_DRAWER_WIDTH, MAX_DRAWER_WIDTH)) const [executionTree, setExecution] = useState([]) const [expandedItems, setExpandedItems] = useState([]) @@ -318,7 +322,7 @@ export const ExecutionDetails = ({ open, isPublic, execution, metadata, onClose, // Show success message dispatch( enqueueSnackbarAction({ - message: 'ID copied to clipboard', + message: t('agentExecution.messages.copyToClipboard.id'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -433,7 +437,7 @@ export const ExecutionDetails = ({ open, isPublic, execution, metadata, onClose, // Create a virtual node for this iteration const iterationNodeId = `${parentId}_${iterationIndex}` - const iterationLabel = `Iteration #${iterationIndex}` + const iterationLabel = t('agentExecution.details.nodeIteration', { index: iterationIndex }) // Determine status based on child nodes const childNodes = nodeIds.map((id) => nodeMap.get(id)) @@ -642,7 +646,9 @@ export const ExecutionDetails = ({ open, isPublic, execution, metadata, onClose, // Show success message dispatch( enqueueSnackbarAction({ - message: newIsPublic ? 'Execution shared publicly' : 'Execution is no longer public', + message: t( + newIsPublic ? 'agentExecution.messages.sharePublicly.shared' : 'agentExecution.messages.sharePublicly.notShared' + ), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -744,7 +750,11 @@ export const ExecutionDetails = ({ open, isPublic, execution, metadata, onClose, sx={{ pl: 1 }} icon={} variant='outlined' - label={localMetadata?.agentflow?.name || localMetadata?.agentflow?.id || 'Go to AgentFlow'} + label={ + localMetadata?.agentflow?.name || + localMetadata?.agentflow?.id || + t('agentExecution.details.content.goToAgentFlow') + } className={'button'} onClick={() => window.open(`/v2/agentcanvas/${localMetadata?.agentflow?.id}`, '_blank')} /> @@ -752,7 +762,7 @@ export const ExecutionDetails = ({ open, isPublic, execution, metadata, onClose, {!isPublic && ( @@ -760,7 +770,7 @@ export const ExecutionDetails = ({ open, isPublic, execution, metadata, onClose, sx={{ ml: 1, pl: 1 }} icon={} variant='outlined' - label={copied ? 'Copied!' : 'Copy ID'} + label={t(copied ? 'common.messages.copied' : 'agentExecution.actions.copy.title.id')} className={'button'} onClick={copyToClipboard} /> @@ -778,7 +788,7 @@ export const ExecutionDetails = ({ open, isPublic, execution, metadata, onClose, ) } variant='outlined' - label={updateExecutionApi.loading ? 'Updating...' : 'Share'} + label={t(updateExecutionApi.loading ? 'agentExecution.details.content.updating' : 'common.actions.share')} className={'button'} onClick={() => onSharePublicly()} disabled={updateExecutionApi.loading} @@ -796,7 +806,11 @@ export const ExecutionDetails = ({ open, isPublic, execution, metadata, onClose, ) } variant='outlined' - label={updateExecutionApi.loading ? 'Updating...' : 'Public'} + label={t( + updateExecutionApi.loading + ? 'agentExecution.details.content.updating' + : 'agentExecution.details.content.public' + )} className={'button'} onClick={() => setShowShareDialog(true)} disabled={updateExecutionApi.loading} @@ -805,7 +819,9 @@ export const ExecutionDetails = ({ open, isPublic, execution, metadata, onClose, - {metadata?.updatedDate ? moment(metadata.updatedDate).format('MMM D, YYYY h:mm A') : 'N/A'} + {metadata?.updatedDate + ? moment(metadata.updatedDate).format(t('common.formats.dateMonthShortDayYearTime12')) + : t('agentExecution.details.content.notAvailable')} onRefresh(localMetadata?.id)} @@ -816,7 +832,7 @@ export const ExecutionDetails = ({ open, isPublic, execution, metadata, onClose, backgroundColor: (theme) => theme.palette.primary.main + '20' } }} - title='Refresh execution data' + title={t('agentExecution.details.actions.refresh')} > @@ -851,7 +867,7 @@ export const ExecutionDetails = ({ open, isPublic, execution, metadata, onClose, onProceedSuccess={onProceedSuccess} /> ) : ( - No data available for this item + {t('agentExecution.details.content.noDataAvailable')} )} @@ -860,7 +876,7 @@ export const ExecutionDetails = ({ open, isPublic, execution, metadata, onClose, // Resize handle component (shared between modes) const resizeHandle = ( !isLoading && setOpenFeedbackDialog(false)}> - Provide Feedback + {t('agentExecution.dialogs.provideFeedback.title')} diff --git a/packages/ui/src/views/agentexecutions/PublicExecutionDetails.jsx b/packages/ui/src/views/agentexecutions/PublicExecutionDetails.jsx index ed650ac29fc..4bc4519474a 100644 --- a/packages/ui/src/views/agentexecutions/PublicExecutionDetails.jsx +++ b/packages/ui/src/views/agentexecutions/PublicExecutionDetails.jsx @@ -14,9 +14,13 @@ import { Box, Card, Stack, Typography, useTheme, CircularProgress } from '@mui/m import { IconCircleXFilled } from '@tabler/icons-react' import { alpha } from '@mui/material/styles' +// i18n +import { useTranslation } from 'react-i18next' + // ==============================|| PublicExecutionDetails ||============================== // const PublicExecutionDetails = () => { + const { t } = useTranslation() const { id: executionId } = useParams() const theme = useTheme() @@ -77,10 +81,10 @@ const PublicExecutionDetails = () => { - Invalid Execution + {t('agentExecution.invalidExecution.title')} - {`The execution you're looking for doesn't exist or you don't have permission to view it.`} + {t('agentExecution.invalidExecution.caption')} diff --git a/packages/ui/src/views/agentexecutions/ShareExecutionDialog.jsx b/packages/ui/src/views/agentexecutions/ShareExecutionDialog.jsx index b35635f7c28..dc5bde7b915 100644 --- a/packages/ui/src/views/agentexecutions/ShareExecutionDialog.jsx +++ b/packages/ui/src/views/agentexecutions/ShareExecutionDialog.jsx @@ -15,7 +15,11 @@ import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackba import executionsApi from '@/api/executions' import useApi from '@/hooks/useApi' +// i18n +import { useTranslation } from 'react-i18next' + const ShareExecutionDialog = ({ show, executionId, onClose, onUnshare }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const theme = useTheme() const dispatch = useDispatch() @@ -35,7 +39,7 @@ const ShareExecutionDialog = ({ show, executionId, onClose, onUnshare }) => { // Show success message dispatch( enqueueSnackbarAction({ - message: 'Link copied to clipboard', + message: t('agentExecution.messages.copyToClipboard.link'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -63,11 +67,11 @@ const ShareExecutionDialog = ({ show, executionId, onClose, onUnshare }) => { const component = show ? ( - Public Trace Link + {t('agentExecution.publicTraceLink.title')} - Anyone with the link below can view this execution trace. + {t('agentExecution.publicTraceLink.description')} {/* Link Display Box */} @@ -95,9 +99,9 @@ const ShareExecutionDialog = ({ show, executionId, onClose, onUnshare }) => { > {shareableLink} - + @@ -105,9 +109,9 @@ const ShareExecutionDialog = ({ show, executionId, onClose, onUnshare }) => { {/* Actions */} - + diff --git a/packages/ui/src/views/agentexecutions/index.jsx b/packages/ui/src/views/agentexecutions/index.jsx index 2a6e366509b..4f6a52b3a28 100644 --- a/packages/ui/src/views/agentexecutions/index.jsx +++ b/packages/ui/src/views/agentexecutions/index.jsx @@ -44,9 +44,13 @@ import { ExecutionsListTable } from '@/ui-component/table/ExecutionsListTable' import { omit } from 'lodash' import { ExecutionDetails } from './ExecutionDetails' +// i18n +import { useTranslation } from 'react-i18next' + // ==============================|| AGENT EXECUTIONS ||============================== // const AgentExecutions = () => { + const { t } = useTranslation() const theme = useTheme() const customization = useSelector((state) => state.customization) const borderColor = theme.palette.grey[900] + 25 @@ -236,18 +240,18 @@ const AgentExecutions = () => { ) : ( - + {/* Filter Section */} - State + {t('common.labels.state')} @@ -281,7 +285,7 @@ const AgentExecutions = () => { customInput={ { customInput={ { handleFilterChange('agentflowName', e.target.value)} size='small' @@ -333,7 +337,7 @@ const AgentExecutions = () => { handleFilterChange('sessionId', e.target.value)} size='small' @@ -352,13 +356,13 @@ const AgentExecutions = () => { onClick={() => applyFilters(currentPage, pageLimit)} size='small' > - Apply + {t('common.actions.apply')} - + { aria-labelledby='alert-dialog-title' aria-describedby='alert-dialog-description' > - Confirm Deletion + {t('agentExecution.dialogs.confirmDeletion.title')} - Are you sure you want to delete {selectedExecutionIds.length} execution - {selectedExecutionIds.length !== 1 ? 's' : ''}? This action cannot be undone. + {t('agentExecution.dialogs.confirmDeletion.description', { count: selectedExecutionIds.length })} @@ -453,7 +456,7 @@ const AgentExecutions = () => { alt='execution_empty' /> -
No Executions Yet
+
{t('agentExecution.dialogs.confirmDeletion.executionEmpty')}
)}
diff --git a/packages/ui/src/views/agentflows/index.jsx b/packages/ui/src/views/agentflows/index.jsx index 9ee745c16fc..b53cde4351c 100644 --- a/packages/ui/src/views/agentflows/index.jsx +++ b/packages/ui/src/views/agentflows/index.jsx @@ -31,9 +31,13 @@ import { useError } from '@/store/context/ErrorContext' // icons import { IconAlertTriangle, IconLayoutGrid, IconList, IconPlus, IconX } from '@tabler/icons-react' +// i18n +import { useTranslation, Trans } from 'react-i18next' + // ==============================|| AGENTS ||============================== // const Agentflows = () => { + const { t } = useTranslation() const navigate = useNavigate() const theme = useTheme() const customization = useSelector((state) => state.customization) @@ -178,9 +182,9 @@ const Agentflows = () => { { }} variant='contained' value='v2' - title='V2' + title={t('agentFlows.v1.toggle.v2')} > - - V2 + + {t('agentFlows.v1.toggle.v2')} { }} variant='contained' value='v1' - title='V1' + title={t('agentFlows.v1.toggle.v1')} > - V1 + {t('agentFlows.v1.toggle.v1')} { }} variant='contained' value='card' - title='Card View' + title={t('common.actions.cardView')} > @@ -243,7 +247,7 @@ const Agentflows = () => { }} variant='contained' value='list' - title='List View' + title={t('common.actions.listView')} > @@ -255,7 +259,7 @@ const Agentflows = () => { startIcon={} sx={{ borderRadius: 2, height: 40 }} > - Add New + {t('common.actions.addNew')} @@ -283,11 +287,10 @@ const Agentflows = () => { }} /> - V1 Agentflows are deprecated. We recommend migrating to V2 for improved performance and - continued support. + }} /> { alt='AgentsEmptySVG' /> -
No Agents Yet
+
{t('agentFlows.v1.empty')}
)} diff --git a/packages/ui/src/views/agentflowsv2/AgentFlowNode.jsx b/packages/ui/src/views/agentflowsv2/AgentFlowNode.jsx index 3205612eefc..11a5c635a65 100644 --- a/packages/ui/src/views/agentflowsv2/AgentFlowNode.jsx +++ b/packages/ui/src/views/agentflowsv2/AgentFlowNode.jsx @@ -34,6 +34,9 @@ import CancelIcon from '@mui/icons-material/Cancel' // const import { baseURL, AGENTFLOW_ICONS } from '@/store/constant' +// i18n +import { useTranslation } from 'react-i18next' + const CardWrapper = styled(MainCard)(({ theme }) => ({ background: theme.palette.card.main, color: theme.darkTextPrimary, @@ -55,6 +58,7 @@ const StyledNodeToolbar = styled(NodeToolbar)(({ theme }) => ({ // ===========================|| CANVAS NODE ||=========================== // const AgentFlowNode = ({ data }) => { + const { t } = useTranslation() const theme = useTheme() const customization = useSelector((state) => state.customization) const canvas = useSelector((state) => state.canvas) @@ -179,8 +183,8 @@ const AgentFlowNode = ({ data }) => { useEffect(() => { const nodeOutdatedMessage = (oldVersion, newVersion) => - `Node version ${oldVersion} outdated\nUpdate to latest version ${newVersion}` - const nodeVersionEmptyMessage = (newVersion) => `Node outdated\nUpdate to latest version ${newVersion}` + t('agentFlows.v2.messages.nodeVersion.outdated', { oldVersion: oldVersion, newVersion: newVersion }) + const nodeVersionEmptyMessage = (newVersion) => t('agentFlows.v2.messages.nodeVersion.versionEmpty', { newVersion: newVersion }) const componentNode = canvas.componentNodes.find((nd) => nd.name === data.name) if (componentNode) { @@ -189,26 +193,23 @@ const AgentFlowNode = ({ data }) => { } else if (data.version && componentNode.version > data.version) { setWarningMessage(nodeOutdatedMessage(data.version, componentNode.version)) } else if (componentNode.badge === 'DEPRECATING') { - setWarningMessage( - componentNode?.deprecateMessage ?? - 'This node will be deprecated in the next release. Change to a new node tagged with NEW' - ) + setWarningMessage(componentNode?.deprecateMessage ?? t('agentFlows.v2.messages.nodeVersion.deprecated')) } else if (componentNode.warning) { setWarningMessage(componentNode.warning) } else { setWarningMessage('') } } - }, [canvas.componentNodes, data.name, data.version]) + }, [canvas.componentNodes, data.name, data.version, t]) return (
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}> - + {data.name !== 'startAgentflow' && ( { duplicateNode(data.id) }} @@ -224,7 +225,7 @@ const AgentFlowNode = ({ data }) => { )} { deleteNode(data.id) }} @@ -239,7 +240,7 @@ const AgentFlowNode = ({ data }) => { { setInfoDialogProps({ data }) setShowInfoDialog(true) @@ -273,7 +274,7 @@ const AgentFlowNode = ({ data }) => { border={false} > {data && data.status && ( - + { + const { t } = useTranslation() const theme = useTheme() const navigate = useNavigate() const customization = useSelector((state) => state.customization) @@ -75,7 +79,7 @@ const AgentflowCanvas = () => { const URLpath = document.location.pathname.toString().split('/') const chatflowId = URLpath[URLpath.length - 1] === 'canvas' || URLpath[URLpath.length - 1] === 'agentcanvas' ? '' : URLpath[URLpath.length - 1] - const canvasTitle = URLpath.includes('agentcanvas') ? 'Agent' : 'Chatflow' + const canvasTitle = t(URLpath.includes('agentcanvas') ? 'agentFlows.v2.agent' : 'common.labels.chatflow') const { confirm } = useConfirm() @@ -172,10 +176,10 @@ const AgentflowCanvas = () => { const handleDeleteFlow = async () => { const confirmPayload = { - title: `Delete`, - description: `Delete ${canvasTitle} ${chatflow.name}?`, - confirmButtonName: 'Delete', - cancelButtonName: 'Cancel' + title: t('common.dialogs.delete'), + description: t('agentFlows.v2.dialogs.delete.description', { canvasTitle: canvasTitle, name: chatflow.name }), + confirmButtonName: t('common.actions.delete'), + cancelButtonName: t('common.actions.cancel') } const isConfirmed = await confirm(confirmPayload) @@ -305,7 +309,7 @@ const AgentflowCanvas = () => { if (nodeData.name === 'startAgentflow' && nodes.find((node) => node.data.name === 'startAgentflow')) { enqueueSnackbar({ - message: 'Only one start node is allowed', + message: t('agentFlows.v2.messages.dragAndDrop.errors.onlyStart'), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -359,7 +363,7 @@ const AgentflowCanvas = () => { // We can't have nested iteration nodes if (nodeData.name === 'iterationAgentflow') { enqueueSnackbar({ - message: 'Nested iteration node is not supported yet', + message: t('agentFlows.v2.messages.dragAndDrop.errors.nestedNotSupport'), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -377,7 +381,7 @@ const AgentflowCanvas = () => { // We can't have human input node inside iteration node if (nodeData.name === 'humanInputAgentflow') { enqueueSnackbar({ - message: 'Human input node is not supported inside Iteration node', + message: t('agentFlows.v2.messages.dragAndDrop.errors.humanNotSupport'), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -473,7 +477,7 @@ const AgentflowCanvas = () => { const saveChatflowSuccess = () => { dispatch({ type: REMOVE_DIRTY }) enqueueSnackbar({ - message: `${canvasTitle} saved`, + message: t('agentFlows.v2.messages.save.success', { canvasTitle: canvasTitle }), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -532,11 +536,16 @@ const AgentflowCanvas = () => { setEdges(initialFlow.edges || []) dispatch({ type: SET_CHATFLOW, chatflow }) } else if (getSpecificChatflowApi.error) { - errorFailed(`Failed to retrieve ${canvasTitle}: ${getSpecificChatflowApi.error.response.data.message}`) + errorFailed( + t('agentFlows.v2.messages.errors.failedRetrieve', { + canvasTitle: canvasTitle, + msg: getSpecificChatflowApi.error.response.data.message + }) + ) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [getSpecificChatflowApi.data, getSpecificChatflowApi.error]) + }, [getSpecificChatflowApi.data, getSpecificChatflowApi.error, t]) // Create new chatflow successful useEffect(() => { @@ -546,11 +555,16 @@ const AgentflowCanvas = () => { saveChatflowSuccess() window.history.replaceState(state, null, `/v2/agentcanvas/${chatflow.id}`) } else if (createNewChatflowApi.error) { - errorFailed(`Failed to save ${canvasTitle}: ${createNewChatflowApi.error.response.data.message}`) + errorFailed( + t('agentFlows.v2.messages.save.error', { + canvasTitle: canvasTitle, + msg: getSpecificChatflowApi.error.response.data.message + }) + ) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [createNewChatflowApi.data, createNewChatflowApi.error]) + }, [createNewChatflowApi.data, createNewChatflowApi.error, t]) // Update chatflow successful useEffect(() => { @@ -558,11 +572,16 @@ const AgentflowCanvas = () => { dispatch({ type: SET_CHATFLOW, chatflow: updateChatflowApi.data }) saveChatflowSuccess() } else if (updateChatflowApi.error) { - errorFailed(`Failed to save ${canvasTitle}: ${updateChatflowApi.error.response.data.message}`) + errorFailed( + t('agentFlows.v2.messages.save.error', { + canvasTitle: canvasTitle, + msg: getSpecificChatflowApi.error.response.data.message + }) + ) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [updateChatflowApi.data, updateChatflowApi.error]) + }, [updateChatflowApi.data, updateChatflowApi.error, t]) useEffect(() => { setChatflow(canvasDataStore.chatflow) @@ -590,7 +609,7 @@ const AgentflowCanvas = () => { dispatch({ type: SET_CHATFLOW, chatflow: { - name: `Untitled ${canvasTitle}` + name: t('agentFlows.v2.untitled') } }) } @@ -635,7 +654,7 @@ const AgentflowCanvas = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [templateFlowData]) - usePrompt('You have unsaved changes! Do you want to navigate away?', canvasDataStore.isDirty) + usePrompt(t('agentFlows.v2.unsavedWarn'), canvasDataStore.isDirty) const [chatPopupOpen, setChatPopupOpen] = useState(false) @@ -738,8 +757,8 @@ const AgentflowCanvas = () => { onClick={() => { setIsSnappingEnabled(!isSnappingEnabled) }} - title='toggle snapping' - aria-label='toggle snapping' + title={t('agentFlows.v2.actions.toggleSnapping')} + aria-label={t('agentFlows.v2.actions.toggleSnapping')} > {isSnappingEnabled ? : } @@ -748,8 +767,8 @@ const AgentflowCanvas = () => { onClick={() => { setIsBackgroundEnabled(!isBackgroundEnabled) }} - title='toggle background' - aria-label='toggle background' + title={t('agentFlows.v2.actions.toggleBackground')} + aria-label={t('agentFlows.v2.actions.toggleBackground')} > {isBackgroundEnabled ? : } @@ -789,8 +808,8 @@ const AgentflowCanvas = () => { } }} size='small' - aria-label='sync' - title='Sync Nodes' + aria-label={t('agentFlows.v2.sync')} + title={t('agentFlows.v2.syncNodes')} onClick={() => syncNodes()} > diff --git a/packages/ui/src/views/agentflowsv2/ConfigInput.jsx b/packages/ui/src/views/agentflowsv2/ConfigInput.jsx index 6ce7aac9195..79c038fe22b 100644 --- a/packages/ui/src/views/agentflowsv2/ConfigInput.jsx +++ b/packages/ui/src/views/agentflowsv2/ConfigInput.jsx @@ -19,7 +19,11 @@ import { initNode, showHideInputParams, initializeDefaultNodeData } from '@/util import { flowContext } from '@/store/context/ReactFlowContext' import { FLOWISE_CREDENTIAL_ID } from '@/store/constant' +// i18n +import { useTranslation } from 'react-i18next' + export const ConfigInput = ({ data, inputParam, disabled = false, arrayIndex = null, parentParamForArray = null }) => { + const { t } = useTranslation() const theme = useTheme() const { reactFlowInstance } = useContext(flowContext) @@ -294,7 +298,9 @@ export const ConfigInput = ({ data, inputParam, disabled = false, arrayIndex = n } sx={{ background: 'transparent' }}>
- {selectedComponentNodeData?.label} Parameters + + {t('agentFlows.v2.parameters', { label: selectedComponentNodeData?.label })} +
{selectedComponentNodeData?.warning && ( { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const dispatch = useDispatch() const theme = useTheme() @@ -125,7 +129,7 @@ const EditNodeDialog = ({ show, dialogProps, onCancel }) => { {data?.id && ( - + { } }} /> - + { - + ({ background: theme.palette.card.main, color: theme.darkTextPrimary, @@ -49,6 +52,7 @@ const StyledNodeToolbar = styled(NodeToolbar)(({ theme }) => ({ // ===========================|| ITERATION NODE ||=========================== // const IterationNode = ({ data }) => { + const { t } = useTranslation() const theme = useTheme() const customization = useSelector((state) => state.customization) const ref = useRef(null) @@ -214,10 +218,10 @@ const IterationNode = ({ data }) => { - + { duplicateNode(data.id) }} @@ -232,7 +236,7 @@ const IterationNode = ({ data }) => { { deleteNode(data.id) }} @@ -247,7 +251,7 @@ const IterationNode = ({ data }) => { { setInfoDialogProps({ data }) setShowInfoDialog(true) @@ -283,7 +287,7 @@ const IterationNode = ({ data }) => { border={false} > {data && data.status && ( - + { + const { t } = useTranslation() const theme = useTheme() const navigate = useNavigate() const customization = useSelector((state) => state.customization) @@ -132,8 +136,8 @@ const MarketplaceCanvasV2 = () => { onClick={() => { setIsSnappingEnabled(!isSnappingEnabled) }} - title='toggle snapping' - aria-label='toggle snapping' + title={t('agentFlows.v2.actions.toggleSnapping')} + aria-label={t('agentFlows.v2.actions.toggleSnapping')} > {isSnappingEnabled ? : } @@ -142,8 +146,8 @@ const MarketplaceCanvasV2 = () => { onClick={() => { setIsBackgroundEnabled(!isBackgroundEnabled) }} - title='toggle background' - aria-label='toggle background' + title={t('agentFlows.v2.actions.toggleBackground')} + aria-label={t('agentFlows.v2.actions.toggleBackground')} > {isBackgroundEnabled ? : } diff --git a/packages/ui/src/views/agentflowsv2/StickyNote.jsx b/packages/ui/src/views/agentflowsv2/StickyNote.jsx index c154adfa608..f5568c11e05 100644 --- a/packages/ui/src/views/agentflowsv2/StickyNote.jsx +++ b/packages/ui/src/views/agentflowsv2/StickyNote.jsx @@ -15,6 +15,9 @@ import MainCard from '@/ui-component/cards/MainCard' // const import { flowContext } from '@/store/context/ReactFlowContext' +// i18n +import { useTranslation } from 'react-i18next' + const CardWrapper = styled(MainCard)(({ theme }) => ({ background: theme.palette.card.main, color: theme.darkTextPrimary, @@ -34,6 +37,7 @@ const StyledNodeToolbar = styled(NodeToolbar)(({ theme }) => ({ })) const StickyNote = ({ data }) => { + const { t } = useTranslation() const theme = useTheme() const customization = useSelector((state) => state.customization) const ref = useRef(null) @@ -62,10 +66,10 @@ const StickyNote = ({ data }) => { return (
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}> - + { duplicateNode(data.id) }} @@ -80,7 +84,7 @@ const StickyNote = ({ data }) => { { deleteNode(data.id) }} diff --git a/packages/ui/src/views/apikey/APIKeyDialog.jsx b/packages/ui/src/views/apikey/APIKeyDialog.jsx index 438ed91b645..ba69fcfed3b 100644 --- a/packages/ui/src/views/apikey/APIKeyDialog.jsx +++ b/packages/ui/src/views/apikey/APIKeyDialog.jsx @@ -40,7 +40,11 @@ import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions' import './APIKeyDialog.css' +// i18n +import { useTranslation } from 'react-i18next' + const APIKeyDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const theme = useTheme() @@ -225,7 +229,7 @@ const APIKeyDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) => { }) if (createResp.data) { enqueueSnackbar({ - message: 'New API key added', + message: t('apiKey.messages.add.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -241,9 +245,9 @@ const APIKeyDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) => { } catch (error) { if (setError) setError(error) enqueueSnackbar({ - message: `Failed to add new API key: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('apiKey.messages.add.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -278,7 +282,7 @@ const APIKeyDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) => { }) if (saveResp.data) { enqueueSnackbar({ - message: 'API Key saved', + message: t('apiKey.messages.save.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -294,9 +298,9 @@ const APIKeyDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) => { } catch (error) { if (setError) setError(error) enqueueSnackbar({ - message: `Failed to save API key: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('apiKey.messages.save.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -354,7 +358,7 @@ const APIKeyDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) => { {dialogProps.type === 'EDIT' && ( - API Key + {t('apiKey.title')} { {dialogProps.key.apiKey} { navigator.clipboard.writeText(dialogProps.key.apiKey) @@ -395,7 +399,7 @@ const APIKeyDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) => { }} > - Copied! + {t('common.messages.copied')} @@ -405,14 +409,15 @@ const APIKeyDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) => {
- *  Key Name + *   + {t('apiKey.inputs.key.title')} setKeyName(e.target.value)} @@ -420,7 +425,8 @@ const APIKeyDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) => {

- *  Permissions + *   + {t('apiKey.inputs.permissions')}

{permissions && @@ -434,7 +440,7 @@ const APIKeyDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) => { .toUpperCase()}
@@ -463,7 +469,7 @@ const APIKeyDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) => { ({ @@ -73,6 +76,7 @@ const StyledTableRow = styled(TableRow)(() => ({ })) function APIKeyRow(props) { + const { t } = useTranslation() const [open, setOpen] = useState(false) const theme = useTheme() @@ -90,10 +94,10 @@ function APIKeyRow(props) { : `${props.apiKey.apiKey.substring(0, 2)}${'•'.repeat(18)}${props.apiKey.apiKey.substring( props.apiKey.apiKey.length - 5 )}`} - + - + {props.showApiKeys.includes(props.apiKey.apiKey) ? : } - Copied! + {t('common.messages.copied')} @@ -140,22 +144,22 @@ function APIKeyRow(props) { {props.apiKey.chatFlows.length}{' '} {props.apiKey.chatFlows.length > 0 && ( - setOpen(!open)}> + setOpen(!open)}> {props.apiKey.chatFlows.length > 0 && open ? : } )} - {moment(props.apiKey.createdAt).format('MMMM Do, YYYY')} + {moment(props.apiKey.createdAt).format(t('common.formats.dateMonthDayYear'))} - + - + @@ -166,19 +170,21 @@ function APIKeyRow(props) { -
+
- Chatflow Name - Modified On - Category + {t('apiKey.chatflowTable.name')} + {t('apiKey.chatflowTable.modifiedOn')} + {t('apiKey.chatflowTable.category')} {props.apiKey.chatFlows.map((flow, index) => ( {flow.flowName} - {moment(flow.updatedDate).format('MMMM Do, YYYY')} + + {moment(flow.updatedDate).format(t('common.formats.dateMonthDayYear'))} +   {flow.category && @@ -214,6 +220,7 @@ APIKeyRow.propTypes = { onDeleteClick: PropTypes.func } const APIKey = () => { + const { t } = useTranslation() const theme = useTheme() const customization = useSelector((state) => state.customization) @@ -283,10 +290,10 @@ const APIKey = () => { const addNew = () => { const dialogProp = { - title: 'Add New API Key', + title: t('apiKey.actions.addKey'), type: 'ADD', - cancelButtonName: 'Cancel', - confirmButtonName: 'Add', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.add'), customBtnId: 'btn_confirmAddingApiKey' } setDialogProps(dialogProp) @@ -295,10 +302,10 @@ const APIKey = () => { const edit = (key) => { const dialogProp = { - title: 'Edit API Key', + title: t('apiKey.actions.editKey'), type: 'EDIT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Save', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.save'), customBtnId: 'btn_confirmEditingApiKey', key } @@ -308,13 +315,13 @@ const APIKey = () => { const deleteKey = async (key) => { const confirmPayload = { - title: `Delete`, + title: t('common.dialogs.delete'), description: key.chatFlows.length === 0 - ? `Delete key [${key.keyName}] ? ` - : `Delete key [${key.keyName}] ?\n There are ${key.chatFlows.length} chatflows using this key.`, - confirmButtonName: 'Delete', - cancelButtonName: 'Cancel', + ? t('apiKey.dialogs.delete.description.simple', { name: key.keyName }) + : t('apiKey.dialogs.delete.description.inUse', { name: key.keyName, count: key.chatFlows.length }), + confirmButtonName: t('common.actions.delete'), + cancelButtonName: t('common.actions.cancel'), customBtnId: 'btn_initiateDeleteApiKey' } const isConfirmed = await confirm(confirmPayload) @@ -324,7 +331,7 @@ const APIKey = () => { const deleteResp = await apiKeyApi.deleteAPI(key.id) if (deleteResp.data) { enqueueSnackbar({ - message: 'API key deleted', + message: t('apiKey.messages.delete.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -339,9 +346,9 @@ const APIKey = () => { } } catch (error) { enqueueSnackbar({ - message: `Failed to delete API key: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('apiKey.messages.delete.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -389,9 +396,9 @@ const APIKey = () => { { startIcon={} id='btn_createApiKey' > - Create Key + {t('apiKey.actions.createKey')} {!isLoading && apiKeys?.length <= 0 ? ( @@ -413,7 +420,7 @@ const APIKey = () => { alt='APIEmptySVG' /> -
No API Keys Yet
+
{t('apiKey.notFound')}
) : ( <> @@ -421,7 +428,7 @@ const APIKey = () => { sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }} component={Paper} > -
+
{ }} > - Key Name - API Key - Permissions - Usage - Updated + {t('apiKey.keyTable.keyName')} + {t('apiKey.keyTable.apiKey')} + {t('apiKey.keyTable.permissions')} + {t('apiKey.keyTable.usage')} + {t('apiKey.keyTable.updated')} diff --git a/packages/ui/src/views/assistants/custom/AddCustomAssistantDialog.jsx b/packages/ui/src/views/assistants/custom/AddCustomAssistantDialog.jsx index 20c1b79f28f..0a9ec001b84 100644 --- a/packages/ui/src/views/assistants/custom/AddCustomAssistantDialog.jsx +++ b/packages/ui/src/views/assistants/custom/AddCustomAssistantDialog.jsx @@ -26,7 +26,11 @@ import assistantsApi from '@/api/assistants' // utils import useNotifier from '@/utils/useNotifier' +// i18n +import { useTranslation } from 'react-i18next' + const AddCustomAssistantDialog = ({ show, dialogProps, onCancel, onConfirm }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const dispatch = useDispatch() @@ -58,7 +62,7 @@ const AddCustomAssistantDialog = ({ show, dialogProps, onCancel, onConfirm }) => const createResp = await assistantsApi.createNewAssistant(obj) if (createResp.data) { enqueueSnackbar({ - message: 'New Custom Assistant created.', + message: t('assistants.cards.customAssistant.messages.create.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -73,9 +77,9 @@ const AddCustomAssistantDialog = ({ show, dialogProps, onCancel, onConfirm }) => } } catch (err) { enqueueSnackbar({ - message: `Failed to add new Custom Assistant: ${ - typeof err.response.data === 'object' ? err.response.data.message : err.response.data - }`, + message: t('assistants.cards.customAssistant.messages.create.error', { + msg: typeof err.response.data === 'object' ? err.response.data.message : err.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -110,7 +114,8 @@ const AddCustomAssistantDialog = ({ show, dialogProps, onCancel, onConfirm }) =>
- Name * + {t('common.labels.name')} +  *
@@ -127,7 +132,7 @@ const AddCustomAssistantDialog = ({ show, dialogProps, onCancel, onConfirm }) => - + createCustomAssistant()}> {dialogProps.confirmButtonName} diff --git a/packages/ui/src/views/assistants/custom/CustomAssistantConfigurePreview.jsx b/packages/ui/src/views/assistants/custom/CustomAssistantConfigurePreview.jsx index 057241529ac..fdbae382fef 100644 --- a/packages/ui/src/views/assistants/custom/CustomAssistantConfigurePreview.jsx +++ b/packages/ui/src/views/assistants/custom/CustomAssistantConfigurePreview.jsx @@ -58,6 +58,9 @@ import { initNode, showHideInputParams } from '@/utils/genericHelper' import useNotifier from '@/utils/useNotifier' import { toolAgentFlow } from './toolAgentFlow' +// i18n +import { useTranslation } from 'react-i18next' + // ===========================|| CustomAssistantConfigurePreview ||=========================== // const MemoizedFullPageChat = memo( @@ -76,6 +79,7 @@ MemoizedFullPageChat.propTypes = { } const CustomAssistantConfigurePreview = () => { + const { t } = useTranslation() const navigate = useNavigate() const theme = useTheme() const settingsRef = useRef() @@ -152,7 +156,7 @@ const CustomAssistantConfigurePreview = () => { const displayWarning = () => { enqueueSnackbar({ - message: 'Please fill in all mandatory fields.', + message: t('assistants.cards.customAssistant.messages.warn'), options: { key: new Date().getTime() + Math.random(), variant: 'warning', @@ -269,7 +273,7 @@ const CustomAssistantConfigurePreview = () => { if (saveAssistantResp.data) { setLoading(false) enqueueSnackbar({ - message: 'Assistant saved successfully', + message: t('assistants.cards.customAssistant.messages.save.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -285,9 +289,9 @@ const CustomAssistantConfigurePreview = () => { } catch (error) { setLoading(false) enqueueSnackbar({ - message: `Failed to save assistant: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('assistants.cards.customAssistant.messages.save.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -421,8 +425,10 @@ const CustomAssistantConfigurePreview = () => { try { const config = {} - const nodes = toolAgentFlow.nodes - const edges = toolAgentFlow.edges + const flow = toolAgentFlow(t) + + const nodes = flow.nodes + const edges = flow.edges const chatModelId = `${selectedChatModel.name}_0` const existingChatModelId = nodes.find((node) => node.data.category === 'Chat Models')?.id @@ -488,9 +494,9 @@ const CustomAssistantConfigurePreview = () => { } catch (error) { console.error('Error preparing config', error) enqueueSnackbar({ - message: `Failed to save assistant: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('assistants.cards.customAssistant.messages.save.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -512,20 +518,20 @@ const CustomAssistantConfigurePreview = () => { handleDeleteFlow() } else if (setting === 'viewMessages') { setViewMessagesDialogProps({ - title: 'View Messages', + title: t('assistants.cards.customAssistant.settings.viewMessages'), chatflow: canvas.chatflow, isChatflow: false }) setViewMessagesDialogOpen(true) } else if (setting === 'viewLeads') { setViewLeadsDialogProps({ - title: 'View Leads', + title: t('assistants.cards.customAssistant.settings.viewLeads'), chatflow: canvas.chatflow }) setViewLeadsDialogOpen(true) } else if (setting === 'chatflowConfiguration') { setChatflowConfigurationDialogProps({ - title: `Assistant Configuration`, + title: t('assistants.cards.customAssistant.settings.configuration'), chatflow: canvas.chatflow }) setChatflowConfigurationDialogOpen(true) @@ -534,10 +540,10 @@ const CustomAssistantConfigurePreview = () => { const handleDeleteFlow = async () => { const confirmPayload = { - title: `Delete`, - description: `Delete ${selectedCustomAssistant.name}?`, - confirmButtonName: 'Delete', - cancelButtonName: 'Cancel' + title: t('common.dialogs.delete'), + description: t('assistants.dialogs.delete.description', { name: selectedCustomAssistant.name }), + confirmButtonName: t('common.actions.delete'), + cancelButtonName: t('common.actions.cancel') } const isConfirmed = await confirm(confirmPayload) @@ -570,12 +576,12 @@ const CustomAssistantConfigurePreview = () => { const dialogProps = { value, inputParam: { - label: 'Instructions', + label: t('assistants.cards.customAssistant.instructions'), name: 'instructions', type: 'string' }, - confirmButtonName: 'Save', - cancelButtonName: 'Cancel' + confirmButtonName: t('common.actions.save'), + cancelButtonName: t('common.actions.cancel') } setExpandDialogProps(dialogProps) setShowExpandDialog(true) @@ -611,7 +617,7 @@ const CustomAssistantConfigurePreview = () => { }) setSelectedDocumentStores(newSelectedDocumentStores) enqueueSnackbar({ - message: 'Document Store Tool Description generated successfully', + message: t('assistants.cards.customAssistant.messages.docStore.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -650,8 +656,8 @@ const CustomAssistantConfigurePreview = () => { } setAssistantPromptGeneratorDialogProps({ - title: 'Generate Instructions', - description: 'You can generate a prompt template by sharing basic details about your task.', + title: t('assistants.cards.customAssistant.generateInstructions.title'), + description: t('assistants.cards.customAssistant.generateInstructions.description'), data: { selectedChatModel } }) setAssistantPromptGeneratorDialogOpen(true) @@ -659,7 +665,7 @@ const CustomAssistantConfigurePreview = () => { const onAPIDialogClick = () => { setAPIDialogProps({ - title: 'Embed in website or use as API', + title: t('assistants.cards.customAssistant.embedInWebsite'), chatflowid: customAssistantFlowId, chatflowApiKeyId: canvas.chatflow.apikeyid, isSessionMemory: true @@ -790,11 +796,15 @@ const CustomAssistantConfigurePreview = () => { const chatflow = getSpecificChatflowApi.data dispatch({ type: SET_CHATFLOW, chatflow }) } else if (getSpecificChatflowApi.error) { - setError(`Failed to retrieve: ${getSpecificChatflowApi.error.response.data.message}`) + setError( + t('assistants.cards.customAssistant.messages.errors.failedRetrieve', { + msg: getSpecificChatflowApi.error.response.data.message + }) + ) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [getSpecificChatflowApi.data, getSpecificChatflowApi.error]) + }, [getSpecificChatflowApi.data, getSpecificChatflowApi.error, t]) useEffect(() => { if (getSpecificAssistantApi.error) { @@ -863,8 +873,8 @@ const CustomAssistantConfigurePreview = () => { navigate(-1)} > @@ -875,7 +885,10 @@ const CustomAssistantConfigurePreview = () => {
{customAssistantFlowId && !loadingAssistant && ( - + { )} - + { {customAssistantFlowId && !loadingAssistant && ( - + { )} {!customAssistantFlowId && !loadingAssistant && ( - + { >
- Select Model * + {t('common.labels.selectModel')} +  *
{ } } }} - value={selectedChatModel ? selectedChatModel?.name : 'choose an option'} + value={selectedChatModel ? selectedChatModel?.name : t('components.dropdown.chooseOption')} /> { > - Instructions * + {t('assistants.cards.customAssistant.instructions')} +  *
{ height: 25, width: 25 }} - title='Expand' + title={t('common.actions.expand')} color='secondary' onClick={() => onExpandDialogClicked(customAssistantInstruction)} > @@ -1030,14 +1053,14 @@ const CustomAssistantConfigurePreview = () => { {selectedChatModel?.name && ( )}
@@ -1061,8 +1084,8 @@ const CustomAssistantConfigurePreview = () => { }} > - Knowledge (Document Stores) - + {t('assistants.cards.customAssistant.inputs.knowledge.title')} + { onDocStoreItemSelected(newValue) } }} - value={selectedDocumentStores.map((ds) => ds.id) ?? 'choose an option'} + value={selectedDocumentStores.map((ds) => ds.id) ?? t('components.dropdown.chooseOption')} /> {selectedDocumentStores.length > 0 && ( - Describe Knowledge * + {t('assistants.cards.customAssistant.inputs.describeKnowledge.title')} +  * - + )} {selectedDocumentStores.map((ds, index) => { @@ -1117,14 +1143,14 @@ const CustomAssistantConfigurePreview = () => {
{selectedChatModel?.name && ( )} @@ -1141,8 +1167,12 @@ const CustomAssistantConfigurePreview = () => { }} /> - Return Source Documents - + + {t('assistants.cards.customAssistant.inputs.returnSource.title')} + + { }} > - Tools - + {t('common.labels.tools')} + {selectedTools.map((tool, index) => { return ( @@ -1208,7 +1238,8 @@ const CustomAssistantConfigurePreview = () => {
- Tool * + {t('assistants.cards.customAssistant.inputs.tools.tool')} +  *
{ } } }} - value={tool?.name || 'choose an option'} + value={tool?.name || t('components.dropdown.chooseOption')} /> {tool && Object.keys(tool).length === 0 && ( @@ -1278,19 +1309,19 @@ const CustomAssistantConfigurePreview = () => { })} {selectedChatModel && Object.keys(selectedChatModel).length > 0 && ( )} diff --git a/packages/ui/src/views/assistants/custom/CustomAssistantLayout.jsx b/packages/ui/src/views/assistants/custom/CustomAssistantLayout.jsx index 6597bfeb6e7..bf2f8f51fc6 100644 --- a/packages/ui/src/views/assistants/custom/CustomAssistantLayout.jsx +++ b/packages/ui/src/views/assistants/custom/CustomAssistantLayout.jsx @@ -23,9 +23,13 @@ import useApi from '@/hooks/useApi' // icons import { IconPlus } from '@tabler/icons-react' +// i18n +import { useTranslation } from 'react-i18next' + // ==============================|| CustomAssistantLayout ||============================== // const CustomAssistantLayout = () => { + const { t } = useTranslation() const navigate = useNavigate() const getAllAssistantsApi = useApi(assistantsApi.getAllAssistants) @@ -42,10 +46,10 @@ const CustomAssistantLayout = () => { const addNew = () => { const dialogProp = { - title: 'Add New Custom Assistant', + title: t('assistants.actions.addCustomAssistant'), type: 'ADD', - cancelButtonName: 'Cancel', - confirmButtonName: 'Add' + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.add') } setDialogProps(dialogProp) setShowDialog(true) @@ -98,9 +102,9 @@ const CustomAssistantLayout = () => { isBackButton={true} onSearchChange={onSearchChange} search={true} - searchPlaceholder='Search Assistants' - title='Custom Assistant' - description='Create custom assistants with your choice of LLMs' + searchPlaceholder={t('assistants.cards.customAssistant.searchPlaceholder')} + title={t('assistants.cards.customAssistant.title')} + description={t('assistants.cards.customAssistant.description')} onBack={() => navigate(-1)} > { onClick={addNew} startIcon={} > - Add + {t('common.actions.add')} {isLoading ? ( @@ -144,7 +148,7 @@ const CustomAssistantLayout = () => { alt='AssistantEmptySVG' /> -
No Custom Assistants Added Yet
+
{t('assistants.cards.customAssistant.notFound')}
)} diff --git a/packages/ui/src/views/assistants/custom/toolAgentFlow.js b/packages/ui/src/views/assistants/custom/toolAgentFlow.js index a39bf47bde1..9144101365a 100644 --- a/packages/ui/src/views/assistants/custom/toolAgentFlow.js +++ b/packages/ui/src/views/assistants/custom/toolAgentFlow.js @@ -1,336 +1,342 @@ -export const toolAgentFlow = { - nodes: [ - { - id: 'bufferMemory_0', - data: { +// [TODO]: Add i18n support for categories +export const toolAgentFlow = (t) => { + return { + nodes: [ + { id: 'bufferMemory_0', - label: 'Buffer Memory', - version: 2, - name: 'bufferMemory', - type: 'BufferMemory', - baseClasses: ['BufferMemory', 'BaseChatMemory', 'BaseMemory'], - category: 'Memory', - description: 'Retrieve chat messages stored in database', - inputParams: [ - { - label: 'Session Id', - name: 'sessionId', - type: 'string', - description: - 'If not specified, a random id will be used. Learn more', - default: '', - additionalParams: true, - optional: true, - id: 'bufferMemory_0-input-sessionId-string' + data: { + id: 'bufferMemory_0', + label: t('assistants.cards.customAssistant.agentFlow.bufferMemory.title'), + version: 2, + name: 'bufferMemory', + type: 'BufferMemory', + baseClasses: ['BufferMemory', 'BaseChatMemory', 'BaseMemory'], + category: 'Memory', + description: t('assistants.cards.customAssistant.agentFlow.bufferMemory.description'), + inputParams: [ + { + label: t('assistants.cards.customAssistant.agentFlow.bufferMemory.inputs.sessionId.title'), + name: 'sessionId', + type: 'string', + description: t('assistants.cards.customAssistant.agentFlow.bufferMemory.inputs.sessionId.description', { + docsUrl: 'https://docs.flowiseai.com/memory#ui-and-embedded-chat' + }), + default: '', + additionalParams: true, + optional: true, + id: 'bufferMemory_0-input-sessionId-string' + }, + { + label: t('assistants.cards.customAssistant.agentFlow.bufferMemory.inputs.memoryKey.title'), + name: 'memoryKey', + type: 'string', + default: 'chat_history', + additionalParams: true, + id: 'bufferMemory_0-input-memoryKey-string' + } + ], + inputAnchors: [], + inputs: { + sessionId: '', + memoryKey: 'chat_history' }, - { - label: 'Memory Key', - name: 'memoryKey', - type: 'string', - default: 'chat_history', - additionalParams: true, - id: 'bufferMemory_0-input-memoryKey-string' - } - ], - inputAnchors: [], - inputs: { - sessionId: '', - memoryKey: 'chat_history' - }, - outputAnchors: [ - { - id: 'bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory', - name: 'bufferMemory', - label: 'BufferMemory', - description: 'Retrieve chat messages stored in database', - type: 'BufferMemory | BaseChatMemory | BaseMemory' - } - ], - outputs: {} - } - }, - { - id: 'chatOpenAI_0', - data: { + outputAnchors: [ + { + id: 'bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory', + name: 'bufferMemory', + label: t('assistants.cards.customAssistant.agentFlow.bufferMemory.outputs.bufferMemory.title'), + description: t('assistants.cards.customAssistant.agentFlow.bufferMemory.description'), + type: 'BufferMemory | BaseChatMemory | BaseMemory' + } + ], + outputs: {} + } + }, + { id: 'chatOpenAI_0', - label: 'ChatOpenAI', - version: 8, - name: 'chatOpenAI', - type: 'ChatOpenAI', - baseClasses: ['ChatOpenAI', 'BaseChatModel', 'BaseLanguageModel', 'Runnable'], - category: 'Chat Models', - description: 'Wrapper around OpenAI large language models that use the Chat endpoint', - inputParams: [ - { - label: 'Connect Credential', - name: 'credential', - type: 'credential', - credentialNames: ['openAIApi'], - id: 'chatOpenAI_0-input-credential-credential' - }, - { - label: 'Model Name', - name: 'modelName', - type: 'asyncOptions', - loadMethod: 'listModels', - default: 'gpt-4o-mini', - id: 'chatOpenAI_0-input-modelName-asyncOptions' - }, - { - label: 'Temperature', - name: 'temperature', - type: 'number', - step: 0.1, - default: 0.9, - optional: true, - id: 'chatOpenAI_0-input-temperature-number' - }, - { - label: 'Streaming', - name: 'streaming', - type: 'boolean', - default: true, - optional: true, - additionalParams: true, - id: 'chatOpenAI_0-input-streaming-boolean' - }, - { - label: 'Max Tokens', - name: 'maxTokens', - type: 'number', - step: 1, - optional: true, - additionalParams: true, - id: 'chatOpenAI_0-input-maxTokens-number' - }, - { - label: 'Top Probability', - name: 'topP', - type: 'number', - step: 0.1, - optional: true, - additionalParams: true, - id: 'chatOpenAI_0-input-topP-number' - }, - { - label: 'Frequency Penalty', - name: 'frequencyPenalty', - type: 'number', - step: 0.1, - optional: true, - additionalParams: true, - id: 'chatOpenAI_0-input-frequencyPenalty-number' - }, - { - label: 'Presence Penalty', - name: 'presencePenalty', - type: 'number', - step: 0.1, - optional: true, - additionalParams: true, - id: 'chatOpenAI_0-input-presencePenalty-number' - }, - { - label: 'Timeout', - name: 'timeout', - type: 'number', - step: 1, - optional: true, - additionalParams: true, - id: 'chatOpenAI_0-input-timeout-number' - }, - { - label: 'BasePath', - name: 'basepath', - type: 'string', - optional: true, - additionalParams: true, - id: 'chatOpenAI_0-input-basepath-string' + data: { + id: 'chatOpenAI_0', + label: t('assistants.cards.customAssistant.agentFlow.chatOpenAI.title'), + version: 8, + name: 'chatOpenAI', + type: 'ChatOpenAI', + baseClasses: ['ChatOpenAI', 'BaseChatModel', 'BaseLanguageModel', 'Runnable'], + category: 'Chat Models', + description: t('assistants.cards.customAssistant.agentFlow.chatOpenAI.description'), + inputParams: [ + { + label: t('assistants.cards.customAssistant.agentFlow.chatOpenAI.inputs.credential.title'), + name: 'credential', + type: 'credential', + credentialNames: ['openAIApi'], + id: 'chatOpenAI_0-input-credential-credential' + }, + { + label: t('assistants.cards.customAssistant.agentFlow.chatOpenAI.inputs.modelName.title'), + name: 'modelName', + type: 'asyncOptions', + loadMethod: 'listModels', + default: 'gpt-4o-mini', + id: 'chatOpenAI_0-input-modelName-asyncOptions' + }, + { + label: t('assistants.cards.customAssistant.agentFlow.chatOpenAI.inputs.temperature.title'), + name: 'temperature', + type: 'number', + step: 0.1, + default: 0.9, + optional: true, + id: 'chatOpenAI_0-input-temperature-number' + }, + { + label: t('assistants.cards.customAssistant.agentFlow.chatOpenAI.inputs.streaming.title'), + name: 'streaming', + type: 'boolean', + default: true, + optional: true, + additionalParams: true, + id: 'chatOpenAI_0-input-streaming-boolean' + }, + { + label: t('assistants.cards.customAssistant.agentFlow.chatOpenAI.inputs.maxTokens.title'), + name: 'maxTokens', + type: 'number', + step: 1, + optional: true, + additionalParams: true, + id: 'chatOpenAI_0-input-maxTokens-number' + }, + { + label: t('assistants.cards.customAssistant.agentFlow.chatOpenAI.inputs.topP.title'), + name: 'topP', + type: 'number', + step: 0.1, + optional: true, + additionalParams: true, + id: 'chatOpenAI_0-input-topP-number' + }, + { + label: t('assistants.cards.customAssistant.agentFlow.chatOpenAI.inputs.frequencyPenalty.title'), + name: 'frequencyPenalty', + type: 'number', + step: 0.1, + optional: true, + additionalParams: true, + id: 'chatOpenAI_0-input-frequencyPenalty-number' + }, + { + label: t('assistants.cards.customAssistant.agentFlow.chatOpenAI.inputs.presencePenalty.title'), + name: 'presencePenalty', + type: 'number', + step: 0.1, + optional: true, + additionalParams: true, + id: 'chatOpenAI_0-input-presencePenalty-number' + }, + { + label: t('assistants.cards.customAssistant.agentFlow.chatOpenAI.inputs.timeout.title'), + name: 'timeout', + type: 'number', + step: 1, + optional: true, + additionalParams: true, + id: 'chatOpenAI_0-input-timeout-number' + }, + { + label: t('assistants.cards.customAssistant.agentFlow.chatOpenAI.inputs.basepath.title'), + name: 'basepath', + type: 'string', + optional: true, + additionalParams: true, + id: 'chatOpenAI_0-input-basepath-string' + }, + { + label: t('assistants.cards.customAssistant.agentFlow.chatOpenAI.inputs.proxyUrl.title'), + name: 'proxyUrl', + type: 'string', + optional: true, + additionalParams: true, + id: 'chatOpenAI_0-input-proxyUrl-string' + }, + { + label: t('assistants.cards.customAssistant.agentFlow.chatOpenAI.inputs.stopSequence.title'), + name: 'stopSequence', + type: 'string', + rows: 4, + optional: true, + description: t('assistants.cards.customAssistant.agentFlow.chatOpenAI.inputs.stopSequence.description'), + additionalParams: true, + id: 'chatOpenAI_0-input-stopSequence-string' + }, + { + label: t('assistants.cards.customAssistant.agentFlow.chatOpenAI.inputs.baseOptions.title'), + name: 'baseOptions', + type: 'json', + optional: true, + additionalParams: true, + id: 'chatOpenAI_0-input-baseOptions-json' + }, + { + label: t('assistants.cards.customAssistant.agentFlow.chatOpenAI.inputs.allowImageUploads.title'), + name: 'allowImageUploads', + type: 'boolean', + description: t('assistants.cards.customAssistant.agentFlow.chatOpenAI.inputs.allowImageUploads.description', { + docsUrl: 'https://docs.flowiseai.com/using-flowise/uploads#image' + }), + default: false, + optional: true, + id: 'chatOpenAI_0-input-allowImageUploads-boolean' + } + ], + inputAnchors: [ + { + label: t('assistants.cards.customAssistant.agentFlow.chatOpenAI.inputAnchors.cache.title'), + name: 'cache', + type: 'BaseCache', + optional: true, + id: 'chatOpenAI_0-input-cache-BaseCache' + } + ], + inputs: { + cache: '', + modelName: 'gpt-4o-mini', + temperature: 0.9, + streaming: true, + maxTokens: '', + topP: '', + frequencyPenalty: '', + presencePenalty: '', + timeout: '', + basepath: '', + proxyUrl: '', + stopSequence: '', + baseOptions: '', + allowImageUploads: '' }, - { - label: 'Proxy Url', - name: 'proxyUrl', - type: 'string', - optional: true, - additionalParams: true, - id: 'chatOpenAI_0-input-proxyUrl-string' - }, - { - label: 'Stop Sequence', - name: 'stopSequence', - type: 'string', - rows: 4, - optional: true, - description: 'List of stop words to use when generating. Use comma to separate multiple stop words.', - additionalParams: true, - id: 'chatOpenAI_0-input-stopSequence-string' - }, - { - label: 'Base Options', - name: 'baseOptions', - type: 'json', - optional: true, - additionalParams: true, - id: 'chatOpenAI_0-input-baseOptions-json' - }, - { - label: 'Allow Image Uploads', - name: 'allowImageUploads', - type: 'boolean', - description: - 'Allow image input. Refer to the docs for more details.', - default: false, - optional: true, - id: 'chatOpenAI_0-input-allowImageUploads-boolean' - } - ], - inputAnchors: [ - { - label: 'Cache', - name: 'cache', - type: 'BaseCache', - optional: true, - id: 'chatOpenAI_0-input-cache-BaseCache' - } - ], - inputs: { - cache: '', - modelName: 'gpt-4o-mini', - temperature: 0.9, - streaming: true, - maxTokens: '', - topP: '', - frequencyPenalty: '', - presencePenalty: '', - timeout: '', - basepath: '', - proxyUrl: '', - stopSequence: '', - baseOptions: '', - allowImageUploads: '' - }, - outputAnchors: [ - { - id: 'chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable', - name: 'chatOpenAI', - label: 'ChatOpenAI', - description: 'Wrapper around OpenAI large language models that use the Chat endpoint', - type: 'ChatOpenAI | BaseChatModel | BaseLanguageModel | Runnable' - } - ], - outputs: {} - } - }, - { - id: 'toolAgent_0', - data: { + outputAnchors: [ + { + id: 'chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable', + name: 'chatOpenAI', + label: t('assistants.cards.customAssistant.agentFlow.chatOpenAI.outputs.chatOpenAI.title'), + description: t('assistants.cards.customAssistant.agentFlow.chatOpenAI.description'), + type: 'ChatOpenAI | BaseChatModel | BaseLanguageModel | Runnable' + } + ], + outputs: {} + } + }, + { id: 'toolAgent_0', - label: 'Tool Agent', - version: 2, - name: 'toolAgent', - type: 'AgentExecutor', - baseClasses: ['AgentExecutor', 'BaseChain', 'Runnable'], - category: 'Agents', - description: 'Agent that uses Function Calling to pick the tools and args to call', - inputParams: [ - { - label: 'System Message', - name: 'systemMessage', - type: 'string', - default: 'You are a helpful AI assistant.', - description: 'If Chat Prompt Template is provided, this will be ignored', - rows: 4, - optional: true, - additionalParams: true, - id: 'toolAgent_0-input-systemMessage-string' - }, - { - label: 'Max Iterations', - name: 'maxIterations', - type: 'number', - optional: true, - additionalParams: true, - id: 'toolAgent_0-input-maxIterations-number' - } - ], - inputAnchors: [ - { - label: 'Tools', - name: 'tools', - type: 'Tool', - list: true, - id: 'toolAgent_0-input-tools-Tool' + data: { + id: 'toolAgent_0', + label: t('assistants.cards.customAssistant.agentFlow.toolAgent.title'), + version: 2, + name: 'toolAgent', + type: 'AgentExecutor', + baseClasses: ['AgentExecutor', 'BaseChain', 'Runnable'], + category: 'Agents', + description: t('assistants.cards.customAssistant.agentFlow.toolAgent.description'), + inputParams: [ + { + label: t('assistants.cards.customAssistant.agentFlow.toolAgent.inputs.systemMessage.title'), + name: 'systemMessage', + type: 'string', + default: 'You are a helpful AI assistant.', + description: t('assistants.cards.customAssistant.agentFlow.toolAgent.inputs.systemMessage.description'), + rows: 4, + optional: true, + additionalParams: true, + id: 'toolAgent_0-input-systemMessage-string' + }, + { + label: t('assistants.cards.customAssistant.agentFlow.toolAgent.inputs.maxIterations.title'), + name: 'maxIterations', + type: 'number', + optional: true, + additionalParams: true, + id: 'toolAgent_0-input-maxIterations-number' + } + ], + inputAnchors: [ + { + label: t('common.labels.tools'), + name: 'tools', + type: 'Tool', + list: true, + id: 'toolAgent_0-input-tools-Tool' + }, + { + label: t('assistants.cards.customAssistant.agentFlow.toolAgent.inputAnchors.memory.title'), + name: 'memory', + type: 'BaseChatMemory', + id: 'toolAgent_0-input-memory-BaseChatMemory' + }, + { + label: t('assistants.cards.customAssistant.agentFlow.toolAgent.inputAnchors.model.title'), + name: 'model', + type: 'BaseChatModel', + description: t('assistants.cards.customAssistant.agentFlow.toolAgent.inputAnchors.model.description'), + id: 'toolAgent_0-input-model-BaseChatModel' + }, + { + label: t('assistants.cards.customAssistant.agentFlow.toolAgent.inputAnchors.chatPromptTemplate.title'), + name: 'chatPromptTemplate', + type: 'ChatPromptTemplate', + description: t( + 'assistants.cards.customAssistant.agentFlow.toolAgent.inputAnchors.chatPromptTemplate.description' + ), + optional: true, + id: 'toolAgent_0-input-chatPromptTemplate-ChatPromptTemplate' + }, + { + label: t('assistants.cards.customAssistant.agentFlow.toolAgent.inputAnchors.inputModeration.title'), + description: t('assistants.cards.customAssistant.agentFlow.toolAgent.inputAnchors.inputModeration.description'), + name: 'inputModeration', + type: 'Moderation', + optional: true, + list: true, + id: 'toolAgent_0-input-inputModeration-Moderation' + } + ], + inputs: { + tools: [], + memory: '{{bufferMemory_0.data.instance}}', + model: '{{chatOpenAI_0.data.instance}}', + chatPromptTemplate: '', + systemMessage: 'You are helpful assistant', + inputModeration: '', + maxIterations: '' }, - { - label: 'Memory', - name: 'memory', - type: 'BaseChatMemory', - id: 'toolAgent_0-input-memory-BaseChatMemory' - }, - { - label: 'Tool Calling Chat Model', - name: 'model', - type: 'BaseChatModel', - description: - 'Only compatible with models that are capable of function calling: ChatOpenAI, ChatMistral, ChatAnthropic, ChatGoogleGenerativeAI, ChatVertexAI, GroqChat', - id: 'toolAgent_0-input-model-BaseChatModel' - }, - { - label: 'Chat Prompt Template', - name: 'chatPromptTemplate', - type: 'ChatPromptTemplate', - description: 'Override existing prompt with Chat Prompt Template. Human Message must includes {input} variable', - optional: true, - id: 'toolAgent_0-input-chatPromptTemplate-ChatPromptTemplate' - }, - { - label: 'Input Moderation', - description: 'Detect text that could generate harmful output and prevent it from being sent to the language model', - name: 'inputModeration', - type: 'Moderation', - optional: true, - list: true, - id: 'toolAgent_0-input-inputModeration-Moderation' - } - ], - inputs: { - tools: [], - memory: '{{bufferMemory_0.data.instance}}', - model: '{{chatOpenAI_0.data.instance}}', - chatPromptTemplate: '', - systemMessage: 'You are helpful assistant', - inputModeration: '', - maxIterations: '' - }, - outputAnchors: [ - { - id: 'toolAgent_0-output-toolAgent-AgentExecutor|BaseChain|Runnable', - name: 'toolAgent', - label: 'AgentExecutor', - description: 'Agent that uses Function Calling to pick the tools and args to call', - type: 'AgentExecutor | BaseChain | Runnable' - } - ], - outputs: {} + outputAnchors: [ + { + id: 'toolAgent_0-output-toolAgent-AgentExecutor|BaseChain|Runnable', + name: 'toolAgent', + label: t('assistants.cards.customAssistant.agentFlow.toolAgent.outputs.toolAgent.title'), + description: t('assistants.cards.customAssistant.agentFlow.toolAgent.description'), + type: 'AgentExecutor | BaseChain | Runnable' + } + ], + outputs: {} + } + } + ], + edges: [ + { + source: 'bufferMemory_0', + sourceHandle: 'bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory', + target: 'toolAgent_0', + targetHandle: 'toolAgent_0-input-memory-BaseChatMemory', + type: 'buttonedge', + id: 'bufferMemory_0-bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory-toolAgent_0-toolAgent_0-input-memory-BaseChatMemory' + }, + { + source: 'chatOpenAI_0', + sourceHandle: 'chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable', + target: 'toolAgent_0', + targetHandle: 'toolAgent_0-input-model-BaseChatModel', + type: 'buttonedge', + id: 'chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-toolAgent_0-toolAgent_0-input-model-BaseChatModel' } - } - ], - edges: [ - { - source: 'bufferMemory_0', - sourceHandle: 'bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory', - target: 'toolAgent_0', - targetHandle: 'toolAgent_0-input-memory-BaseChatMemory', - type: 'buttonedge', - id: 'bufferMemory_0-bufferMemory_0-output-bufferMemory-BufferMemory|BaseChatMemory|BaseMemory-toolAgent_0-toolAgent_0-input-memory-BaseChatMemory' - }, - { - source: 'chatOpenAI_0', - sourceHandle: 'chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable', - target: 'toolAgent_0', - targetHandle: 'toolAgent_0-input-model-BaseChatModel', - type: 'buttonedge', - id: 'chatOpenAI_0-chatOpenAI_0-output-chatOpenAI-ChatOpenAI|BaseChatModel|BaseLanguageModel|Runnable-toolAgent_0-toolAgent_0-input-model-BaseChatModel' - } - ] + ] + } } diff --git a/packages/ui/src/views/assistants/index.jsx b/packages/ui/src/views/assistants/index.jsx index bd7af08a56c..c412cf5b277 100644 --- a/packages/ui/src/views/assistants/index.jsx +++ b/packages/ui/src/views/assistants/index.jsx @@ -12,20 +12,22 @@ import ViewHeader from '@/layout/MainLayout/ViewHeader' // icons import { IconRobotFace, IconBrandOpenai } from '@tabler/icons-react' +// i18n +import { useTranslation } from 'react-i18next' + const cards = [ { - title: 'Custom Assistant', - description: 'Create custom assistant using your choice of LLMs', + title: 'assistants.cards.customAssistant.title', + description: 'assistants.cards.customAssistant.description', icon: , - iconText: 'Custom', + iconText: 'assistants.cards.customAssistant.label', gradient: 'linear-gradient(135deg, #fff8e14e 0%, #ffcc802f 100%)' }, { - title: 'OpenAI Assistant', - description: - 'Create assistant using OpenAI Assistant API. This option is being deprecated; consider using Custom Assistant instead.', + title: 'assistants.cards.openAi.title', + description: 'assistants.cards.openAi.description.deprecated', icon: , - iconText: 'OpenAI', + iconText: 'common.providers.openAI', gradient: 'linear-gradient(135deg, #c9ffd85f 0%, #a0f0b567 100%)', deprecating: true } @@ -54,6 +56,7 @@ const FeatureIcon = styled('div')(() => ({ })) const FeatureCards = () => { + const { t } = useTranslation() const navigate = useNavigate() const theme = useTheme() const customization = useSelector((state) => state.customization) @@ -98,12 +101,14 @@ const FeatureCards = () => { {card.icon} - {card.iconText} + {t(card.iconText)} - {card.deprecating && } + {card.deprecating && ( + + )} -

{card.title}

-

{card.description}

+

{t(card.title)}

+

{t(card.description)}

))} @@ -114,14 +119,12 @@ const FeatureCards = () => { // ==============================|| ASSISTANTS ||============================== // const Assistants = () => { + const { t } = useTranslation() return ( <> - + diff --git a/packages/ui/src/views/assistants/openai/AssistantDialog.jsx b/packages/ui/src/views/assistants/openai/AssistantDialog.jsx index 59de3c0329b..fe685dfb2d0 100644 --- a/packages/ui/src/views/assistants/openai/AssistantDialog.jsx +++ b/packages/ui/src/views/assistants/openai/AssistantDialog.jsx @@ -45,6 +45,9 @@ import useNotifier from '@/utils/useNotifier' import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions' import { maxScroll } from '@/store/constant' +// i18n +import { useTranslation } from 'react-i18next' + const assistantAvailableModels = [ { label: 'gpt-4.1', @@ -117,6 +120,7 @@ const assistantAvailableModels = [ ] const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') useNotifier() const dispatch = useDispatch() @@ -200,13 +204,13 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = useEffect(() => { if (getAssistantObjApi.error) { - let errMsg = 'Internal Server Error' + let errMsg = t('common.errors.internalServerError') let error = getAssistantObjApi.error if (error?.response?.data) { errMsg = typeof error.response.data === 'object' ? error.response.data.message : error.response.data } enqueueSnackbar({ - message: `Failed to get assistant: ${errMsg}`, + message: t('assistants.cards.openAi.messages.loadAssistant.error', { msg: errMsg }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -230,7 +234,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = errMsg = typeof error.response.data === 'object' ? error.response.data.message : error.response.data } enqueueSnackbar({ - message: `Failed to get assistant: ${errMsg}`, + message: t('assistants.cards.openAi.messages.loadAssistant.error', { msg: errMsg }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -334,10 +338,10 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = const onEditAssistantVectorStoreClick = (vectorStoreObject) => { const dialogProp = { - title: `Edit ${vectorStoreObject.name ? vectorStoreObject.name : vectorStoreObject.id}`, + title: t('assistants.actions.editName', { name: vectorStoreObject.name ? vectorStoreObject.name : vectorStoreObject.id }), type: 'EDIT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Save', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.save'), data: vectorStoreObject, credential: assistantCredential } @@ -347,10 +351,10 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = const onAddAssistantVectorStoreClick = () => { const dialogProp = { - title: `Add Vector Store`, + title: t('assistants.actions.addVectorStore'), type: 'ADD', - cancelButtonName: 'Cancel', - confirmButtonName: 'Add', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.add'), credential: assistantCredential } setAssistantVectorStoreDialogProps(dialogProp) @@ -381,7 +385,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = const createResp = await assistantsApi.createNewAssistant(obj) if (createResp.data) { enqueueSnackbar({ - message: 'New Assistant added', + message: t('assistants.cards.openAi.messages.addAssistant.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -397,9 +401,9 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = setLoading(false) } catch (error) { enqueueSnackbar({ - message: `Failed to add new Assistant: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('assistants.cards.openAi.messages.addAssistant.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -436,7 +440,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = const saveResp = await assistantsApi.updateAssistant(assistantId, obj) if (saveResp.data) { enqueueSnackbar({ - message: 'Assistant saved', + message: t('assistants.cards.openAi.messages.save.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -452,9 +456,9 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = setLoading(false) } catch (error) { enqueueSnackbar({ - message: `Failed to save Assistant: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('assistants.cards.openAi.messages.save.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -477,7 +481,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = if (getResp.data) { syncData(getResp.data) enqueueSnackbar({ - message: 'Assistant successfully synced!', + message: t('assistants.cards.openAi.messages.sync.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -492,9 +496,9 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = setLoading(false) } catch (error) { enqueueSnackbar({ - message: `Failed to sync Assistant: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('assistants.cards.openAi.messages.sync.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -517,7 +521,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = const uploadResp = await assistantsApi.uploadFilesToAssistantVectorStore(vectorStoreId, assistantCredential, formData) if (uploadResp.data) { enqueueSnackbar({ - message: 'File uploaded successfully!', + message: t('assistants.cards.openAi.messages.upload.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -543,9 +547,9 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = setLoading(false) } catch (error) { enqueueSnackbar({ - message: `Failed to upload file: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('assistants.cards.openAi.messages.upload.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -567,7 +571,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = const uploadResp = await assistantsApi.uploadFilesToAssistant(assistantCredential, formData) if (uploadResp.data) { enqueueSnackbar({ - message: 'File uploaded successfully!', + message: t('assistants.cards.openAi.messages.upload.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -595,9 +599,9 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = setLoading(false) } catch (error) { enqueueSnackbar({ - message: `Failed to upload file: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('assistants.cards.openAi.messages.upload.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -626,9 +630,9 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = const onDeleteClick = () => { setDeleteDialogProps({ - title: `Delete Assistant`, - description: `Select delete method for ${assistantName}`, - cancelButtonName: 'Cancel' + title: t('assistants.actions.deleteAssistant.title'), + description: t('assistants.actions.deleteAssistant.description', { name: assistantName }), + cancelButtonName: t('common.actions.cancel') }) setDeleteDialogOpen(true) } @@ -639,7 +643,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = const delResp = await assistantsApi.deleteAssistant(assistantId, isDeleteBoth) if (delResp.data) { enqueueSnackbar({ - message: 'Assistant deleted', + message: t('assistants.cards.openAi.messages.delete.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -654,9 +658,9 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = } } catch (error) { enqueueSnackbar({ - message: `Failed to delete Assistant: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('assistants.cards.openAi.messages.delete.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -723,7 +727,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = - OpenAI Credential + {t('assistants.cards.openAi.agentFlow.openAi.title')}  * @@ -731,7 +735,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = key={assistantCredential} data={assistantCredential ? { credential: assistantCredential } : {}} inputParam={{ - label: 'Connect Credential', + label: t('assistants.cards.openAi.agentFlow.connectCredential.title'), name: 'credential', type: 'credential', credentialNames: ['openAIApi'] @@ -742,7 +746,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = - Assistant Model + {t('assistants.cards.openAi.agentFlow.assistantModel.title')}  * @@ -751,20 +755,20 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = name={assistantModel} options={assistantAvailableModels} onSelect={(newValue) => setAssistantModel(newValue)} - value={assistantModel ?? 'choose an option'} + value={assistantModel ?? t('components.dropdown.chooseOption')} /> - Assistant Name - + {t('assistants.cards.openAi.agentFlow.name.title')} + setAssistantName(e.target.value)} @@ -772,15 +776,15 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = - Assistant Description - + {t('assistants.cards.openAi.agentFlow.description.title')} + - Assistant Icon Src + {t('assistants.cards.openAi.agentFlow.icon.title')}
- Assistant Instruction - + {t('assistants.cards.openAi.agentFlow.instruction.title')} + - Assistant Temperature - + {t('assistants.cards.openAi.agentFlow.temperature.title')} + - Assistant Top P - + {t('assistants.cards.openAi.agentFlow.topP.title')} + - Assistant Tools - + {t('assistants.cards.openAi.agentFlow.tools.title')} + @@ -917,8 +911,12 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = - Code Interpreter Files - + + {t('assistants.cards.openAi.agentFlow.codeInterpreterFiles.title')} + + {toolResources?.code_interpreter?.files?.length > 0 && (
@@ -958,7 +956,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = key={uploadCodeInterpreterFiles} fileType='*' formDataUpload={true} - value={uploadCodeInterpreterFiles ?? 'Choose a file to upload'} + value={uploadCodeInterpreterFiles ?? t('components.file.chooseFile')} onChange={(newValue) => setUploadCodeInterpreterFiles(newValue)} onFormDataChange={(formData) => uploadFormDataToCodeInterpreter(formData)} /> @@ -969,8 +967,10 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = - File Search Files - + + {t('assistants.cards.openAi.agentFlow.fileSearchFiles.title')} + + {toolResources?.file_search?.vector_store_object && ( onAddAssistantVectorStoreClick()} > - Add Vector Store + {t('assistants.actions.addVectorStore')} ) : ( setUploadVectorStoreFiles(newValue)} onFormDataChange={(formData) => uploadFormDataToVectorStore(formData)} /> @@ -1061,7 +1061,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = variant='contained' onClick={() => onSyncClick()} > - Sync + {t('assistants.actions.sync')} )} {dialogProps.type === 'EDIT' && ( @@ -1071,7 +1071,7 @@ const AssistantDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) = variant='contained' onClick={() => onDeleteClick()} > - Delete + {t('common.actions.delete')} )} { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const dispatch = useDispatch() @@ -49,7 +53,9 @@ const AssistantVectorStoreDialog = ({ show, dialogProps, onCancel, onConfirm, on const [name, setName] = useState('') const [isExpirationOn, setExpirationOnOff] = useState(false) const [expirationDays, setExpirationDays] = useState(7) - const [availableVectorStoreOptions, setAvailableVectorStoreOptions] = useState([{ label: '- Create New -', name: '-create-' }]) + const [availableVectorStoreOptions, setAvailableVectorStoreOptions] = useState([ + { label: t('components.dropdown.createNewItemLabel'), name: '-create-' } + ]) const [selectedVectorStore, setSelectedVectorStore] = useState('') const [loading, setLoading] = useState(false) @@ -86,18 +92,19 @@ const AssistantVectorStoreDialog = ({ show, dialogProps, onCancel, onConfirm, on vectorStores.push({ label: listAssistantVectorStoreApi.data[i]?.name ?? listAssistantVectorStoreApi.data[i].id, name: listAssistantVectorStoreApi.data[i].id, - description: `${listAssistantVectorStoreApi.data[i]?.file_counts?.total} files (${formatBytes( - listAssistantVectorStoreApi.data[i]?.usage_bytes - )})` + description: t('assistants.cards.vectorStorage.files', { + count: listAssistantVectorStoreApi.data[i]?.file_counts?.total, + size: formatBytes(listAssistantVectorStoreApi.data[i]?.usage_bytes) + }) }) } vectorStores = vectorStores.filter((vs) => vs.name !== '-create-') - vectorStores.unshift({ label: '- Create New -', name: '-create-' }) + vectorStores.unshift({ label: t('components.dropdown.createNewItemLabel'), name: '-create-' }) setAvailableVectorStoreOptions(vectorStores) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [listAssistantVectorStoreApi.data]) + }, [listAssistantVectorStoreApi.data, t]) useEffect(() => { if (getAssistantVectorStoreApi.error && setError) { @@ -119,7 +126,7 @@ const AssistantVectorStoreDialog = ({ show, dialogProps, onCancel, onConfirm, on setExpirationOnOff(false) setExpirationDays(7) setSelectedVectorStore('') - setAvailableVectorStoreOptions([{ label: '- Create New -', name: '-create-' }]) + setAvailableVectorStoreOptions([{ label: t('components.dropdown.createNewItemLabel'), name: '-create-' }]) setLoading(false) } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -137,7 +144,7 @@ const AssistantVectorStoreDialog = ({ show, dialogProps, onCancel, onConfirm, on const deleteResp = await assistantsApi.deleteAssistantVectorStore(selectedVectorStore, dialogProps.credential) if (deleteResp.data) { enqueueSnackbar({ - message: 'Vector Store deleted', + message: t('assistants.cards.vectorStorage.messages.delete.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -154,9 +161,9 @@ const AssistantVectorStoreDialog = ({ show, dialogProps, onCancel, onConfirm, on } catch (error) { if (setError) setError(error) enqueueSnackbar({ - message: `Failed to delete Vector Store: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('assistants.cards.vectorStorage.messages.delete.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -183,7 +190,7 @@ const AssistantVectorStoreDialog = ({ show, dialogProps, onCancel, onConfirm, on const createResp = await assistantsApi.createAssistantVectorStore(dialogProps.credential, obj) if (createResp.data) { enqueueSnackbar({ - message: 'New Vector Store added', + message: t('assistants.cards.vectorStorage.messages.add.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -200,9 +207,9 @@ const AssistantVectorStoreDialog = ({ show, dialogProps, onCancel, onConfirm, on } catch (error) { if (setError) setError(error) enqueueSnackbar({ - message: `Failed to add new Vector Store: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('assistants.cards.vectorStorage.messages.add.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -229,7 +236,7 @@ const AssistantVectorStoreDialog = ({ show, dialogProps, onCancel, onConfirm, on const saveResp = await assistantsApi.updateAssistantVectorStore(selectedVectorStoreId, dialogProps.credential, saveObj) if (saveResp.data) { enqueueSnackbar({ - message: 'Vector Store saved', + message: t('assistants.cards.vectorStorage.messages.save.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -251,9 +258,9 @@ const AssistantVectorStoreDialog = ({ show, dialogProps, onCancel, onConfirm, on } catch (error) { if (setError) setError(error) enqueueSnackbar({ - message: `Failed to save Vector Store: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('assistants.cards.vectorStorage.messages.save.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -286,7 +293,7 @@ const AssistantVectorStoreDialog = ({ show, dialogProps, onCancel, onConfirm, on - Select Vector Store + {t('assistants.cards.vectorStorage.inputs.select.title')}  * @@ -304,7 +311,7 @@ const AssistantVectorStoreDialog = ({ show, dialogProps, onCancel, onConfirm, on getAssistantVectorStoreApi.request(newValue, dialogProps.credential) } }} - value={selectedVectorStore ?? 'choose an option'} + value={selectedVectorStore ?? t('components.dropdown.chooseOption')} /> @@ -312,13 +319,13 @@ const AssistantVectorStoreDialog = ({ show, dialogProps, onCancel, onConfirm, on <> - Vector Store Name + {t('assistants.cards.vectorStorage.inputs.name.title')} setName(e.target.value)} /> @@ -326,7 +333,7 @@ const AssistantVectorStoreDialog = ({ show, dialogProps, onCancel, onConfirm, on - Vector Store Expiration + {t('assistants.cards.vectorStorage.inputs.expiration.title')} setExpirationOnOff(newValue)} value={isExpirationOn} /> @@ -335,7 +342,7 @@ const AssistantVectorStoreDialog = ({ show, dialogProps, onCancel, onConfirm, on - Expiration Days + {t('assistants.cards.vectorStorage.inputs.date.title')}  * @@ -354,7 +361,7 @@ const AssistantVectorStoreDialog = ({ show, dialogProps, onCancel, onConfirm, on {dialogProps.type === 'EDIT' && ( deleteVectorStore()}> - Delete + {t('common.actions.delete')} )} { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const component = show ? ( @@ -22,10 +26,10 @@ const DeleteConfirmDialog = ({ show, dialogProps, onCancel, onDelete, onDeleteBo {dialogProps.description}
- OpenAI and Flowise + {t('assistants.actions.openAiFlowise')}
diff --git a/packages/ui/src/views/assistants/openai/LoadAssistantDialog.jsx b/packages/ui/src/views/assistants/openai/LoadAssistantDialog.jsx index dc29e46d162..563051c65c4 100644 --- a/packages/ui/src/views/assistants/openai/LoadAssistantDialog.jsx +++ b/packages/ui/src/views/assistants/openai/LoadAssistantDialog.jsx @@ -8,7 +8,11 @@ import { StyledButton } from '@/ui-component/button/StyledButton' import assistantsApi from '@/api/assistants' import useApi from '@/hooks/useApi' +// i18n +import { useTranslation } from 'react-i18next' + const LoadAssistantDialog = ({ show, dialogProps, onCancel, onAssistantSelected, setError }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const getAllAvailableAssistantsApi = useApi(assistantsApi.getAllAvailableAssistants) @@ -62,7 +66,7 @@ const LoadAssistantDialog = ({ show, dialogProps, onCancel, onAssistantSelected, - OpenAI Credential + {t('assistants.cards.openAi.agentFlow.openAi.title')}  * @@ -70,7 +74,7 @@ const LoadAssistantDialog = ({ show, dialogProps, onCancel, onAssistantSelected, key={credentialId} data={credentialId ? { credential: credentialId } : {}} inputParam={{ - label: 'Connect Credential', + label: t('assistants.cards.openAi.agentFlow.connectCredential.title'), name: 'credential', type: 'credential', credentialNames: ['openAIApi'] @@ -85,7 +89,7 @@ const LoadAssistantDialog = ({ show, dialogProps, onCancel, onAssistantSelected, - Assistants + {t('assistants.cards.openAi.agentFlow.assistants.title')}  * @@ -93,7 +97,7 @@ const LoadAssistantDialog = ({ show, dialogProps, onCancel, onAssistantSelected, name={selectedOpenAIAssistantId} options={availableAssistantsOptions} onSelect={(newValue) => setSelectedOpenAIAssistantId(newValue)} - value={selectedOpenAIAssistantId ?? 'choose an option'} + value={selectedOpenAIAssistantId ?? t('components.dropdown.chooseOption')} /> )} @@ -101,7 +105,7 @@ const LoadAssistantDialog = ({ show, dialogProps, onCancel, onAssistantSelected, {selectedOpenAIAssistantId && ( onAssistantSelected(selectedOpenAIAssistantId, credentialId)}> - Load + {t('common.actions.load')} )} diff --git a/packages/ui/src/views/assistants/openai/OpenAIAssistantLayout.jsx b/packages/ui/src/views/assistants/openai/OpenAIAssistantLayout.jsx index 865245c6bc2..30bd65ef5a8 100644 --- a/packages/ui/src/views/assistants/openai/OpenAIAssistantLayout.jsx +++ b/packages/ui/src/views/assistants/openai/OpenAIAssistantLayout.jsx @@ -24,9 +24,13 @@ import { IconPlus, IconFileUpload } from '@tabler/icons-react' import AssistantEmptySVG from '@/assets/images/assistant_empty.svg' import { gridSpacing } from '@/store/constant' +// i18n +import { useTranslation } from 'react-i18next' + // ==============================|| OpenAIAssistantLayout ||============================== // const OpenAIAssistantLayout = () => { + const { t } = useTranslation() const navigate = useNavigate() const getAllAssistantsApi = useApi(assistantsApi.getAllAssistants) @@ -40,7 +44,7 @@ const OpenAIAssistantLayout = () => { const loadExisting = () => { const dialogProp = { - title: 'Load Existing Assistant' + title: t('assistants.actions.loadExisting') } setLoadDialogProps(dialogProp) setShowLoadDialog(true) @@ -58,10 +62,10 @@ const OpenAIAssistantLayout = () => { const addNew = (selectedOpenAIAssistantId, credential) => { const dialogProp = { - title: 'Add New Assistant', + title: t('assistants.actions.addAssistant'), type: 'ADD', - cancelButtonName: 'Cancel', - confirmButtonName: 'Add', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.add'), selectedOpenAIAssistantId, credential } @@ -71,10 +75,10 @@ const OpenAIAssistantLayout = () => { const edit = (selectedAssistant) => { const dialogProp = { - title: 'Edit Assistant', + title: t('assistants.actions.editAssistant'), type: 'EDIT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Save', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.save'), data: selectedAssistant } setDialogProps(dialogProp) @@ -118,9 +122,9 @@ const OpenAIAssistantLayout = () => { isBackButton={true} onSearchChange={onSearchChange} search={true} - searchPlaceholder='Search Assistants' - title='OpenAI Assistant' - description='Create assistants using OpenAI Assistant API' + searchPlaceholder={t('assistants.cards.openAi.searchPlaceholder')} + title={t('assistants.cards.openAi.title')} + description={t('assistants.cards.openAi.description.simple')} onBack={() => navigate(-1)} > { startIcon={} sx={{ borderRadius: 2, height: 40 }} > - Load + {t('common.actions.load')} { onClick={addNew} startIcon={} > - Add + {t('common.actions.add')} {isLoading ? ( @@ -173,7 +177,7 @@ const OpenAIAssistantLayout = () => { alt='AssistantEmptySVG' /> -
No OpenAI Assistants Added Yet
+
{t('assistants.cards.openAi.notFound')}
)} diff --git a/packages/ui/src/views/auth/confirm-email-change.jsx b/packages/ui/src/views/auth/confirm-email-change.jsx index 277ca5aa4d2..d5a4bec2411 100644 --- a/packages/ui/src/views/auth/confirm-email-change.jsx +++ b/packages/ui/src/views/auth/confirm-email-change.jsx @@ -16,7 +16,11 @@ import useApi from '@/hooks/useApi' // icons import { IconCheck, IconX } from '@tabler/icons-react' +// i18n +import { useTranslation } from 'react-i18next' + const ConfirmEmailChange = () => { + const { t } = useTranslation() const confirmApi = useApi(accountApi.confirmEmailChange) const [searchParams] = useSearchParams() @@ -71,7 +75,7 @@ const ConfirmEmailChange = () => { height: '48px' }} /> - Confirming email change... + {t('auth.confirmEmailChange.loading')} )} {errorMessage && ( @@ -90,7 +94,7 @@ const ConfirmEmailChange = () => { >
- Confirmation failed. + {t('auth.confirmEmailChange.failed')} {errorMessage} @@ -112,9 +116,9 @@ const ConfirmEmailChange = () => { >
- Email updated successfully. + {t('auth.confirmEmailChange.success.title')} - Please sign in with your new email address. + {t('auth.confirmEmailChange.success.description')} )} diff --git a/packages/ui/src/views/auth/expired.jsx b/packages/ui/src/views/auth/expired.jsx index 6f893d5d666..b84c6eda9a8 100644 --- a/packages/ui/src/views/auth/expired.jsx +++ b/packages/ui/src/views/auth/expired.jsx @@ -3,9 +3,13 @@ import { Box, Stack, Typography } from '@mui/material' import contactSupport from '@/assets/images/contact_support.svg' import { StyledButton } from '@/ui-component/button/StyledButton' +// i18n +import { useTranslation } from 'react-i18next' + // ==============================|| License Expired Page ||============================== // const LicenseExpired = () => { + const { t } = useTranslation() return ( <> @@ -28,13 +32,13 @@ const LicenseExpired = () => { contact support - Your enterprise license has expired + {t('auth.expired.expired')} - Please contact our support team to renew your license. + {t('auth.expired.contact')} - Contact Support + {t('auth.actions.contact')} diff --git a/packages/ui/src/views/auth/forgotPassword.jsx b/packages/ui/src/views/auth/forgotPassword.jsx index 7e375a1253c..bbf8d522806 100644 --- a/packages/ui/src/views/auth/forgotPassword.jsx +++ b/packages/ui/src/views/auth/forgotPassword.jsx @@ -24,14 +24,18 @@ import useNotifier from '@/utils/useNotifier' // Icons import { IconCircleCheck, IconExclamationCircle } from '@tabler/icons-react' +// i18n +import { useTranslation, Trans } from 'react-i18next' + // ==============================|| ForgotPasswordPage ||============================== // const ForgotPasswordPage = () => { + const { t } = useTranslation() const theme = useTheme() useNotifier() const usernameInput = { - label: 'Username', + label: t('auth.forgotPassword.username'), name: 'username', type: 'email', placeholder: 'user@company.com' @@ -71,23 +75,23 @@ const ForgotPasswordPage = () => { : forgotPasswordApi.error.response.data setResponseMsg({ type: 'error', - msg: errMessage ?? 'Failed to send instructions, please contact your administrator.' + msg: errMessage ?? t('auth.forgotPassword.messages.error') }) setLoading(false) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [forgotPasswordApi.error]) + }, [forgotPasswordApi.error, t]) useEffect(() => { if (forgotPasswordApi.data) { setResponseMsg({ type: 'success', - msg: 'Password reset instructions sent to the email.' + msg: t('auth.forgotPassword.messages.success') }) setLoading(false) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [forgotPasswordApi.data]) + }, [forgotPasswordApi.data, t]) return ( <> @@ -109,13 +113,12 @@ const ForgotPasswordPage = () => { )} - Forgot Password? + {t('auth.forgotPassword.title')} - Have a reset password code?{' '} - - Change your password here - - . + }} + />
@@ -123,7 +126,8 @@ const ForgotPasswordPage = () => {
- Email * + {t('common.labels.email')} +  *
@@ -136,7 +140,7 @@ const ForgotPasswordPage = () => { /> {isEnterpriseLicensed && ( - If you forgot the email you used for signing up, please contact your administrator. + {t('auth.forgotPassword.caption')} )} @@ -146,7 +150,7 @@ const ForgotPasswordPage = () => { disabled={!usernameVal} type='submit' > - Send Reset Password Instructions + {t('auth.actions.send')} diff --git a/packages/ui/src/views/auth/login.jsx b/packages/ui/src/views/auth/login.jsx index 54eb7a83370..6f74916a57f 100644 --- a/packages/ui/src/views/auth/login.jsx +++ b/packages/ui/src/views/auth/login.jsx @@ -1,10 +1,12 @@ import { useEffect, useState } from 'react' // material-ui +import { Box } from '@mui/material' import { BackdropLoader } from '@/ui-component/loading/BackdropLoader' // project imports import MainCard from '@/ui-component/cards/MainCard' +import LanguageSwitcher from '@/ui-component/language/LanguageSwitcher' // API import authApi from '@/api/auth' @@ -38,6 +40,16 @@ const ResolveLoginPage = () => { return ( <> {loading && } + + + ) } diff --git a/packages/ui/src/views/auth/loginActivity.jsx b/packages/ui/src/views/auth/loginActivity.jsx index 46b855a8ce1..49aa07c9dbe 100644 --- a/packages/ui/src/views/auth/loginActivity.jsx +++ b/packages/ui/src/views/auth/loginActivity.jsx @@ -49,14 +49,17 @@ import { IconChevronLeft, IconChevronRight, IconCircleX, IconLogin, IconLogout } // store import { useError } from '@/store/context/ErrorContext' -const activityTypes = [ - 'Login Success', - 'Logout Success', - 'Unknown User', - 'Incorrect Credential', - 'User Disabled', - 'No Assigned Workspace', - 'Unknown Activity' +// i18n +import { useTranslation } from 'react-i18next' + +const unknownActivity = [ + 'auth.loginActivity.types.loginSuccess', + 'auth.loginActivity.types.logoutSuccess', + 'auth.loginActivity.types.unknownUser', + 'auth.loginActivity.types.incorrectCredential', + 'auth.loginActivity.types.userDisabled', + 'auth.loginActivity.types.noAssignedWorkspace', + 'auth.loginActivity.types.unknownActivity' ] const MenuProps = { PaperProps: { @@ -86,6 +89,7 @@ DatePickerCustomInput.propTypes = { onClick: PropTypes.func } const LoginActivity = () => { + const { t } = useTranslation() const theme = useTheme() const customization = useSelector((state) => state.customization) useNotifier() @@ -146,35 +150,35 @@ const LoginActivity = () => { function getActivityDescription(activityCode) { switch (activityCode) { case 0: - return 'Login Success' + return 'auth.loginActivity.types.loginSuccess' case 1: - return 'Logout Success' + return 'auth.loginActivity.types.logoutSuccess' case -1: - return 'Unknown User' + return 'auth.loginActivity.types.unknownUser' case -2: - return 'Incorrect Credential' + return 'auth.loginActivity.types.incorrectCredential' case -3: - return 'User Disabled' + return 'auth.loginActivity.types.userDisabled' case -4: - return 'No Assigned Workspace' + return 'auth.loginActivity.types.noAssignedWorkspace' default: - return 'Unknown Activity' + return 'auth.loginActivity.types.unknownActivity' } } function getActivityCode(activityDescription) { switch (activityDescription) { - case 'Login Success': + case 'auth.loginActivity.types.loginSuccess': return 0 - case 'Logout Success': + case 'auth.loginActivity.types.logoutSuccess': return 1 - case 'Unknown User': + case 'auth.loginActivity.types.unknownUser': return -1 - case 'Incorrect Credential': + case 'auth.loginActivity.types.incorrectCredential': return -2 - case 'User Disabled': + case 'auth.loginActivity.types.userDisabled': return -3 - case 'No Assigned Workspace': + case 'auth.loginActivity.types.noAssignedWorkspace': return -4 default: return -99 @@ -217,7 +221,7 @@ const LoginActivity = () => { ) : ( - +
{ }} >
- From: + {t('auth.loginActivity.inputs.from')} onStartDateSelected(date)} @@ -249,7 +253,7 @@ const LoginActivity = () => { />
- To: + {t('auth.loginActivity.inputs.to')} onEndDateSelected(date)} @@ -273,7 +277,7 @@ const LoginActivity = () => { }} > - Filter By + {t('auth.loginActivity.inputs.filterBy')} @@ -334,7 +338,7 @@ const LoginActivity = () => { } /> - Showing {Math.min(start, totalRecords)}-{end} of {totalRecords} Records + {t('auth.loginActivity.showing')} changePage(currentPage + 1)} @@ -362,7 +366,7 @@ const LoginActivity = () => { sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }} component={Paper} > -
+
{ }} > - Activity - User - Date - Method - Message + {t('auth.loginActivity.table.activity')} + {t('common.labels.user')} + {t('auth.loginActivity.table.date')} + {t('auth.loginActivity.table.method')} + {t('auth.loginActivity.table.message')} @@ -475,15 +479,17 @@ const LoginActivity = () => { /> )} -
{getActivityDescription(item.activityCode)}
+
{t(getActivityDescription(item.activityCode))}
{item.username} - {moment(item.attemptedDateTime).format('MMMM Do, YYYY, HH:mm')} + {moment(item.attemptedDateTime).format( + t('auth.loginActivity.formats.date') + )} - {item.loginMode ? item.loginMode : 'Email/Password'} + {item.loginMode ? item.loginMode : t('auth.loginActivity.emailPassword')} {item.message} diff --git a/packages/ui/src/views/auth/rateLimited.jsx b/packages/ui/src/views/auth/rateLimited.jsx index 44b8a85dd6f..094bfc8bd00 100644 --- a/packages/ui/src/views/auth/rateLimited.jsx +++ b/packages/ui/src/views/auth/rateLimited.jsx @@ -3,9 +3,13 @@ import { Link, useLocation } from 'react-router-dom' import unauthorizedSVG from '@/assets/images/unauthorized.svg' import MainCard from '@/ui-component/cards/MainCard' +// i18n +import { useTranslation } from 'react-i18next' + // ==============================|| RateLimitedPage ||============================== // const RateLimitedPage = () => { + const { t } = useTranslation() const location = useLocation() const retryAfter = location.state?.retryAfter || 60 @@ -32,14 +36,14 @@ const RateLimitedPage = () => { rateLimitedSVG - 429 Too Many Requests + {t('auth.rateLimit.title')} - {`You have made too many requests in a short period of time. Please wait ${retryAfter}s before trying again.`} + {t('auth.rateLimit.description', { retryAfter: retryAfter })} diff --git a/packages/ui/src/views/auth/register.jsx b/packages/ui/src/views/auth/register.jsx index 40311e0cb9e..d9c65e61a0a 100644 --- a/packages/ui/src/views/auth/register.jsx +++ b/packages/ui/src/views/auth/register.jsx @@ -9,6 +9,7 @@ import { Alert, Box, Button, Divider, Icon, List, ListItemText, OutlinedInput, S import { StyledButton } from '@/ui-component/button/StyledButton' import { Input } from '@/ui-component/input/Input' import { BackdropLoader } from '@/ui-component/loading/BackdropLoader' +import LanguageSwitcher from '@/ui-component/language/LanguageSwitcher' // API import accountApi from '@/api/account.api' @@ -33,70 +34,76 @@ import { store } from '@/store' import { loginSuccess } from '@/store/reducers/authSlice' import { IconCircleCheck, IconExclamationCircle } from '@tabler/icons-react' +// i18n +import { useTranslation, Trans } from 'react-i18next' + // ==============================|| Register ||============================== // // IMPORTANT: when updating this schema, update the schema on the server as well // packages/server/src/enterprise/Interface.Enterprise.ts -const RegisterEnterpriseUserSchema = z - .object({ - username: z.string().min(1, 'Name is required'), - email: z.string().min(1, 'Email is required').email('Invalid email address'), - password: passwordSchema, - confirmPassword: z.string().min(1, 'Confirm Password is required'), - token: z.string().min(1, 'Invite Code is required') - }) - .refine((data) => data.password === data.confirmPassword, { - message: "Passwords don't match", - path: ['confirmPassword'] - }) - -const RegisterCloudUserSchema = z - .object({ - username: z.string().min(1, 'Name is required'), - email: z.string().min(1, 'Email is required').email('Invalid email address'), - password: passwordSchema, - confirmPassword: z.string().min(1, 'Confirm Password is required') - }) - .refine((data) => data.password === data.confirmPassword, { - message: "Passwords don't match", - path: ['confirmPassword'] - }) +const RegisterEnterpriseUserSchema = (t) => + z + .object({ + username: z.string().min(1, t('common.validation.name.required')), + email: z.string().min(1, t('common.validation.email.required')).email(t('common.validation.email.invalid')), + password: passwordSchema(t), + confirmPassword: z.string().min(1, t('common.validation.confirm.required')), + token: z.string().min(1, t('common.validation.invite.required')) + }) + .refine((data) => data.password === data.confirmPassword, { + message: t('common.validation.confirm.match'), + path: ['confirmPassword'] + }) + +const RegisterCloudUserSchema = (t) => + z + .object({ + username: z.string().min(1, t('common.validation.name.required')), + email: z.string().min(1, t('common.validation.email.required')).email(t('common.validation.email.invalid')), + password: passwordSchema(t), + confirmPassword: z.string().min(1, t('common.validation.confirm.required')) + }) + .refine((data) => data.password === data.confirmPassword, { + message: t('common.validation.confirm.match'), + path: ['confirmPassword'] + }) const RegisterPage = () => { + const { t } = useTranslation() const theme = useTheme() useNotifier() const { isEnterpriseLicensed, isCloud, isOpenSource } = useConfig() const usernameInput = { - label: 'Username', + label: t('auth.inputs.username.title'), name: 'username', type: 'text', - placeholder: 'John Doe' + placeholder: t('auth.inputs.username.placeholder') } const passwordInput = { - label: 'Password', + label: t('auth.inputs.password.title'), name: 'password', type: 'password', placeholder: '********' } const confirmPasswordInput = { - label: 'Confirm Password', + label: t('auth.inputs.confirm.title'), name: 'confirmPassword', type: 'password', placeholder: '********' } const emailInput = { - label: 'EMail', + label: t('common.labels.email'), name: 'email', type: 'email', placeholder: 'user@company.com' } const inviteCodeInput = { - label: 'Invite Code', + label: t('auth.inputs.inviteCode.title'), name: 'inviteCode', type: 'text' } @@ -125,7 +132,7 @@ const RegisterPage = () => { event.preventDefault() setAuthRateLimitError(null) if (isEnterpriseLicensed) { - const result = RegisterEnterpriseUserSchema.safeParse({ + const result = RegisterEnterpriseUserSchema(t).safeParse({ username, email, token, @@ -150,7 +157,7 @@ const RegisterPage = () => { } else if (isCloud) { const formData = new FormData(event.target) const referral = formData.get('referral') - const result = RegisterCloudUserSchema.safeParse({ + const result = RegisterCloudUserSchema(t).safeParse({ username, email, password, @@ -184,11 +191,9 @@ const RegisterPage = () => { useEffect(() => { if (registerApi.error) { if (isEnterpriseLicensed) { - setAuthError( - `Error in registering user. Please contact your administrator. (${registerApi.error?.response?.data?.message})` - ) + setAuthError(t('auth.register.messages.error.enterprise', { msg: registerApi.error?.response?.data?.message })) } else if (isCloud) { - setAuthError(`Error in registering user. Please try again.`) + setAuthError(t('auth.register.messages.error.simple')) } setLoading(false) } @@ -241,9 +246,9 @@ const RegisterPage = () => { setUsername('') setEmail('') if (isEnterpriseLicensed) { - setSuccessMsg('Registration Successful. You will be redirected to the sign in page shortly.') + setSuccessMsg(t('auth.register.messages.success.enterprise')) } else if (isCloud) { - setSuccessMsg('To complete your registration, please click on the verification link we sent to your email address') + setSuccessMsg(t('auth.register.messages.success.cloud')) } setTimeout(() => { navigate('/signin') @@ -290,13 +295,12 @@ const RegisterPage = () => { )} - Sign Up + {t('auth.register.signup')} - Already have an account?{' '} - - Sign In - - . + }} + />
@@ -304,25 +308,27 @@ const RegisterPage = () => {
- Full Name * + {t('auth.inputs.fullName.title')} +  *
setUsername(newValue)} value={username} showDialog={false} /> - Is used for display purposes only. + {t('auth.inputs.fullName.caption')}
- Email * + {t('common.labels.email')} +  *
@@ -333,50 +339,50 @@ const RegisterPage = () => { showDialog={false} /> - Kindly use a valid email address. Will be used as login id. + {t('auth.inputs.email.caption')}
{isEnterpriseLicensed && (
- Invite Code * + {t('auth.inputs.inviteCode.title')} +  *
setToken(e.target.value)} value={token} /> - Please copy the token you would have received in your email. + {t('auth.inputs.inviteCode.caption')}
)}
- Password * + {t('auth.inputs.password.title')} +  *
setPassword(newValue)} value={password} /> - - Password must be at least 8 characters long and contain at least one lowercase letter, one uppercase - letter, one digit, and one special character. - + {t('auth.inputs.password.caption')}
- Confirm Password * + {t('auth.inputs.confirm.title')} +  *
@@ -386,13 +392,13 @@ const RegisterPage = () => { value={confirmPassword} /> - Confirm your password. Must match the password typed above. + {t('auth.inputs.confirm.caption')}
- Create Account + {t('auth.actions.create')} - {configuredSsoProviders.length > 0 && OR} + {configuredSsoProviders.length > 0 && {t('auth.signin.or')}} {configuredSsoProviders && configuredSsoProviders.map( (ssoProvider) => @@ -409,7 +415,7 @@ const RegisterPage = () => { } > - Sign In With Microsoft + {t('auth.actions.microsoft')} ) )} @@ -428,7 +434,7 @@ const RegisterPage = () => { } > - Sign In With Google + {t('auth.actions.google')} ) )} @@ -447,7 +453,7 @@ const RegisterPage = () => { } > - Sign In With Auth0 by Okta + {t('auth.actions.auth0')} ) )} @@ -466,7 +472,7 @@ const RegisterPage = () => { } > - Sign In With Github + {t('auth.actions.github')} ) )} @@ -474,6 +480,16 @@ const RegisterPage = () => { + + + {loading && } ) diff --git a/packages/ui/src/views/auth/resetPassword.jsx b/packages/ui/src/views/auth/resetPassword.jsx index f451b8a20bd..40f78cd636d 100644 --- a/packages/ui/src/views/auth/resetPassword.jsx +++ b/packages/ui/src/views/auth/resetPassword.jsx @@ -25,9 +25,13 @@ import { useError } from '@/store/context/ErrorContext' // Icons import { IconExclamationCircle, IconX } from '@tabler/icons-react' +// i18n +import { useTranslation, Trans } from 'react-i18next' + // ==============================|| ResetPasswordPage ||============================== // const ResetPasswordPage = () => { + const { t } = useTranslation() const theme = useTheme() useNotifier() const navigate = useNavigate() @@ -36,28 +40,28 @@ const ResetPasswordPage = () => { const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) const emailInput = { - label: 'Email', + label: t('common.labels.email'), name: 'email', type: 'email', placeholder: 'user@company.com' } const passwordInput = { - label: 'Password', + label: t('auth.inputs.password.title'), name: 'password', type: 'password', placeholder: '********' } const confirmPasswordInput = { - label: 'Confirm Password', + label: t('auth.inputs.confirm.title'), name: 'confirmPassword', type: 'password', placeholder: '********' } const resetPasswordInput = { - label: 'Reset Token', + label: t('auth.inputs.reset.title'), name: 'resetToken', type: 'text' } @@ -85,10 +89,10 @@ const ResetPasswordPage = () => { setAuthErrors([]) setAuthRateLimitError(null) if (!tokenVal) { - validationErrors.push('Token cannot be left blank!') + validationErrors.push(t('auth.resetPassword.validation.token')) } if (newPasswordVal !== confirmPasswordVal) { - validationErrors.push('New Password and Confirm Password do not match.') + validationErrors.push(t('auth.resetPassword.validation.match')) } const passwordErrors = validatePassword(newPasswordVal) if (passwordErrors.length > 0) { @@ -112,7 +116,7 @@ const ResetPasswordPage = () => { setLoading(false) if (updateResponse.data) { enqueueSnackbar({ - message: 'Password reset successful', + message: t('auth.resetPassword.messages.reset.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -133,7 +137,7 @@ const ResetPasswordPage = () => { setLoading(false) setAuthErrors([typeof error.response.data === 'object' ? error.response.data.message : error.response.data]) enqueueSnackbar({ - message: `Failed to reset password!`, + message: t('auth.resetPassword.messages.reset.error'), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -172,12 +176,12 @@ const ResetPasswordPage = () => { )} - Reset Password + {t('auth.resetPassword.title')} - - Back to Login - - . + }} + />
@@ -185,7 +189,8 @@ const ResetPasswordPage = () => {
- Email * + {t('common.labels.email')} +  *
@@ -200,14 +205,15 @@ const ResetPasswordPage = () => {
- Reset Token * + {t('auth.inputs.reset.title')} +  *
{ sx={{ mt: '8px' }} /> - Please copy the token you received in your email. + {t('auth.inputs.reset.caption')}
- New Password * + {t('auth.inputs.newPassword.title')} +  *
@@ -234,16 +241,14 @@ const ResetPasswordPage = () => { showDialog={false} /> - - Password must be at least 8 characters long and contain at least one lowercase letter, one uppercase - letter, one digit, and one special character. - + {t('auth.inputs.password.caption')}
- Confirm Password * + {t('auth.inputs.confirm.title')} +  *
@@ -254,12 +259,12 @@ const ResetPasswordPage = () => { showDialog={false} /> - Confirm your new password. Must match the password typed above. + {t('auth.inputs.confirm.caption')}
- Update Password + {t('auth.actions.update')} diff --git a/packages/ui/src/views/auth/signIn.jsx b/packages/ui/src/views/auth/signIn.jsx index 6328ef8ebe3..0aa3d1446cf 100644 --- a/packages/ui/src/views/auth/signIn.jsx +++ b/packages/ui/src/views/auth/signIn.jsx @@ -10,6 +10,7 @@ import { LoadingButton } from '@mui/lab' // project imports import MainCard from '@/ui-component/cards/MainCard' import { Input } from '@/ui-component/input/Input' +import LanguageSwitcher from '@/ui-component/language/LanguageSwitcher' // Hooks import useApi from '@/hooks/useApi' @@ -24,6 +25,7 @@ import ssoApi from '@/api/sso' // utils import useNotifier from '@/utils/useNotifier' +import { applyPersistedLanguage } from '@/utils/language' // store import { loginSuccess, logoutSuccess } from '@/store/reducers/authSlice' @@ -35,22 +37,26 @@ import GoogleSSOLoginIcon from '@/assets/images/google.svg' import Auth0SSOLoginIcon from '@/assets/images/auth0.svg' import GithubSSOLoginIcon from '@/assets/images/github.svg' +// i18n +import { useTranslation, Trans } from 'react-i18next' + // ==============================|| SignInPage ||============================== // const SignInPage = () => { + const { t } = useTranslation() const theme = useTheme() useSelector((state) => state.customization) useNotifier() const { isEnterpriseLicensed, isCloud, isOpenSource } = useConfig() const usernameInput = { - label: 'Username', + label: t('auth.inputs.username.title'), name: 'username', type: 'email', placeholder: 'user@company.com' } const passwordInput = { - label: 'Password', + label: t('auth.inputs.password.title'), name: 'password', type: 'password', placeholder: '********', @@ -97,6 +103,7 @@ const SignInPage = () => { useEffect(() => { store.dispatch(logoutSuccess()) + applyPersistedLanguage() setAuthRateLimitError(null) if (!isOpenSource) { getDefaultProvidersApi.request() @@ -169,10 +176,10 @@ const SignInPage = () => { try { await resendVerificationApi.request({ email: usernameVal }) setAuthError(undefined) - setSuccessMessage('Verification email has been sent successfully.') + setSuccessMessage(t('auth.signin.messages.success')) setShowResendButton(false) } catch (error) { - setAuthError(error.response?.data?.message || 'Failed to send verification email.') + setAuthError(error.response?.data?.message || t('auth.signin.messages.error')) } } @@ -192,34 +199,32 @@ const SignInPage = () => { )} {authError && ( } variant='filled' severity='error'> - {authError} + {showResendButton ? t('auth.signin.unverified') : authError} )} {showResendButton && ( )} - Sign In + {t('auth.signin.title')} {isCloud && ( - Don't have an account?{' '} - - Sign up for free - - . + }} + /> )} {isEnterpriseLicensed && ( - Have an invite code?{' '} - - Sign up for an account - - . + }} + /> )} @@ -228,7 +233,8 @@ const SignInPage = () => {
- Email * + {t('common.labels.email')} +  *
@@ -242,14 +248,15 @@ const SignInPage = () => {
- Password * + {t('auth.inputs.password.title')} +  *
setPasswordVal(newValue)} value={passwordVal} /> - Forgot password? + {t('auth.forgotPassword.title')}
@@ -259,9 +266,11 @@ const SignInPage = () => { style={{ borderRadius: 12, height: 40, marginRight: 5 }} type='submit' > - Login + {t('auth.actions.login')} - {configuredSsoProviders && configuredSsoProviders.length > 0 && OR} + {configuredSsoProviders && configuredSsoProviders.length > 0 && ( + {t('auth.signin.or')} + )} {configuredSsoProviders && configuredSsoProviders.map( (ssoProvider) => @@ -278,7 +287,7 @@ const SignInPage = () => { } > - Sign In With Microsoft + {t('auth.actions.microsoft')} ) )} @@ -297,7 +306,7 @@ const SignInPage = () => { } > - Sign In With Google + {t('auth.actions.google')} ) )} @@ -316,7 +325,7 @@ const SignInPage = () => { } > - Sign In With Auth0 by Okta + {t('auth.actions.auth0')} ) )} @@ -335,7 +344,7 @@ const SignInPage = () => { } > - Sign In With Github + {t('auth.actions.github')} ) )} @@ -343,6 +352,16 @@ const SignInPage = () => { + + + ) } diff --git a/packages/ui/src/views/auth/ssoConfig.jsx b/packages/ui/src/views/auth/ssoConfig.jsx index b61aa40d709..7289f216a79 100644 --- a/packages/ui/src/views/auth/ssoConfig.jsx +++ b/packages/ui/src/views/auth/ssoConfig.jsx @@ -33,10 +33,14 @@ import GithubSVG from '@/assets/images/github.svg' // const import { gridSpacing } from '@/store/constant' +// i18n +import { useTranslation } from 'react-i18next' + // API never sends clientSecret; show asterisks when a secret is already configured const PLACEHOLDER_SECRET = '********' const SSOConfigPage = () => { + const { t } = useTranslation() useNotifier() const { error, setError } = useError() const theme = useTheme() @@ -84,42 +88,42 @@ const SSOConfigPage = () => { const validateAzureFields = (validationErrors) => { if (!azureTenantID) { - validationErrors.push('Azure TenantID cannot be left blank!') + validationErrors.push(t('auth.sso.validation.azure.tenantId')) } if (!azureClientID) { - validationErrors.push('Azure ClientID cannot be left blank!') + validationErrors.push(t('auth.sso.validation.azure.clientId')) } if (!azureClientSecret) { - validationErrors.push('Azure Client Secret cannot be left blank!') + validationErrors.push(t('auth.sso.validation.azure.clientSecret')) } } const validateGoogleFields = (validationErrors) => { if (!googleClientID) { - validationErrors.push('Google ClientID cannot be left blank!') + validationErrors.push(t('auth.sso.validation.google.clientId')) } if (!googleClientSecret) { - validationErrors.push('Google Client Secret cannot be left blank!') + validationErrors.push(t('auth.sso.validation.google.clientSecret')) } } const validateGithubFields = (validationErrors) => { if (!githubClientID) { - validationErrors.push('Github ClientID cannot be left blank!') + validationErrors.push(t('auth.sso.validation.github.clientId')) } if (!githubClientSecret) { - validationErrors.push('Github Client Secret cannot be left blank!') + validationErrors.push(t('auth.sso.validation.github.clientSecret')) } } const validateAuth0Fields = (validationErrors) => { if (!auth0Domain) { - validationErrors.push('Auth0 Domain cannot be left blank!') + validationErrors.push(t('auth.sso.validation.auth0.domain')) } if (!auth0ClientID) { - validationErrors.push('Auth0 ClientID cannot be left blank!') + validationErrors.push(t('auth.sso.validation.auth0.clientId')) } if (!auth0ClientSecret) { - validationErrors.push('Auth0 Client Secret cannot be left blank!') + validationErrors.push(t('auth.sso.validation.auth0.clientSecret')) } } @@ -147,7 +151,7 @@ const SSOConfigPage = () => { userId: currentUser.id, providers: [ { - providerLabel: 'Microsoft', + providerLabel: t('common.providers.microsoft'), providerName: 'azure', config: { tenantID: azureTenantID, @@ -157,7 +161,7 @@ const SSOConfigPage = () => { status: azureConfigEnabled ? 'enable' : 'disable' }, { - providerLabel: 'Google', + providerLabel: t('common.providers.google'), providerName: 'google', config: { clientID: googleClientID, @@ -166,7 +170,7 @@ const SSOConfigPage = () => { status: googleConfigEnabled ? 'enable' : 'disable' }, { - providerLabel: 'Auth0', + providerLabel: t('common.providers.auth0'), providerName: 'auth0', config: { domain: auth0Domain, @@ -176,7 +180,7 @@ const SSOConfigPage = () => { status: auth0ConfigEnabled ? 'enable' : 'disable' }, { - providerLabel: 'Github', + providerLabel: t('common.providers.github'), providerName: 'github', config: { clientID: githubClientID, @@ -202,7 +206,7 @@ const SSOConfigPage = () => { setLoading(false) if (updateResponse.data) { enqueueSnackbar({ - message: 'SSO Configuration Updated!', + message: t('auth.sso.messages.update.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -218,7 +222,7 @@ const SSOConfigPage = () => { setLoading(false) setAuthErrors([typeof error.response.data === 'object' ? error.response.data.message : error.response.data]) enqueueSnackbar({ - message: `Failed to update SSO Configuration.`, + message: t('auth.sso.messages.update.error'), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -264,7 +268,7 @@ const SSOConfigPage = () => { setLoading(false) if (updateResponse.data?.message) { enqueueSnackbar({ - message: `${getSelectedProviderName()} SSO Configuration is Valid!`, + message: t('auth.sso.messages.validation.success', { providerName: getSelectedProviderName() }), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -295,7 +299,7 @@ const SSOConfigPage = () => { setLoading(false) setAuthErrors([typeof error.response.data === 'object' ? error.response.data.message : error.response.data]) enqueueSnackbar({ - message: `Failed to verify ${getSelectedProviderName()} SSO Configuration.`, + message: t('auth.sso.messages.validation.error', { providerName: getSelectedProviderName() }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -414,7 +418,7 @@ const SSOConfigPage = () => { ) : ( - + {authErrors && authErrors.length > 0 && (
{
)} - setTabValue(val)} aria-label='tabs'> + setTabValue(val)} + aria-label={t('common.labels.tabs')} + > } @@ -460,7 +469,7 @@ const SSOConfigPage = () => { value={0} label={ <> - Microsoft + {t('common.providers.microsoft')} {azureConfigEnabled && (
{ value={1} label={ <> - Google + {t('common.providers.google')} {googleConfigEnabled && (
{ value={2} label={ <> - Auth0 + {t('common.providers.auth0')} {auth0ConfigEnabled && (
{ value={3} label={ <> - Github + {t('common.providers.github')} {githubConfigEnabled && (
{ }} > - Enable SSO Login + + {' '} + {t('auth.inputs.enableSso.title')} + { {azureCallbackURL} { navigator.clipboard.writeText(azureCallbackURL) @@ -667,7 +679,7 @@ const SSOConfigPage = () => {
- Tenant ID + {t('auth.inputs.tenantId.title')}
{ type='string' fullWidth size='small' - placeholder='Tenant ID' + placeholder={t('auth.inputs.tenantId.title')} name='azureTenantID' onChange={(e) => setAzureTenantID(e.target.value)} value={azureTenantID} @@ -684,7 +696,8 @@ const SSOConfigPage = () => {
- Client ID * + {t('auth.inputs.clientId.title')} +  *
@@ -693,7 +706,7 @@ const SSOConfigPage = () => { type='string' fullWidth size='small' - placeholder='Client ID' + placeholder={t('auth.inputs.clientId.title')} name='azureClientID' onChange={(e) => setAzureClientID(e.target.value)} value={azureClientID} @@ -702,7 +715,8 @@ const SSOConfigPage = () => {
- Client Secret * + {t('auth.inputs.clientSecret.title')} +  *
@@ -711,7 +725,7 @@ const SSOConfigPage = () => { type='password' fullWidth size='small' - placeholder='Client Secret' + placeholder={t('auth.inputs.clientSecret.title')} name='azureClientSecret' onChange={(e) => setAzureClientSecret(e.target.value)} value={azureClientSecret} @@ -728,7 +742,10 @@ const SSOConfigPage = () => { }} > - Enable SSO Login + + {' '} + {t('auth.inputs.enableSso.title')} + { {googleCallbackURL} { navigator.clipboard.writeText(googleCallbackURL) @@ -767,7 +784,8 @@ const SSOConfigPage = () => {
- Client ID * + {t('auth.inputs.clientId.title')} +  *
@@ -776,7 +794,7 @@ const SSOConfigPage = () => { type='string' fullWidth size='small' - placeholder='Client ID' + placeholder={t('auth.inputs.clientId.title')} name='googleClientID' onChange={(e) => setGoogleClientID(e.target.value)} value={googleClientID} @@ -785,7 +803,8 @@ const SSOConfigPage = () => {
- Client Secret * + {t('auth.inputs.clientSecret.title')} +  *
@@ -794,7 +813,7 @@ const SSOConfigPage = () => { type='password' fullWidth size='small' - placeholder='Client Secret' + placeholder={t('auth.inputs.clientSecret.title')} name='googleClientSecret' onChange={(e) => setGoogleClientSecret(e.target.value)} value={googleClientSecret} @@ -811,7 +830,10 @@ const SSOConfigPage = () => { }} > - Enable SSO Login + + {' '} + {t('auth.inputs.enableSso.title')} + { {auth0CallbackURL} { navigator.clipboard.writeText(auth0CallbackURL) @@ -849,7 +871,7 @@ const SSOConfigPage = () => {
- Auth0 Domain + {t('auth.inputs.domain.title')}
{ type='string' fullWidth size='small' - placeholder='Auth0 Domain' + placeholder={t('auth.inputs.domain.title')} name='auth0Domain' onChange={(e) => setAuth0Domain(e.target.value)} value={auth0Domain} @@ -866,7 +888,8 @@ const SSOConfigPage = () => {
- Client ID * + {t('auth.inputs.clientId.title')} +  *
@@ -875,7 +898,7 @@ const SSOConfigPage = () => { type='string' fullWidth size='small' - placeholder='Client ID' + placeholder={t('auth.inputs.clientId.title')} name='auth0ClientID' onChange={(e) => setAuth0ClientID(e.target.value)} value={auth0ClientID} @@ -884,7 +907,8 @@ const SSOConfigPage = () => {
- Client Secret * + {t('auth.inputs.clientSecret.title')} +  *
@@ -893,7 +917,7 @@ const SSOConfigPage = () => { type='password' fullWidth size='small' - placeholder='Client Secret' + placeholder={t('auth.inputs.clientSecret.title')} name='auth0ClientSecret' onChange={(e) => setAuth0ClientSecret(e.target.value)} value={auth0ClientSecret} @@ -910,7 +934,10 @@ const SSOConfigPage = () => { }} > - Enable SSO Login + + {' '} + {t('auth.inputs.enableSso.title')} + { {githubCallbackURL} { navigator.clipboard.writeText(githubCallbackURL) @@ -949,7 +976,8 @@ const SSOConfigPage = () => {
- Client ID * + {t('auth.inputs.clientId.title')} +  *
@@ -958,7 +986,7 @@ const SSOConfigPage = () => { type='string' fullWidth size='small' - placeholder='Client ID' + placeholder={t('auth.inputs.clientId.title')} name='githubClientID' onChange={(e) => setGithubClientID(e.target.value)} value={githubClientID} @@ -967,7 +995,8 @@ const SSOConfigPage = () => {
- Client Secret * + {t('auth.inputs.clientSecret.title')} +  *
@@ -976,7 +1005,7 @@ const SSOConfigPage = () => { type='password' fullWidth size='small' - placeholder='Client Secret' + placeholder={t('auth.inputs.clientSecret.title')} name='githubClientSecret' onChange={(e) => setGithubClientSecret(e.target.value)} value={githubClientSecret} @@ -993,7 +1022,7 @@ const SSOConfigPage = () => { style={{ marginBottom: 10, marginTop: 10, marginRight: 10 }} onClick={() => validateAndTest(getSelectedProviderName())} > - {'Test ' + getSelectedProviderName() + ' Configuration'} + {t('auth.actions.test', { providerName: getSelectedProviderName() })} { variant='contained' onClick={() => validateAndSubmit()} > - Save + {t('common.actions.save')}
@@ -1023,7 +1052,7 @@ const SSOConfigPage = () => { }} > - Copied! + {t('common.messages.copied')} diff --git a/packages/ui/src/views/auth/ssoSuccess.jsx b/packages/ui/src/views/auth/ssoSuccess.jsx index 9476aa64644..ae643a0f6eb 100644 --- a/packages/ui/src/views/auth/ssoSuccess.jsx +++ b/packages/ui/src/views/auth/ssoSuccess.jsx @@ -1,10 +1,21 @@ import { useEffect } from 'react' import { useLocation, useNavigate } from 'react-router-dom' + +// material-ui +import { Box } from '@mui/material' + +// project imports +import LanguageSwitcher from '@/ui-component/language/LanguageSwitcher' + import { store } from '@/store' import { loginSuccess } from '@/store/reducers/authSlice' import authApi from '@/api/auth' +// i18n +import { useTranslation } from 'react-i18next' + const SSOSuccess = () => { + const { t } = useTranslation() const location = useLocation() const navigate = useNavigate() @@ -36,10 +47,22 @@ const SSOSuccess = () => { }, [location.search]) return ( -
-

Loading dashboard...

-

Loading data...

-
+ <> +
+

{t('auth.sso.loading.dashboard')}

+

{t('auth.sso.loading.data')}

+
+ + + + ) } diff --git a/packages/ui/src/views/auth/unauthorized.jsx b/packages/ui/src/views/auth/unauthorized.jsx index 492007741cf..412cbe51287 100644 --- a/packages/ui/src/views/auth/unauthorized.jsx +++ b/packages/ui/src/views/auth/unauthorized.jsx @@ -5,9 +5,13 @@ import { StyledButton } from '@/ui-component/button/StyledButton' import { Link } from 'react-router-dom' import { useSelector } from 'react-redux' +// i18n +import { useTranslation } from 'react-i18next' + // ==============================|| UnauthorizedPage ||============================== // const UnauthorizedPage = () => { + const { t } = useTranslation() const currentUser = useSelector((state) => state.auth.user) return ( @@ -36,18 +40,18 @@ const UnauthorizedPage = () => { />
- 403 Forbidden + {t('auth.unauthorized.forbidden')} - You do not have permission to access this page. + {t('auth.unauthorized.permission')} {currentUser ? ( - Back to Home + {t('auth.actions.backHome')} ) : ( - Back to Login + {t('auth.actions.backLogin')} )} diff --git a/packages/ui/src/views/auth/verify-email.jsx b/packages/ui/src/views/auth/verify-email.jsx index 1a5d050741d..1c91f652503 100644 --- a/packages/ui/src/views/auth/verify-email.jsx +++ b/packages/ui/src/views/auth/verify-email.jsx @@ -18,7 +18,11 @@ import { IconCheck } from '@tabler/icons-react' import { useState } from 'react' import { IconX } from '@tabler/icons-react' +// i18n +import { useTranslation } from 'react-i18next' + const VerifyEmail = () => { + const { t } = useTranslation() const accountVerifyApi = useApi(accountApi.verifyAccountEmail) const [searchParams] = useSearchParams() @@ -73,7 +77,7 @@ const VerifyEmail = () => { height: '48px' }} /> - Verifying Email... + {t('auth.verify.loading')} )} {verificationError && ( @@ -92,7 +96,7 @@ const VerifyEmail = () => { >
- Verification Failed. + {t('auth.verify.failed')} )} {verificationSuccess && ( @@ -111,7 +115,7 @@ const VerifyEmail = () => { >
- Email Verified Successfully. + {t('auth.verify.success')} )} diff --git a/packages/ui/src/views/canvas/AddNodes.jsx b/packages/ui/src/views/canvas/AddNodes.jsx index 6a7d492caa6..75d65ea9e80 100644 --- a/packages/ui/src/views/canvas/AddNodes.jsx +++ b/packages/ui/src/views/canvas/AddNodes.jsx @@ -47,6 +47,9 @@ import utilNodesPNG from '@/assets/images/utilNodes.png' import { baseURL, AGENTFLOW_ICONS } from '@/store/constant' import { SET_COMPONENT_NODES } from '@/store/actions' +// i18n +import { useTranslation } from 'react-i18next' + // ==============================|| ADD NODES||============================== // function a11yProps(index) { return { @@ -71,6 +74,7 @@ const blacklistForChatflowCanvas = { } const AddNodes = ({ nodesData, node, isAgentCanvas, isAgentflowv2, onFlowGenerated }) => { + const { t } = useTranslation() const theme = useTheme() const customization = useSelector((state) => state.customization) const dispatch = useDispatch() @@ -391,9 +395,8 @@ const AddNodes = ({ nodesData, node, isAgentCanvas, isAgentflowv2, onFlowGenerat const handleOpenDialog = () => { setOpenDialog(true) setDialogProps({ - title: 'What would you like to build?', - description: - 'Enter your prompt to generate an agentflow. Performance may vary with different models. Only nodes and edges are generated, you will need to fill in the input fields for each node.' + title: t('canvas.dialogs.build.title'), + description: t('canvas.dialogs.build.description') }) } @@ -413,8 +416,8 @@ const AddNodes = ({ nodesData, node, isAgentCanvas, isAgentflowv2, onFlowGenerat ref={anchorRef} size='small' color='primary' - aria-label='add' - title='Add Node' + aria-label={t('canvas.actions.addNode.label')} + title={t('canvas.actions.addNode.title')} onClick={handleToggle} > {open ? : } @@ -432,8 +435,8 @@ const AddNodes = ({ nodesData, node, isAgentCanvas, isAgentflowv2, onFlowGenerat onClick={handleOpenDialog} size='small' color='primary' - aria-label='generate' - title='Generate Agentflow' + aria-label={t('common.actions.generate')} + title={t('canvas.actions.denerateAgentflow.title')} > @@ -472,7 +475,7 @@ const AddNodes = ({ nodesData, node, isAgentCanvas, isAgentflowv2, onFlowGenerat - Add Nodes + {t('canvas.addNodes.title')} filterSearch(e.target.value)} - placeholder='Search nodes' + placeholder={t('canvas.inputs.search.placeholder')} startAdornment={ @@ -497,7 +500,7 @@ const AddNodes = ({ nodesData, node, isAgentCanvas, isAgentflowv2, onFlowGenerat color: theme.palette.grey[900] } }} - title='Clear Search' + title={t('common.actions.clearSearch')} > {!isAgentCanvas && ( @@ -520,7 +523,7 @@ const AddNodes = ({ nodesData, node, isAgentCanvas, isAgentflowv2, onFlowGenerat variant='fullWidth' value={tabValue} onChange={handleTabChange} - aria-label='tabs' + aria-label={t('common.labels.tabs')} > {['LangChain', 'LlamaIndex', 'Utilities'].map((item, index) => ( { + const { t } = useTranslation() const theme = useTheme() const dispatch = useDispatch() const navigate = useNavigate() @@ -63,7 +67,7 @@ const CanvasHeader = ({ chatflow, isAgentCanvas, isAgentflowV2, handleSaveFlow, const [savePermission, setSavePermission] = useState(isAgentCanvas ? 'agentflows:create' : 'chatflows:create') - const title = isAgentCanvas ? 'Agents' : 'Chatflow' + const title = t(isAgentCanvas ? 'common.labels.agents' : 'common.labels.chatflow') const updateChatflowApi = useApi(chatflowsApi.updateChatflow) const canvas = useSelector((state) => state.canvas) @@ -75,21 +79,21 @@ const CanvasHeader = ({ chatflow, isAgentCanvas, isAgentflowV2, handleSaveFlow, handleDeleteFlow() } else if (setting === 'viewMessages') { setViewMessagesDialogProps({ - title: 'View Messages', + title: t('canvas.dialogs.viewMessages'), chatflow: chatflow, isChatflow: isAgentflowV2 ? false : true }) setViewMessagesDialogOpen(true) } else if (setting === 'viewLeads') { setViewLeadsDialogProps({ - title: 'View Leads', + title: t('canvas.dialogs.viewLeads'), chatflow: chatflow }) setViewLeadsDialogOpen(true) } else if (setting === 'saveAsTemplate') { if (canvas.isDirty) { enqueueSnackbar({ - message: 'Please save the flow before exporting as template', + message: t('canvas.messages.saveAsTemplate.error'), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -104,19 +108,19 @@ const CanvasHeader = ({ chatflow, isAgentCanvas, isAgentflowV2, handleSaveFlow, return } setExportAsTemplateDialogProps({ - title: 'Export As Template', + title: t('canvas.dialogs.exportTemplate'), chatflow: chatflow }) setExportAsTemplateDialogOpen(true) } else if (setting === 'viewUpsertHistory') { setUpsertHistoryDialogProps({ - title: 'View Upsert History', + title: t('canvas.dialogs.viewUpsertHistory'), chatflow: chatflow }) setUpsertHistoryDialogOpen(true) } else if (setting === 'chatflowConfiguration') { setChatflowConfigurationDialogProps({ - title: `${title} Configuration`, + title: t('canvas.dialogs.configuration', { title: title }), chatflow: chatflow }) setChatflowConfigurationDialogOpen(true) @@ -202,7 +206,7 @@ const CanvasHeader = ({ chatflow, isAgentCanvas, isAgentflowV2, handleSaveFlow, } setAPIDialogProps({ - title: 'Embed in website or use as API', + title: t('canvas.dialogs.embed'), chatflowid: chatflow.id, chatflowApiKeyId: chatflow.apikeyid, isFormDataRequired, @@ -241,7 +245,7 @@ const CanvasHeader = ({ chatflow, isAgentCanvas, isAgentflowV2, handleSaveFlow, // if configuration dialog is open, update its data if (chatflowConfigurationDialogOpen) { setChatflowConfigurationDialogProps({ - title: `${title} Configuration`, + title: t('canvas.dialogs.configuration', { title: title }), chatflow }) } @@ -253,7 +257,7 @@ const CanvasHeader = ({ chatflow, isAgentCanvas, isAgentflowV2, handleSaveFlow, - + {chatflow?.id && ( - + - + - + {chatflow?.id && ( - + )} - + - + setFlowDialogOpen(false)} onConfirm={onConfirmSaveName} diff --git a/packages/ui/src/views/canvas/CanvasNode.jsx b/packages/ui/src/views/canvas/CanvasNode.jsx index 03d9e51ee2c..920c2cc54cf 100644 --- a/packages/ui/src/views/canvas/CanvasNode.jsx +++ b/packages/ui/src/views/canvas/CanvasNode.jsx @@ -21,9 +21,13 @@ import { IconTrash, IconCopy, IconInfoCircle, IconAlertTriangle } from '@tabler/ import { flowContext } from '@/store/context/ReactFlowContext' import LlamaindexPNG from '@/assets/images/llamaindex.png' +// i18n +import { useTranslation } from 'react-i18next' + // ===========================|| CANVAS NODE ||=========================== // const CanvasNode = ({ data }) => { + const { t } = useTranslation() const theme = useTheme() const canvas = useSelector((state) => state.canvas) const { deleteNode, duplicateNode } = useContext(flowContext) @@ -49,16 +53,17 @@ const CanvasNode = ({ data }) => { else return !canvas.canvasDialogShow && open } - const nodeOutdatedMessage = (oldVersion, newVersion) => `Node version ${oldVersion} outdated\nUpdate to latest version ${newVersion}` + const nodeOutdatedMessage = (oldVersion, newVersion) => + t('canvas.messages.nodeVersion.outdated', { oldVersion: oldVersion, newVersion: newVersion }) - const nodeVersionEmptyMessage = (newVersion) => `Node outdated\nUpdate to latest version ${newVersion}` + const nodeVersionEmptyMessage = (newVersion) => t('canvas.messages.nodeVersion.versionEmpty', { newVersion: newVersion }) const onDialogClicked = () => { const dialogProps = { data, inputParams: data.inputParams.filter((inputParam) => !inputParam.hidden).filter((param) => param.additionalParams), - confirmButtonName: 'Save', - cancelButtonName: 'Cancel' + confirmButtonName: t('common.actions.save'), + cancelButtonName: t('common.actions.cancel') } setDialogProps(dialogProps) setShowDialog(true) @@ -78,10 +83,7 @@ const CanvasNode = ({ data }) => { } else if (data.version && componentNode.version > data.version) { setWarningMessage(nodeOutdatedMessage(data.version, componentNode.version)) } else if (componentNode.badge === 'DEPRECATING') { - setWarningMessage( - componentNode?.deprecateMessage ?? - 'This node will be deprecated in the next release. Change to a new node tagged with NEW' - ) + setWarningMessage(componentNode?.deprecateMessage ?? t('canvas.messages.nodeVersion.deprecated')) } else if (componentNode.warning) { setWarningMessage(componentNode.warning) } else { @@ -114,7 +116,7 @@ const CanvasNode = ({ data }) => { }} > { duplicateNode(data.id) }} @@ -124,7 +126,7 @@ const CanvasNode = ({ data }) => { { deleteNode(data.id) }} @@ -134,7 +136,7 @@ const CanvasNode = ({ data }) => { { setInfoDialogProps({ data }) setShowInfoDialog(true) @@ -217,7 +219,7 @@ const CanvasNode = ({ data }) => { textAlign: 'center' }} > - Inputs + {t('canvas.inputsLabel')} @@ -255,7 +257,7 @@ const CanvasNode = ({ data }) => { }} >
)} @@ -268,7 +270,7 @@ const CanvasNode = ({ data }) => { textAlign: 'center' }} > - Output + {t('common.labels.output')} )} diff --git a/packages/ui/src/views/canvas/CredentialInputHandler.jsx b/packages/ui/src/views/canvas/CredentialInputHandler.jsx index e713a22d1fa..409747290c4 100644 --- a/packages/ui/src/views/canvas/CredentialInputHandler.jsx +++ b/packages/ui/src/views/canvas/CredentialInputHandler.jsx @@ -15,9 +15,13 @@ import credentialsApi from '@/api/credentials' import { useAuth } from '@/hooks/useAuth' import { FLOWISE_CREDENTIAL_ID } from '@/store/constant' +// i18n +import { useTranslation } from 'react-i18next' + // ===========================|| CredentialInputHandler ||=========================== // const CredentialInputHandler = ({ inputParam, data, onSelect, disabled = false }) => { + const { t } = useTranslation() const ref = useRef(null) const [credentialId, setCredentialId] = useState(data?.credential || (data?.inputs && data.inputs[FLOWISE_CREDENTIAL_ID]) || '') const [showCredentialListDialog, setShowCredentialListDialog] = useState(false) @@ -30,8 +34,8 @@ const CredentialInputHandler = ({ inputParam, data, onSelect, disabled = false } const editCredential = (credentialId) => { const dialogProp = { type: 'EDIT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Save', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.save'), credentialId } setSpecificCredentialDialogProps(dialogProp) @@ -50,7 +54,7 @@ const CredentialInputHandler = ({ inputParam, data, onSelect, disabled = false } if (componentCredentialsResp.data) { if (Array.isArray(componentCredentialsResp.data)) { const dialogProp = { - title: 'Add New Credential', + title: t('canvas.dialogs.addNewCredential'), componentsCredentials: componentCredentialsResp.data } setCredentialListDialogProps(dialogProp) @@ -58,8 +62,8 @@ const CredentialInputHandler = ({ inputParam, data, onSelect, disabled = false } } else { const dialogProp = { type: 'ADD', - cancelButtonName: 'Cancel', - confirmButtonName: 'Add', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.add'), credentialComponent: componentCredentialsResp.data } setSpecificCredentialDialogProps(dialogProp) @@ -83,8 +87,8 @@ const CredentialInputHandler = ({ inputParam, data, onSelect, disabled = false } setShowCredentialListDialog(false) const dialogProp = { type: 'ADD', - cancelButtonName: 'Cancel', - confirmButtonName: 'Add', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.add'), credentialComponent } setSpecificCredentialDialogProps(dialogProp) @@ -105,7 +109,7 @@ const CredentialInputHandler = ({ inputParam, data, onSelect, disabled = false } disabled={disabled} name={inputParam.name} nodeData={data} - value={credentialId ?? 'choose an option'} + value={credentialId ?? t('components.dropdown.chooseOption')} isCreateNewOption={hasPermission('credentials:create')} credentialNames={inputParam.credentialNames} onSelect={(newValue) => { @@ -115,7 +119,12 @@ const CredentialInputHandler = ({ inputParam, data, onSelect, disabled = false } onCreateNew={() => addAsyncOption(inputParam.name)} /> {credentialId && hasPermission('credentials:update') && ( - editCredential(credentialId)}> + editCredential(credentialId)} + > )} diff --git a/packages/ui/src/views/canvas/NodeInputHandler.jsx b/packages/ui/src/views/canvas/NodeInputHandler.jsx index 04175e0872b..bb5a82d96a1 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.jsx +++ b/packages/ui/src/views/canvas/NodeInputHandler.jsx @@ -80,6 +80,9 @@ import useNotifier from '@/utils/useNotifier' import { baseURL, FLOWISE_CREDENTIAL_ID } from '@/store/constant' import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackbarAction } from '@/store/actions' +// i18n +import { useTranslation } from 'react-i18next' + const EDITABLE_OPTIONS = ['selectedTool', 'selectedAssistant'] const CustomWidthTooltip = styled(({ className, ...props }) => )({ @@ -121,6 +124,7 @@ const NodeInputHandler = ({ onHideNodeInfoDialog, onCustomDataChange }) => { + const { t } = useTranslation() const theme = useTheme() const customization = useSelector((state) => state.customization) const ref = useRef(null) @@ -191,8 +195,8 @@ const NodeInputHandler = ({ nodes: reactFlowInstance?.getNodes() || [], edges: reactFlowInstance?.getEdges() || [], nodeId: data.id, - confirmButtonName: 'Save', - cancelButtonName: 'Cancel' + confirmButtonName: t('common.actions.save'), + cancelButtonName: t('common.actions.cancel') } if (inputParam.acceptVariable) { setExpandRichDialogProps(dialogProps) @@ -208,8 +212,8 @@ const NodeInputHandler = ({ data, inputParam, disabled, - confirmButtonName: 'Save', - cancelButtonName: 'Cancel' + confirmButtonName: t('common.actions.save'), + cancelButtonName: t('common.actions.cancel') } setConditionDialogProps(dialogProps) setShowConditionDialog(true) @@ -235,8 +239,8 @@ const NodeInputHandler = ({ relativeLinksMethod, limit, selectedLinks, - confirmButtonName: 'Save', - cancelButtonName: 'Cancel' + confirmButtonName: t('common.actions.save'), + cancelButtonName: t('common.actions.cancel') } setManageScrapedLinksDialogProps(dialogProps) setShowManageScrapedLinksDialog(true) @@ -316,7 +320,7 @@ const NodeInputHandler = ({ for (const node of nodes) { preLoadOptions.push({ value: `$${node.id}`, - label: `Output from ${node.data.id}` + label: t('canvas.outputFrom', { id: node.data.id }) }) } } @@ -341,7 +345,7 @@ const NodeInputHandler = ({ for (const key of keys) { preLoadOptions.push({ value: `$flow.state.${key}`, - label: `Value from ${key}` + label: t('canvas.valueFrom', { key: key }) }) } } catch (error) { @@ -417,7 +421,7 @@ const NodeInputHandler = ({ preLoadOptions.push({ name: `{{ ${node.data.id} }}`, label: `{{ ${node.data.id} }}`, - description: `Output from ${node.data.id}` + description: t('canvas.outputFrom', { id: node.data.id }) }) } } @@ -485,18 +489,18 @@ const NodeInputHandler = ({ const editAsyncOption = (inputParamName, inputValue) => { if (inputParamName === 'selectedTool') { setAsyncOptionEditDialogProps({ - title: 'Edit Tool', + title: t('canvas.actions.editTool'), type: 'EDIT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Save', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.save'), toolId: inputValue }) } else if (inputParamName === 'selectedAssistant') { setAsyncOptionEditDialogProps({ - title: 'Edit Assistant', + title: t('canvas.actions.editAssistant'), type: 'EDIT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Save', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.save'), assistantId: inputValue }) } @@ -506,17 +510,17 @@ const NodeInputHandler = ({ const addAsyncOption = (inputParamName) => { if (inputParamName === 'selectedTool') { setAsyncOptionEditDialogProps({ - title: 'Add New Tool', + title: t('canvas.actions.addNewTool'), type: 'ADD', - cancelButtonName: 'Cancel', - confirmButtonName: 'Add' + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.add') }) } else if (inputParamName === 'selectedAssistant') { setAsyncOptionEditDialogProps({ - title: 'Add New Assistant', + title: t('canvas.actions.addNewAssistant'), type: 'ADD', - cancelButtonName: 'Cancel', - confirmButtonName: 'Add' + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.add') }) } setAsyncOptionEditDialog(inputParamName) @@ -584,7 +588,7 @@ const NodeInputHandler = ({ const displayWarning = () => { enqueueSnackbar({ - message: 'Please fill in all mandatory fields.', + message: t('canvas.messages.warn'), options: { key: new Date().getTime() + Math.random(), variant: 'warning', @@ -600,7 +604,7 @@ const NodeInputHandler = ({ const generateDocStoreToolDesc = async (storeId) => { if (!storeId) { enqueueSnackbar({ - message: 'Please select a knowledge base', + message: t('canvas.messages.docStoreTool.error'), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -640,7 +644,7 @@ const NodeInputHandler = ({ // Update the input value directly data.inputs[inputParam.name] = content enqueueSnackbar({ - message: 'Document Store Tool Description generated successfully', + message: t('canvas.messages.docStoreTool.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -688,7 +692,7 @@ const NodeInputHandler = ({ // Update the input value directly data.inputs[inputParam.name] = content enqueueSnackbar({ - message: 'Document Store Tool Description generated successfully', + message: t('canvas.messages.docStoreTool.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -736,8 +740,8 @@ const NodeInputHandler = ({ if (existingModel) { // Open prompt generator dialog directly with existing model setPromptGeneratorDialogProps({ - title: 'Generate Instructions', - description: 'You can generate a prompt template by sharing basic details about your task.', + title: t('canvas.dialogs.generateInstructions.title'), + description: t('canvas.dialogs.generateInstructions.description'), data: { selectedChatModel: { name: existingModel, @@ -757,8 +761,8 @@ const NodeInputHandler = ({ setModelSelectionCallback(() => async (selectedModel) => { // After model selection, open prompt generator dialog setPromptGeneratorDialogProps({ - title: 'Generate Instructions', - description: 'You can generate a prompt template by sharing basic details about your task.', + title: t('canvas.dialogs.generateInstructions.title'), + description: t('canvas.dialogs.generateInstructions.description'), data: { selectedChatModel: selectedModel } }) setPromptGeneratorDialogOpen(true) @@ -841,7 +845,7 @@ const NodeInputHandler = ({ onClick={() => onShowPromptHubButtonClicked()} endIcon={} > - Langchain Hub + {t('canvas.actions.langchainHub')} setIsNvidiaNIMDialogOpen(true)} > - Setup NIM Locally + {t('canvas.actions.setupNimLocally')} )} @@ -902,13 +906,13 @@ const NodeInputHandler = ({ )} {inputParam.acceptVariable && inputParam.type === 'string' && ( - + )} {inputParam.generateDocStoreDescription && ( onExpandDialogClicked(data.inputs[inputParam.name] ?? inputParam.default ?? '', inputParam) @@ -989,7 +993,7 @@ const NodeInputHandler = ({ setTabValue(val) data.inputs[`${inputParam.tabIdentifier}_${data.id}`] = inputParam.tabs[val].name }} - aria-label='tabs' + aria-label={t('common.labels.tabs')} variant='fullWidth' defaultValue={getTabValue(inputParam)} > @@ -1019,7 +1023,7 @@ const NodeInputHandler = ({ disabled={disabled} fileType={inputParam.fileType || '*'} onChange={(newValue) => (data.inputs[inputParam.name] = newValue)} - value={data.inputs[inputParam.name] ?? inputParam.default ?? 'Choose a file to upload'} + value={data.inputs[inputParam.name] ?? inputParam.default ?? t('components.file.chooseFile')} /> )} {inputParam.type === 'boolean' && ( @@ -1049,7 +1053,7 @@ const NodeInputHandler = ({ setReloadTimestamp(Date.now().toString()) }} > - See Example + {t('canvas.actions.seeExample')} )}
@@ -1153,7 +1157,7 @@ const NodeInputHandler = ({ options={getDropdownOptions(inputParam)} freeSolo={inputParam.freeSolo} onSelect={(newValue) => handleDataChange({ inputParam, newValue })} - value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'} + value={data.inputs[inputParam.name] ?? inputParam.default ?? t('components.dropdown.chooseOption')} />
)} @@ -1164,7 +1168,7 @@ const NodeInputHandler = ({ name={inputParam.name} options={getDropdownOptions(inputParam)} onSelect={(newValue) => handleDataChange({ inputParam, newValue })} - value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'} + value={data.inputs[inputParam.name] ?? inputParam.default ?? t('components.dropdown.chooseOption')} />
)} @@ -1179,7 +1183,7 @@ const NodeInputHandler = ({ disabled={disabled} name={inputParam.name} nodeData={data} - value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'} + value={data.inputs[inputParam.name] ?? inputParam.default ?? t('components.dropdown.chooseOption')} freeSolo={inputParam.freeSolo} multiple={inputParam.type === 'asyncMultiOptions'} isCreateNewOption={EDITABLE_OPTIONS.includes(inputParam.name)} @@ -1191,7 +1195,7 @@ const NodeInputHandler = ({ /> {EDITABLE_OPTIONS.includes(inputParam.name) && data.inputs[inputParam.name] && ( editAsyncOption(inputParam.name, data.inputs[inputParam.name])} @@ -1201,7 +1205,7 @@ const NodeInputHandler = ({ )} {inputParam.refresh && ( setReloadTimestamp(Date.now().toString())} @@ -1253,7 +1257,7 @@ const NodeInputHandler = ({ ) } > - Manage Links + {t('common.actions.manageLinks')} - Select Model + {t('common.labels.selectModel')} @@ -1355,7 +1359,7 @@ const NodeInputHandler = ({ } } }} - value={selectedTempChatModel?.name ?? 'choose an option'} + value={selectedTempChatModel?.name ?? t('components.dropdown.chooseOption')} /> {selectedTempChatModel && Object.keys(selectedTempChatModel).length > 0 && ( @@ -1376,7 +1380,7 @@ const NodeInputHandler = ({ setSelectedTempChatModel({}) }} > - Cancel + {t('common.actions.cancel')} @@ -1408,7 +1412,7 @@ const NodeInputHandler = ({ setPromptGeneratorDialogOpen(false) } catch (error) { enqueueSnackbar({ - message: 'Error setting generated instruction', + message: t('canvas.messages.generate.error'), options: { key: new Date().getTime() + Math.random(), variant: 'error', diff --git a/packages/ui/src/views/canvas/NodeOutputHandler.jsx b/packages/ui/src/views/canvas/NodeOutputHandler.jsx index 1ed5f46a2fd..85c997ffa09 100644 --- a/packages/ui/src/views/canvas/NodeOutputHandler.jsx +++ b/packages/ui/src/views/canvas/NodeOutputHandler.jsx @@ -10,6 +10,9 @@ import { flowContext } from '@/store/context/ReactFlowContext' import { isValidConnection } from '@/utils/genericHelper' import { Dropdown } from '@/ui-component/dropdown/Dropdown' +// i18n +import { useTranslation } from 'react-i18next' + const CustomWidthTooltip = styled(({ className, ...props }) => )({ [`& .${tooltipClasses.tooltip}`]: { maxWidth: 500 @@ -19,6 +22,7 @@ const CustomWidthTooltip = styled(({ className, ...props }) => { + const { t } = useTranslation() const theme = useTheme() const ref = useRef(null) const updateNodeInternals = useUpdateNodeInternals() @@ -148,7 +152,7 @@ const NodeOutputHandler = ({ outputAnchor, data, disabled = false }) => {
- True + {t('canvas.true')}
@@ -175,7 +179,7 @@ const NodeOutputHandler = ({ outputAnchor, data, disabled = false }) => {
- False + {t('canvas.false')}
@@ -215,7 +219,7 @@ const NodeOutputHandler = ({ outputAnchor, data, disabled = false }) => { setDropdownValue(newValue) data.outputs[outputAnchor.name] = newValue }} - value={data.outputs[outputAnchor.name] ?? outputAnchor.default ?? 'choose an option'} + value={data.outputs[outputAnchor.name] ?? outputAnchor.default ?? t('components.dropdown.chooseOption')} />
diff --git a/packages/ui/src/views/canvas/StickyNote.jsx b/packages/ui/src/views/canvas/StickyNote.jsx index 938a18b78f5..13b92bb4601 100644 --- a/packages/ui/src/views/canvas/StickyNote.jsx +++ b/packages/ui/src/views/canvas/StickyNote.jsx @@ -15,7 +15,11 @@ import { Input } from '@/ui-component/input/Input' // const import { flowContext } from '@/store/context/ReactFlowContext' +// i18n +import { useTranslation } from 'react-i18next' + const StickyNote = ({ data }) => { + const { t } = useTranslation() const theme = useTheme() const canvas = useSelector((state) => state.canvas) const customization = useSelector((state) => state.customization) @@ -74,7 +78,7 @@ const StickyNote = ({ data }) => { }} > { duplicateNode(data.id) }} @@ -88,7 +92,7 @@ const StickyNote = ({ data }) => { { deleteNode(data.id) }} diff --git a/packages/ui/src/views/canvas/index.jsx b/packages/ui/src/views/canvas/index.jsx index 8835706ecf3..384a279743c 100644 --- a/packages/ui/src/views/canvas/index.jsx +++ b/packages/ui/src/views/canvas/index.jsx @@ -55,12 +55,16 @@ import { usePrompt } from '@/utils/usePrompt' // const import { FLOWISE_CREDENTIAL_ID } from '@/store/constant' +// i18n +import { useTranslation } from 'react-i18next' + const nodeTypes = { customNode: CanvasNode, stickyNote: StickyNote } const edgeTypes = { buttonedge: ButtonEdge } // ==============================|| CANVAS ||============================== // const Canvas = () => { + const { t } = useTranslation() const theme = useTheme() const navigate = useNavigate() const { hasAssignedWorkspace } = useAuth() @@ -72,7 +76,7 @@ const Canvas = () => { const chatflowId = URLpath[URLpath.length - 1] === 'canvas' || URLpath[URLpath.length - 1] === 'agentcanvas' ? '' : URLpath[URLpath.length - 1] const isAgentCanvas = URLpath.includes('agentcanvas') ? true : false - const canvasTitle = URLpath.includes('agentcanvas') ? 'Agent' : 'Chatflow' + const canvasTitle = t(URLpath.includes('agentcanvas') ? 'common.labels.agents' : 'common.labels.chatflow') const { confirm } = useConfirm() @@ -178,10 +182,10 @@ const Canvas = () => { const handleDeleteFlow = async () => { const confirmPayload = { - title: `Delete`, - description: `Delete ${canvasTitle} ${chatflow.name}?`, - confirmButtonName: 'Delete', - cancelButtonName: 'Cancel' + title: t('common.dialogs.delete'), + description: t('canvas.dialogs.delete.description', { title: canvasTitle, name: chatflow.name }), + confirmButtonName: t('common.actions.delete'), + cancelButtonName: t('common.actions.cancel') } const isConfirmed = await confirm(confirmPayload) @@ -349,7 +353,7 @@ const Canvas = () => { const saveChatflowSuccess = () => { dispatch({ type: REMOVE_DIRTY }) enqueueSnackbar({ - message: `${canvasTitle} saved`, + message: t('canvas.messages.saveChatflow.success', { title: canvasTitle }), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -420,11 +424,13 @@ const Canvas = () => { setEdges(initialFlow.edges || []) dispatch({ type: SET_CHATFLOW, chatflow }) } else if (getSpecificChatflowApi.error) { - errorFailed(`Failed to retrieve ${canvasTitle}: ${getSpecificChatflowApi.error.response.data.message}`) + errorFailed( + t('canvas.messages.saveChatflow.error', { title: canvasTitle, msg: getSpecificChatflowApi.error.response.data.message }) + ) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [getSpecificChatflowApi.data, getSpecificChatflowApi.error]) + }, [getSpecificChatflowApi.data, getSpecificChatflowApi.error, t]) // Create new chatflow successful useEffect(() => { @@ -434,11 +440,13 @@ const Canvas = () => { saveChatflowSuccess() window.history.replaceState(state, null, `/${isAgentCanvas ? 'agentcanvas' : 'canvas'}/${chatflow.id}`) } else if (createNewChatflowApi.error) { - errorFailed(`Failed to retrieve ${canvasTitle}: ${createNewChatflowApi.error.response.data.message}`) + errorFailed( + t('canvas.messages.saveChatflow.error', { title: canvasTitle, msg: getSpecificChatflowApi.error.response.data.message }) + ) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [createNewChatflowApi.data, createNewChatflowApi.error]) + }, [createNewChatflowApi.data, createNewChatflowApi.error, t]) // Update chatflow successful useEffect(() => { @@ -447,21 +455,23 @@ const Canvas = () => { setLasUpdatedDateTime(updateChatflowApi.data.updatedDate) saveChatflowSuccess() } else if (updateChatflowApi.error) { - errorFailed(`Failed to retrieve ${canvasTitle}: ${updateChatflowApi.error.response.data.message}`) + errorFailed( + t('canvas.messages.saveChatflow.error', { title: canvasTitle, msg: getSpecificChatflowApi.error.response.data.message }) + ) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [updateChatflowApi.data, updateChatflowApi.error]) + }, [updateChatflowApi.data, updateChatflowApi.error, t]) // check if chatflow has changed before saving useEffect(() => { const checkIfHasChanged = async () => { if (getHasChatflowChangedApi.data?.hasChanged === true) { const confirmPayload = { - title: `Confirm Change`, - description: `${canvasTitle} ${chatflow.name} has changed since you have opened, overwrite changes?`, - confirmButtonName: 'Confirm', - cancelButtonName: 'Cancel' + title: t('canvas.dialogs.confirmChange.title'), + description: t('canvas.dialogs.confirmChange.description', { title: canvasTitle, name: chatflow.name }), + confirmButtonName: t('common.actions.confirm'), + cancelButtonName: t('common.actions.cancel') } const isConfirmed = await confirm(confirmPayload) @@ -481,7 +491,7 @@ const Canvas = () => { } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [getHasChatflowChangedApi.data, getHasChatflowChangedApi.error]) + }, [getHasChatflowChangedApi.data, getHasChatflowChangedApi.error, t]) useEffect(() => { setChatflow(canvasDataStore.chatflow) @@ -511,7 +521,7 @@ const Canvas = () => { dispatch({ type: SET_CHATFLOW, chatflow: { - name: `Untitled ${canvasTitle}` + name: t('canvas.untitled', { title: canvasTitle }) } }) } @@ -556,7 +566,7 @@ const Canvas = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [templateFlowData]) - usePrompt('You have unsaved changes! Do you want to navigate away?', canvasDataStore.isDirty) + usePrompt(t('canvas.unsaved'), canvasDataStore.isDirty) return ( <> @@ -617,8 +627,8 @@ const Canvas = () => { onClick={() => { setIsSnappingEnabled(!isSnappingEnabled) }} - title='toggle snapping' - aria-label='toggle snapping' + title={t('canvas.actions.toggleSnapping')} + aria-label={t('canvas.actions.toggleSnapping')} > {isSnappingEnabled ? : } @@ -627,8 +637,8 @@ const Canvas = () => { onClick={() => { setIsBackgroundEnabled(!isBackgroundEnabled) }} - title='toggle background' - aria-label='toggle background' + title={t('canvas.actions.toggleBackground')} + aria-label={t('canvas.actions.toggleBackground')} > {isBackgroundEnabled ? : } @@ -648,8 +658,8 @@ const Canvas = () => { } }} size='small' - aria-label='sync' - title='Sync Nodes' + aria-label={t('canvas.actions.sync.label')} + title={t('canvas.actions.sync.title')} onClick={() => syncNodes()} > diff --git a/packages/ui/src/views/chatbot/index.jsx b/packages/ui/src/views/chatbot/index.jsx index 1bf8b723d0b..161aaef3bf0 100644 --- a/packages/ui/src/views/chatbot/index.jsx +++ b/packages/ui/src/views/chatbot/index.jsx @@ -15,9 +15,13 @@ import { alpha } from '@mui/material/styles' //Const import { baseURL } from '@/store/constant' +// i18n +import { useTranslation } from 'react-i18next' + // ==============================|| Chatbot ||============================== // const ChatbotFull = () => { + const { t } = useTranslation() const URLpath = document.location.pathname.toString().split('/') const chatflowId = URLpath[URLpath.length - 1] === 'chatbot' ? '' : URLpath[URLpath.length - 1] const theme = useTheme() @@ -92,10 +96,10 @@ const ChatbotFull = () => { - Invalid Chatbot + {t('chatbot.invalid.title')} - {`The chatbot you're looking for doesn't exist or requires API key authentication.`} + {t('chatbot.invalid.description')} diff --git a/packages/ui/src/views/chatflows/APICodeDialog.jsx b/packages/ui/src/views/chatflows/APICodeDialog.jsx index 26acf8fe270..f5ff13c0506 100644 --- a/packages/ui/src/views/chatflows/APICodeDialog.jsx +++ b/packages/ui/src/views/chatflows/APICodeDialog.jsx @@ -56,6 +56,9 @@ import { TableViewOnly } from '@/ui-component/table/Table' // Helpers import { unshiftFiles, getConfigExamplesForJS, getConfigExamplesForPython, getConfigExamplesForCurl } from '@/utils/genericHelper' +// i18n +import { useTranslation, Trans } from 'react-i18next' + function TabPanel(props) { const { children, value, index, ...other } = props return ( @@ -85,6 +88,7 @@ function a11yProps(index) { } const APICodeDialog = ({ show, dialogProps, onCancel }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const navigate = useNavigate() const dispatch = useDispatch() @@ -93,7 +97,13 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => { const apiConfig = chatflow?.apiConfig ? JSON.parse(chatflow.apiConfig) : {} const overrideConfigStatus = apiConfig?.overrideConfig?.status !== undefined ? apiConfig.overrideConfig.status : false - const codes = ['Embed', 'Python', 'JavaScript', 'cURL', 'Share Chatbot'] + const codes = [ + { id: 'Embed', label: 'chatflows.codes.embed.title' }, + { id: 'Python', label: 'chatflows.codes.python' }, + { id: 'JavaScript', label: 'chatflows.codes.js' }, + { id: 'cURL', label: 'chatflows.codes.cUrl' }, + { id: 'Share Chatbot', label: 'chatflows.codes.shareChatbot' } + ] const [value, setValue] = useState(0) const [apiKeys, setAPIKeys] = useState([]) const [chatflowApiKeyId, setChatflowApiKeyId] = useState('') @@ -118,7 +128,7 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => { const options = [ { - label: 'No Authorization', + label: t('chatflows.options.noAuthorization'), name: '' } ] @@ -132,13 +142,13 @@ const APICodeDialog = ({ show, dialogProps, onCancel }) => { if (isGlobal || hasPermission('apikeys:create')) { options.push({ - label: '- Add New Key -', + label: t('chatflows.options.addKey'), name: 'addnewkey' }) } return options - }, [getAllAPIKeysApi.data, isGlobal, hasPermission]) + }, [getAllAPIKeysApi.data, isGlobal, hasPermission, t]) const onCheckBoxChanged = (newVal) => { setCheckbox(newVal) @@ -712,15 +722,19 @@ formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")`
- + {codes.map((codeLang, index) => ( + code } iconPosition='start' key={index} - label={codeLang} + label={t(codeLang.label)} {...a11yProps(index)} > ))} @@ -733,7 +747,7 @@ formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")` disableClearable={true} options={keyOptions} onSelect={(newValue) => onApiKeySelected(newValue)} - value={dialogProps.chatflowApiKeyId ?? chatflowApiKeyId ?? 'Choose an API key'} + value={dialogProps.chatflowApiKeyId ?? chatflowApiKeyId ?? t('chatflows.choose')} />
@@ -741,30 +755,30 @@ formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")`
{codes.map((codeLang, index) => ( - {(codeLang === 'Embed' || codeLang === 'Share Chatbot') && chatflowApiKeyId && ( + {(codeLang.id === 'Embed' || codeLang.id === 'Share Chatbot') && chatflowApiKeyId && ( <> -

You cannot use API key while embedding/sharing chatbot.

-

- Please select "No Authorization" from the dropdown at the top right corner. -

+

{t('chatflows.codes.embed.warnApi')}

+

{t('chatflows.codes.embed.select')}

)} - {codeLang === 'Embed' && !chatflowApiKeyId && } - {codeLang !== 'Embed' && codeLang !== 'Share Chatbot' && codeLang !== 'Configuration' && ( + {codeLang.id === 'Embed' && !chatflowApiKeyId && } + {codeLang.id !== 'Embed' && codeLang.id !== 'Share Chatbot' && codeLang.id !== 'Configuration' && ( <> - + {checkboxVal && getConfigApi.data && getConfigApi.data.length > 0 && ( <> - - You can override existing input configuration of the chatflow with overrideConfig property. - + {t('chatflows.config.override')}
- { - 'For security reason, override config is disabled by default. You can change this by going into Chatflow Configuration -> Security tab, and enable the property you want to override.' - } -  Refer{' '} - - here - {' '} - for more details + {t('chatflows.config.secWarn')} +   + + ) + }} + />
@@ -804,7 +821,7 @@ formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")` - Nodes + {t('chatflows.nodes')} {Object.keys(nodeConfig) .sort() @@ -870,9 +887,12 @@ formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")` - Variables + {t('common.labels.variables')} - + @@ -910,7 +930,7 @@ formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")` > - You can also specify multiple values for a config parameter by specifying the node id + {t('chatflows.specifyMultipleValues')}
@@ -918,10 +938,10 @@ formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")` theme={atomOneDark} text={ dialogProps.isFormDataRequired - ? getMultiConfigCodeWithFormData(codeLang) + ? getMultiConfigCodeWithFormData(codeLang.id) : getMultiConfigCode() } - language={getLang(codeLang)} + language={getLang(codeLang.id)} showLineNumbers={false} wrapLines /> @@ -931,16 +951,24 @@ formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")` )} {getIsChatflowStreamingApi.data?.isStreaming && (

- Read  - - here - -  on how to stream response back to application + + ) + }} + />

)} )} - {codeLang === 'Share Chatbot' && !chatflowApiKeyId && ( + {codeLang.id === 'Share Chatbot' && !chatflowApiKeyId && ( )} diff --git a/packages/ui/src/views/chatflows/EmbedChat.jsx b/packages/ui/src/views/chatflows/EmbedChat.jsx index 2c17966423c..4d1b6c43b57 100644 --- a/packages/ui/src/views/chatflows/EmbedChat.jsx +++ b/packages/ui/src/views/chatflows/EmbedChat.jsx @@ -10,6 +10,10 @@ import { CheckboxInput } from '@/ui-component/checkbox/Checkbox' // Const import { baseURL } from '@/store/constant' +// i18n +import { useTranslation, Trans } from 'react-i18next' +import i18next from 'i18next' + function TabPanel(props) { const { children, value, index, ...other } = props return ( @@ -102,17 +106,17 @@ export const defaultThemeConfig = { }, tooltip: { showTooltip: true, - tooltipMessage: 'Hi There 👋!', + tooltipMessage: i18next.t('chatflows.chat.tooltipMessage'), tooltipBackgroundColor: 'black', tooltipTextColor: 'white', tooltipFontSize: 16 }, disclaimer: { - title: 'Disclaimer', - message: 'By using this chatbot, you agree to the Terms & Condition', + title: i18next.t('chatflows.chat.disclaimer.title'), + message: i18next.t('chatflows.chat.disclaimer.message'), textColor: 'black', buttonColor: '#3b82f6', - buttonText: 'Start Chatting', + buttonText: i18next.t('chatflows.chat.disclaimer.buttonText'), buttonTextColor: 'white', blurredBackgroundColor: 'rgba(0, 0, 0, 0.4)', backgroundColor: 'white' @@ -121,19 +125,19 @@ export const defaultThemeConfig = { chatWindow: { showTitle: true, showAgentMessages: true, - title: 'Flowise Bot', + title: i18next.t('chatflows.chat.chatWindow.title'), titleAvatarSrc: 'https://raw.githubusercontent.com/walkxcode/dashboard-icons/main/svg/google-messages.svg', - welcomeMessage: 'Hello! This is custom welcome message', - errorMessage: 'This is a custom error message', + welcomeMessage: i18next.t('chatflows.chat.chatWindow.welcomeMessage'), + errorMessage: i18next.t('chatflows.chat.chatWindow.errorMessage'), backgroundColor: '#ffffff', - backgroundImage: 'enter image path or link', + backgroundImage: i18next.t('chatflows.chat.chatWindow.backgroundImage'), height: 700, width: 400, fontSize: 16, starterPrompts: ['What is a bot?', 'Who are you?'], starterPromptFontSize: 15, clearChatOnReload: false, - sourceDocsTitle: 'Sources:', + sourceDocsTitle: i18next.t('chatflows.chat.chatWindow.sourceDocsTitle'), renderHTML: true, botMessage: { backgroundColor: '#f7f8ff', @@ -148,12 +152,12 @@ export const defaultThemeConfig = { avatarSrc: 'https://raw.githubusercontent.com/zahidkhawaja/langchain-chat-nextjs/main/public/usericon.png' }, textInput: { - placeholder: 'Type your question', + placeholder: i18next.t('chatflows.chat.textInput.placeholder'), backgroundColor: '#ffffff', textColor: '#303235', sendButtonColor: '#3B81F6', maxChars: 50, - maxCharsWarningMessage: 'You exceeded the characters limit. Please input less than 50 characters.', + maxCharsWarningMessage: i18next.t('chatflows.chat.textInput.maxCharsWarningMessage'), autoFocus: true, sendMessageSound: true, sendSoundLocation: 'send_message.mp3', @@ -169,8 +173,8 @@ export const defaultThemeConfig = { }, footer: { textColor: '#303235', - text: 'Powered by', - company: 'Flowise', + text: i18next.t('chatflows.chat.footer.text'), + company: i18next.t('chatflows.chat.footer.company'), companyLink: 'https://flowiseai.com' } } @@ -286,7 +290,13 @@ const App = () => { } const EmbedChat = ({ chatflowid }) => { - const codes = ['Popup Html', 'Fullpage Html', 'Popup React', 'Fullpage React'] + const { t } = useTranslation() + const codes = [ + { id: 'Popup Html', label: 'chatflows.codes.popupHtml' }, + { id: 'Fullpage Html', label: 'chatflows.codes.fullpageHtml' }, + { id: 'Popup React', label: 'chatflows.codes.popupReact' }, + { id: 'Fullpage React', label: 'chatflows.codes.fullpageReact' } + ] const [value, setValue] = useState(0) const [embedChatCheckboxVal, setEmbedChatCheckbox] = useState(false) @@ -332,9 +342,9 @@ const EmbedChat = ({ chatflowid }) => { <>
- + {codes.map((codeLang, index) => ( - + ))}
@@ -345,30 +355,41 @@ const EmbedChat = ({ chatflowid }) => { {(value === 0 || value === 1) && ( <> - Paste this anywhere in the {``} tag of your html file. + {t('chatflows.bodyTag.prefix')} {''} {t('chatflows.bodyTag.suffix')}

- You can also specify a  - - version - - : {`https://cdn.jsdelivr.net/npm/flowise-embed@/dist/web.js`} + , + code: , + a: ( + // eslint-disable-next-line jsx-a11y/anchor-has-content + + ) + }} + /> +  {`https://cdn.jsdelivr.net/npm/flowise-embed@/dist/web.js`}

)} - + - + {embedChatCheckboxVal && ( { + const { t } = useTranslation() const dispatch = useDispatch() const theme = useTheme() const chatflow = useSelector((state) => state.canvas.chatflow) @@ -184,7 +188,7 @@ const ShareChatbot = ({ isSessionMemory, isAgentCanvas }) => { }) if (saveResp.data) { enqueueSnackbar({ - message: 'Chatbot Configuration Saved', + message: t('chatflows.messages.save.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -199,9 +203,9 @@ const ShareChatbot = ({ isSessionMemory, isAgentCanvas }) => { } } catch (error) { enqueueSnackbar({ - message: `Failed to save Chatbot Configuration: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('chatflows.messages.save.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -221,7 +225,7 @@ const ShareChatbot = ({ isSessionMemory, isAgentCanvas }) => { const saveResp = await chatflowsApi.updateChatflow(chatflowid, { isPublic: checked }) if (saveResp.data) { enqueueSnackbar({ - message: 'Chatbot Configuration Saved', + message: t('chatflows.messages.save.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -236,9 +240,9 @@ const ShareChatbot = ({ isSessionMemory, isAgentCanvas }) => { } } catch (error) { enqueueSnackbar({ - message: `Failed to save Chatbot Configuration: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('chatflows.messages.save.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -429,7 +433,7 @@ const ShareChatbot = ({ isSessionMemory, isAgentCanvas }) => { {`${baseURL}/chatbot/${chatflowid}`} { navigator.clipboard.writeText(`${baseURL}/chatbot/${chatflowid}`) @@ -441,7 +445,11 @@ const ShareChatbot = ({ isSessionMemory, isAgentCanvas }) => { > - window.open(`${baseURL}/chatbot/${chatflowid}`, '_blank')}> + window.open(`${baseURL}/chatbot/${chatflowid}`, '_blank')} + >
@@ -454,86 +462,100 @@ const ShareChatbot = ({ isSessionMemory, isAgentCanvas }) => { onSwitchChange(event.target.checked) }} /> - Make Public - + {t('chatflows.actions.makePublic.title')} +
- Title Settings + {t('chatflows.cards.titleSettings')} - {textField(title, 'title', 'Title', 'string', 'Flowise Assistant')} + {textField(title, 'title', t('chatflows.inputs.title.title'), 'string', t('chatflows.inputs.title.placeholder'))} {textField( titleAvatarSrc, 'titleAvatarSrc', - 'Title Avatar Link', + t('chatflows.inputs.titleAvatarSrc.title'), 'string', `https://raw.githubusercontent.com/FlowiseAI/Flowise/main/assets/FloWiseAI_dark.png` )} - {colorField(titleBackgroundColor, 'titleBackgroundColor', 'Title Background Color')} - {colorField(titleTextColor, 'titleTextColor', 'Title TextColor')} + {colorField(titleBackgroundColor, 'titleBackgroundColor', t('chatflows.inputs.titleBackgroundColor.title'))} + {colorField(titleTextColor, 'titleTextColor', t('chatflows.inputs.titleTextColor.title'))} - General Settings + {t('chatflows.cards.generalSettings')} - {textField(welcomeMessage, 'welcomeMessage', 'Welcome Message', 'string', 'Hello! This is custom welcome message')} - {textField(errorMessage, 'errorMessage', 'Error Message', 'string', 'This is custom error message')} - {colorField(backgroundColor, 'backgroundColor', 'Background Color')} - {textField(fontSize, 'fontSize', 'Font Size', 'number')} - {colorField(poweredByTextColor, 'poweredByTextColor', 'PoweredBy TextColor')} - {isAgentCanvas && booleanField(showAgentMessages, 'showAgentMessages', 'Show agent reasonings when using Agentflow')} - {booleanField(renderHTML, 'renderHTML', 'Render HTML on the chat')} - {isSessionMemory && - booleanField(generateNewSession, 'generateNewSession', 'Start new session when chatbot link is opened or refreshed')} + {textField( + welcomeMessage, + 'welcomeMessage', + t('chatflows.inputs.welcomeMessage.title'), + 'string', + t('chatflows.inputs.welcomeMessage.placeholder') + )} + {textField( + errorMessage, + 'errorMessage', + t('chatflows.inputs.errorMessage.title'), + 'string', + t('chatflows.inputs.errorMessage.placeholder') + )} + {colorField(backgroundColor, 'backgroundColor', t('chatflows.inputs.backgroundColor.title'))} + {textField(fontSize, 'fontSize', t('chatflows.inputs.fontSize.title'), 'number')} + {colorField(poweredByTextColor, 'poweredByTextColor', t('chatflows.inputs.poweredByTextColor.title'))} + {isAgentCanvas && booleanField(showAgentMessages, 'showAgentMessages', t('chatflows.inputs.showAgentMessages.title'))} + {booleanField(renderHTML, 'renderHTML', t('chatflows.inputs.renderHTML.title'))} + {isSessionMemory && booleanField(generateNewSession, 'generateNewSession', t('chatflows.inputs.generateNewSession.title'))} - Bot Message + {t('chatflows.cards.botMessage')} - {colorField(botMessageBackgroundColor, 'botMessageBackgroundColor', 'Background Color')} - {colorField(botMessageTextColor, 'botMessageTextColor', 'Text Color')} + {colorField(botMessageBackgroundColor, 'botMessageBackgroundColor', t('chatflows.inputs.backgroundColor.title'))} + {colorField(botMessageTextColor, 'botMessageTextColor', t('chatflows.inputs.textColor.title'))} {textField( botMessageAvatarSrc, 'botMessageAvatarSrc', - 'Avatar Link', + t('chatflows.inputs.avatarSrc.title'), 'string', `https://raw.githubusercontent.com/zahidkhawaja/langchain-chat-nextjs/main/public/parroticon.png` )} - {booleanField(botMessageShowAvatar, 'botMessageShowAvatar', 'Show Avatar')} + {booleanField(botMessageShowAvatar, 'botMessageShowAvatar', t('chatflows.inputs.showAvatar.title'))} - User Message + {t('chatflows.cards.userMessage')} - {colorField(userMessageBackgroundColor, 'userMessageBackgroundColor', 'Background Color')} - {colorField(userMessageTextColor, 'userMessageTextColor', 'Text Color')} + {colorField(userMessageBackgroundColor, 'userMessageBackgroundColor', t('chatflows.inputs.backgroundColor.title'))} + {colorField(userMessageTextColor, 'userMessageTextColor', t('chatflows.inputs.textColor.title'))} {textField( userMessageAvatarSrc, 'userMessageAvatarSrc', - 'Avatar Link', + t('chatflows.inputs.avatarSrc.title'), 'string', `https://raw.githubusercontent.com/zahidkhawaja/langchain-chat-nextjs/main/public/usericon.png` )} - {booleanField(userMessageShowAvatar, 'userMessageShowAvatar', 'Show Avatar')} + {booleanField(userMessageShowAvatar, 'userMessageShowAvatar', t('chatflows.inputs.showAvatar.title'))} - Text Input + {t('chatflows.cards.textInput')} - {colorField(textInputBackgroundColor, 'textInputBackgroundColor', 'Background Color')} - {colorField(textInputTextColor, 'textInputTextColor', 'Text Color')} - {textField(textInputPlaceholder, 'textInputPlaceholder', 'TextInput Placeholder', 'string', `Type question..`)} - {colorField(textInputSendButtonColor, 'textInputSendButtonColor', 'TextIntput Send Button Color')} + {colorField(textInputBackgroundColor, 'textInputBackgroundColor', t('chatflows.inputs.backgroundColor.title'))} + {colorField(textInputTextColor, 'textInputTextColor', t('chatflows.inputs.textColor.title'))} + {textField( + textInputPlaceholder, + 'textInputPlaceholder', + t('chatflows.inputs.textInputPlaceholder.title'), + 'string', + t('chatflows.inputs.textInputPlaceholder.placeholder') + )} + {colorField(textInputSendButtonColor, 'textInputSendButtonColor', t('chatflows.inputs.textInputSendButtonColor.title'))} { variant='contained' onClick={() => onSave()} > - Save Changes + {t('chatflows.actions.saveChanges')} { }} > - Copied! + {t('common.messages.copied')} diff --git a/packages/ui/src/views/chatflows/index.jsx b/packages/ui/src/views/chatflows/index.jsx index 3a094b54e4f..4fef71dce34 100644 --- a/packages/ui/src/views/chatflows/index.jsx +++ b/packages/ui/src/views/chatflows/index.jsx @@ -30,9 +30,13 @@ import { useError } from '@/store/context/ErrorContext' // icons import { IconLayoutGrid, IconList, IconPlus } from '@tabler/icons-react' +// i18n +import { useTranslation } from 'react-i18next' + // ==============================|| CHATFLOWS ||============================== // const Chatflows = () => { + const { t } = useTranslation() const navigate = useNavigate() const theme = useTheme() @@ -138,9 +142,9 @@ const Chatflows = () => { { }} variant='contained' value='card' - title='Card View' + title={t('common.actions.cardView')} > @@ -170,7 +174,7 @@ const Chatflows = () => { }} variant='contained' value='list' - title='List View' + title={t('common.actions.listView')} > @@ -182,7 +186,7 @@ const Chatflows = () => { startIcon={} sx={{ borderRadius: 2, height: 40 }} > - Add New + {t('common.actions.addNew')} @@ -226,7 +230,7 @@ const Chatflows = () => { alt='WorkflowEmptySVG' /> -
No Chatflows Yet
+
{t('chatflows.notFound')}
)} diff --git a/packages/ui/src/views/chatmessage/AgentExecutedDataCard.jsx b/packages/ui/src/views/chatmessage/AgentExecutedDataCard.jsx index f4559b67b62..f3f08fb0d6f 100644 --- a/packages/ui/src/views/chatmessage/AgentExecutedDataCard.jsx +++ b/packages/ui/src/views/chatmessage/AgentExecutedDataCard.jsx @@ -47,6 +47,9 @@ import { useTheme } from '@mui/material/styles' import { FLOWISE_CREDENTIAL_ID, AGENTFLOW_ICONS } from '@/store/constant' import { NodeExecutionDetails } from '@/views/agentexecutions/NodeExecutionDetails' +// i18n +import { useTranslation } from 'react-i18next' + const getIconColor = (status) => { switch (status) { case 'FINISHED': @@ -116,6 +119,7 @@ const StyledTreeItemLabelText = styled(Typography)(({ theme }) => ({ })) function CustomLabel({ icon: Icon, itemStatus, children, name, label, data, metadata, ...other }) { + const { t } = useTranslation() const [openDialog, setOpenDialog] = useState(false) const handleOpenDialog = (event) => { @@ -179,7 +183,7 @@ function CustomLabel({ icon: Icon, itemStatus, children, name, label, data, meta ) : ( - No data available for this item + {t('chatmessage.notFound')} )} - + @@ -325,6 +329,7 @@ CustomTreeItem.propTypes = { CustomTreeItem.displayName = 'CustomTreeItem' const AgentExecutedDataCard = ({ status, execution, agentflowId, sessionId }) => { + const { t } = useTranslation() const [executionTree, setExecution] = useState([]) const [expandedItems, setExpandedItems] = useState([]) const [selectedItem, setSelectedItem] = useState(null) @@ -411,7 +416,7 @@ const AgentExecutedDataCard = ({ status, execution, agentflowId, sessionId }) => // Create a virtual node for this iteration const iterationNodeId = `${parentId}_${iterationIndex}` - const iterationLabel = `Iteration #${iterationIndex}` + const iterationLabel = t('chatmessage.iteration', { index: iterationIndex }) // Determine status based on child nodes const childNodes = nodeIds.map((id) => nodeMap.get(id)) @@ -718,7 +723,7 @@ const AgentExecutedDataCard = ({ status, execution, agentflowId, sessionId }) => fontWeight: 500 }} > - Process Flow + {t('chatmessage.processFlow')} diff --git a/packages/ui/src/views/chatmessage/AgentReasoningCard.jsx b/packages/ui/src/views/chatmessage/AgentReasoningCard.jsx index ef69b49b54e..4286316731e 100644 --- a/packages/ui/src/views/chatmessage/AgentReasoningCard.jsx +++ b/packages/ui/src/views/chatmessage/AgentReasoningCard.jsx @@ -4,6 +4,9 @@ import { MemoizedReactMarkdown } from '@/ui-component/markdown/MemoizedReactMark import nextAgentGIF from '@/assets/images/next-agent.gif' import PropTypes from 'prop-types' +// i18n +import { useTranslation } from 'react-i18next' + const AgentReasoningCard = ({ agent, index, @@ -19,6 +22,7 @@ const AgentReasoningCard = ({ onURLClick, getLabel }) => { + const { t } = useTranslation() if (agent.nextAgent) { return ( } - onClick={() => onSourceDialogClick(tool, 'Used Tools')} + onClick={() => onSourceDialogClick(tool, t('chatmessage.usedTools'))} /> ) : null })} @@ -106,13 +110,13 @@ const AgentReasoningCard = ({
} - onClick={() => onSourceDialogClick(agent.state, 'State')} + onClick={() => onSourceDialogClick(agent.state, t('common.labels.state'))} />
)} @@ -137,7 +141,7 @@ const AgentReasoningCard = ({ )} {agent.instructions &&

{agent.instructions}

} - {agent.messages.length === 0 && !agent.instructions &&

Finished

} + {agent.messages.length === 0 && !agent.instructions &&

{t('chatmessage.finished')}

} {agent.sourceDocuments && agent.sourceDocuments.length > 0 && (
{removeDuplicateURL(agent).map((source, index) => { diff --git a/packages/ui/src/views/chatmessage/ChatExpandDialog.jsx b/packages/ui/src/views/chatmessage/ChatExpandDialog.jsx index 702d778de9d..7a1216aaf02 100644 --- a/packages/ui/src/views/chatmessage/ChatExpandDialog.jsx +++ b/packages/ui/src/views/chatmessage/ChatExpandDialog.jsx @@ -7,7 +7,11 @@ import ChatMessage from './ChatMessage' import { StyledButton } from '@/ui-component/button/StyledButton' import { IconEraser } from '@tabler/icons-react' +// i18n +import { useTranslation } from 'react-i18next' + const ChatExpandDialog = ({ show, dialogProps, isAgentCanvas, onClear, onCancel, previews, setPreviews }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const customization = useSelector((state) => state.customization) @@ -29,16 +33,22 @@ const ChatExpandDialog = ({ show, dialogProps, isAgentCanvas, onClear, onCancel, } > - Clear Chat + {t('chatmessage.actions.clearChat')} )} {!customization.isDarkMode && ( - )}
diff --git a/packages/ui/src/views/chatmessage/ChatMessage.jsx b/packages/ui/src/views/chatmessage/ChatMessage.jsx index bf55a262022..9a46bde13fc 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.jsx +++ b/packages/ui/src/views/chatmessage/ChatMessage.jsx @@ -92,6 +92,8 @@ import FollowUpPromptsCard from '@/ui-component/cards/FollowUpPromptsCard' // History import { ChatInputHistory } from './ChatInputHistory' +// i18n + const messageImageStyle = { width: '128px', height: '128px', @@ -160,7 +162,7 @@ const CardWithDeleteOverlay = ({ item, disabled, customization, onDelete }) => { disabled={disabled} onClick={() => onDelete(item)} startIcon={} - title='Remove attachment' + title={t('chatmessage.actions.removeAttachment')} sx={{ position: 'absolute', top: 0, @@ -338,7 +340,7 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP } } if (!acceptFile) { - alert(`Cannot upload file. Kindly check the allowed file types and maximum allowed size.`) + alert(t('chatmessage.errors.cannotUpload')) } return acceptFile } @@ -827,7 +829,7 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP inputRef.current?.focus() }, 100) enqueueSnackbar({ - message: 'Message stopped', + message: t('chatmessage.messages.message.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -840,8 +842,12 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP }) } - const handleError = (message = 'Oops! There seems to be an error. Please try again.') => { - message = message.replace(`Unable to parse JSON response from chat agent.\n\n`, '') + const handleError = (message) => { + if (!message) { + message = t('chatmessage.errors.tryAgain') + } else { + message = message.replace(`Unable to parse JSON response from chat agent.\n\n`, '') + } setMessages((prevMessages) => [...prevMessages, { message, type: 'apiMessage' }]) setLoading(false) setUserInput('') @@ -1046,7 +1052,7 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP try { uploads = await handleFileUploads(uploads) } catch (error) { - handleError('Unable to upload documents') + handleError(t('chatmessage.errors.unableUpload')) return } @@ -1701,8 +1707,7 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP setMessages((prevMessages) => { let allMessages = [...cloneDeep(prevMessages)] if (allMessages[allMessages.length - 1].type !== 'leadCaptureMessage') return allMessages - allMessages[allMessages.length - 1].message = - leadsConfig.successMessage || 'Thank you for submitting your contact information.' + allMessages[allMessages.length - 1].message = leadsConfig.successMessage || t('chatmessage.submitting') return allMessages }) } @@ -1857,7 +1862,7 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP } else { console.error('Error with TTS:', error) enqueueSnackbar({ - message: `TTS failed: ${error.message}`, + message: t('chatmessage.messages.tts.error', { msg: error.message }), options: { variant: 'error' } }) } @@ -2386,10 +2391,10 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP }} > - {formTitle || 'Please Fill Out The Form'} + {formTitle || t('chatmessage.fillForm')} - {formDescription || 'Complete all fields below to continue'} + {formDescription || t('chatmessage.completeForm')} {/* Form inputs */} @@ -2426,7 +2431,7 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP background: 'linear-gradient(45deg, #673ab7 30%, #1e88e5 90%)' }} > - {loading ? 'Submitting...' : 'Submit'} + {t(loading ? 'chatmessage.submittingLoading' : 'common.actions.submit')} @@ -2448,7 +2453,7 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP {isDragActive && (getAllowChatFlowUploads.data?.isImageUploadAllowed || getAllowChatFlowUploads.data?.isRAGFileUploadAllowed) && ( - Drop here to upload + {t('chatmessage.dragDrop.here')} {[ ...getAllowChatFlowUploads.data.imgUploadSizeAndTypes, ...getAllowChatFlowUploads.data.fileUploadSizeAndTypes @@ -2457,7 +2462,9 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP <> {allowed.fileTypes?.join(', ')} {allowed.maxUploadSize && ( - Max Allowed Size: {allowed.maxUploadSize} MB + + {t('chatmessage.dragDrop.maxSize', { size: allowed.maxUploadSize })} + )} ) @@ -2587,7 +2594,7 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP variant='outlined' clickable icon={} - onClick={() => onSourceDialogClick(tool, 'Called Tools')} + onClick={() => onSourceDialogClick(tool, t('chatmessage.calledTools'))} /> ) : null })} @@ -2622,7 +2629,7 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP color={tool.error ? theme.palette.error.main : undefined} /> } - onClick={() => onSourceDialogClick(tool, 'Used Tools')} + onClick={() => onSourceDialogClick(tool, t('chatmessage.usedTools'))} /> ) : null })} @@ -2655,7 +2662,7 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP }} > - {leadsConfig.title || 'Let us know where we can reach you:'} + {leadsConfig.title || t('chatmessage.know')}
setLeadEmail(e.target.value)} @@ -2695,7 +2702,7 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP id='leadPhone' type='number' fullWidth - placeholder='Phone Number' + placeholder={t('common.labels.phoneNumber')} name='leadPhone' value={leadPhone} onChange={(e) => setLeadPhone(e.target.value)} @@ -2713,7 +2720,7 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP type='submit' sx={{ borderRadius: '20px' }} > - {isLeadSaving ? 'Saving...' : 'Save'} + {t(isLeadSaving ? 'chatmessage.savingLoading' : 'common.actions.save')}
@@ -2940,7 +2947,7 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP - Try these prompts + {t('chatmessage.tryThese')}
- - To record audio, use modern browsers like Chrome or Firefox that support audio recording. - + {t('chatmessage.unsupproted')}
@@ -3002,7 +3007,7 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP 00:00 - {isLoadingRecording && Sending...} + {isLoadingRecording && {t('chatmessage.sendingLoading')}}
@@ -3030,7 +3035,7 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP onKeyDown={handleEnter} id='userInput' name='userInput' - placeholder={loading ? 'Waiting for response...' : 'Type your question...'} + placeholder={t(loading ? 'chatmessage.waitingResponse' : 'chatmessage.typeQuestion')} value={userInput} onChange={onChange} multiline={true} @@ -3149,7 +3154,9 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP handleAbort()} disabled={isMessageStopping} @@ -3208,13 +3215,13 @@ const ChatMessage = ({ open, chatflowid, isAgentCanvas, isDialog, previews, setP setFeedback('') }} > - Provide Feedback + {t('chatmessage.provideFeedback')} - + diff --git a/packages/ui/src/views/chatmessage/ChatPopUp.jsx b/packages/ui/src/views/chatmessage/ChatPopUp.jsx index 1731feb7eca..16d96de423a 100644 --- a/packages/ui/src/views/chatmessage/ChatPopUp.jsx +++ b/packages/ui/src/views/chatmessage/ChatPopUp.jsx @@ -27,7 +27,11 @@ import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackba // Utils import { getLocalStorageChatflow, removeLocalStorageChatHistory } from '@/utils/genericHelper' +// i18n +import { useTranslation } from 'react-i18next' + const ChatPopUp = ({ chatflowid, isAgentCanvas, onOpenChange }) => { + const { t } = useTranslation() const theme = useTheme() const { confirm } = useConfirm() const dispatch = useDispatch() @@ -86,10 +90,10 @@ const ChatPopUp = ({ chatflowid, isAgentCanvas, onOpenChange }) => { const clearChat = async () => { const confirmPayload = { - title: `Clear Chat History`, - description: `Are you sure you want to clear all chat history?`, - confirmButtonName: 'Clear', - cancelButtonName: 'Cancel' + title: t('chatmessage.actions.clearHistory.title'), + description: t('chatmessage.actions.clearHistory.description'), + confirmButtonName: t('common.actions.clear'), + cancelButtonName: t('common.actions.cancel') } const isConfirmed = await confirm(confirmPayload) @@ -101,7 +105,7 @@ const ChatPopUp = ({ chatflowid, isAgentCanvas, onOpenChange }) => { removeLocalStorageChatHistory(chatflowid) resetChatDialog() enqueueSnackbar({ - message: 'Succesfully cleared all chat history', + message: t('chatmessage.messages.clear.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -147,8 +151,8 @@ const ChatPopUp = ({ chatflowid, isAgentCanvas, onOpenChange }) => { ref={anchorRef} size='small' color='secondary' - aria-label='chat' - title='Chat' + aria-label={t('chatmessage.actions.chat')} + title={t('chatmessage.actions.chat')} onClick={handleToggle} > {open ? : } @@ -160,8 +164,8 @@ const ChatPopUp = ({ chatflowid, isAgentCanvas, onOpenChange }) => { onClick={clearChat} size='small' color='error' - aria-label='clear' - title='Clear Chat History' + aria-label={t('common.actions.clear')} + title={t('chatmessage.actions.clearHistory.title')} > @@ -172,8 +176,8 @@ const ChatPopUp = ({ chatflowid, isAgentCanvas, onOpenChange }) => { onClick={expandChat} size='small' color='primary' - aria-label='expand' - title='Expand Chat' + aria-label={t('common.actions.expand')} + title={t('chatmessage.actions.expandChat')} > diff --git a/packages/ui/src/views/chatmessage/ThinkingCard.jsx b/packages/ui/src/views/chatmessage/ThinkingCard.jsx index 4c5b3771cd9..11aebee446f 100644 --- a/packages/ui/src/views/chatmessage/ThinkingCard.jsx +++ b/packages/ui/src/views/chatmessage/ThinkingCard.jsx @@ -4,7 +4,11 @@ import { Box, Collapse, Typography, CircularProgress } from '@mui/material' import { useTheme } from '@mui/material/styles' import { IconChevronDown, IconChevronRight, IconBrain } from '@tabler/icons-react' +// i18n +import { useTranslation } from 'react-i18next' + const ThinkingCard = ({ thinking, thinkingDuration, isThinking, customization }) => { + const { t } = useTranslation() const theme = useTheme() const [isExpanded, setIsExpanded] = useState(false) @@ -30,12 +34,12 @@ const ThinkingCard = ({ thinking, thinkingDuration, isThinking, customization }) // Determine header text const getHeaderText = () => { if (isThinking) { - return 'Thinking...' + return t('chatmessage.thinking.loading') } if (thinkingDuration !== undefined && thinkingDuration !== null) { - return `Thought for ${thinkingDuration} second${thinkingDuration !== 1 ? 's' : ''}` + return t('chatmessage.thinking.done', { count: thinkingDuration }) } - return 'Thinking...' + return t('chatmessage.thinking.loading') } return ( diff --git a/packages/ui/src/views/chatmessage/ValidationPopUp.jsx b/packages/ui/src/views/chatmessage/ValidationPopUp.jsx index 1cd9b97c2f4..d2a5b7d109e 100644 --- a/packages/ui/src/views/chatmessage/ValidationPopUp.jsx +++ b/packages/ui/src/views/chatmessage/ValidationPopUp.jsx @@ -22,9 +22,13 @@ import useNotifier from '@/utils/useNotifier' import { enqueueSnackbar as enqueueSnackbarAction } from '@/store/actions' import { AGENTFLOW_ICONS } from '@/store/constant' +// i18n +import { useTranslation } from 'react-i18next' + // Utils const ValidationPopUp = ({ chatflowid, hidden }) => { + const { t } = useTranslation() const theme = useTheme() const dispatch = useDispatch() const customization = useSelector((state) => state.customization) @@ -60,7 +64,7 @@ const ValidationPopUp = ({ chatflowid, hidden }) => { if (response.data.length === 0) { enqueueSnackbar({ - message: 'No issues found in your flow!', + message: t('chatmessage.messages.validation.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -71,7 +75,7 @@ const ValidationPopUp = ({ chatflowid, hidden }) => { } catch (error) { console.error(error) enqueueSnackbar({ - message: error.message || 'Failed to validate flow', + message: error.message || t('chatmessage.messages.validation.error'), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -145,8 +149,8 @@ const ValidationPopUp = ({ chatflowid, hidden }) => { ref={anchorRef} size='small' color='teal' - aria-label='validation' - title='Validate Nodes' + aria-label={t('chatmessage.actions.validation')} + title={t('chatmessage.actions.validateNodes')} onClick={handleToggle} > {open ? : } @@ -189,7 +193,7 @@ const ValidationPopUp = ({ chatflowid, hidden }) => { shadow={theme.shadows[16]} > - Checklist ({previews.length}) + {t('chatmessage.checklist', { count: previews.length })} diff --git a/packages/ui/src/views/credentials/AddEditCredentialDialog.jsx b/packages/ui/src/views/credentials/AddEditCredentialDialog.jsx index fecdb30a74f..20d09cd5a20 100644 --- a/packages/ui/src/views/credentials/AddEditCredentialDialog.jsx +++ b/packages/ui/src/views/credentials/AddEditCredentialDialog.jsx @@ -32,7 +32,11 @@ import { baseURL, REDACTED_CREDENTIAL_VALUE } from '@/store/constant' import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions' import keySVG from '@/assets/images/key.svg' +// i18n +import { useTranslation } from 'react-i18next' + const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const dispatch = useDispatch() @@ -127,7 +131,7 @@ const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm, setEr const createResp = await credentialsApi.createCredential(obj) if (createResp.data) { enqueueSnackbar({ - message: 'New Credential added', + message: t('credentials.messages.add.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -143,9 +147,9 @@ const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm, setEr } catch (error) { if (setError) setError(error) enqueueSnackbar({ - message: `Failed to add new Credential: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('credentials.messages.add.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -179,7 +183,7 @@ const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm, setEr const saveResp = await credentialsApi.updateCredential(credential.id, saveObj) if (saveResp.data) { enqueueSnackbar({ - message: 'Credential saved', + message: t('credentials.messages.save.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -195,9 +199,9 @@ const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm, setEr } catch (error) { if (setError) setError(error) enqueueSnackbar({ - message: `Failed to save Credential: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('credentials.messages.save.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -276,7 +280,7 @@ const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm, setEr if (event.data.type === 'OAUTH2_SUCCESS') { enqueueSnackbar({ - message: 'OAuth2 authorization completed successfully', + message: t('credentials.messages.setOAuth2.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -290,7 +294,7 @@ const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm, setEr onConfirm(credentialId) } else if (event.data.type === 'OAUTH2_ERROR') { enqueueSnackbar({ - message: event.data.message || 'OAuth2 authorization failed', + message: event.data.message || t('credentials.messages.setOAuth2.errors.simple'), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -341,7 +345,9 @@ const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm, setEr console.error('OAuth2 authorization error:', error) if (setError) setError(error) enqueueSnackbar({ - message: `OAuth2 authorization failed: ${error.response?.data?.message || error.message || 'Unknown error'}`, + message: t('credentials.messages.setOAuth2.errors.withMsg', { + msg: error.response?.data?.message || error.message || t('common.errors.unknownError') + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -419,7 +425,7 @@ const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm, setEr }} > - Cannot edit shared credential. + {t('credentials.cannotShare')}
)} @@ -444,7 +450,7 @@ const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm, setEr - Credential Name + {t('credentials.inputs.credentialName')}  * @@ -462,7 +468,7 @@ const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm, setEr {!shared && componentCredential && componentCredential.name && componentCredential.name.includes('OAuth2') && ( - OAuth Redirect URL + {t('credentials.inputs.oAuthRedirectUrl')} )} diff --git a/packages/ui/src/views/credentials/CredentialInputHandler.jsx b/packages/ui/src/views/credentials/CredentialInputHandler.jsx index 26411168865..38e81dee299 100644 --- a/packages/ui/src/views/credentials/CredentialInputHandler.jsx +++ b/packages/ui/src/views/credentials/CredentialInputHandler.jsx @@ -13,9 +13,13 @@ import { SwitchInput } from '@/ui-component/switch/Switch' import { JsonEditorInput } from '@/ui-component/json/JsonEditor' import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser' +// i18n +import { useTranslation } from 'react-i18next' + // ===========================|| NodeInputHandler ||=========================== // const CredentialInputHandler = ({ inputParam, data, disabled = false }) => { + const { t } = useTranslation() const customization = useSelector((state) => state.customization) const ref = useRef(null) @@ -27,8 +31,8 @@ const CredentialInputHandler = ({ inputParam, data, disabled = false }) => { value, inputParam, disabled, - confirmButtonName: 'Save', - cancelButtonName: 'Cancel' + confirmButtonName: t('common.actions.save'), + cancelButtonName: t('common.actions.cancel') } setExpandDialogProps(dialogProp) setShowExpandDialog(true) @@ -58,7 +62,7 @@ const CredentialInputHandler = ({ inputParam, data, disabled = false }) => { height: 25, width: 25 }} - title='Expand' + title={t('common.actions.expand')} color='primary' onClick={() => onExpandDialogClicked(data[inputParam.name] ?? inputParam.default ?? '', inputParam)} > @@ -117,7 +121,7 @@ const CredentialInputHandler = ({ inputParam, data, disabled = false }) => { name={inputParam.name} options={inputParam.options} onSelect={(newValue) => (data[inputParam.name] = newValue)} - value={data[inputParam.name] ?? inputParam.default ?? 'choose an option'} + value={data[inputParam.name] ?? inputParam.default ?? t('components.dropdown.chooseOption')} /> )} diff --git a/packages/ui/src/views/credentials/CredentialListDialog.jsx b/packages/ui/src/views/credentials/CredentialListDialog.jsx index edf5d6e23dd..d1f49b2e22d 100644 --- a/packages/ui/src/views/credentials/CredentialListDialog.jsx +++ b/packages/ui/src/views/credentials/CredentialListDialog.jsx @@ -11,7 +11,11 @@ import { baseURL } from '@/store/constant' import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions' import keySVG from '@/assets/images/key.svg' +// i18n +import { useTranslation } from 'react-i18next' + const CredentialListDialog = ({ show, dialogProps, onCancel, onCredentialSelected }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const dispatch = useDispatch() const theme = useTheme() @@ -70,7 +74,7 @@ const CredentialListDialog = ({ show, dialogProps, onCancel, onCredentialSelecte id='input-search-credential' value={searchValue} onChange={(e) => filterSearch(e.target.value)} - placeholder='Search credential' + placeholder={t('credentials.inputs.search')} startAdornment={ @@ -86,7 +90,7 @@ const CredentialListDialog = ({ show, dialogProps, onCancel, onCredentialSelecte color: theme.palette.grey[900] } }} - title='Clear Search' + title={t('common.actions.clearSearch')} > diff --git a/packages/ui/src/views/credentials/index.jsx b/packages/ui/src/views/credentials/index.jsx index 3ab68e34bc9..c5d19616162 100644 --- a/packages/ui/src/views/credentials/index.jsx +++ b/packages/ui/src/views/credentials/index.jsx @@ -51,6 +51,9 @@ import { SET_COMPONENT_CREDENTIALS } from '@/store/actions' import { useError } from '@/store/context/ErrorContext' import ShareWithWorkspaceDialog from '@/ui-component/dialog/ShareWithWorkspaceDialog' +// i18n +import { useTranslation } from 'react-i18next' + const StyledTableCell = styled(TableCell)(({ theme }) => ({ borderColor: theme.palette.grey[900] + 25, padding: '6px 16px', @@ -74,6 +77,7 @@ const StyledTableRow = styled(TableRow)(() => ({ // ==============================|| Credentials ||============================== // const Credentials = () => { + const { t } = useTranslation() const theme = useTheme() const customization = useSelector((state) => state.customization) const dispatch = useDispatch() @@ -109,7 +113,7 @@ const Credentials = () => { const listCredential = () => { const dialogProp = { - title: 'Add New Credential', + title: t('credentials.dialogs.add'), componentsCredentials } setCredentialListDialogProps(dialogProp) @@ -119,8 +123,8 @@ const Credentials = () => { const addNew = (credentialComponent) => { const dialogProp = { type: 'ADD', - cancelButtonName: 'Cancel', - confirmButtonName: 'Add', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.add'), credentialComponent } setSpecificCredentialDialogProps(dialogProp) @@ -130,8 +134,8 @@ const Credentials = () => { const edit = (credential) => { const dialogProp = { type: 'EDIT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Save', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.save'), data: credential } setSpecificCredentialDialogProps(dialogProp) @@ -141,12 +145,12 @@ const Credentials = () => { const share = (credential) => { const dialogProps = { type: 'EDIT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Share', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.share'), data: { id: credential.id, name: credential.name, - title: 'Share Credential', + title: t('credentials.dialogs.share'), itemType: 'credential' } } @@ -156,10 +160,10 @@ const Credentials = () => { const deleteCredential = async (credential) => { const confirmPayload = { - title: `Delete`, - description: `Delete credential ${credential.name}?`, - confirmButtonName: 'Delete', - cancelButtonName: 'Cancel' + title: t('common.dialogs.delete'), + description: t('credentials.dialogs.delete.description', { name: credential.name }), + confirmButtonName: t('common.actions.delete'), + cancelButtonName: t('common.actions.cancel') } const isConfirmed = await confirm(confirmPayload) @@ -168,7 +172,7 @@ const Credentials = () => { const deleteResp = await credentialsApi.deleteCredential(credential.id) if (deleteResp.data) { enqueueSnackbar({ - message: 'Credential deleted', + message: t('credentials.messages.delete.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -183,9 +187,9 @@ const Credentials = () => { } } catch (error) { enqueueSnackbar({ - message: `Failed to delete Credential: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('credentials.messages.delete.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -245,9 +249,9 @@ const Credentials = () => { { onClick={listCredential} startIcon={} > - Add Credential + {t('credentials.actions.addCredential')} {!isLoading && credentials.length <= 0 ? ( @@ -268,7 +272,7 @@ const Credentials = () => { alt='CredentialEmptySVG' /> -
No Credentials Yet
+
{t('credentials.notFound')}
) : ( { }} > - Name - Last Updated - Created + {t('common.labels.name')} + {t('credentials.table.lastUpdated')} + {t('credentials.table.created')} @@ -380,10 +384,14 @@ const Credentials = () => { - {moment(credential.updatedDate).format('MMMM Do, YYYY HH:mm:ss')} + {moment(credential.updatedDate).format( + t('common.formats.dateMonthDayYearTime24Long') + )} - {moment(credential.createdDate).format('MMMM Do, YYYY HH:mm:ss')} + {moment(credential.createdDate).format( + t('common.formats.dateMonthDayYearTime24Long') + )} {!credential.shared && ( <> @@ -391,7 +399,7 @@ const Credentials = () => { share(credential)} > @@ -401,7 +409,7 @@ const Credentials = () => { edit(credential)} > @@ -411,7 +419,7 @@ const Credentials = () => { deleteCredential(credential)} > @@ -422,7 +430,9 @@ const Credentials = () => { )} {credential.shared && ( <> - Shared Credential + + {t('credentials.table.title')} + )} diff --git a/packages/ui/src/views/datasets/AddEditDatasetDialog.jsx b/packages/ui/src/views/datasets/AddEditDatasetDialog.jsx index babb70eb5bb..09403e5c490 100644 --- a/packages/ui/src/views/datasets/AddEditDatasetDialog.jsx +++ b/packages/ui/src/views/datasets/AddEditDatasetDialog.jsx @@ -25,17 +25,14 @@ import datasetApi from '@/api/dataset' // utils import useNotifier from '@/utils/useNotifier' +// i18n +import { useTranslation } from 'react-i18next' + // const import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions' -const CSVFORMAT = `Only the first 2 columns will be considered: ----------------------------- -| Input | Output | ----------------------------- -| test input | test output | ----------------------------- -` const AddEditDatasetDialog = ({ show, dialogProps, onCancel, onConfirm }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const dispatch = useDispatch() @@ -94,7 +91,7 @@ const AddEditDatasetDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const createResp = await datasetApi.createDataset(obj) if (createResp.data) { enqueueSnackbar({ - message: 'New Dataset added', + message: t('datasets.messages.addDataset.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -109,9 +106,9 @@ const AddEditDatasetDialog = ({ show, dialogProps, onCancel, onConfirm }) => { } } catch (error) { enqueueSnackbar({ - message: `Failed to add new Dataset: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('datasets.messages.addDataset.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -137,7 +134,7 @@ const AddEditDatasetDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const saveResp = await datasetApi.updateDataset(dataset.id, saveObj) if (saveResp.data) { enqueueSnackbar({ - message: 'Dataset saved', + message: t('datasets.messages.saveDataset.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -152,9 +149,9 @@ const AddEditDatasetDialog = ({ show, dialogProps, onCancel, onConfirm }) => { } } catch (error) { enqueueSnackbar({ - message: `Failed to save Dataset: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('datasets.messages.saveDataset.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -182,14 +179,15 @@ const AddEditDatasetDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
- {dialogProps.type === 'ADD' ? 'Add Dataset' : 'Edit Dataset'} + {t(dialogProps.type === 'ADD' ? 'datasets.dialogs.addDataset' : 'datasets.dialogs.editDataset')}
- Name * + {t('common.labels.name')} +  *
@@ -205,7 +203,7 @@ const AddEditDatasetDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
- Description + {t('common.labels.description')}
{
- Upload CSV - ${CSVFORMAT}`} /> + {t('datasets.inputs.uploadCsv.title')} + ${t('datasets.inputs.uploadCsv.tooltip')}`} + />
@@ -233,13 +234,9 @@ const AddEditDatasetDialog = ({ show, dialogProps, onCancel, onConfirm }) => { disabled={false} fileType='.csv' onChange={(newValue) => setSelectedFile(newValue)} - value={selectedFile ?? 'Choose a file to upload'} - /> - +
)}
diff --git a/packages/ui/src/views/datasets/AddEditDatasetRowDialog.jsx b/packages/ui/src/views/datasets/AddEditDatasetRowDialog.jsx index 920a33a6c25..e8f55f9a717 100644 --- a/packages/ui/src/views/datasets/AddEditDatasetRowDialog.jsx +++ b/packages/ui/src/views/datasets/AddEditDatasetRowDialog.jsx @@ -25,7 +25,11 @@ import useNotifier from '@/utils/useNotifier' // const import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions' +// i18n +import { useTranslation } from 'react-i18next' + const AddEditDatasetRowDialog = ({ show, dialogProps, onCancel, onConfirm }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const dispatch = useDispatch() @@ -83,7 +87,7 @@ const AddEditDatasetRowDialog = ({ show, dialogProps, onCancel, onConfirm }) => const createResp = await datasetApi.createDatasetRow(obj) if (createResp.data) { enqueueSnackbar({ - message: 'New Row added for the given Dataset', + message: t('datasets.messages.addRow.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -98,9 +102,9 @@ const AddEditDatasetRowDialog = ({ show, dialogProps, onCancel, onConfirm }) => } } catch (error) { enqueueSnackbar({ - message: `Failed to add new row in the Dataset: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('datasets.messages.addRow.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -126,7 +130,7 @@ const AddEditDatasetRowDialog = ({ show, dialogProps, onCancel, onConfirm }) => const saveResp = await datasetApi.updateDatasetRow(row.id, saveObj) if (saveResp.data) { enqueueSnackbar({ - message: 'Dataset Row saved', + message: t('datasets.messages.saveRow.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -141,9 +145,9 @@ const AddEditDatasetRowDialog = ({ show, dialogProps, onCancel, onConfirm }) => } } catch (error) { enqueueSnackbar({ - message: `Failed to save Dataset Row: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('datasets.messages.saveRow.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -171,14 +175,17 @@ const AddEditDatasetRowDialog = ({ show, dialogProps, onCancel, onConfirm }) =>
- {dialogProps.type === 'ADD' ? `Add Item to ${datasetName} Dataset` : `Edit Item in ${datasetName} Dataset`} + {t(dialogProps.type === 'ADD' ? `datasets.dialogs.addRow` : `datasets.dialogs.editRow`, { + name: datasetName + })}
- Input * + {t('datasets.inputs.input')} +  *
@@ -198,7 +205,8 @@ const AddEditDatasetRowDialog = ({ show, dialogProps, onCancel, onConfirm }) =>
- Anticipated Output * + {t('datasets.inputs.anticipatedOutput')} +  *
diff --git a/packages/ui/src/views/datasets/DatasetItems.jsx b/packages/ui/src/views/datasets/DatasetItems.jsx index 868f17fe3b0..9515213df0b 100644 --- a/packages/ui/src/views/datasets/DatasetItems.jsx +++ b/packages/ui/src/views/datasets/DatasetItems.jsx @@ -47,9 +47,13 @@ import empty_datasetSVG from '@/assets/images/empty_datasets.svg' import { IconTrash, IconPlus, IconX, IconUpload, IconArrowsDownUp } from '@tabler/icons-react' import DragIndicatorIcon from '@mui/icons-material/DragIndicator' +// i18n +import { useTranslation } from 'react-i18next' + // ==============================|| Dataset Items ||============================== // const EvalDatasetRows = () => { + const { t } = useTranslation() const theme = useTheme() const customization = useSelector((state) => state.customization) const dispatch = useDispatch() @@ -159,8 +163,8 @@ const EvalDatasetRows = () => { const addNew = () => { const dialogProp = { type: 'ADD', - cancelButtonName: 'Cancel', - confirmButtonName: 'Add', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.add'), data: { datasetId: datasetId, datasetName: dataset.name @@ -173,8 +177,8 @@ const EvalDatasetRows = () => { const uploadCSV = () => { const dialogProp = { type: 'ADD', - cancelButtonName: 'Cancel', - confirmButtonName: 'Upload', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('datasets.actions.upload'), data: { datasetId: datasetId, datasetName: dataset.name @@ -187,8 +191,8 @@ const EvalDatasetRows = () => { const editDs = () => { const dialogProp = { type: 'EDIT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Save', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.save'), data: dataset } setDatasetDialogProps(dialogProp) @@ -198,8 +202,8 @@ const EvalDatasetRows = () => { const edit = (item) => { const dialogProp = { type: 'EDIT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Save', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.save'), data: { datasetName: dataset.name, ...item @@ -211,10 +215,10 @@ const EvalDatasetRows = () => { const deleteDatasetItems = async () => { const confirmPayload = { - title: `Delete`, - description: `Delete ${selected.length} dataset items?`, - confirmButtonName: 'Delete', - cancelButtonName: 'Cancel' + title: t('common.dialogs.delete'), + description: t('datasets.dialogs.delete.description.items', { count: selected.length }), + confirmButtonName: t('common.actions.delete'), + cancelButtonName: t('common.actions.cancel') } const isConfirmed = await confirm(confirmPayload) @@ -223,7 +227,7 @@ const EvalDatasetRows = () => { const deleteResp = await datasetsApi.deleteDatasetItems(selected) if (deleteResp.data) { enqueueSnackbar({ - message: 'Dataset Items deleted', + message: t('datasets.messages.deleteRow.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -238,9 +242,9 @@ const EvalDatasetRows = () => { } } catch (error) { enqueueSnackbar({ - message: `Failed to delete dataset items: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('datasets.messages.deleteRow.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -295,7 +299,7 @@ const EvalDatasetRows = () => { onEdit={editDs} onBack={() => window.history.back()} search={false} - title={`Dataset : ${dataset?.name || ''}`} + title={t('datasets.dataset', { name: dataset?.name || '' })} description={dataset?.description} > { onClick={uploadCSV} startIcon={} > - Upload CSV + {t('datasets.actions.uploadCsv')} { onClick={addNew} startIcon={} > - New Item + {t('datasets.actions.newItem')} {selected.length > 0 && ( @@ -327,7 +331,7 @@ const EvalDatasetRows = () => { color='error' startIcon={} > - Delete {selected.length} {selected.length === 1 ? 'item' : 'items'} + {t('datasets.actions.deleteItem', { count: selected.length })} )} {!isLoading && dataset?.rows?.length <= 0 ? ( @@ -339,7 +343,7 @@ const EvalDatasetRows = () => { alt='empty_datasetSVG' /> -
No Dataset Items Yet
+
{t('datasets.notFoundRows')}
{ startIcon={} onClick={addNew} > - New Item + {t('datasets.actions.newItem')} ) : ( @@ -356,7 +360,7 @@ const EvalDatasetRows = () => { sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }} component={Paper} > -
+
{ checked={selected.length === (dataset?.rows || []).length} onChange={onSelectAllClick} inputProps={{ - 'aria-label': 'select all' + 'aria-label': t('common.actions.selectAll') }} /> - Input - Expected Output + {t('datasets.table.input')} + {t('datasets.table.expectedOutput')} @@ -470,7 +474,7 @@ const EvalDatasetRows = () => {
- Use the drag icon at (extreme right) to reorder the dataset items + {t('datasets.datasetTooltip')} {/* Pagination and Page Size Controls */} diff --git a/packages/ui/src/views/datasets/UploadCSVFileDialog.jsx b/packages/ui/src/views/datasets/UploadCSVFileDialog.jsx index 355a0176725..3480b3e8079 100644 --- a/packages/ui/src/views/datasets/UploadCSVFileDialog.jsx +++ b/packages/ui/src/views/datasets/UploadCSVFileDialog.jsx @@ -25,15 +25,12 @@ import useNotifier from '@/utils/useNotifier' // const import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions' -const CSVFORMAT = `Only the first 2 columns will be considered: ----------------------------- -| Input | Output | ----------------------------- -| test input | test output | ----------------------------- -` + +// i18n +import { useTranslation } from 'react-i18next' const UploadCSVFileDialog = ({ show, dialogProps, onCancel, onConfirm }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const dispatch = useDispatch() @@ -81,7 +78,7 @@ const UploadCSVFileDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const createResp = await datasetApi.createDatasetRow(obj) if (createResp.data) { enqueueSnackbar({ - message: 'New Row added for the given Dataset', + message: t('datasets.messages.addRow.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -96,9 +93,9 @@ const UploadCSVFileDialog = ({ show, dialogProps, onCancel, onConfirm }) => { } } catch (error) { enqueueSnackbar({ - message: `Failed to add new row in the Dataset: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('datasets.messages.addRow.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -144,15 +141,15 @@ const UploadCSVFileDialog = ({ show, dialogProps, onCancel, onConfirm }) => { }} /> - {'Upload Items to [' + datasetName + '] Dataset'} + {t('datasets.dialogs.upload', { name: datasetName })}
- Upload CSV - ${CSVFORMAT}`} /> + {t('datasets.inputs.uploadCsv.title')} + ${t('datasets.inputs.uploadCsv.tooltip')}`} />
@@ -160,13 +157,9 @@ const UploadCSVFileDialog = ({ show, dialogProps, onCancel, onConfirm }) => { disabled={false} fileType='.csv' onChange={(newValue) => setSelectedFile(newValue)} - value={selectedFile ?? 'Choose a file to upload'} - /> - +
diff --git a/packages/ui/src/views/datasets/index.jsx b/packages/ui/src/views/datasets/index.jsx index 95fdfb1e305..ae374756f8e 100644 --- a/packages/ui/src/views/datasets/index.jsx +++ b/packages/ui/src/views/datasets/index.jsx @@ -49,9 +49,13 @@ import { truncateString } from '@/utils/genericHelper' import { useError } from '@/store/context/ErrorContext' +// i18n +import { useTranslation } from 'react-i18next' + // ==============================|| Datasets ||============================== // const EvalDatasets = () => { + const { t } = useTranslation() const navigate = useNavigate() const theme = useTheme() const { confirm } = useConfirm() @@ -101,8 +105,8 @@ const EvalDatasets = () => { const addNew = () => { const dialogProp = { type: 'ADD', - cancelButtonName: 'Cancel', - confirmButtonName: 'Add', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.add'), data: {} } setDatasetDialogProps(dialogProp) @@ -112,8 +116,8 @@ const EvalDatasets = () => { const edit = (dataset) => { const dialogProp = { type: 'EDIT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Save', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.save'), data: dataset } setDatasetDialogProps(dialogProp) @@ -122,10 +126,10 @@ const EvalDatasets = () => { const deleteDataset = async (dataset) => { const confirmPayload = { - title: `Delete`, - description: `Delete dataset ${dataset.name}?`, - confirmButtonName: 'Delete', - cancelButtonName: 'Cancel' + title: t('common.dialogs.delete'), + description: t('datasets.dialogs.delete.description.datasets', { name: dataset.name }), + confirmButtonName: t('common.actions.delete'), + cancelButtonName: t('common.actions.cancel') } const isConfirmed = await confirm(confirmPayload) @@ -134,7 +138,7 @@ const EvalDatasets = () => { const deleteResp = await datasetsApi.deleteDataset(dataset.id) if (deleteResp.data) { enqueueSnackbar({ - message: 'Dataset deleted', + message: t('datasets.messages.deleteDataset.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -149,9 +153,9 @@ const EvalDatasets = () => { } } catch (error) { enqueueSnackbar({ - message: `Failed to delete dataset: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('datasets.messages.deleteDataset.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -205,7 +209,7 @@ const EvalDatasets = () => { isEditButton={false} onSearchChange={onSearchChange} search={true} - title='Datasets' + title={t('datasets.title')} description='' > { onClick={addNew} startIcon={} > - Add New + {t('common.actions.addNew')} {!isLoading && datasets.length <= 0 ? ( @@ -227,7 +231,7 @@ const EvalDatasets = () => { alt='empty_datasetSVG' />
-
No Datasets Yet
+
{t('datasets.notFoundDataset')}
) : ( <> @@ -245,10 +249,10 @@ const EvalDatasets = () => { }} > - Name - Description - Rows - Last Updated + {t('common.labels.name')} + {t('common.labels.description')} + {t('datasets.table.rows')} + {t('datasets.table.lastUpdated')} @@ -320,11 +324,17 @@ const EvalDatasets = () => { goToRows(ds)}>{ds?.rowCount} goToRows(ds)}> - {moment(ds.updatedDate).format('MMMM Do YYYY, hh:mm A')} + {moment(ds.updatedDate).format( + t('common.formats.dateMonthDayYearTime12Short') + )} - edit(ds)}> + edit(ds)} + > @@ -332,7 +342,7 @@ const EvalDatasets = () => { deleteDataset(ds)} > diff --git a/packages/ui/src/views/docstore/AddDocStoreDialog.jsx b/packages/ui/src/views/docstore/AddDocStoreDialog.jsx index 0198c94984e..5cb8a18a4bd 100644 --- a/packages/ui/src/views/docstore/AddDocStoreDialog.jsx +++ b/packages/ui/src/views/docstore/AddDocStoreDialog.jsx @@ -25,7 +25,11 @@ import documentStoreApi from '@/api/documentstore' // utils import useNotifier from '@/utils/useNotifier' +// i18n +import { useTranslation } from 'react-i18next' + const AddDocStoreDialog = ({ show, dialogProps, onCancel, onConfirm }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const dispatch = useDispatch() @@ -74,7 +78,7 @@ const AddDocStoreDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const createResp = await documentStoreApi.createDocumentStore(obj) if (createResp.data) { enqueueSnackbar({ - message: 'New Document Store created.', + message: t('docstore.messages.create.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -93,9 +97,9 @@ const AddDocStoreDialog = ({ show, dialogProps, onCancel, onConfirm }) => { } } catch (error) { enqueueSnackbar({ - message: `Failed to add new Document Store: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('docstore.messages.create.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -121,7 +125,7 @@ const AddDocStoreDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const saveResp = await documentStoreApi.updateDocumentStore(docStoreId, saveObj) if (saveResp.data) { enqueueSnackbar({ - message: 'Document Store Updated!', + message: t('docstore.messages.update.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -140,9 +144,9 @@ const AddDocStoreDialog = ({ show, dialogProps, onCancel, onConfirm }) => { } } catch (error) { enqueueSnackbar({ - message: `Failed to update Document Store: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('docstore.messages.update.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -177,7 +181,8 @@ const AddDocStoreDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
- Name * + {t('common.labels.name')} +  *
@@ -194,7 +199,7 @@ const AddDocStoreDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
- Description + {t('common.labels.description')}
@@ -212,7 +217,7 @@ const AddDocStoreDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
- + { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const customization = useSelector((state) => state.customization) const dispatch = useDispatch() @@ -83,7 +87,7 @@ const ComponentsListDialog = ({ show, dialogProps, onCancel, apiCall, onSelected id='input-search-credential' value={searchValue} onChange={(e) => onSearchChange(e.target.value)} - placeholder='Search' + placeholder={t('common.actions.search')} startAdornment={ @@ -99,7 +103,7 @@ const ComponentsListDialog = ({ show, dialogProps, onCancel, apiCall, onSelected color: theme.palette.grey[900] } }} - title='Clear Search' + title={t('common.actions.clearSearch')} > diff --git a/packages/ui/src/views/docstore/DeleteDocStoreDialog.jsx b/packages/ui/src/views/docstore/DeleteDocStoreDialog.jsx index 261d9a38af8..4b002a4011d 100644 --- a/packages/ui/src/views/docstore/DeleteDocStoreDialog.jsx +++ b/packages/ui/src/views/docstore/DeleteDocStoreDialog.jsx @@ -38,7 +38,11 @@ import nodesApi from '@/api/nodes' import useApi from '@/hooks/useApi' import { initNode } from '@/utils/genericHelper' +// i18n +import { useTranslation, Trans } from 'react-i18next' + const DeleteDocStoreDialog = ({ show, dialogProps, onCancel, onDelete }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const theme = useTheme() const [nodeConfigExpanded, setNodeConfigExpanded] = useState({}) @@ -199,17 +203,20 @@ const DeleteDocStoreDialog = ({ show, dialogProps, onCancel, onDelete }) => { > - Note: Without a Record Manager configured, only the document chunks will be removed from the - document store. The actual vector embeddings in your vector store database will remain unchanged. To enable - automatic cleanup of vector store data, please configure a Record Manager.{' '} - - Learn more - + , + a: ( + + ) + }} + />
)} @@ -217,11 +224,11 @@ const DeleteDocStoreDialog = ({ show, dialogProps, onCancel, onDelete }) => { - Configuration + {t('docstore.labels.configuration')} - +
@@ -294,10 +301,10 @@ const DeleteDocStoreDialog = ({ show, dialogProps, onCancel, onDelete }) => { diff --git a/packages/ui/src/views/docstore/DocStoreAPIDialog.jsx b/packages/ui/src/views/docstore/DocStoreAPIDialog.jsx index 74e51400c5a..38a2a09c00d 100644 --- a/packages/ui/src/views/docstore/DocStoreAPIDialog.jsx +++ b/packages/ui/src/views/docstore/DocStoreAPIDialog.jsx @@ -23,7 +23,11 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore' import { IconInfoCircle } from '@tabler/icons-react' import { baseURL } from '@/store/constant' +// i18n +import { useTranslation } from 'react-i18next' + const DocStoreAPIDialog = ({ show, dialogProps, onCancel }) => { + const { t } = useTranslation() const [nodeConfig, setNodeConfig] = useState({}) const [values, setValues] = useState('') const theme = useTheme() @@ -33,7 +37,7 @@ const DocStoreAPIDialog = ({ show, dialogProps, onCancel }) => { const getConfigApi = useApi(documentstoreApi.getDocumentStoreConfig) const formDataRequest = () => { - return `With the Upsert API, you can choose an existing document and reuse the same configuration for upserting. + return `${t('docstore.api.upsertNote')} \`\`\`python import requests @@ -135,7 +139,7 @@ curl -X POST ${baseURL}/api/v1/document-store/upsert/${dialogProps.storeId} \\ } const jsonDataRequest = () => { - return `With the Upsert API, you can choose an existing document and reuse the same configuration for upserting. + return `${t('docstore.api.upsertNote')} \`\`\`python import requests @@ -349,7 +353,7 @@ curl -X POST ${baseURL}/api/v1/document-store/upsert/${dialogProps.storeId} \\ }} /> - Note: Upsert API can only be used when the existing document loader has been upserted before. + {t('docstore.labels.note')} {t('docstore.api.upsertNote')} @@ -357,7 +361,7 @@ curl -X POST ${baseURL}/api/v1/document-store/upsert/${dialogProps.storeId} \\ {values} - You can override existing configurations: + {t('docstore.labels.override')} diff --git a/packages/ui/src/views/docstore/DocStoreInputHandler.jsx b/packages/ui/src/views/docstore/DocStoreInputHandler.jsx index 184d9d76b90..6ddbd771b05 100644 --- a/packages/ui/src/views/docstore/DocStoreInputHandler.jsx +++ b/packages/ui/src/views/docstore/DocStoreInputHandler.jsx @@ -26,9 +26,13 @@ import { flowContext } from '@/store/context/ReactFlowContext' // const import { FLOWISE_CREDENTIAL_ID } from '@/store/constant' +// i18n +import { useTranslation } from 'react-i18next' + // ===========================|| DocStoreInputHandler ||=========================== // const DocStoreInputHandler = ({ inputParam, data, disabled = false, onNodeDataChange }) => { + const { t } = useTranslation() const customization = useSelector((state) => state.customization) const flowContextValue = useContext(flowContext) const nodeDataChangeHandler = onNodeDataChange || flowContextValue?.onNodeDataChange @@ -51,8 +55,8 @@ const DocStoreInputHandler = ({ inputParam, data, disabled = false, onNodeDataCh value, inputParam, disabled, - confirmButtonName: 'Save', - cancelButtonName: 'Cancel' + confirmButtonName: t('common.actions.save'), + cancelButtonName: t('common.actions.cancel') } setExpandDialogProps(dialogProps) setShowExpandDialog(true) @@ -64,8 +68,8 @@ const DocStoreInputHandler = ({ inputParam, data, disabled = false, onNodeDataCh relativeLinksMethod, limit, selectedLinks, - confirmButtonName: 'Save', - cancelButtonName: 'Cancel' + confirmButtonName: t('common.actions.save'), + cancelButtonName: t('common.actions.cancel') } setManageScrapedLinksDialogProps(dialogProps) setShowManageScrapedLinksDialog(true) @@ -109,7 +113,7 @@ const DocStoreInputHandler = ({ inputParam, data, disabled = false, onNodeDataCh height: 25, width: 25 }} - title='Expand' + title={t('common.actions.expand')} color='primary' onClick={() => onExpandDialogClicked(data.inputs[inputParam.name] ?? inputParam.default ?? '', inputParam) @@ -157,7 +161,7 @@ const DocStoreInputHandler = ({ inputParam, data, disabled = false, onNodeDataCh disabled={disabled} fileType={inputParam.fileType || '*'} onChange={(newValue) => handleDataChange({ inputParam, newValue })} - value={data.inputs[inputParam.name] ?? inputParam.default ?? 'Choose a file to upload'} + value={data.inputs[inputParam.name] ?? inputParam.default ?? t('components.file.chooseFile')} /> )} {inputParam.type === 'boolean' && ( @@ -219,7 +223,7 @@ const DocStoreInputHandler = ({ inputParam, data, disabled = false, onNodeDataCh name={inputParam.name} options={inputParam.options} onSelect={(newValue) => handleDataChange({ inputParam, newValue })} - value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'} + value={data.inputs[inputParam.name] ?? inputParam.default ?? t('components.dropdown.chooseOption')} /> )} {inputParam.type === 'multiOptions' && ( @@ -229,7 +233,7 @@ const DocStoreInputHandler = ({ inputParam, data, disabled = false, onNodeDataCh name={inputParam.name} options={inputParam.options} onSelect={(newValue) => handleDataChange({ inputParam, newValue })} - value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'} + value={data.inputs[inputParam.name] ?? inputParam.default ?? t('components.dropdown.chooseOption')} /> )} {(inputParam.type === 'asyncOptions' || inputParam.type === 'asyncMultiOptions') && ( @@ -244,7 +248,9 @@ const DocStoreInputHandler = ({ inputParam, data, disabled = false, onNodeDataCh nodeData={data} freeSolo={inputParam.freeSolo} multiple={inputParam.type === 'asyncMultiOptions'} - value={data.inputs[inputParam.name] ?? inputParam.default ?? 'choose an option'} + value={ + data.inputs[inputParam.name] ?? inputParam.default ?? t('components.dropdown.chooseOption') + } onSelect={(newValue) => handleDataChange({ inputParam, newValue })} onCreateNew={() => addAsyncOption(inputParam.name)} fullWidth={true} @@ -252,7 +258,7 @@ const DocStoreInputHandler = ({ inputParam, data, disabled = false, onNodeDataCh {inputParam.refresh && ( setReloadTimestamp(Date.now().toString())} @@ -289,7 +295,7 @@ const DocStoreInputHandler = ({ inputParam, data, disabled = false, onNodeDataCh ) } > - Manage Links + {t('common.actions.manageLinks')} { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const dispatch = useDispatch() const theme = useTheme() @@ -83,7 +87,7 @@ const DocumentLoaderListDialog = ({ show, dialogProps, onCancel, onDocLoaderSele id='input-search-credential' value={searchValue} onChange={(e) => onSearchChange(e.target.value)} - placeholder='Search' + placeholder={t('common.actions.search')} startAdornment={ @@ -99,7 +103,7 @@ const DocumentLoaderListDialog = ({ show, dialogProps, onCancel, onDocLoaderSele color: theme.palette.grey[900] } }} - title='Clear Search' + title={t('common.actions.clearSearch')} > diff --git a/packages/ui/src/views/docstore/DocumentStoreDetail.jsx b/packages/ui/src/views/docstore/DocumentStoreDetail.jsx index dac334fa4fc..dabc66e9bbc 100644 --- a/packages/ui/src/views/docstore/DocumentStoreDetail.jsx +++ b/packages/ui/src/views/docstore/DocumentStoreDetail.jsx @@ -66,6 +66,9 @@ import doc_store_details_emptySVG from '@/assets/images/doc_store_details_empty. import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackbarAction } from '@/store/actions' import { useError } from '@/store/context/ErrorContext' +// i18n +import { useTranslation } from 'react-i18next' + // ==============================|| DOCUMENTS ||============================== // const StyledTableCell = styled(TableCell)(({ theme }) => ({ @@ -125,6 +128,7 @@ const StyledMenu = styled((props) => ( })) const DocumentStoreDetails = () => { + const { t } = useTranslation() const theme = useTheme() const customization = useSelector((state) => state.customization) const navigate = useNavigate() @@ -180,7 +184,7 @@ const DocumentStoreDetails = () => { const listLoaders = () => { const dialogProp = { - title: 'Select Document Loader' + title: t('docstore.dialogs.selectDocumentLoader') } setDocumentLoaderListDialogProps(dialogProp) setShowDocumentLoaderListDialog(true) @@ -206,7 +210,7 @@ const DocumentStoreDetails = () => { setBackdropLoading(false) if (deleteResp.data) { enqueueSnackbar({ - message: 'Store, Loader and associated document chunks deleted', + message: t('docstore.messages.deleteStore.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -223,9 +227,9 @@ const DocumentStoreDetails = () => { setBackdropLoading(false) setError(error) enqueueSnackbar({ - message: `Failed to delete Document Store: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('docstore.messages.deleteStore.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -247,7 +251,7 @@ const DocumentStoreDetails = () => { setBackdropLoading(false) if (deleteResp.data) { enqueueSnackbar({ - message: 'Loader and associated document chunks deleted', + message: t('docstore.messages.deleteLoader.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -264,9 +268,9 @@ const DocumentStoreDetails = () => { setError(error) setBackdropLoading(false) enqueueSnackbar({ - message: `Failed to delete Document Loader: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('docstore.messages.deleteLoader.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -284,7 +288,7 @@ const DocumentStoreDetails = () => { const onLoaderDelete = (file, vectorStoreConfig, recordManagerConfig) => { // Get the display name in the format "LoaderName (sourceName)" - const loaderName = file.loaderName || 'Unknown' + const loaderName = file.loaderName || t('docstore.common.unknown') let sourceName = '' // Prefer files.name when files array exists and has items @@ -303,7 +307,7 @@ const DocumentStoreDetails = () => { const displayName = sourceName ? `${loaderName} (${sourceName})` : loaderName - let description = `Delete "${displayName}"? This will delete all the associated document chunks from the document store.` + let description = t('docstore.dialogs.delete.description.loader.simple', { name: displayName }) if ( recordManagerConfig && @@ -311,7 +315,7 @@ const DocumentStoreDetails = () => { Object.keys(recordManagerConfig).length > 0 && Object.keys(vectorStoreConfig).length > 0 ) { - description = `Delete "${displayName}"? This will delete all the associated document chunks from the document store and remove the actual data from the vector store database.` + description = t('docstore.dialogs.delete.description.loader.withData', { name: displayName }) } const props = { @@ -328,7 +332,7 @@ const DocumentStoreDetails = () => { } const onStoreDelete = (vectorStoreConfig, recordManagerConfig) => { - let description = `Delete Store ${getSpecificDocumentStore.data?.name}? This will delete all the associated loaders and document chunks from the document store.` + let description = t('docstore.dialogs.delete.description.store.simple', { name: getSpecificDocumentStore.data?.name }) if ( recordManagerConfig && @@ -336,11 +340,11 @@ const DocumentStoreDetails = () => { Object.keys(recordManagerConfig).length > 0 && Object.keys(vectorStoreConfig).length > 0 ) { - description = `Delete Store ${getSpecificDocumentStore.data?.name}? This will delete all the associated loaders and document chunks from the document store, and remove the actual data from the vector store database.` + description = t('docstore.dialogs.delete.description.store.withData', { name: getSpecificDocumentStore.data?.name }) } const props = { - title: `Delete`, + title: t('common.dialogs.delete'), description, vectorStoreConfig, recordManagerConfig, @@ -353,10 +357,10 @@ const DocumentStoreDetails = () => { const onStoreRefresh = async (storeId) => { const confirmPayload = { - title: `Refresh all loaders and upsert all chunks?`, - description: `This will re-process all loaders and upsert all chunks. This action might take some time.`, - confirmButtonName: 'Refresh', - cancelButtonName: 'Cancel' + title: t('docstore.dialogs.confirm.title'), + description: t('docstore.dialogs.confirm.description'), + confirmButtonName: t('common.actions.refresh'), + cancelButtonName: t('common.actions.cancel') } const isConfirmed = await confirm(confirmPayload) @@ -367,7 +371,7 @@ const DocumentStoreDetails = () => { const resp = await documentsApi.refreshLoader(storeId) if (resp.data) { enqueueSnackbar({ - message: 'Document store refresh successfully!', + message: t('docstore.messages.storeRefresh.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -383,9 +387,9 @@ const DocumentStoreDetails = () => { } catch (error) { setBackdropLoading(false) enqueueSnackbar({ - message: `Failed to refresh document store: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('docstore.messages.storeRefresh.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -407,10 +411,10 @@ const DocumentStoreDetails = () => { id: documentStore.id } const dialogProp = { - title: 'Edit Document Store', + title: t('docstore.dialogs.editDocumentStore'), type: 'EDIT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Update', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('docstore.actions.update'), data: data } setDialogProps(dialogProp) @@ -430,7 +434,7 @@ const DocumentStoreDetails = () => { const onViewUpsertAPI = (storeId, loaderId) => { const props = { - title: `Upsert API`, + title: t('docstore.dialogs.upsertApi'), storeId, loaderId } @@ -487,7 +491,7 @@ const DocumentStoreDetails = () => { onClick={onConfirm} size='small' color='primary' - title='Refresh Document Store' + title={t('docstore.actions.refreshDocumentStore')} > @@ -499,7 +503,7 @@ const DocumentStoreDetails = () => { startIcon={} onClick={listLoaders} > - Add Document Loader + {t('docstore.actions.addDocumentLoader')} { disableRipple > - View & Edit Chunks + {t('docstore.actions.menu.viewEditChunks')} { disableRipple > - Upsert All Chunks + {t('docstore.actions.menu.upsertAllChunks')} { disableRipple > - Retrieval Query + {t('docstore.actions.menu.retrievalQuery')} onStoreRefresh(documentStore.id)} disableRipple - title='Re-process all loaders and upsert all chunks' + title={t('docstore.actions.menu.refresh')} > - Refresh + {t('common.actions.refresh')} @@ -579,7 +583,7 @@ const DocumentStoreDetails = () => { disableRipple > - Delete + {t('common.actions.delete')} @@ -600,7 +604,7 @@ const DocumentStoreDetails = () => { }} > - Chatflows Used: + {t('docstore.labels.chatflowsUsed')} {getSpecificDocumentStore.data.whereUsed.map((chatflowUsed, index) => ( { alt='doc_store_details_emptySVG' /> -
No Document Added Yet
+
{t('docstore.empty.documents')}
} onClick={listLoaders} > - Add Document Loader + {t('docstore.actions.addDocumentLoader')}
) : ( @@ -643,7 +647,7 @@ const DocumentStoreDetails = () => { sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }} component={Paper} > -
+
{ >   - Loader - Splitter - Source(s) - Chunks - Chars + {t('docstore.table.loader')} + {t('docstore.table.splitter')} + {t('docstore.table.source')} + {t('docstore.table.chunks')} + {t('docstore.table.chars')} - Actions + {t('docstore.table.actions')} @@ -755,7 +759,7 @@ const DocumentStoreDetails = () => { color='warning' style={{ color: 'darkred', fontWeight: 500, fontStyle: 'italic', fontSize: 12 }} > - Some files are pending processing. Please Refresh to get the latest status. + {t('docstore.labels.pendingRefresh')} )} @@ -800,6 +804,7 @@ const DocumentStoreDetails = () => { } function LoaderRow(props) { + const { t } = useTranslation() const [anchorEl, setAnchorEl] = useState(null) const open = Boolean(anchorEl) @@ -830,7 +835,7 @@ function LoaderRow(props) { // Return format: "LoaderName (sourceName)" or just "LoaderName" if no source if (!sourceName) { - return loaderName || 'No source' + return loaderName || t('docstore.common.noSource') } return loaderName ? `${loaderName} (${sourceName})` : sourceName } @@ -852,7 +857,7 @@ function LoaderRow(props) { {props.loader.loaderName} - {props.loader.splitterName ?? 'None'} + {props.loader.splitterName ?? t('common.labels.none')} {formatSources(props.loader.files, props.loader.source)} @@ -894,7 +899,7 @@ function LoaderRow(props) { disableRipple > - Preview & Process + {t('docstore.actions.menu.previewProcess')} @@ -906,7 +911,7 @@ function LoaderRow(props) { disableRipple > - View & Edit Chunks + {t('docstore.actions.menu.viewEditChunks')} @@ -918,7 +923,7 @@ function LoaderRow(props) { disableRipple > - Upsert Chunks + {t('docstore.actions.menu.upsertChunks')} @@ -930,7 +935,7 @@ function LoaderRow(props) { disableRipple > - View API + {t('docstore.actions.menu.viewApi')} @@ -943,7 +948,7 @@ function LoaderRow(props) { disableRipple > - Delete + {t('common.actions.delete')} diff --git a/packages/ui/src/views/docstore/ExpandedChunkDialog.jsx b/packages/ui/src/views/docstore/ExpandedChunkDialog.jsx index 0d7976e00a2..4867d2f24ca 100644 --- a/packages/ui/src/views/docstore/ExpandedChunkDialog.jsx +++ b/packages/ui/src/views/docstore/ExpandedChunkDialog.jsx @@ -13,7 +13,11 @@ import { IconEdit, IconTrash, IconX, IconLanguage } from '@tabler/icons-react' import { CodeEditor } from '@/ui-component/editor/CodeEditor' import { PermissionButton, PermissionIconButton } from '@/ui-component/button/RBACButtons' +// i18n +import { useTranslation } from 'react-i18next' + const ExpandedChunkDialog = ({ show, dialogProps, onCancel, onChunkEdit, onDeleteChunk, isReadOnly }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const customization = useSelector((state) => state.customization) @@ -93,15 +97,15 @@ const ExpandedChunkDialog = ({ show, dialogProps, onCancel, onChunkEdit, onDelet onClick={() => setIsEdit(true)} size='small' color='primary' - title='Edit Chunk' + title={t('docstore.actions.editChunk')} sx={{ ml: 2 }} > )} {isEdit && !isReadOnly && ( - )} {isEdit && !isReadOnly && ( @@ -109,11 +113,11 @@ const ExpandedChunkDialog = ({ show, dialogProps, onCancel, onChunkEdit, onDelet permissionId={'documentStores:preview-process'} onClick={() => onEditSaved(true)} color='primary' - title='Save' + title={t('common.actions.save')} variant='contained' sx={{ ml: 2, mr: 1 }} > - Save + {t('common.actions.save')} )} {!isEdit && !isReadOnly && ( @@ -122,13 +126,13 @@ const ExpandedChunkDialog = ({ show, dialogProps, onCancel, onChunkEdit, onDelet onClick={() => onDeleteChunk(selectedChunk)} size='small' color='error' - title='Delete Chunk' + title={t('docstore.actions.deleteChunk')} sx={{ ml: 1 }} > )} - + @@ -157,7 +161,7 @@ const ExpandedChunkDialog = ({ show, dialogProps, onCancel, onChunkEdit, onDelet }} > - {selectedChunk?.pageContent?.length} characters + {t('docstore.preview.characters', { count: selectedChunk?.pageContent?.length })}
{!isEdit && ( diff --git a/packages/ui/src/views/docstore/LoaderConfigPreviewChunks.jsx b/packages/ui/src/views/docstore/LoaderConfigPreviewChunks.jsx index 618d67c135e..5bfc1e37bcf 100644 --- a/packages/ui/src/views/docstore/LoaderConfigPreviewChunks.jsx +++ b/packages/ui/src/views/docstore/LoaderConfigPreviewChunks.jsx @@ -38,6 +38,9 @@ import { useError } from '@/store/context/ErrorContext' import { initNode, showHideInputParams } from '@/utils/genericHelper' import useNotifier from '@/utils/useNotifier' +// i18n +import { useTranslation } from 'react-i18next' + const CardWrapper = styled(MainCard)(({ theme }) => ({ background: theme.palette.card.main, color: theme.darkTextPrimary, @@ -60,6 +63,7 @@ const CardWrapper = styled(MainCard)(({ theme }) => ({ // ===========================|| DOCUMENT LOADER CHUNKS ||=========================== // const LoaderConfigPreviewChunks = () => { + const { t } = useTranslation() const customization = useSelector((state) => state.customization) const navigate = useNavigate() const theme = useTheme() @@ -158,7 +162,7 @@ const LoaderConfigPreviewChunks = () => { if (!canSubmit) { const fieldsList = missingFields.join(', ') enqueueSnackbar({ - message: `Please fill in the following mandatory fields: ${fieldsList}`, + message: t(`docstore.validation.requiredFieldsList`, { fields: fieldsList }), options: { key: new Date().getTime() + Math.random(), variant: 'warning', @@ -190,9 +194,9 @@ const LoaderConfigPreviewChunks = () => { } catch (error) { setLoading(false) enqueueSnackbar({ - message: `Failed to preview chunks: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t(`docstore.messages.previewChunks.error`, { + mag: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -216,7 +220,7 @@ const LoaderConfigPreviewChunks = () => { setLoading(false) if (saveResp.data) { enqueueSnackbar({ - message: 'File submitted for processing. Redirecting to Document Store..', + message: t('docstore.messages.save.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -234,9 +238,9 @@ const LoaderConfigPreviewChunks = () => { } catch (error) { setLoading(false) enqueueSnackbar({ - message: `Failed to process chunking: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('docstore.messages.save.error', { + mag: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -338,7 +342,7 @@ const LoaderConfigPreviewChunks = () => { label: splitter.label, name: splitter.name })) - options.unshift({ label: 'None', name: 'none' }) + options.unshift({ label: t('common.labels.none'), name: 'none' }) setTextSplitterOptions(options) // If this is a document store edit config, set the existing input values @@ -397,7 +401,13 @@ const LoaderConfigPreviewChunks = () => { }} > - navigate(-1)}> + navigate(-1)} + > @@ -439,7 +449,7 @@ const LoaderConfigPreviewChunks = () => { sx={{ borderRadius: 2, height: '100%' }} startIcon={} > - Process + {t('docstore.actions.process')} @@ -459,11 +469,12 @@ const LoaderConfigPreviewChunks = () => { fullWidth sx={{ mt: 1 }} size='small' - label={ + label={t( selectedDocumentLoader?.label?.toLowerCase().includes('loader') - ? selectedDocumentLoader.label + ' name' - : selectedDocumentLoader?.label + ' Loader Name' - } + ? 'docstore.inputs.loaderName.simple' + : 'docstore.inputs.loaderName.loader', + { name: selectedDocumentLoader?.label } + )} value={loaderName} onChange={(e) => setLoaderName(e.target.value)} /> @@ -486,7 +497,7 @@ const LoaderConfigPreviewChunks = () => { {(splitterOptions ?? []).find( (splitter) => splitter.name === selectedTextSplitter?.name - )?.label ?? 'Select Text Splitter'} + )?.label ?? t('docstore.preview.selectTextSplitter')}
{
- Splitter + {t('docstore.labels.splitter')} { > - Preview Chunks + {t('docstore.actions.previewChunks')} @@ -614,10 +625,10 @@ const LoaderConfigPreviewChunks = () => { {documentChunks && documentChunks.length > 0 && ( <> - {currentPreviewCount} of {totalChunks} Chunks + {t('docstore.preview.count', { current: currentPreviewCount, total: totalChunks })} - Show Chunks in Preview + {t('docstore.preview.showChunks')}
{ /> - Preview + {t('docstore.actions.preview')}
@@ -656,7 +667,10 @@ const LoaderConfigPreviewChunks = () => { - {`#${index + 1}. Characters: ${row.pageContent.length}`} + {t('docstore.preview.chunkCharacters', { + index: index + 1, + total: row.pageContent.length + })} {row.pageContent} diff --git a/packages/ui/src/views/docstore/ShowStoredChunks.jsx b/packages/ui/src/views/docstore/ShowStoredChunks.jsx index 68e973a79af..412b7c762ba 100644 --- a/packages/ui/src/views/docstore/ShowStoredChunks.jsx +++ b/packages/ui/src/views/docstore/ShowStoredChunks.jsx @@ -32,6 +32,9 @@ import { getFileName } from '@/utils/genericHelper' import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackbarAction } from '@/store/actions' import { useError } from '@/store/context/ErrorContext' +// i18n +import { useTranslation } from 'react-i18next' + const CardWrapper = styled(MainCard)(({ theme }) => ({ background: theme.palette.card.main, color: theme.darkTextPrimary, @@ -52,6 +55,7 @@ const CardWrapper = styled(MainCard)(({ theme }) => ({ })) const ShowStoredChunks = () => { + const { t } = useTranslation() const customization = useSelector((state) => state.customization) const navigate = useNavigate() const dispatch = useDispatch() @@ -105,7 +109,7 @@ const ShowStoredChunks = () => { ) if (editResp.data) { enqueueSnackbar({ - message: 'Document chunk successfully edited!', + message: t('docstore.messages.edit.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -122,9 +126,9 @@ const ShowStoredChunks = () => { } catch (error) { setLoading(false) enqueueSnackbar({ - message: `Failed to edit chunk: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('docstore.messages.edit.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -140,10 +144,10 @@ const ShowStoredChunks = () => { const onDeleteChunk = async (chunk) => { const confirmPayload = { - title: `Delete`, - description: `Delete chunk ${chunk.id} ? This action cannot be undone.`, - confirmButtonName: 'Delete', - cancelButtonName: 'Cancel' + title: t('common.dialogs.delete'), + description: t('docstore.dialogs.delete.description.chunk', { id: chunk.id }), + confirmButtonName: t('common.actions.delete'), + cancelButtonName: t('common.actions.cancel') } const isConfirmed = await confirm(confirmPayload) @@ -154,7 +158,7 @@ const ShowStoredChunks = () => { const delResp = await documentsApi.deleteChunkFromStore(chunk.storeId, chunk.docId, chunk.id) if (delResp.data) { enqueueSnackbar({ - message: 'Document chunk successfully deleted!', + message: t('docstore.messages.deleteChunk.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -171,9 +175,9 @@ const ShowStoredChunks = () => { } catch (error) { setLoading(false) enqueueSnackbar({ - message: `Failed to delete chunk: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('docstore.messages.deleteChunk.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -319,7 +323,11 @@ const ShowStoredChunks = () => { } />
- Showing {Math.min(start, totalChunks)}-{end} of {totalChunks} chunks + {t('docstore.preview.showing', { + start: Math.min(start, totalChunks), + end: end, + total: totalChunks + })} changePage(currentPage + 1)} @@ -342,7 +350,7 @@ const ShowStoredChunks = () => {
- {getChunksApi.data?.characters?.toLocaleString()} characters + {t('docstore.preview.characters', { count: getChunksApi.data?.characters?.toLocaleString() })}
@@ -364,7 +372,7 @@ const ShowStoredChunks = () => { alt='chunks_emptySVG' /> -
No Chunks
+
{t('docstore.empty.chunks')}
)} {documentChunks.length > 0 && @@ -378,7 +386,10 @@ const ShowStoredChunks = () => { - {`#${row.chunkNo}. Characters: ${row.pageContent.length}`} + {t('docstore.preview.chunkCharacters', { + index: row.chunkNo, + total: row.pageContent.length + })} {row.pageContent} diff --git a/packages/ui/src/views/docstore/UpsertHistoryDetailsDialog.jsx b/packages/ui/src/views/docstore/UpsertHistoryDetailsDialog.jsx index 8059f8b4bad..e6d84cd7c3d 100644 --- a/packages/ui/src/views/docstore/UpsertHistoryDetailsDialog.jsx +++ b/packages/ui/src/views/docstore/UpsertHistoryDetailsDialog.jsx @@ -24,7 +24,11 @@ import StatsCard from '@/ui-component/cards/StatsCard' // const import { baseURL } from '@/store/constant' +// i18n +import { useTranslation } from 'react-i18next' + const UpsertHistoryDetailsDialog = ({ show, dialogProps, onCancel }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const [nodeConfigExpanded, setNodeConfigExpanded] = useState({}) @@ -54,14 +58,14 @@ const UpsertHistoryDetailsDialog = ({ show, dialogProps, onCancel }) => { marginTop: '10px' }} > - - - - + + + +
-
+
diff --git a/packages/ui/src/views/docstore/UpsertHistorySideDrawer.jsx b/packages/ui/src/views/docstore/UpsertHistorySideDrawer.jsx index cc834648f5c..c7a10da6c16 100644 --- a/packages/ui/src/views/docstore/UpsertHistorySideDrawer.jsx +++ b/packages/ui/src/views/docstore/UpsertHistorySideDrawer.jsx @@ -18,7 +18,11 @@ import HistoryEmptySVG from '@/assets/images/upsert_history_empty.svg' import vectorstoreApi from '@/api/vectorstore' import useApi from '@/hooks/useApi' +// i18n +import { useTranslation } from 'react-i18next' + const UpsertHistorySideDrawer = ({ show, dialogProps, onClickFunction, onSelectHistoryDetails }) => { + const { t } = useTranslation() const onOpen = () => {} const [upsertHistory, setUpsertHistory] = useState([]) @@ -40,7 +44,7 @@ const UpsertHistorySideDrawer = ({ show, dialogProps, onClickFunction, onSelectH <> onClickFunction()} onOpen={onOpen}> ( - {moment(history.date).format('DD-MMM-YYYY, hh:mm:ss A')} + {moment(history.date).format(t('common.formats.dateDayMonthShortYearTime12Seconds'))} @@ -62,16 +66,24 @@ const UpsertHistorySideDrawer = ({ show, dialogProps, onClickFunction, onSelectH {history.result.numAdded !== undefined && history.result.numAdded > 0 && ( - Added: {history.result.numAdded} + + {t('docstore.history.added', { count: history.result.numAdded })} + )} {history.result.numUpdated !== undefined && history.result.numUpdated > 0 && ( - Updated: {history.result.numUpdated} + + {t('docstore.history.updated', { count: history.result.numUpdated })} + )} {history.result.numSkipped !== undefined && history.result.numSkipped > 0 && ( - Skipped: {history.result.numSkipped} + + {t('docstore.history.skipped', { count: history.result.numSkipped })} + )} {history.result.numDeleted !== undefined && history.result.numDeleted > 0 && ( - Deleted: {history.result.numDeleted} + + {t('docstore.history.deleted', { count: history.result.numDeleted })} + )} @@ -93,7 +105,7 @@ const UpsertHistorySideDrawer = ({ show, dialogProps, onClickFunction, onSelectH alt='HistoryEmptySVG' /> -
No Upsert History Yet
+
{t('docstore.empty.history')}
)} diff --git a/packages/ui/src/views/docstore/VectorStoreConfigure.jsx b/packages/ui/src/views/docstore/VectorStoreConfigure.jsx index 13636b77f79..6a49689afbe 100644 --- a/packages/ui/src/views/docstore/VectorStoreConfigure.jsx +++ b/packages/ui/src/views/docstore/VectorStoreConfigure.jsx @@ -46,7 +46,11 @@ import useNotifier from '@/utils/useNotifier' // const const steps = ['Embeddings', 'Vector Store', 'Record Manager'] +// i18n +import { useTranslation } from 'react-i18next' + const VectorStoreConfigure = () => { + const { t } = useTranslation() const navigate = useNavigate() const dispatch = useDispatch() const { hasAssignedWorkspace } = useAuth() @@ -129,7 +133,7 @@ const VectorStoreConfigure = () => { const showEmbeddingsList = () => { const dialogProp = { - title: 'Select Embeddings Provider' + title: t('docstore.dialogs.selectEmbeddingsProvider') } setDialogProps(dialogProp) setShowEmbeddingsListDialog(true) @@ -153,7 +157,7 @@ const VectorStoreConfigure = () => { const showVectorStoreList = () => { const dialogProp = { - title: 'Select a Vector Store Provider' + title: t('docstore.dialogs.selectVectorStoreProvider') } setDialogProps(dialogProp) setShowVectorStoreListDialog(true) @@ -171,7 +175,7 @@ const VectorStoreConfigure = () => { const showRecordManagerList = () => { const dialogProp = { - title: 'Select a Record Manager' + title: t('docstore.dialogs.selectRecordManager') } setDialogProps(dialogProp) setShowRecordManagerListDialog(true) @@ -187,7 +191,7 @@ const VectorStoreConfigure = () => { const onSelectHistoryDetails = (history) => { const props = { - title: moment(history.date).format('DD-MMM-YYYY, hh:mm:ss A'), + title: moment(history.date).format(t('common.formats.dateDayMonthShortYearTime12Seconds')), numAdded: history.result.numAdded, numUpdated: history.result.numUpdated, numSkipped: history.result.numSkipped, @@ -228,7 +232,7 @@ const VectorStoreConfigure = () => { if (!canSubmit) { enqueueSnackbar({ - message: 'Please fill in all mandatory fields.', + message: t('docstore.validation.requiredFields'), options: { key: new Date().getTime() + Math.random(), variant: 'warning', @@ -358,7 +362,7 @@ const VectorStoreConfigure = () => { const getLoaderDisplayName = (loader) => { if (!loader) return '' - const loaderName = loader.loaderName || 'Unknown' + const loaderName = loader.loaderName || t('docstore.common.unknown') let sourceName = '' // Prefer files.name when files array exists and has items @@ -392,7 +396,7 @@ const VectorStoreConfigure = () => { if (saveVectorStoreConfigApi.data) { setLoading(false) enqueueSnackbar({ - message: 'Configuration saved successfully', + message: t('docstore.messages.saveConfiguration.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -518,7 +522,7 @@ const VectorStoreConfigure = () => { isBackButton={true} search={false} title={getViewHeaderTitle()} - description='Configure Embeddings, Vector Store and Record Manager' + description={t('docstore.vectorStore.description')} onBack={() => navigate(-1)} > {(Object.keys(selectedEmbeddingsProvider).length > 0 || @@ -533,7 +537,7 @@ const VectorStoreConfigure = () => { startIcon={} onClick={() => resetVectorStoreConfig()} > - Reset + {t('docstore.actions.reset')} )} {(Object.keys(selectedEmbeddingsProvider).length > 0 || @@ -548,7 +552,7 @@ const VectorStoreConfigure = () => { startIcon={} onClick={() => saveVectorStoreConfig()} > - Save Config + {t('docstore.actions.saveConfig')} )} {Object.keys(selectedEmbeddingsProvider).length > 0 && @@ -566,10 +570,15 @@ const VectorStoreConfigure = () => { startIcon={} onClick={() => tryAndInsertIntoStore()} > - Upsert + {t('docstore.actions.upsert')} )} - + @@ -596,7 +605,7 @@ const VectorStoreConfigure = () => { } }} > - Select Embeddings + {t('docstore.actions.selectEmbeddings')} ) : ( @@ -712,7 +721,7 @@ const VectorStoreConfigure = () => { }} disabled={isVectorStoreDisabled()} > - Select Vector Store + {t('docstore.actions.selectVectorStore')} ) : ( @@ -836,9 +845,11 @@ const VectorStoreConfigure = () => { }} disabled={isRecordManagerDisabled()} > - {isRecordManagerUnavailable - ? 'Record Manager is not applicable for selected Vector Store' - : 'Select Record Manager'} + {t( + isRecordManagerUnavailable + ? 'docstore.vectorStore.recordManagerUnavailable' + : 'docstore.actions.selectRecordManager' + )} ) : ( diff --git a/packages/ui/src/views/docstore/VectorStoreQuery.jsx b/packages/ui/src/views/docstore/VectorStoreQuery.jsx index f0650ae41e7..d5432c7fa6f 100644 --- a/packages/ui/src/views/docstore/VectorStoreQuery.jsx +++ b/packages/ui/src/views/docstore/VectorStoreQuery.jsx @@ -34,6 +34,9 @@ import { baseURL } from '@/store/constant' import { initNode, showHideInputParams } from '@/utils/genericHelper' import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackbarAction } from '@/store/actions' +// i18n +import { useTranslation } from 'react-i18next' + const CardWrapper = styled(MainCard)(({ theme }) => ({ background: theme.palette.card.main, color: theme.darkTextPrimary, @@ -54,6 +57,7 @@ const CardWrapper = styled(MainCard)(({ theme }) => ({ })) const VectorStoreQuery = () => { + const { t } = useTranslation() const customization = useSelector((state) => state.customization) const navigate = useNavigate() const theme = useTheme() @@ -153,7 +157,7 @@ const VectorStoreQuery = () => { setLoading(false) if (updateResp.data) { enqueueSnackbar({ - message: 'Vector Store Config Successfully Updated', + message: t('docstore.messages.vectorStoreUpdated.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -169,7 +173,7 @@ const VectorStoreQuery = () => { setLoading(false) const errorData = error.response?.data || `${error.response?.status}: ${error.response?.statusText}` enqueueSnackbar({ - message: `Failed to update vector store config: ${errorData}`, + message: t('docstore.messages.vectorStoreUpdated.error', { msg: errorData }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -261,8 +265,8 @@ const VectorStoreQuery = () => { navigate(-1)} > { startIcon={} onClick={saveConfig} > - Save Config + {t('docstore.actions.saveConfig')}
@@ -283,7 +287,8 @@ const VectorStoreQuery = () => {
- Enter your Query * + {t('docstore.inputs.query')} +  *
@@ -413,10 +418,13 @@ const VectorStoreQuery = () => { />
- Retrieved Documents + {t('docstore.retrieval.results.title')} {timeTaken > -1 && ( - Count: {documentChunks.length}. Time taken: {timeTaken} millis. + {t('docstore.retrieval.results.count', { + count: documentChunks.length, + time: timeTaken + })} )} {retrievalError && ( @@ -443,7 +451,7 @@ const VectorStoreQuery = () => { alt='chunks_emptySVG' />
-
No Documents Retrieved
+
{t('docstore.retrieval.results.empty')}
)} @@ -458,7 +466,10 @@ const VectorStoreQuery = () => { - {`#${row.chunkNo}. Characters: ${row.pageContent.length}`} + {t('docstore.preview.chunkCharacters', { + index: row.chunkNo, + total: row.pageContent.length + })} {row.pageContent} diff --git a/packages/ui/src/views/docstore/index.jsx b/packages/ui/src/views/docstore/index.jsx index bf593d0587b..98c6b255349 100644 --- a/packages/ui/src/views/docstore/index.jsx +++ b/packages/ui/src/views/docstore/index.jsx @@ -34,6 +34,9 @@ import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackba // utils import useNotifier from '@/utils/useNotifier' +// i18n +import { useTranslation } from 'react-i18next' + // ==============================|| DOCUMENTS ||============================== // const getDocStoreActionButtonSx = (theme) => ({ p: 0.5, @@ -47,6 +50,7 @@ const getDocStoreActionButtonSx = (theme) => ({ }) const Documents = () => { + const { t } = useTranslation() const theme = useTheme() const dispatch = useDispatch() const navigate = useNavigate() @@ -107,7 +111,7 @@ const Documents = () => { return error.message } - return 'Unknown error' + return t('common.errors.unknownError') } const goToDocumentStore = (id) => { @@ -116,10 +120,10 @@ const Documents = () => { const addNew = () => { const dialogProp = { - title: 'Add New Document Store', + title: t('docstore.dialogs.addNewDocumentStore'), type: 'ADD', - cancelButtonName: 'Cancel', - confirmButtonName: 'Add' + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.add') } setDialogProps(dialogProp) setShowDialog(true) @@ -161,10 +165,10 @@ const Documents = () => { const renameDocumentStore = () => { if (!selectedDocumentStore) return const dialogProp = { - title: 'Rename Document Store', + title: t('docstore.dialogs.renameDocumentStore'), type: 'EDIT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Save', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.save'), data: { id: selectedDocumentStore.id, name: selectedDocumentStore.name, @@ -181,7 +185,7 @@ const Documents = () => { const documentStoreToDelete = selectedDocumentStore handleActionMenuClose() - let description = `Delete store [${documentStoreToDelete.name}]? This will remove this document store from the list.` + let description = t('docstore.dialogs.delete.description.document.simple', { name: documentStoreToDelete.name }) if ( documentStoreToDelete.recordManagerConfig && @@ -189,11 +193,11 @@ const Documents = () => { Object.keys(documentStoreToDelete.recordManagerConfig).length > 0 && Object.keys(documentStoreToDelete.vectorStoreConfig).length > 0 ) { - description = `Delete store [${documentStoreToDelete.name}]? This will remove this document store from the list and remove the actual data from the vector store database.` + description = t('docstore.dialogs.delete.description.document.withData', { name: documentStoreToDelete.name }) } setDeleteDocStoreDialogProps({ - title: 'Delete', + title: t('common.dialogs.delete'), description, vectorStoreConfig: documentStoreToDelete.vectorStoreConfig, recordManagerConfig: documentStoreToDelete.recordManagerConfig, @@ -214,7 +218,7 @@ const Documents = () => { const deleteResp = await documentsApi.deleteDocumentStore(storeId) if (deleteResp.data) { enqueueSnackbar({ - message: 'Document Store deleted.', + message: t('docstore.messages.deleteDocument.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -238,7 +242,7 @@ const Documents = () => { const errorMessage = getDeleteErrorMessage(error) enqueueSnackbar({ - message: `Failed to delete Document Store: ${errorMessage}`, + message: t('docstore.messages.deleteDocument.error', { msg: errorMessage }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -328,9 +332,9 @@ const Documents = () => { {hasDocStores && ( { }} variant='contained' value='card' - title='Card View' + title={t('common.actions.cardView')} > @@ -360,7 +364,7 @@ const Documents = () => { }} variant='contained' value='list' - title='List View' + title={t('common.actions.listView')} > @@ -374,7 +378,7 @@ const Documents = () => { startIcon={} id='btn_createVariable' > - Add New + {t('common.actions.addNew')} {!hasDocStores ? ( @@ -386,7 +390,7 @@ const Documents = () => { alt='doc_store_empty' />
-
No Document Stores Created Yet
+
{t('docstore.empty.documentStores')}
) : ( @@ -403,7 +407,7 @@ const Documents = () => { {canManageDocumentStore && ( { - Rename + {t('docstore.actions.rename')} )} {canDeleteDocumentStore && ( @@ -480,7 +484,7 @@ const Documents = () => { - Delete + {t('common.actions.delete')} )} diff --git a/packages/ui/src/views/evaluations/ChartLatency.jsx b/packages/ui/src/views/evaluations/ChartLatency.jsx index 762ac533ac2..e3a547962ff 100644 --- a/packages/ui/src/views/evaluations/ChartLatency.jsx +++ b/packages/ui/src/views/evaluations/ChartLatency.jsx @@ -5,7 +5,11 @@ const empty = [] const COLORS = ['#00C49F', '#0088FE', '#82ca9d', '#113333', '#FF3322'] +// i18n +import { useTranslation } from 'react-i18next' + export const ChartLatency = ({ data, flowNames, onClick }) => { + const { t } = useTranslation() return ( { { /> { + const { t } = useTranslation() return ( { { /> { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const theme = useTheme() useNotifier() + const evaluatorsOptions = useMemo(() => getEvaluators(t), [t]) + const getAllChatflowsApi = useApi(chatflowsApi.getAllChatflows) const getAllAgentflowsApi = useApi(chatflowsApi.getAllAgentflows) @@ -215,7 +225,7 @@ const CreateEvaluationDialog = ({ show, dialogProps, onCancel, onConfirm }) => { {steps.map((label) => ( - {label} + {t(label)} ))} @@ -377,7 +387,7 @@ const CreateEvaluationDialog = ({ show, dialogProps, onCancel, onConfirm }) => { label: JSON.parse(assistant.details).name || '', name: assistant.id, type: 'Custom Assistant', - description: 'Custom Assistant' + description: t('evaluations.customAssistant') }) } return assistantNames @@ -395,7 +405,7 @@ const CreateEvaluationDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
- {'Start New Evaluation'} + {t('evaluations.dialogs.startNewEvaluation.title')}
@@ -430,7 +440,7 @@ const CreateEvaluationDialog = ({ show, dialogProps, onCancel, onConfirm }) => { }} /> - Fill all the mandatory fields + {t('evaluations.validations.requiredFields')} )} @@ -438,18 +448,22 @@ const CreateEvaluationDialog = ({ show, dialogProps, onCancel, onConfirm }) => { {activeStep === 0 && ( <> - Select dataset to be tested on flows + {t('evaluations.dialogs.startNewEvaluation.datasets.select')} - Uses the input column from the dataset to execute selected - Chatflow(s), and compares the results with the output column. + + }} + /> - The following metrics will be computed: + {t('evaluations.dialogs.startNewEvaluation.datasets.computed')} - {evaluatorsOptions + {evaluatorsOptions(t) .filter((opt) => opt.type === 'numeric' && opt.name !== 'chain') .map((evaluator, index) => ( @@ -460,11 +474,10 @@ const CreateEvaluationDialog = ({ show, dialogProps, onCancel, onConfirm }) => { {activeStep === 1 && ( <> - Unit Test your flows by adding custom evaluators + {t('evaluations.dialogs.startNewEvaluation.evaluators.unit')} - Post execution, all the chosen evaluators will be executed on the results. Each evaluator will grade the - results based on the criteria defined and return a pass/fail indicator. + {t('evaluations.dialogs.startNewEvaluation.evaluators.execution')} { {activeStep === 2 && ( <> - Grade flows using an LLM + {t('evaluations.dialogs.startNewEvaluation.metrics.gradeFlows')} - Post execution, grades the answers by using an LLM. Used to generate comparative scores or reasoning or - other custom defined criteria. + {t('evaluations.dialogs.startNewEvaluation.metrics.execution')} )} @@ -491,15 +503,16 @@ const CreateEvaluationDialog = ({ show, dialogProps, onCancel, onConfirm }) => { <> - Name * + {t('common.labels.name')} +  * - + setEvaluationName(e.target.value)} @@ -507,11 +520,12 @@ const CreateEvaluationDialog = ({ show, dialogProps, onCancel, onConfirm }) => { - Dataset to use * + {t('evaluations.inputs.datasetUse.title')} +  * setDataset(newValue)} value={dataset} @@ -519,7 +533,7 @@ const CreateEvaluationDialog = ({ show, dialogProps, onCancel, onConfirm }) => { - Treat all dataset rows as one conversation ? + {t('evaluations.inputs.rowsOneConversation')} {
- Select your flows to Evaluate + {t('evaluations.dialogs.startNewEvaluation.datasets.selectFlows')}  * - {' '} - Chatflows + {' '} + {t('common.labels.chatflows')} { value='Agentflow v2' onChange={onChangeFlowType} />{' '} - Agentflows (v2) + {t('evaluations.inputs.checkBox.agentflowV2')} { value='Custom Assistant' onChange={onChangeFlowType} />{' '} - Custom Assistants + {t('evaluations.inputs.checkBox.customAssistants')}
flowTypes.includes(f.type))} onSelect={(newValue) => setChatflow(newValue)} - value={chatflow ?? chatflow ?? 'choose an option'} + value={chatflow ?? chatflow ?? t('components.dropdown.chooseOption')} />
@@ -567,7 +587,7 @@ const CreateEvaluationDialog = ({ show, dialogProps, onCancel, onConfirm }) => { {activeStep === 1 && ( <> - Select the Evaluators + {t('evaluations.inputs.selectEvaluators')} { <> - Use an LLM to grade the results ? + {t('evaluations.inputs.useLLMgradeResults')} { {useLLM && availableModels.length > 0 && ( - Select Model + {t('common.labels.selectModel')} { )} {useLLM && availableModels.length === 0 && ( - Enter the Model Name + {t('evaluations.inputs.enterModelName.title')} setSelectedModel(e.target.value)} @@ -620,14 +640,14 @@ const CreateEvaluationDialog = ({ show, dialogProps, onCancel, onConfirm }) => { )} {useLLM && chatLLMs.find((llm) => llm.name === selectedLLM)?.credential && ( - Select Credential + {t('evaluations.inputs.selectCredential.title')} { )} {useLLM && ( - Select Evaluators + {t('evaluations.inputs.selectEval')} {
{activeStep === 1 && selectedSimpleEvaluators.length === 0 && ( )} {activeStep === 1 && selectedSimpleEvaluators.length > 0 && ( )} {activeStep !== 1 && ( @@ -686,7 +706,7 @@ const CreateEvaluationDialog = ({ show, dialogProps, onCancel, onConfirm }) => { variant='contained' onClick={() => goNext(activeStep)} > - {activeStep === steps.length - 1 ? 'Start Evaluation' : 'Next'} + {t(activeStep === steps.length - 1 ? 'evaluations.actions.startEvaluation' : 'common.actions.next')} )} diff --git a/packages/ui/src/views/evaluations/EvalsResultDialog.jsx b/packages/ui/src/views/evaluations/EvalsResultDialog.jsx index a74c2b76eb4..a038379c852 100644 --- a/packages/ui/src/views/evaluations/EvalsResultDialog.jsx +++ b/packages/ui/src/views/evaluations/EvalsResultDialog.jsx @@ -29,9 +29,13 @@ import PaidIcon from '@mui/icons-material/Paid' // Project imports import { StyledTableCell, StyledTableRow } from '@/ui-component/table/TableStyles' +// i18n +import { useTranslation } from 'react-i18next' + // const const EvalsResultDialog = ({ show, dialogProps, onCancel, openDetailsDrawer }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const customization = useSelector((state) => state.customization) const theme = useTheme() @@ -80,7 +84,7 @@ const EvalsResultDialog = ({ show, dialogProps, onCancel, openDetailsDrawer }) = }} > - Flows Used: + {t('evaluations.dialogs.evalResult.flowsUsed')} {(dialogProps.data.evaluation.chatflowName || []).map((chatflowUsed, index) => ( )}
@@ -124,8 +128,8 @@ const EvalsResultDialog = ({ show, dialogProps, onCancel, openDetailsDrawer }) = >   - Input - Expected Output + {t('evaluations.input')} + {t('evaluations.dialogs.evalResult.expectedOutput')} {dialogProps.data && dialogProps.data.evaluation.chatflowId?.map((chatflowId, index) => ( @@ -167,12 +171,14 @@ const EvalsResultDialog = ({ show, dialogProps, onCancel, openDetailsDrawer }) = - Actual Output + {t('evaluations.dialogs.evalResult.actualOutput')} {dialogProps.data.customEvalsDefined && dialogProps.data.showCustomEvals && ( - Evaluator + {t('evaluations.dialogs.evalResult.evaluator')} + )} + {dialogProps.data.evaluation?.evaluationType === 'llm' && ( + {t('evaluations.dialogs.evalResult.LLMEvaluation')} )} - {dialogProps.data.evaluation?.evaluationType === 'llm' && LLM Evaluation} ))} @@ -214,22 +220,22 @@ const EvalsResultDialog = ({ show, dialogProps, onCancel, openDetailsDrawer }) = variant='outlined' icon={} size='small' - label={ - item.metrics[index]?.totalCost - ? 'Total Cost: ' + item.metrics[index]?.totalCost - : 'Total Cost: N/A' - } + label={t('evaluations.dialogs.evalResult.totalCost', { + value: + item.metrics[index]?.totalCost || + t('evaluations.notAvailable') + })} sx={{ mr: 1, mb: 1 }} /> } - label={ - item.metrics[index]?.totalTokens - ? 'Total Tokens: ' + item.metrics[index]?.totalTokens - : 'Total Tokens: N/A' - } + label={t('evaluations.dialogs.evalResult.totalTokens', { + value: + item.metrics[index]?.totalTokens || + t('evaluations.notAvailable') + })} sx={{ mr: 1, mb: 1 }} /> {dialogProps.data.showTokenMetrics && ( @@ -238,24 +244,25 @@ const EvalsResultDialog = ({ show, dialogProps, onCancel, openDetailsDrawer }) = variant='outlined' size='small' icon={} - label={ - item.metrics[index]?.promptTokens - ? 'Prompt Tokens: ' + - item.metrics[index]?.promptTokens - : 'Prompt Tokens: N/A' - } + label={t('evaluations.dialogs.evalResult.promptTokens', { + value: + item.metrics[index]?.promptTokens || + t('evaluations.notAvailable') + })} sx={{ mr: 1, mb: 1 }} />{' '} } - label={ - item.metrics[index]?.completionTokens - ? 'Completion Tokens: ' + - item.metrics[index]?.completionTokens - : 'Completion Tokens: N/A' - } + label={t( + 'evaluations.dialogs.evalResult.completionTokens', + { + value: + item.metrics[index]?.completionTokens || + t('evaluations.notAvailable') + } + )} sx={{ mr: 1, mb: 1 }} />{' '} @@ -266,23 +273,22 @@ const EvalsResultDialog = ({ show, dialogProps, onCancel, openDetailsDrawer }) = variant='outlined' size='small' icon={} - label={ - item.metrics[index]?.promptCost - ? 'Prompt Cost: ' + item.metrics[index]?.promptCost - : 'Prompt Cost: N/A' - } + label={t('evaluations.dialogs.evalResult.promptCost', { + value: + item.metrics[index]?.promptCost || + t('evaluations.notAvailable') + })} sx={{ mr: 1, mb: 1 }} />{' '} } - label={ - item.metrics[index]?.completionCost - ? 'Completion Cost: ' + - item.metrics[index]?.completionCost - : 'Completion Cost: N/A' - } + label={t('evaluations.dialogs.evalResult.completionCost', { + value: + item.metrics[index]?.completionCost || + t('evaluations.notAvailable') + })} sx={{ mr: 1, mb: 1 }} />{' '} @@ -291,11 +297,11 @@ const EvalsResultDialog = ({ show, dialogProps, onCancel, openDetailsDrawer }) = variant='outlined' size='small' icon={} - label={ - item.metrics[index]?.apiLatency - ? 'API Latency: ' + item.metrics[index]?.apiLatency - : 'API Latency: N/A' - } + label={t('evaluations.dialogs.evalResult.apiLatency', { + value: + item.metrics[index]?.apiLatency || + t('evaluations.notAvailable') + })} sx={{ mr: 1, mb: 1 }} />{' '} {dialogProps.data.showLatencyMetrics && ( @@ -305,11 +311,14 @@ const EvalsResultDialog = ({ show, dialogProps, onCancel, openDetailsDrawer }) = variant='outlined' size='small' icon={} - label={ - item.metrics[index]?.chain - ? 'Chain Latency: ' + item.metrics[index]?.chain - : 'Chain Latency: N/A' - } + label={t( + 'evaluations.dialogs.evalResult.chainLatency', + { + value: + item.metrics[index]?.chain || + t('evaluations.notAvailable') + } + )} sx={{ mr: 1, mb: 1 }} /> )}{' '} @@ -319,10 +328,10 @@ const EvalsResultDialog = ({ show, dialogProps, onCancel, openDetailsDrawer }) = icon={} size='small' sx={{ mr: 1, mb: 1 }} - label={ - 'Retriever Latency: ' + - item.metrics[index]?.retriever - } + label={t( + 'evaluations.dialogs.evalResult.retrieverLatency', + { value: item.metrics[index]?.retriever } + )} /> )}{' '} {item.metrics[index]?.tool && ( @@ -331,18 +340,20 @@ const EvalsResultDialog = ({ show, dialogProps, onCancel, openDetailsDrawer }) = icon={} size='small' sx={{ mr: 1, mb: 1 }} - label={'Tool Latency: ' + item.metrics[index]?.tool} + label={t('evaluations.dialogs.evalResult.toolLatency', { + value: item.metrics[index]?.tool + })} /> )}{' '} } size='small' - label={ - item.metrics[index]?.llm - ? 'LLM Latency: ' + item.metrics[index]?.llm - : 'LLM Latency: N/A' - } + label={t('evaluations.dialogs.evalResult.LLMLatency', { + value: + item.metrics[index]?.llm || + t('evaluations.notAvailable') + })} sx={{ mr: 1, mb: 1 }} />{' '} diff --git a/packages/ui/src/views/evaluations/EvaluationResult.jsx b/packages/ui/src/views/evaluations/EvaluationResult.jsx index 6fdde95b759..d5c462b9118 100644 --- a/packages/ui/src/views/evaluations/EvaluationResult.jsx +++ b/packages/ui/src/views/evaluations/EvaluationResult.jsx @@ -70,9 +70,13 @@ import { //const import { useError } from '@/store/context/ErrorContext' +// i18n +import { useTranslation } from 'react-i18next' + // ==============================|| EvaluationResults ||============================== // const EvalEvaluationRows = () => { + const { t } = useTranslation() const navigate = useNavigate() const theme = useTheme() const customization = useSelector((state) => state.customization) @@ -182,17 +186,17 @@ const EvalEvaluationRows = () => { const runAgain = async () => { const confirmPayload = { - title: `Run Again`, - description: `Initiate Rerun for Evaluation ${evaluation.name}?`, - confirmButtonName: 'Yes', - cancelButtonName: 'No' + title: t('evaluations.dialogs.runAgain.title'), + description: t('evaluations.dialogs.runAgain.desceiption', { name: evaluation.name }), + confirmButtonName: t('common.actions.yes'), + cancelButtonName: t('common.actions.no') } const isConfirmed = await confirm(confirmPayload) if (isConfirmed) { runAgainApi.request(evaluation?.id) enqueueSnackbar({ - message: "Evaluation '" + evaluation.name + "' is running. Redirecting to evaluations page.", + message: t('evaluations.messages.runAgain.success', { name: evaluation.name }), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -379,14 +383,21 @@ const EvalEvaluationRows = () => { isEditButton={false} onBack={goBack} search={false} - title={'Evaluation: ' + selectedEvaluationName} - description={evaluation?.runDate ? moment(evaluation?.runDate).format('DD-MMM-YYYY, hh:mm:ss A') : ''} + title={t('evaluations.dialogs.evalResult.title')} + description={ + evaluation?.runDate + ? moment(evaluation?.runDate).format(t('common.formats.dateDayMonthShortYearTime12Seconds')) + : '' + } > {evaluation?.versionCount > 1 && ( )} {evaluation?.versionCount > 1 && ( @@ -397,7 +408,7 @@ const EvalEvaluationRows = () => { color='primary' onClick={openVersionsDrawer} > - Version history + {t('evaluations.actions.versionHistory')} )} { disabled={outdated?.errors?.length > 0} onClick={runAgain} > - Re-run Evaluation + {t('evaluations.actions.rerunEvaluation')} @@ -434,17 +445,13 @@ const EvalEvaluationRows = () => {
- {outdated?.errors?.length > 0 && ( - This evaluation cannot be re-run, due to the following errors - )} - {outdated?.errors?.length === 0 && ( - The following items are outdated, re-run the evaluation for the latest results. - )} + {outdated?.errors?.length > 0 && {t('evaluations.dialogs.evalResult.errors.cannotRerun')}} + {outdated?.errors?.length === 0 && {t('evaluations.dialogs.evalResult.errors.outdated')}} {outdated.dataset && outdated?.errors?.length === 0 && ( <>
- Dataset: + {t('evaluations.dialogs.evalResult.dataset')} { {outdated.chatflows && outdated?.errors?.length === 0 && outdated.chatflows.length > 0 && ( <>
- Flows: + {t('evaluations.dialogs.evalResult.flows')} {outdated.chatflows.map((chatflow, index) => ( { {customEvalsDefined && ( )} {showCharts && ( @@ -566,7 +573,7 @@ const EvalEvaluationRows = () => { }} @@ -578,7 +585,7 @@ const EvalEvaluationRows = () => { }} @@ -596,7 +603,7 @@ const EvalEvaluationRows = () => { }} @@ -628,7 +635,7 @@ const EvalEvaluationRows = () => { }} > - Flows Used: + {t('evaluations.dialogs.evalResult.flowsUsed')} {(evaluation.chatflowName || []).map((chatflowUsed, index) => ( { startIcon={} onClick={() => openTableDialog()} > - Expand + {t('common.actions.expand')} @@ -665,8 +672,8 @@ const EvalEvaluationRows = () => { >   - Input - Expected Output + {t('evaluations.input')} + {t('evaluations.dialogs.evalResult.expectedOutput')} {evaluation.chatflowId?.map((chatflowId, index) => ( { - Actual Output + {t('evaluations.dialogs.evalResult.actualOutput')} - {customEvalsDefined && showCustomEvals && Evaluator} - {evaluation?.evaluationType === 'llm' && LLM Evaluation} + {customEvalsDefined && showCustomEvals && ( + {t('evaluations.dialogs.evalResult.evaluator')} + )} + {evaluation?.evaluationType === 'llm' && ( + {t('evaluations.dialogs.evalResult.LLMEvaluation')} + )} ))} @@ -772,24 +783,22 @@ const EvalEvaluationRows = () => { variant='outlined' icon={} size='small' - label={ - item.metrics[index]?.totalCost - ? 'Total Cost: ' + - item.metrics[index]?.totalCost - : 'Total Cost: N/A' - } + label={t('evaluations.dialogs.evalResult.totalCost', { + value: + item.metrics[index]?.totalCost || + t('evaluations.notAvailable') + })} sx={{ mr: 1, mb: 1 }} /> } - label={ - item.metrics[index]?.totalTokens - ? 'Total Tokens: ' + - item.metrics[index]?.totalTokens - : 'Total Tokens: N/A' - } + label={t('evaluations.dialogs.evalResult.totalTokens', { + value: + item.metrics[index]?.totalTokens || + t('evaluations.notAvailable') + })} sx={{ mr: 1, mb: 1 }} /> {showTokenMetrics && ( @@ -798,24 +807,29 @@ const EvalEvaluationRows = () => { variant='outlined' size='small' icon={} - label={ - item.metrics[index]?.promptTokens - ? 'Prompt Tokens: ' + - item.metrics[index]?.promptTokens - : 'Prompt Tokens: N/A' - } + label={t( + 'evaluations.dialogs.evalResult.promptTokens', + { + value: + item.metrics[index]?.promptTokens || + t('evaluations.notAvailable') + } + )} sx={{ mr: 1, mb: 1 }} />{' '} } - label={ - item.metrics[index]?.completionTokens - ? 'Completion Tokens: ' + - item.metrics[index]?.completionTokens - : 'Completion Tokens: N/A' - } + label={t( + 'evaluations.dialogs.evalResult.completionTokens', + { + value: + item.metrics[index] + ?.completionTokens || + t('evaluations.notAvailable') + } + )} sx={{ mr: 1, mb: 1 }} />{' '} @@ -826,24 +840,29 @@ const EvalEvaluationRows = () => { variant='outlined' size='small' icon={} - label={ - item.metrics[index]?.promptCost - ? 'Prompt Cost: ' + - item.metrics[index]?.promptCost - : 'Prompt Cost: N/A' - } + label={t( + 'evaluations.dialogs.evalResult.promptCost', + { + value: + item.metrics[index]?.promptCost || + t('evaluations.notAvailable') + } + )} sx={{ mr: 1, mb: 1 }} />{' '} } - label={ - item.metrics[index]?.completionCost - ? 'Completion Cost: ' + - item.metrics[index]?.completionCost - : 'Completion Cost: N/A' - } + label={t( + 'evaluations.dialogs.evalResult.completionCost', + { + value: + item.metrics[index] + ?.completionCost || + t('evaluations.notAvailable') + } + )} sx={{ mr: 1, mb: 1 }} />{' '} @@ -852,12 +871,11 @@ const EvalEvaluationRows = () => { variant='outlined' size='small' icon={} - label={ - item.metrics[index]?.apiLatency - ? 'API Latency: ' + - item.metrics[index]?.apiLatency - : 'API Latency: N/A' - } + label={t('evaluations.dialogs.evalResult.apiLatency', { + value: + item.metrics[index]?.apiLatency || + t('evaluations.notAvailable') + })} sx={{ mr: 1, mb: 1 }} />{' '} {showLatencyMetrics && ( @@ -867,12 +885,14 @@ const EvalEvaluationRows = () => { variant='outlined' size='small' icon={} - label={ - item.metrics[index]?.chain - ? 'Chain Latency: ' + - item.metrics[index]?.chain - : 'Chain Latency: N/A' - } + label={t( + 'evaluations.dialogs.evalResult.chainLatency', + { + value: + item.metrics[index]?.chain || + t('evaluations.notAvailable') + } + )} sx={{ mr: 1, mb: 1 }} /> )}{' '} @@ -882,10 +902,13 @@ const EvalEvaluationRows = () => { icon={} size='small' sx={{ mr: 1, mb: 1 }} - label={ - 'Retriever Latency: ' + - item.metrics[index]?.retriever - } + label={t( + 'evaluations.dialogs.evalResult.retrieverLatency', + { + value: item.metrics[index] + ?.retriever + } + )} /> )}{' '} {item.metrics[index]?.tool && ( @@ -894,22 +917,26 @@ const EvalEvaluationRows = () => { icon={} size='small' sx={{ mr: 1, mb: 1 }} - label={ - 'Tool Latency: ' + - item.metrics[index]?.tool - } + label={t( + 'evaluations.dialogs.evalResult.toolLatency', + { + value: item.metrics[index]?.tool + } + )} /> )}{' '} } size='small' - label={ - item.metrics[index]?.llm - ? 'LLM Latency: ' + - item.metrics[index]?.llm - : 'LLM Latency: N/A' - } + label={t( + 'evaluations.dialogs.evalResult.LLMLatency', + { + value: + item.metrics[index]?.llm || + t('evaluations.notAvailable') + } + )} sx={{ mr: 1, mb: 1 }} />{' '} diff --git a/packages/ui/src/views/evaluations/EvaluationResultSideDrawer.jsx b/packages/ui/src/views/evaluations/EvaluationResultSideDrawer.jsx index c415fb983a5..3065ec005d7 100644 --- a/packages/ui/src/views/evaluations/EvaluationResultSideDrawer.jsx +++ b/packages/ui/src/views/evaluations/EvaluationResultSideDrawer.jsx @@ -1,3 +1,4 @@ +import { useMemo } from 'react' import PropTypes from 'prop-types' import { CardContent, @@ -17,14 +18,21 @@ import { import { IconHierarchy, IconUsersGroup, IconRobot } from '@tabler/icons-react' import { useSelector } from 'react-redux' -import { evaluators as evaluatorsOptions, numericOperators } from '../evaluators/evaluatorConstant' +import { getEvaluators, getNumericOperators } from '../evaluators/evaluatorConstant' import TableCell from '@mui/material/TableCell' import { Close } from '@mui/icons-material' +// i18n +import { useTranslation } from 'react-i18next' + const EvaluationResultSideDrawer = ({ show, dialogProps, onClickFunction }) => { + const { t } = useTranslation() const onOpen = () => {} const customization = useSelector((state) => state.customization) + const evaluatorsOptions = useMemo(() => getEvaluators(t), [t]) + const numericOperators = useMemo(() => getNumericOperators(t), [t]) + const getEvaluatorValue = (evaluator) => { if (evaluator.type === 'text') { return '"' + evaluator.value + '"' @@ -57,14 +65,14 @@ const EvaluationResultSideDrawer = ({ show, dialogProps, onClickFunction }) => { onClickFunction()} onOpen={onOpen}>
- Evaluation Details + {t('evaluations.dialogs.evalResult.evaluationDetails')}
- Evaluation Id + {t('evaluations.dialogs.evalResult.evaluationId')} {dialogProps.data.evaluationId} @@ -75,7 +83,7 @@ const EvaluationResultSideDrawer = ({ show, dialogProps, onClickFunction }) => {
- Input + {t('evaluations.input')} {dialogProps.data.input}
@@ -86,7 +94,7 @@ const EvaluationResultSideDrawer = ({ show, dialogProps, onClickFunction }) => {
- Expected Output + {t('evaluations.dialogs.evalResult.expectedOutput')} {dialogProps.data.expectedOutput}
@@ -117,7 +125,11 @@ const EvaluationResultSideDrawer = ({ show, dialogProps, onClickFunction }) => {
- {dialogProps.data.errors[index] === '' ? 'Actual Output' : 'Error'} + {t( + dialogProps.data.errors[index] === '' + ? 'evaluations.dialogs.evalResult.actualOutput' + : 'common.labels.error' + )} {dialogProps.data.errors[index] === '' ? ( @@ -147,48 +159,50 @@ const EvaluationResultSideDrawer = ({ show, dialogProps, onClickFunction }) => {
- Latency Metrics + {t('evaluations.actions.latencyMetrics')} {dialogProps.data.metrics[index]?.chain && ( )} {dialogProps.data.metrics[index]?.retriever && ( )} {dialogProps.data.metrics[index]?.tool && ( )} @@ -199,25 +213,25 @@ const EvaluationResultSideDrawer = ({ show, dialogProps, onClickFunction }) => { {dialogProps.data.metrics[index]?.nested_metrics ? ( - Tokens + {t('common.labels.tokens')}
- Node + {t('evaluations.dialogs.evalResult.node')} - Provider & Model + {t('evaluations.dialogs.evalResult.providerModel')} - Input + {t('evaluations.input')} - Output + {t('common.labels.output')} - Total + {t('evaluations.dialogs.evalResult.total.title')} @@ -251,7 +265,7 @@ const EvaluationResultSideDrawer = ({ show, dialogProps, onClickFunction }) => { scope='row' colspan={2} > - Total + {t('evaluations.dialogs.evalResult.total.title')} {dialogProps.data.metrics[index].promptTokens} @@ -269,36 +283,33 @@ const EvaluationResultSideDrawer = ({ show, dialogProps, onClickFunction }) => { ) : ( - Tokens + {t('common.labels.tokens')} @@ -308,25 +319,25 @@ const EvaluationResultSideDrawer = ({ show, dialogProps, onClickFunction }) => { {dialogProps.data.metrics[index]?.nested_metrics ? ( - Cost + {t('evaluations.dialogs.evalResult.cost')}
- Node + {t('evaluations.dialogs.evalResult.node')} - Provider & Model + {t('evaluations.dialogs.evalResult.providerModel')} - Input + {t('evaluations.input')} - Output + {t('common.labels.output')} - Total + {t('evaluations.dialogs.evalResult.total.title')} @@ -359,7 +370,7 @@ const EvaluationResultSideDrawer = ({ show, dialogProps, onClickFunction }) => { scope='row' colspan={2} > - Total + {t('evaluations.dialogs.evalResult.total.title')} {dialogProps.data.metrics[index].promptCost} @@ -377,36 +388,31 @@ const EvaluationResultSideDrawer = ({ show, dialogProps, onClickFunction }) => { ) : ( - Cost + {t('evaluations.dialogs.evalResult.cost')} @@ -420,7 +426,7 @@ const EvaluationResultSideDrawer = ({ show, dialogProps, onClickFunction }) => { dialogProps.data.customEvals[index].length > 0 && ( - Custom Evaluators + {t('evaluations.dialogs.evalResult.customEvaluators')} {dialogProps.data.customEvals[index] && @@ -454,7 +460,7 @@ const EvaluationResultSideDrawer = ({ show, dialogProps, onClickFunction }) => { label={`${ [...evaluatorsOptions, ...numericOperators].find( (opt) => opt.name === evaluator.measure - )?.label || 'Actual Output' + )?.label || t('evaluations.dialogs.evalResult.actualOutput') } ${ [...evaluatorsOptions, ...numericOperators] .find((opt) => opt.name === evaluator.operator) @@ -473,7 +479,7 @@ const EvaluationResultSideDrawer = ({ show, dialogProps, onClickFunction }) => {
- LLM Graded + {t('evaluations.dialogs.evalResult.llmGraded')} {Object.entries(dialogProps.data.llmEvaluators[index]).map(([key, value], index) => ( diff --git a/packages/ui/src/views/evaluations/EvaluationResultVersionsSideDrawer.jsx b/packages/ui/src/views/evaluations/EvaluationResultVersionsSideDrawer.jsx index 91f5ad7c894..4e81f832122 100644 --- a/packages/ui/src/views/evaluations/EvaluationResultVersionsSideDrawer.jsx +++ b/packages/ui/src/views/evaluations/EvaluationResultVersionsSideDrawer.jsx @@ -18,7 +18,11 @@ import { import evaluationApi from '@/api/evaluations' import useApi from '@/hooks/useApi' +// i18n +import { useTranslation } from 'react-i18next' + const EvaluationResultVersionsSideDrawer = ({ show, dialogProps, onClickFunction, onSelectVersion }) => { + const { t } = useTranslation() const onOpen = () => {} const [versions, setVersions] = useState([]) @@ -43,7 +47,7 @@ const EvaluationResultVersionsSideDrawer = ({ show, dialogProps, onClickFunction return ( onClickFunction()} onOpen={onOpen}> ( - {moment(version.runDate).format('DD-MMM-YYYY, hh:mm:ss A')} + {moment(version.runDate).format(t('common.formats.dateDayMonthShortYearTime12Seconds'))} {index !== versions.length - 1 && } - + ))} diff --git a/packages/ui/src/views/evaluations/index.jsx b/packages/ui/src/views/evaluations/index.jsx index 85a2d6c66b1..60cefb63810 100644 --- a/packages/ui/src/views/evaluations/index.jsx +++ b/packages/ui/src/views/evaluations/index.jsx @@ -61,7 +61,11 @@ import { } from '@tabler/icons-react' import empty_evalSVG from '@/assets/images/empty_evals.svg' +// i18n +import { useTranslation } from 'react-i18next' + const EvalsEvaluation = () => { + const { t } = useTranslation() const theme = useTheme() const customization = useSelector((state) => state.customization) const { confirm } = useConfirm() @@ -129,8 +133,8 @@ const EvalsEvaluation = () => { const createEvaluation = () => { const dialogProp = { type: 'ADD', - cancelButtonName: 'Cancel', - confirmButtonName: 'Start New Evaluation', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('evaluations.actions.startNewEvaluation'), data: {} } setDialogProps(dialogProp) @@ -139,12 +143,12 @@ const EvalsEvaluation = () => { const deleteEvaluationsAllVersions = async () => { const confirmPayload = { - title: `Delete`, - description: `Delete ${selected.length} ${ - selected.length > 1 ? 'evaluations' : 'evaluation' - }? This will delete all versions of the evaluation.`, - confirmButtonName: 'Delete', - cancelButtonName: 'Cancel' + title: t('common.dialogs.delete'), + description: t('evaluations.dialogs.delete.description', { + count: selected.length + }), + confirmButtonName: t('common.actions.delete'), + cancelButtonName: t('common.actions.cancel') } const isConfirmed = await confirm(confirmPayload) @@ -154,7 +158,9 @@ const EvalsEvaluation = () => { const deleteResp = await evaluationApi.deleteEvaluations(selected, isDeleteAllVersion) if (deleteResp.data) { enqueueSnackbar({ - message: `${selected.length} ${selected.length > 1 ? 'evaluations' : 'evaluation'} deleted`, + message: t('evaluations.messages.delete.success', { + count: selected.length + }), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -169,9 +175,10 @@ const EvalsEvaluation = () => { } } catch (error) { enqueueSnackbar({ - message: `Failed to delete ${selected.length > 1 ? 'evaluations' : 'evaluation'}: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('evaluations.messages.delete.error', { + count: selected.length, + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -201,7 +208,7 @@ const EvalsEvaluation = () => { // Prepare the data for the table for (let i = 0; i < evalRows.length; i++) { const evalRow = evalRows[i] - evalRows[i].runDate = moment(evalRow.runDate).format('DD-MMM-YYYY, hh:mm:ss A') + evalRows[i].runDate = moment(evalRow.runDate).format(t('common.formats.dateDayMonthShortYearTime12Seconds')) evalRows[i].average_metrics = typeof evalRow.average_metrics === 'object' ? evalRow.average_metrics : JSON.parse(evalRow.average_metrics) evalRows[i].usedFlows = @@ -219,7 +226,7 @@ const EvalsEvaluation = () => { const evalRows = createNewEvaluation.data for (let i = 0; i < evalRows.length; i++) { const evalRow = evalRows[i] - evalRows[i].runDate = moment(evalRow.runDate).format('DD-MMM-YYYY, hh:mm:ss A') + evalRows[i].runDate = moment(evalRow.runDate).format(t('common.formats.dateDayMonthShortYearTime12Seconds')) evalRows[i].average_metrics = typeof evalRow.average_metrics === 'object' ? evalRow.average_metrics : JSON.parse(evalRow.average_metrics) evalRows[i].usedFlows = typeof evalRow.chatflowName === 'object' ? evalRow.chatflowName : JSON.parse(evalRow.chatflowName) @@ -241,11 +248,14 @@ const EvalsEvaluation = () => { if (createNewEvaluation.error) { // Change to Notifstack enqueueSnackbar({ - message: `Failed to create new evaluation: ${ - typeof createNewEvaluation.error.response?.data === 'object' - ? createNewEvaluation.error.response.data.message - : createNewEvaluation.error.response?.data || createNewEvaluation.error.message || 'Unknown error' - }`, + message: t('evaluations.messages.create.error', { + msg: + typeof createNewEvaluation.error.response?.data === 'object' + ? createNewEvaluation.error.response.data.message + : createNewEvaluation.error.response?.data || + createNewEvaluation.error.message || + t('common.errors.unknownError') + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -298,7 +308,7 @@ const EvalsEvaluation = () => { ) : ( - + { } } }} - title={autoRefresh ? 'Disable auto-refresh' : 'Enable auto-refresh (every 5s)'} + title={t(autoRefresh ? 'evaluations.actions.disableAutoRefresh' : 'evaluations.actions.enableAutoRefresh')} > {autoRefresh ? : } @@ -341,7 +351,7 @@ const EvalsEvaluation = () => { } }} onClick={onRefresh} - title='Refresh' + title={t('common.actions.refresh')} > @@ -351,7 +361,7 @@ const EvalsEvaluation = () => { onClick={createEvaluation} startIcon={} > - New Evaluation + {t('evaluations.actions.newEvaluation')} {selected.length > 0 && ( @@ -363,7 +373,7 @@ const EvalsEvaluation = () => { color='error' startIcon={} > - Delete {selected.length} {selected.length === 1 ? 'evaluation' : 'evaluations'} + {t('evaluations.actions.deleteEvaluation', { count: selected.length })} )} {!isTableLoading && rows.length <= 0 ? ( @@ -375,7 +385,7 @@ const EvalsEvaluation = () => { alt='empty_evalSVG' /> -
No Evaluations Yet
+
{t('evaluations.notFound')}
) : ( <> @@ -399,17 +409,17 @@ const EvalsEvaluation = () => { checked={selected.length === (rows.filter((item) => item?.latestEval) || []).length} onChange={onSelectAllClick} inputProps={{ - 'aria-label': 'select all' + 'aria-label': t('common.actions.selectAll') }} />
- Name - Latest Version - Average Metrics - Last Evaluated - Flow(s) - Dataset + {t('common.labels.name')} + {t('evaluations.table.latestVersion')} + {t('evaluations.table.averageMetrics')} + {t('evaluations.table.lastEvaluated')} + {t('evaluations.table.flow')} + {t('evaluations.table.dataset')} @@ -506,6 +516,7 @@ const EvalsEvaluation = () => { } function EvaluationRunRow(props) { + const { t } = useTranslation() const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) @@ -552,10 +563,10 @@ function EvaluationRunRow(props) { const deleteChildEvaluations = async () => { const confirmPayload = { - title: `Delete`, - description: `Delete ${childSelected.length} ${childSelected.length > 1 ? 'evaluations' : 'evaluation'}?`, - confirmButtonName: 'Delete', - cancelButtonName: 'Cancel' + title: t('common.dialogs.delete'), + description: t('evaluations.dialogs.delete.description', { count: childSelected.length }), + confirmButtonName: t('common.actions.delete'), + cancelButtonName: t('common.actions.cancel') } const isConfirmed = await confirm(confirmPayload) @@ -564,7 +575,7 @@ function EvaluationRunRow(props) { const deleteResp = await evaluationApi.deleteEvaluations(childSelected) if (deleteResp.data) { enqueueSnackbar({ - message: `${childSelected.length} evaluations deleted.`, + message: t('evaluations.messages.delete.success', { count: childSelected.length }), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -579,9 +590,10 @@ function EvaluationRunRow(props) { } } catch (error) { enqueueSnackbar({ - message: `Failed to delete Evaluation: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('evaluations.messages.delete.error', { + count: childSelected.length, + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -646,7 +658,7 @@ function EvaluationRunRow(props) { {props.item.version}{' '} {props.item.version > 0 && ( - setOpen(!open)}> + setOpen(!open)}> {props.item.version > 0 && open ? : } )} @@ -657,11 +669,9 @@ function EvaluationRunRow(props) { variant='outlined' size='small' color='info' - label={ - props.item.average_metrics?.totalRuns - ? 'Total Runs: ' + props.item.average_metrics?.totalRuns - : 'Total Runs: N/A' - } + label={t('evaluations.totalRuns', { + value: props.item.average_metrics?.totalRuns || t('evaluations.notAvailable') + })} /> {props.item.average_metrics?.averageCost && ( @@ -670,11 +680,9 @@ function EvaluationRunRow(props) { variant='outlined' size='small' color='info' - label={ - props.item.average_metrics?.averageLatency - ? 'Avg Latency: ' + props.item.average_metrics?.averageLatency + 'ms' - : 'Avg Latency: N/A' - } + label={t('evaluations.avgLatency', { + value: props.item.average_metrics?.averageLatency || t('evaluations.notAvailable') + })} /> {props.item.average_metrics?.passPcnt >= 0 && ( )} - {moment(props.item.runDate).format('DD-MMM-YYYY, hh:mm:ss A')} + + {moment(props.item.runDate).format(t('common.formats.dateDayMonthShortYearTime12Seconds'))} + {props.item?.usedFlows?.map((usedFlow, index) => ( @@ -726,7 +734,7 @@ function EvaluationRunRow(props) { showResults(props.item)} @@ -745,7 +753,7 @@ function EvaluationRunRow(props) { color='error' startIcon={} > - Delete {childSelected.length} {childSelected.length === 1 ? 'evaluation' : 'evaluations'} + {t('evaluations.actions.deleteEvaluation', { count: childSelected.length })} @@ -756,7 +764,7 @@ function EvaluationRunRow(props) { -
+
@@ -766,10 +774,10 @@ function EvaluationRunRow(props) { onChange={onSelectAllChildClick} /> - Version - Last Run - Average Metrics - Status + {t('evaluations.table.version')} + {t('evaluations.table.lastRun')} + {t('evaluations.table.averageMetrics')} + {t('common.labels.status')} @@ -787,7 +795,9 @@ function EvaluationRunRow(props) { {childItem.version} - {moment(childItem.runDate).format('DD-MMM-YYYY, hh:mm:ss A')} + {moment(childItem.runDate).format( + t('common.formats.dateDayMonthShortYearTime12Seconds') + )} {childItem.average_metrics?.averageCost && ( {childItem.average_metrics?.passPcnt >= 0 && ( )} @@ -861,7 +867,7 @@ function EvaluationRunRow(props) { showResults(childItem)} diff --git a/packages/ui/src/views/evaluators/AddEditEvaluatorDialog.jsx b/packages/ui/src/views/evaluators/AddEditEvaluatorDialog.jsx index d113b9a2975..f94d7f528c8 100644 --- a/packages/ui/src/views/evaluators/AddEditEvaluatorDialog.jsx +++ b/packages/ui/src/views/evaluators/AddEditEvaluatorDialog.jsx @@ -30,11 +30,19 @@ import useNotifier from '@/utils/useNotifier' // const import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions' -import { evaluators, evaluatorTypes, numericOperators } from './evaluatorConstant' +import { getEvaluators, getEvaluatorTypes, getNumericOperators } from './evaluatorConstant' + +// i18n +import { useTranslation, Trans } from 'react-i18next' const AddEditEvaluatorDialog = ({ show, dialogProps, onCancel, onConfirm }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') + const evaluators = useMemo(() => getEvaluators(t), [t]) + const numericOperators = useMemo(() => getNumericOperators(t), [t]) + const evaluatorTypes = useMemo(() => getEvaluatorTypes(t), [t]) + const dispatch = useDispatch() // ==============================|| Snackbar ||============================== // @@ -80,8 +88,8 @@ const AddEditEvaluatorDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const dialogProps = { value: prompt, inputParam, - confirmButtonName: 'Save', - cancelButtonName: 'Cancel' + confirmButtonName: t('common.actions.save'), + cancelButtonName: t('common.actions.cancel') } setSamplePromptDialogProps(dialogProps) setShowSamplePromptDialog(true) @@ -90,8 +98,8 @@ const AddEditEvaluatorDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const dialogProps = { value: prompt, inputParam, - confirmButtonName: 'Save', - cancelButtonName: 'Cancel' + confirmButtonName: t('common.actions.save'), + cancelButtonName: t('common.actions.cancel') } setExpandDialogProps(dialogProps) setShowExpandDialog(true) @@ -134,23 +142,28 @@ const AddEditEvaluatorDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const columns = useMemo( () => [ - { field: 'property', headerName: 'Property', editable: true, flex: 1 }, + { field: 'property', headerName: t('evaluators.columns.property'), editable: true, flex: 1 }, { field: 'type', - headerName: 'Type', + headerName: t('common.labels.type'), type: 'singleSelect', valueOptions: ['string', 'number', 'boolean'], editable: true, width: 120 }, - { field: 'description', headerName: 'Description', editable: true, flex: 1 }, - { field: 'required', headerName: 'Required', type: 'boolean', editable: true, width: 80 }, + { field: 'description', headerName: t('common.labels.description'), editable: true, flex: 1 }, + { field: 'required', headerName: t('evaluators.columns.required'), type: 'boolean', editable: true, width: 80 }, { field: 'actions', type: 'actions', width: 80, getActions: (params) => [ - } label='Delete' onClick={deleteItem(params.id)} /> + } + label={t('common.actions.delete')} + onClick={deleteItem(params.id)} + /> ] } ], @@ -195,7 +208,7 @@ const AddEditEvaluatorDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const updateResp = await evaluatorsApi.updateEvaluator(dialogProps.data.id, data) if (updateResp.data) { enqueueSnackbar({ - message: `Evaluator ${name} updated`, + message: t('evaluators.messages.update.success', { name: name }), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -210,9 +223,10 @@ const AddEditEvaluatorDialog = ({ show, dialogProps, onCancel, onConfirm }) => { } } catch (error) { enqueueSnackbar({ - message: `Failed to update Evaluator ${name}: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('evaluators.messages.update.error', { + name: name, + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -254,7 +268,7 @@ const AddEditEvaluatorDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const createResp = await evaluatorsApi.createEvaluator(data) if (createResp.data) { enqueueSnackbar({ - message: 'New Evaluator added', + message: t('evaluators.messages.add.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -269,9 +283,9 @@ const AddEditEvaluatorDialog = ({ show, dialogProps, onCancel, onConfirm }) => { } } catch (error) { enqueueSnackbar({ - message: `Failed to add new Evaluator: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('evaluators.messages.add.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -343,12 +357,12 @@ const AddEditEvaluatorDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
- {dialogProps.type === 'ADD' ? 'Add Evaluator' : 'Edit Evaluator'} + {t(dialogProps.type === 'ADD' ? 'evaluators.dialogs.add' : 'evaluators.dialogs.edit')}
- Name + {t('common.labels.name')} { /> - Evaluator Type + {t('evaluators.inputs.evaluatorType.title')} onEvaluatorTypeChange(newValue)} value={evaluatorType} @@ -372,11 +386,11 @@ const AddEditEvaluatorDialog = ({ show, dialogProps, onCancel, onConfirm }) => { {evaluatorType && evaluatorType !== 'llm' && ( - Available Evaluators + {t('evaluators.inputs.availableEvaluators.title')} setSelectedEvaluator(e)} value={selectedEvaluator} @@ -386,7 +400,7 @@ const AddEditEvaluatorDialog = ({ show, dialogProps, onCancel, onConfirm }) => { {evaluatorType === 'numeric' && selectedEvaluator && ( <> - Select Operator + {t('evaluators.inputs.selectOperator.title')} { /> - Value + {t('evaluators.inputs.value')} { {evaluatorType === 'text' && selectedEvaluator && ( <> - Value + {t('evaluators.inputs.value')} { - Output Schema - + {t('evaluators.outputSchema.title')} + @@ -454,7 +468,7 @@ const AddEditEvaluatorDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
- Prompt + {t('common.labels.prompt')}
{prompt && ( { height: 25, width: 25 }} - title='Expand' + title={t('common.actions.expand')} color='primary' onClick={() => onExpandDialogClicked({ - label: 'Evaluation Prompt', + label: t('evaluators.dialogs.evaluationPrompt'), name: 'evaluationPrompt', type: 'string' }) @@ -507,8 +521,12 @@ const AddEditEvaluatorDialog = ({ show, dialogProps, onCancel, onConfirm }) => { > - You can use {question} {actualOutput}{' '} - {expectedOutput} to inject runtime values into your prompt. + + }} + />
diff --git a/packages/ui/src/views/evaluators/SamplePromptDialog.jsx b/packages/ui/src/views/evaluators/SamplePromptDialog.jsx index e71c6068383..aaebe98fb0a 100644 --- a/packages/ui/src/views/evaluators/SamplePromptDialog.jsx +++ b/packages/ui/src/views/evaluators/SamplePromptDialog.jsx @@ -18,9 +18,13 @@ import { IconTestPipe2 } from '@tabler/icons-react' import useNotifier from '@/utils/useNotifier' // const -import { evaluationPrompts } from '@/views/evaluators/evaluationPrompts' +import { getEvaluationPrompts } from '@/views/evaluators/evaluationPrompts' + +// i18n +import { useTranslation } from 'react-i18next' const SamplePromptDialog = ({ show, dialogProps, onCancel, onConfirm }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') useNotifier() @@ -28,6 +32,8 @@ const SamplePromptDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const [selectedConfig, setSelectedConfig] = useState([]) const [selectedPromptText, setSelectedPromptText] = useState('') + const evaluationPrompts = useMemo(() => getEvaluationPrompts(t), [t]) + useEffect(() => { resetData() return () => { @@ -65,16 +71,16 @@ const SamplePromptDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const columns = useMemo( () => [ - { field: 'property', headerName: 'Property', flex: 1 }, + { field: 'property', headerName: t('evaluators.columns.property'), flex: 1 }, { field: 'type', - headerName: 'Type', + headerName: t('common.labels.type'), type: 'singleSelect', valueOptions: ['string', 'number', 'boolean'], width: 120 }, - { field: 'description', headerName: 'Description', flex: 1 }, - { field: 'required', headerName: 'Required', type: 'boolean', width: 80 }, + { field: 'description', headerName: t('common.labels.description'), flex: 1 }, + { field: 'required', headerName: t('evaluators.columns.required'), type: 'boolean', width: 80 }, { field: 'actions', type: 'actions', @@ -97,7 +103,7 @@ const SamplePromptDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
- Sample Prompts + {t('evaluators.dialogs.samplePrompts')}
@@ -105,12 +111,13 @@ const SamplePromptDialog = ({ show, dialogProps, onCancel, onConfirm }) => { - Available Prompts * + {t('evaluators.inputs.availablePrompts.title')} +  * { - Output Schema - + {t('evaluators.outputSchema.title')} + @@ -130,7 +137,7 @@ const SamplePromptDialog = ({ show, dialogProps, onCancel, onConfirm }) => { {selectedPromptName && (
- Prompt + {t('common.labels.prompt')}
{ variant='contained' onClick={() => onConfirmPrompt()} > - {'Select Prompt'} + {t('evaluators.actions.selectPrompt')} diff --git a/packages/ui/src/views/evaluators/evaluationPrompts.js b/packages/ui/src/views/evaluators/evaluationPrompts.js index e902638754c..e0025691343 100644 --- a/packages/ui/src/views/evaluators/evaluationPrompts.js +++ b/packages/ui/src/views/evaluators/evaluationPrompts.js @@ -1,7 +1,7 @@ -export const evaluationPrompts = [ +export const getEvaluationPrompts = (t) => [ { name: 'correctness', - label: 'Correctness', + label: t('evaluators.evaluationPrompts.correctness'), json: [{ id: 1, property: 'score', description: 'graded score', type: 'number', required: true }], prompt: `Respond with a numeric score based on how well the following response compare to the ground truth. Grade only based expected response: @@ -16,7 +16,7 @@ Do not include any other information in your response. Do not evaluate correctne }, { name: 'hallucination', - label: 'Hallucination', + label: t('evaluators.evaluationPrompts.hallucination'), json: [ { id: 1, property: 'score', description: 'provide a score between 0 and 1', type: 'number', required: true }, { id: 2, property: 'reasoning', description: 'provide a one sentence reasoning', type: 'string', required: true } diff --git a/packages/ui/src/views/evaluators/evaluatorConstant.js b/packages/ui/src/views/evaluators/evaluatorConstant.js index 79272cf0f05..837d378af67 100644 --- a/packages/ui/src/views/evaluators/evaluatorConstant.js +++ b/packages/ui/src/views/evaluators/evaluatorConstant.js @@ -1,143 +1,143 @@ // TODO: Move this to a config file -export const evaluators = [ +export const getEvaluators = (t) => [ { type: 'text', name: 'ContainsAny', - label: 'Contains Any', - description: 'Returns true if any of the specified comma separated values are present in the response.' + label: t('evaluators.inputs.availableEvaluators.options.containsAny.title'), + description: t('evaluators.inputs.availableEvaluators.options.containsAny.description') }, { type: 'text', name: 'ContainsAll', - label: 'Contains All', - description: 'Returns true if ALL of the specified comma separated values are present in the response.' + label: t('evaluators.inputs.availableEvaluators.options.containsAll.title'), + description: t('evaluators.inputs.availableEvaluators.options.containsAll.description') }, { type: 'text', name: 'DoesNotContainAny', - label: 'Does Not Contains Any', - description: 'Returns true if any of the specified comma separated values are present in the response.' + label: t('evaluators.inputs.availableEvaluators.options.doesNotContainAny.title'), + description: t('evaluators.inputs.availableEvaluators.options.doesNotContainAny.description') }, { type: 'text', name: 'DoesNotContainAll', - label: 'Does Not Contains All', - description: 'Returns true if ALL of the specified comma separated values are present in the response.' + label: t('evaluators.inputs.availableEvaluators.options.doesNotContainAll.title'), + description: t('evaluators.inputs.availableEvaluators.options.doesNotContainAll.description') }, { type: 'text', name: 'StartsWith', - label: 'Starts With', - description: 'Returns true if the response starts with the specified value.' + label: t('evaluators.inputs.availableEvaluators.options.startsWith.title'), + description: t('evaluators.inputs.availableEvaluators.options.startsWith.description') }, { type: 'text', name: 'NotStartsWith', - label: 'Does Not Start With', - description: 'Returns true if the response does not start with the specified value.' + label: t('evaluators.inputs.availableEvaluators.options.notStartsWith.title'), + description: t('evaluators.inputs.availableEvaluators.options.notStartsWith.description') }, { type: 'json', name: 'IsValidJSON', - label: 'Is Valid JSON', - description: 'Returns true if the response is a valid JSON.' + label: t('evaluators.inputs.availableEvaluators.options.isValidJSON.title'), + description: t('evaluators.inputs.availableEvaluators.options.isValidJSON.description') }, { type: 'json', name: 'IsNotValidJSON', - label: 'Is Not a Valid JSON', - description: 'Returns true if the response is a not a valid JSON.' + label: t('evaluators.inputs.availableEvaluators.options.isNotValidJSON.title'), + description: t('evaluators.inputs.availableEvaluators.options.isNotValidJSON.description') }, { type: 'numeric', name: 'totalTokens', - label: 'Total Tokens', - description: 'Sum of Prompt Tokens and Completion Tokens.' + label: t('evaluators.inputs.availableEvaluators.options.totalTokens.title'), + description: t('evaluators.inputs.availableEvaluators.options.totalTokens.description') }, { type: 'numeric', - label: 'Prompt Tokens', + label: t('evaluators.inputs.availableEvaluators.options.promptTokens.title'), name: 'promptTokens', - description: 'This is the number of tokens in your prompt.' + description: t('evaluators.inputs.availableEvaluators.options.promptTokens.description') }, { type: 'numeric', - label: 'Completion Tokens', + label: t('evaluators.inputs.availableEvaluators.options.completionTokens.title'), name: 'completionTokens', - description: 'Completion tokens are any tokens that the model generates in response to your input.' + description: t('evaluators.inputs.availableEvaluators.options.completionTokens.description') }, { type: 'numeric', - label: 'Total API Latency', + label: t('evaluators.inputs.availableEvaluators.options.apiLatency.title'), name: 'apiLatency', - description: 'Total time taken for the Flowise Prediction API call (milliseconds).' + description: t('evaluators.inputs.availableEvaluators.options.apiLatency.description') }, { type: 'numeric', - label: 'LLM Latency', + label: t('evaluators.inputs.availableEvaluators.options.llm.title'), name: 'llm', - description: 'Actual LLM invocation time (milliseconds).' + description: t('evaluators.inputs.availableEvaluators.options.llm.description') }, { type: 'numeric', - label: 'Chatflow Latency', + label: t('evaluators.inputs.availableEvaluators.options.chain.title'), name: 'chain', - description: 'Actual time spent in executing the chatflow (milliseconds).' + description: t('evaluators.inputs.availableEvaluators.options.chain.description') }, { type: 'numeric', - label: 'Output Chars Length', + label: t('evaluators.inputs.availableEvaluators.options.responseLength.title'), name: 'responseLength', - description: 'Number of characters in the response.' + description: t('evaluators.inputs.availableEvaluators.options.responseLength.description') } ] -export const evaluatorTypes = [ +export const getEvaluatorTypes = (t) => [ { - label: 'Evaluate Result (Text Based)', + label: t('evaluators.inputs.evaluatorType.options.text.title'), name: 'text', - description: 'Set of Evaluators to evaluate the result of a Chatflow.' + description: t('evaluators.inputs.evaluatorType.options.text.description') }, { - label: 'Evaluate Result (JSON)', + label: t('evaluators.inputs.evaluatorType.options.json.title'), name: 'json', - description: 'Set of Evaluators to evaluate the JSON response of a Chatflow.' + description: t('evaluators.inputs.evaluatorType.options.json.description') }, { - label: 'Evaluate Metrics (Numeric)', + label: t('evaluators.inputs.evaluatorType.options.numeric.title'), name: 'numeric', - description: 'Set of Evaluators that evaluate the metrics (latency, tokens, cost, length of response) of a Chatflow.' + description: t('evaluators.inputs.evaluatorType.options.numeric.description') }, { - label: 'LLM based Grading (JSON)', + label: t('evaluators.inputs.evaluatorType.options.llm.title'), name: 'llm', - description: 'Post execution, grades the answers by using an LLM.' + description: t('evaluators.inputs.evaluatorType.options.llm.description') } ] -export const numericOperators = [ +export const getNumericOperators = (t) => [ { - label: 'Equals', + label: t('evaluators.inputs.selectOperator.operators.equals'), name: 'equals' }, { - label: 'Not Equals', + label: t('evaluators.inputs.selectOperator.operators.notEquals'), name: 'notEquals' }, { - label: 'Greater Than', + label: t('evaluators.inputs.selectOperator.operators.greaterThan'), name: 'greaterThan' }, { - label: 'Less Than', + label: t('evaluators.inputs.selectOperator.operators.lessThan'), name: 'lessThan' }, { - label: 'Greater Than or Equals', + label: t('evaluators.inputs.selectOperator.operators.greaterThanOrEquals'), name: 'greaterThanOrEquals' }, { - label: 'Less Than or Equals', + label: t('evaluators.inputs.selectOperator.operators.lessThanOrEquals'), name: 'lessThanOrEquals' } ] diff --git a/packages/ui/src/views/evaluators/index.jsx b/packages/ui/src/views/evaluators/index.jsx index f1738f92303..5745ed2a7f2 100644 --- a/packages/ui/src/views/evaluators/index.jsx +++ b/packages/ui/src/views/evaluators/index.jsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useMemo, useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' // material-ui @@ -32,11 +32,15 @@ import empty_evaluatorSVG from '@/assets/images/empty_evaluators.svg' import { IconTrash, IconPlus, IconJson, IconX, IconNumber123, IconAbc, IconAugmentedReality } from '@tabler/icons-react' // const -import { evaluators as evaluatorsOptions, numericOperators } from '../evaluators/evaluatorConstant' +import { getEvaluators, getNumericOperators } from '../evaluators/evaluatorConstant' + +// i18n +import { useTranslation } from 'react-i18next' // ==============================|| Evaluators ||============================== // const Evaluators = () => { + const { t } = useTranslation() const theme = useTheme() const customization = useSelector((state) => state.customization) const dispatch = useDispatch() @@ -44,6 +48,9 @@ const Evaluators = () => { useNotifier() const { error } = useError() + const evaluatorsOptions = useMemo(() => getEvaluators(t), [t]) + const numericOperators = useMemo(() => getNumericOperators(t), [t]) + const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args)) const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args)) @@ -80,8 +87,8 @@ const Evaluators = () => { const newEvaluator = () => { const dialogProp = { type: 'ADD', - cancelButtonName: 'Cancel', - confirmButtonName: 'Add', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.add'), data: {} } setDialogProps(dialogProp) @@ -91,8 +98,8 @@ const Evaluators = () => { const edit = (item) => { const dialogProp = { type: 'EDIT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Save', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.save'), data: item } setDialogProps(dialogProp) @@ -101,10 +108,10 @@ const Evaluators = () => { const deleteEvaluator = async (item) => { const confirmPayload = { - title: `Delete`, - description: `Delete Evaluator ${item.name}?`, - confirmButtonName: 'Delete', - cancelButtonName: 'Cancel' + title: t('common.dialogs.delete'), + description: t('evaluators.dialogs.delete.description', { name: item.name }), + confirmButtonName: t('common.actions.delete'), + cancelButtonName: t('common.actions.cancel') } const isConfirmed = await confirm(confirmPayload) @@ -113,7 +120,7 @@ const Evaluators = () => { const deleteResp = await evaluatorsApi.deleteEvaluator(item.id) if (deleteResp.data) { enqueueSnackbar({ - message: 'Evaluator deleted', + message: t('evaluators.messages.delete.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -128,9 +135,9 @@ const Evaluators = () => { } } catch (error) { enqueueSnackbar({ - message: `Failed to delete Evaluator: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('evaluators.messages.delete.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -184,7 +191,7 @@ const Evaluators = () => { isEditButton={false} onSearchChange={onSearchChange} search={true} - title='Evaluators' + title={t('evaluators.title')} description='' > { onClick={newEvaluator} startIcon={} > - New Evaluator + {t('evaluators.actions.newEvaluator')} {!isLoading && evaluators.length <= 0 ? ( @@ -206,7 +213,7 @@ const Evaluators = () => { alt='empty_evaluatorSVG' />
-
No Evaluators Yet
+
{t('evaluators.notFound')}
) : ( <> @@ -224,10 +231,10 @@ const Evaluators = () => { }} > - Type - Name - Details - Last Updated + {t('common.labels.type')} + {t('common.labels.name')} + {t('evaluators.table.details')} + {t('evaluators.table.lastUpdated')} @@ -286,7 +293,7 @@ const Evaluators = () => { } - label='Numeric' + label={t('evaluators.type.numeric')} variant='outlined' /> @@ -295,7 +302,7 @@ const Evaluators = () => { } - label='Text Based' + label={t('evaluators.type.textBased')} variant='outlined' /> @@ -304,7 +311,7 @@ const Evaluators = () => { } - label='JSON Based' + label={t('evaluators.type.jsonBased')} variant='outlined' /> @@ -313,7 +320,7 @@ const Evaluators = () => { } - label='LLM Based' + label={t('evaluators.type.llmBased')} variant='outlined' /> @@ -343,7 +350,7 @@ const Evaluators = () => { }} label={ - Measure:{' '} + {t('evaluators.type.measure')}:{' '} { [ ...evaluatorsOptions, @@ -368,7 +375,7 @@ const Evaluators = () => { }} label={ - Operator:{' '} + {t('evaluators.type.operator')}:{' '} { [ ...evaluatorsOptions, @@ -393,7 +400,7 @@ const Evaluators = () => { }} label={ - Value: {ds?.value} + {t('evaluators.type.value')}: {ds?.value} } /> @@ -419,7 +426,7 @@ const Evaluators = () => { }} label={ - Operator:{' '} + {t('evaluators.type.operator')}:{' '} { [ ...evaluatorsOptions, @@ -444,7 +451,7 @@ const Evaluators = () => { }} label={ - Value: {ds?.value} + {t('evaluators.type.value')}: {ds?.value} } /> @@ -470,7 +477,7 @@ const Evaluators = () => { }} label={ - Operator:{' '} + {t('evaluators.type.operator')}:{' '} { [...evaluatorsOptions].find( (item) => item.name === ds?.operator @@ -501,7 +508,8 @@ const Evaluators = () => { }} label={ - Prompt: {truncateString(ds?.prompt, 100)} + {t('common.labels.prompt')}:{' '} + {truncateString(ds?.prompt, 100)} } /> @@ -519,12 +527,12 @@ const Evaluators = () => { }} label={ - Output Schema Elements:{' '} + {t('evaluators.type.outputSchemaElements')}:{' '} {ds?.outputSchema.length > 0 ? ds?.outputSchema .map((item) => item.property) .join(', ') - : 'None'} + : t('common.labels.none')} } /> @@ -532,12 +540,14 @@ const Evaluators = () => { )} edit(ds)}> - {moment(ds.updatedDate).format('MMMM Do YYYY, hh:mm A')} + {moment(ds.updatedDate).format( + t('common.formats.dateMonthDayYearTime12Short') + )} deleteEvaluator(ds)} > diff --git a/packages/ui/src/views/files/index.jsx b/packages/ui/src/views/files/index.jsx index e5b952c838a..7080ee1f6a5 100644 --- a/packages/ui/src/views/files/index.jsx +++ b/packages/ui/src/views/files/index.jsx @@ -25,9 +25,13 @@ import { IconX } from '@tabler/icons-react' import { useDispatch } from 'react-redux' import { useError } from '@/store/context/ErrorContext' +// i18n +import { useTranslation } from 'react-i18next' + // ==============================|| CHATFLOWS ||============================== // const Files = () => { + const { t } = useTranslation() const { confirm } = useConfirm() const [isLoading, setLoading] = useState(true) @@ -56,10 +60,10 @@ const Files = () => { const handleDeleteFile = async (file) => { const confirmPayload = { - title: `Delete`, - description: `Delete ${file.name}? This process cannot be undone.`, - confirmButtonName: 'Delete', - cancelButtonName: 'Cancel' + title: t('common.dialogs.delete'), + description: t('files.dialogs.delete.description', { name: file.name }), + confirmButtonName: t('common.actions.delete'), + cancelButtonName: t('common.actions.cancel') } const isConfirmed = await confirm(confirmPayload) @@ -68,7 +72,7 @@ const Files = () => { const deleteResponse = await filesApi.deleteFile(file.path) if (deleteResponse?.data) { enqueueSnackbar({ - message: 'File deleted', + message: t('files.messages.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -127,7 +131,12 @@ const Files = () => { ) : ( - + {!isLoading && (!getAllFilesApi.data || getAllFilesApi.data.length === 0) && ( @@ -138,7 +147,7 @@ const Files = () => { alt='WorkflowEmptySVG' />
-
No Files Yet
+
{t('files.notFound')}
)} diff --git a/packages/ui/src/views/marketplaces/MarketplaceCanvas.jsx b/packages/ui/src/views/marketplaces/MarketplaceCanvas.jsx index b1b9c2b1121..cde46fba6e4 100644 --- a/packages/ui/src/views/marketplaces/MarketplaceCanvas.jsx +++ b/packages/ui/src/views/marketplaces/MarketplaceCanvas.jsx @@ -21,9 +21,13 @@ import { IconMagnetFilled, IconMagnetOff, IconArtboard, IconArtboardOff } from ' const nodeTypes = { customNode: MarketplaceCanvasNode, stickyNote: StickyNote } const edgeTypes = { buttonedge: '' } +// i18n +import { useTranslation } from 'react-i18next' + // ==============================|| CANVAS ||============================== // const MarketplaceCanvas = () => { + const { t } = useTranslation() const theme = useTheme() const navigate = useNavigate() const customization = useSelector((state) => state.customization) @@ -110,8 +114,8 @@ const MarketplaceCanvas = () => { onClick={() => { setIsSnappingEnabled(!isSnappingEnabled) }} - title='toggle snapping' - aria-label='toggle snapping' + title={t('marketplaces.actions.toggleSnapping')} + aria-label={t('marketplaces.actions.toggleSnapping')} > {isSnappingEnabled ? : } @@ -120,8 +124,8 @@ const MarketplaceCanvas = () => { onClick={() => { setIsBackgroundEnabled(!isBackgroundEnabled) }} - title='toggle background' - aria-label='toggle background' + title={t('marketplaces.actions.toggleBackground')} + aria-label={t('marketplaces.actions.toggleBackground')} > {isBackgroundEnabled ? : } diff --git a/packages/ui/src/views/marketplaces/MarketplaceCanvasHeader.jsx b/packages/ui/src/views/marketplaces/MarketplaceCanvasHeader.jsx index 7e8e03f0c1d..9ef7c40ec3c 100644 --- a/packages/ui/src/views/marketplaces/MarketplaceCanvasHeader.jsx +++ b/packages/ui/src/views/marketplaces/MarketplaceCanvasHeader.jsx @@ -10,16 +10,20 @@ import { StyledButton } from '@/ui-component/button/StyledButton' import { IconCopy, IconChevronLeft } from '@tabler/icons-react' import { Available } from '@/ui-component/rbac/available' +// i18n +import { useTranslation } from 'react-i18next' + // ==============================|| CANVAS HEADER ||============================== // const MarketplaceCanvasHeader = ({ flowName, flowData, onChatflowCopy }) => { + const { t } = useTranslation() const theme = useTheme() const navigate = useNavigate() return ( <> - + { onChatflowCopy(flowData)} startIcon={} > - Use Template + {t('marketplaces.actions.useTemplate')} diff --git a/packages/ui/src/views/marketplaces/MarketplaceCanvasNode.jsx b/packages/ui/src/views/marketplaces/MarketplaceCanvasNode.jsx index 55adf019fa4..db6ae40888b 100644 --- a/packages/ui/src/views/marketplaces/MarketplaceCanvasNode.jsx +++ b/packages/ui/src/views/marketplaces/MarketplaceCanvasNode.jsx @@ -15,6 +15,9 @@ import AdditionalParamsDialog from '@/ui-component/dialog/AdditionalParamsDialog import { baseURL } from '@/store/constant' import LlamaindexPNG from '@/assets/images/llamaindex.png' +// i18n +import { useTranslation } from 'react-i18next' + const CardWrapper = styled(MainCard)(({ theme }) => ({ background: theme.palette.card.main, color: theme.darkTextPrimary, @@ -32,6 +35,7 @@ const CardWrapper = styled(MainCard)(({ theme }) => ({ // ===========================|| CANVAS NODE ||=========================== // const MarketplaceCanvasNode = ({ data }) => { + const { t } = useTranslation() const theme = useTheme() const [showDialog, setShowDialog] = useState(false) @@ -42,8 +46,8 @@ const MarketplaceCanvasNode = ({ data }) => { data, inputParams: data.inputParams.filter((param) => param.additionalParams), disabled: true, - confirmButtonName: 'Save', - cancelButtonName: 'Cancel' + confirmButtonName: t('common.actions.save'), + cancelButtonName: t('common.actions.cancel') } setDialogProps(dialogProps) setShowDialog(true) @@ -116,7 +120,7 @@ const MarketplaceCanvasNode = ({ data }) => { textAlign: 'center' }} > - Inputs + {t('marketplaces.inputs.inputs')}
@@ -142,7 +146,7 @@ const MarketplaceCanvasNode = ({ data }) => { }} > )} @@ -154,7 +158,7 @@ const MarketplaceCanvasNode = ({ data }) => { textAlign: 'center' }} > - Output + {t('common.labels.output')}
diff --git a/packages/ui/src/views/marketplaces/index.jsx b/packages/ui/src/views/marketplaces/index.jsx index 1f364761414..f09c1c3b0a2 100644 --- a/packages/ui/src/views/marketplaces/index.jsx +++ b/packages/ui/src/views/marketplaces/index.jsx @@ -60,7 +60,13 @@ import { baseURL, AGENTFLOW_ICONS } from '@/store/constant' import { gridSpacing } from '@/store/constant' import { useError } from '@/store/context/ErrorContext' -const badges = ['POPULAR', 'NEW'] +// i18n +import { useTranslation } from 'react-i18next' + +const badges = [ + { id: 'POPULAR', label: 'badges.popular' }, + { id: 'NEW', label: 'badges.new' } +] const types = ['Chatflow', 'AgentflowV2', 'Tool'] const framework = ['Langchain', 'LlamaIndex'] const MenuProps = { @@ -74,6 +80,7 @@ const MenuProps = { // ==============================|| Marketplace ||============================== // const Marketplace = () => { + const { t } = useTranslation() const navigate = useNavigate() const dispatch = useDispatch() useNotifier() @@ -114,15 +121,25 @@ const Marketplace = () => { const [showShareTemplateDialog, setShowShareTemplateDialog] = useState(false) const [shareTemplateDialogProps, setShareTemplateDialogProps] = useState({}) + const getBadgeLocale = (name, t) => { + switch (name) { + case 'POPULAR': + return t('badges.popular') + case 'NEW': + return t('badges.new') + } + return name + } + const share = (template) => { const dialogProps = { type: 'EDIT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Share', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.share'), data: { id: template.id, name: template.name, - title: 'Share Custom Template', + title: t('marketplaces.dialogs.shareCustomTemplate'), itemType: 'custom_template' } } @@ -218,10 +235,10 @@ const Marketplace = () => { const onDeleteCustomTemplate = async (template) => { const confirmPayload = { - title: `Delete`, - description: `Delete Custom Template ${template.name}?`, - confirmButtonName: 'Delete', - cancelButtonName: 'Cancel' + title: t('common.dialogs.delete'), + description: t('marketplaces.dialogs.delete.description', { name: template.name }), + confirmButtonName: t('common.actions.delete'), + cancelButtonName: t('common.actions.cancel') } const isConfirmed = await confirm(confirmPayload) @@ -230,7 +247,7 @@ const Marketplace = () => { const deleteResp = await marketplacesApi.deleteCustomTemplate(template.id) if (deleteResp.data) { enqueueSnackbar({ - message: 'Custom Template deleted successfully!', + message: t('marketplaces.messages.delete.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -245,9 +262,9 @@ const Marketplace = () => { } } catch (error) { enqueueSnackbar({ - message: `Failed to delete custom template: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('marketplaces.messages.delete.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -321,10 +338,10 @@ const Marketplace = () => { const onUseTemplate = (selectedTool) => { const dialogProp = { - title: 'Add New Tool', + title: t('marketplaces.dialogs.addNewTool'), type: 'IMPORT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Add', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.add'), data: selectedTool } setToolDialogProps(dialogProp) @@ -485,7 +502,7 @@ const Marketplace = () => { }} > - Tag + {t('marketplaces.inputs.tag')} @@ -521,7 +538,7 @@ const Marketplace = () => { }} > - Type + {t('common.labels.type')} { multiple value={frameworkFilter} onChange={handleFrameworkFilterChange} - input={} + input={} renderValue={(selected) => selected.join(', ')} MenuProps={MenuProps} sx={getSelectStyles(theme.palette.grey[900] + 25, theme?.customization?.isDarkMode)} @@ -587,9 +604,9 @@ const Marketplace = () => { } onSearchChange={onSearchChange} search={true} - searchPlaceholder='Search Name/Description/Node' - title='Marketplace' - description='Explore and use pre-built templates' + searchPlaceholder={t('marketplaces.searchPlaceholder')} + title={t('marketplaces.title')} + description={t('marketplaces.description')} > { }} variant='contained' value='card' - title='Card View' + title={t('common.actions.cardView')} > @@ -618,7 +635,7 @@ const Marketplace = () => { }} variant='contained' value='list' - title='List View' + title={t('common.actions.listView')} > @@ -626,9 +643,18 @@ const Marketplace = () => { {hasPermission('templates:marketplace') && hasPermission('templates:custom') && ( - - - + + + { ) }} - renderInput={(params) => } + renderInput={(params) => } sx={{ width: 300 }} @@ -732,7 +758,7 @@ const Marketplace = () => { right: 20 } }} - badgeContent={data.badge} + badgeContent={getBadgeLocale(data.badge, t)} color={data.badge === 'POPULAR' ? 'primary' : 'error'} > {(data.type === 'Chatflow' || @@ -794,7 +820,7 @@ const Marketplace = () => { alt='WorkflowEmptySVG' />
-
No Marketplace Yet
+
{t('marketplaces.notFound')}
)} @@ -835,7 +861,7 @@ const Marketplace = () => { onClick={() => clearAllUsecases()} startIcon={} > - Clear All + {t('common.actions.clearAll')} )} {!view || view === 'card' ? ( @@ -865,7 +891,7 @@ const Marketplace = () => { right: 20 } }} - badgeContent={data.badge} + badgeContent={getBadgeLocale(data.badge, t)} color={data.badge === 'POPULAR' ? 'primary' : 'error'} > {(data.type === 'Chatflow' || @@ -927,7 +953,7 @@ const Marketplace = () => { alt='WorkflowEmptySVG' />
-
No Saved Custom Templates
+
{t('marketplaces.notFoundCustomTemplate')}
)} diff --git a/packages/ui/src/views/organization/index.jsx b/packages/ui/src/views/organization/index.jsx index fa31d231529..d4554edda61 100644 --- a/packages/ui/src/views/organization/index.jsx +++ b/packages/ui/src/views/organization/index.jsx @@ -9,6 +9,7 @@ import { Alert, Box, Button, Chip, Divider, Icon, List, ListItemText, Stack, Typ import { StyledButton } from '@/ui-component/button/StyledButton' import { Input } from '@/ui-component/input/Input' import { BackdropLoader } from '@/ui-component/loading/BackdropLoader' +import LanguageSwitcher from '@/ui-component/language/LanguageSwitcher' // API import accountApi from '@/api/account.api' @@ -31,56 +32,61 @@ import AzureSSOLoginIcon from '@/assets/images/microsoft-azure.svg' import { useConfig } from '@/store/context/ConfigContext' import { IconCircleCheck, IconExclamationCircle } from '@tabler/icons-react' +// i18n +import { useTranslation } from 'react-i18next' + // ==============================|| Organization & Admin User Setup ||============================== // // IMPORTANT: when updating this schema, update the schema on the server as well // packages/server/src/enterprise/Interface.Enterprise.ts -const OrgSetupSchema = z - .object({ - username: z.string().min(1, 'Name is required'), - email: z.string().min(1, 'Email is required').email('Invalid email address'), - password: passwordSchema, - confirmPassword: z.string().min(1, 'Confirm Password is required') - }) - .refine((data) => data.password === data.confirmPassword, { - message: "Passwords don't match", - path: ['confirmPassword'] - }) +const OrgSetupSchema = (t) => + z + .object({ + username: z.string().min(1, t('common.validation.name.required')), + email: z.string().min(1, t('common.validation.email.required')).email(t('common.validation.email.invalid')), + password: passwordSchema(t), + confirmPassword: z.string().min(1, t('common.validation.confirm.required')) + }) + .refine((data) => data.password === data.confirmPassword, { + message: t('common.validation.confirm.match'), + path: ['confirmPassword'] + }) const OrganizationSetupPage = () => { + const { t } = useTranslation() useNotifier() const { isEnterpriseLicensed, isOpenSource } = useConfig() const orgNameInput = { - label: 'Organization', + label: t('organization.inputs.organization.title'), name: 'organization', type: 'text', - placeholder: 'Acme' + placeholder: t('organization.inputs.organization.placeholder') } const usernameInput = { - label: 'Username', + label: t('organization.inputs.username.title'), name: 'username', type: 'text', - placeholder: 'John Doe' + placeholder: t('organization.inputs.username.placeholder') } const passwordInput = { - label: 'Password', + label: t('organization.inputs.password.title'), name: 'password', type: 'password', placeholder: '********' } const confirmPasswordInput = { - label: 'Confirm Password', + label: t('organization.inputs.confirmPassword.title'), name: 'confirmPassword', type: 'password', placeholder: '********' } const emailInput = { - label: 'EMail', + label: t('common.labels.email'), name: 'email', type: 'email', placeholder: 'user@company.com' @@ -105,7 +111,7 @@ const OrganizationSetupPage = () => { const register = async (event) => { event.preventDefault() - const result = OrgSetupSchema.safeParse({ + const result = OrgSetupSchema(t).safeParse({ orgName, username, email, @@ -145,15 +151,19 @@ const OrganizationSetupPage = () => { : registerAccountApi.error.response.data let finalErrMessage = '' if (isEnterpriseLicensed) { - finalErrMessage = `Error in registering organization. Please contact your administrator. (${errMessage})` + finalErrMessage = t('organization.errors.registration.enterprise', { + msg: errMessage + }) } else { - finalErrMessage = `Error in registering account: ${errMessage}` + finalErrMessage = t('organization.errors.registration.base', { + msg: errMessage + }) } setAuthError(finalErrMessage) setLoading(false) } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [registerAccountApi.error]) + }, [registerAccountApi.error, t]) useEffect(() => { if (!isOpenSource) { @@ -236,13 +246,9 @@ const OrganizationSetupPage = () => { )} - Setup Account + {t('organization.setup.title')} - {(isOpenSource || isEnterpriseLicensed) && ( - - Account setup does not make any external connections, your data stays securely on your locally hosted server. - - )} + {(isOpenSource || isEnterpriseLicensed) && {t('organization.setup.caption')}}
{isEnterpriseLicensed && ( @@ -250,13 +256,13 @@ const OrganizationSetupPage = () => {
- Organization Name: * + {t('organization.inputs.organizationName')}: *
setOrgName(newValue)} value={orgName} showDialog={false} @@ -264,7 +270,7 @@ const OrganizationSetupPage = () => {
- + @@ -272,25 +278,27 @@ const OrganizationSetupPage = () => {
- Administrator Name * + {t('organization.inputs.administratorName.title')} +  *
setUsername(newValue)} value={username} showDialog={false} /> - Is used for display purposes only. + {t('organization.inputs.administratorName.caption')}
- Administrator Email * + {t('organization.inputs.administratorEmail.title')} +  *
@@ -302,28 +310,27 @@ const OrganizationSetupPage = () => { showDialog={false} /> - Kindly use a valid email address. Will be used as login id. + {t('organization.inputs.administratorEmail.caption')}
- Password * + {t('organization.inputs.password.title')} +  *
setPassword(newValue)} value={password} /> - - Password must be at least 8 characters long and contain at least one lowercase letter, one uppercase - letter, one digit, and one special character. - + {t('organization.inputs.password.caption')}
- Confirm Password * + {t('organization.inputs.confirmPassword.title')} +  *
@@ -333,13 +340,15 @@ const OrganizationSetupPage = () => { value={confirmPassword} /> - Reconfirm your password. Must match the password typed above. + {t('organization.inputs.confirmPassword.caption')}
- Sign Up + {t('organization.actions.signup.title')} - {configuredSsoProviders && configuredSsoProviders.length > 0 && OR} + {configuredSsoProviders && configuredSsoProviders.length > 0 && ( + {t('organization.or')} + )} {configuredSsoProviders && configuredSsoProviders.map( (ssoProvider) => @@ -356,7 +365,7 @@ const OrganizationSetupPage = () => { } > - Sign Up With Microsoft + {t('organization.actions.signup.microsoft')} ) )} @@ -375,7 +384,7 @@ const OrganizationSetupPage = () => { } > - Sign Up With Google + {t('organization.actions.signup.google')} ) )} @@ -394,7 +403,7 @@ const OrganizationSetupPage = () => { } > - Sign Up With Auth0 by Okta + {t('organization.actions.signup.auth0')} ) )} @@ -402,6 +411,16 @@ const OrganizationSetupPage = () => {
+ + + {loading && } ) diff --git a/packages/ui/src/views/roles/CreateEditRoleDialog.jsx b/packages/ui/src/views/roles/CreateEditRoleDialog.jsx index a9760e78e89..235ce57caf8 100644 --- a/packages/ui/src/views/roles/CreateEditRoleDialog.jsx +++ b/packages/ui/src/views/roles/CreateEditRoleDialog.jsx @@ -28,9 +28,13 @@ import useNotifier from '@/utils/useNotifier' // const import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions' +// i18n +import { useTranslation } from 'react-i18next' + import './CreateEditRoleDialog.css' const CreateEditRoleDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const dispatch = useDispatch() @@ -200,7 +204,7 @@ const CreateEditRoleDialog = ({ show, dialogProps, onCancel, onConfirm, setError // if roleName has a space, raise an error if (roleName.indexOf(' ') > -1) { enqueueSnackbar({ - message: `Role Name cannot contain spaces.`, + message: t('roles.messages.create.error'), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -240,7 +244,7 @@ const CreateEditRoleDialog = ({ show, dialogProps, onCancel, onConfirm, setError } if (saveResp.data) { enqueueSnackbar({ - message: dialogProps.type === 'EDIT' ? 'Role Updated Successfully' : 'New Role Created!', + message: t(dialogProps.type === 'EDIT' ? 'roles.messages.update.success' : 'roles.messages.create.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -255,7 +259,9 @@ const CreateEditRoleDialog = ({ show, dialogProps, onCancel, onConfirm, setError } } catch (error) { enqueueSnackbar({ - message: `Failed : ${typeof error.response.data === 'object' ? error.response.data.message : error.response.data}`, + message: t('roles.messages.update.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -310,14 +316,21 @@ const CreateEditRoleDialog = ({ show, dialogProps, onCancel, onConfirm, setError
- {dialogProps.type === 'EDIT' ? 'Edit Role' : dialogProps.type === 'VIEW' ? 'View Role' : 'Create New Role'} + {t( + dialogProps.type === 'EDIT' + ? 'roles.dialogs.edit' + : dialogProps.type === 'VIEW' + ? 'roles.dialogs.view' + : 'roles.dialogs.create' + )}
- *  Role Name + *   + {t('roles.inputs.roleName.title')} - Role Description + {t('roles.inputs.roleDescription.title')}
-

Permissions

+

{t('roles.table.permissions')}

{permissions && Object.keys(permissions).map((category) => ( @@ -365,7 +378,7 @@ const CreateEditRoleDialog = ({ show, dialogProps, onCancel, onConfirm, setError hidden={dialogProps.type === 'VIEW'} onClick={() => handleSelectAll(category)} > - Select All + {t('common.actions.selectAll')}
@@ -397,11 +410,11 @@ const CreateEditRoleDialog = ({ show, dialogProps, onCancel, onConfirm, setError {dialogProps.type !== 'VIEW' && ( - {dialogProps.type !== 'EDIT' ? 'Create Role' : 'Update Role'} + {t(dialogProps.type !== 'EDIT' ? 'roles.actions.createRole' : 'roles.actions.updateRole')} )} diff --git a/packages/ui/src/views/roles/index.jsx b/packages/ui/src/views/roles/index.jsx index b81866735b7..a9201d596d1 100644 --- a/packages/ui/src/views/roles/index.jsx +++ b/packages/ui/src/views/roles/index.jsx @@ -51,6 +51,9 @@ import roles_emptySVG from '@/assets/images/roles_empty.svg' import { useError } from '@/store/context/ErrorContext' +// i18n +import { useTranslation } from 'react-i18next' + const StyledTableCell = styled(TableCell)(({ theme }) => ({ borderColor: theme.palette.grey[900] + 25, @@ -71,6 +74,7 @@ const StyledTableRow = styled(TableRow)(() => ({ })) function ViewPermissionsDrawer(props) { + const { t } = useTranslation() const theme = useTheme() const [permissions, setPermissions] = useState({}) const [selectedPermissions, setSelectedPermissions] = useState({}) @@ -133,7 +137,7 @@ function ViewPermissionsDrawer(props) { )} - Permissions + {t('roles.table.permissions')} {permissions && @@ -182,6 +186,7 @@ ViewPermissionsDrawer.propTypes = { } function ShowRoleRow(props) { + const { t } = useTranslation() const [openAssignedUsersDrawer, setOpenAssignedUsersDrawer] = useState(false) const [openViewPermissionsDrawer, setOpenViewPermissionsDrawer] = useState(false) const [selectedRoleId, setSelectedRoleId] = useState('') @@ -278,7 +283,7 @@ function ShowRoleRow(props) { setOpenViewPermissionsDrawer(!openViewPermissionsDrawer)} > @@ -291,7 +296,7 @@ function ShowRoleRow(props) { {props.role.userCount > 0 && ( handleViewAssignedUsers(props.role.id)} @@ -303,7 +308,7 @@ function ShowRoleRow(props) { props.onEditClick(props.role)} > @@ -313,7 +318,7 @@ function ShowRoleRow(props) { permissionId={'roles:manage'} disabled={props.role.userCount > 0} color='error' - title={props.role.userCount > 0 ? 'Remove users with the role from Workspace first' : 'Delete'} + title={t(props.role.userCount > 0 ? 'roles.removeUsersFirst' : 'common.actions.delete')} onClick={() => props.onDeleteClick(props.role)} > @@ -323,14 +328,14 @@ function ShowRoleRow(props) { setOpenAssignedUsersDrawer(false)} sx={{ minWidth: 320 }}> - Assigned Users + {t('roles.table.assignedUsers')} -
+
handleRequestSort('user')} > - User + {t('common.labels.user')} @@ -353,7 +358,7 @@ function ShowRoleRow(props) { direction={orderBy === 'workspace' ? order : 'asc'} onClick={() => handleRequestSort('workspace')} > - Workspace + {t('common.labels.workspace')} @@ -388,6 +393,7 @@ ShowRoleRow.propTypes = { // ==============================|| Roles ||============================== // const Roles = () => { + const { t } = useTranslation() const theme = useTheme() const customization = useSelector((state) => state.customization) const dispatch = useDispatch() @@ -424,8 +430,8 @@ const Roles = () => { const addNew = () => { const dialogProp = { type: 'ADD', - cancelButtonName: 'Cancel', - confirmButtonName: 'Invite', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('roles.actions.invite'), data: {} } setDialogProps(dialogProp) @@ -435,8 +441,8 @@ const Roles = () => { const edit = (role) => { const dialogProp = { type: 'EDIT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Invite', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('roles.actions.invite'), data: { ...role } @@ -448,8 +454,8 @@ const Roles = () => { const view = (role) => { const dialogProp = { type: 'VIEW', - cancelButtonName: 'Cancel', - confirmButtonName: 'Invite', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('roles.actions.invite'), data: { ...role } @@ -460,10 +466,10 @@ const Roles = () => { const deleteRole = async (role) => { const confirmPayload = { - title: `Delete`, - description: `Delete Role ${role.name}?`, - confirmButtonName: 'Delete', - cancelButtonName: 'Cancel' + title: t('common.dialogs.delete'), + description: t('roles.dialogs.delete.description', { name: role.name }), + confirmButtonName: t('common.actions.delete'), + cancelButtonName: t('common.actions.cancel') } const isConfirmed = await confirm(confirmPayload) @@ -472,7 +478,7 @@ const Roles = () => { const deleteResp = await roleApi.deleteRole(role.id, currentUser.activeOrganizationId) if (deleteResp.data) { enqueueSnackbar({ - message: 'Role deleted', + message: t('roles.messages.delete.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -487,9 +493,9 @@ const Roles = () => { } } catch (error) { enqueueSnackbar({ - message: `Failed to delete Role: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('roles.messages.delete.error', { + mag: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -538,7 +544,12 @@ const Roles = () => { ) : ( - + { startIcon={} id='btn_createUser' > - Add Role + {t('roles.actions.addRole')} {!isLoading && roles.length === 0 ? ( @@ -559,7 +570,7 @@ const Roles = () => { alt='roles_emptySVG' /> -
No Roles Yet
+
{t('roles.notFound')}
) : ( <> @@ -570,7 +581,7 @@ const Roles = () => { sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }} component={Paper} > -
+
{ }} > - Name - Description - Permissions - Assigned Users + {t('common.labels.name')} + {t('common.labels.description')} + {t('roles.table.permissions')} + {t('roles.table.assignedUsers')} diff --git a/packages/ui/src/views/serverlogs/index.jsx b/packages/ui/src/views/serverlogs/index.jsx index a0b935a12ce..df9dceff5ef 100644 --- a/packages/ui/src/views/serverlogs/index.jsx +++ b/packages/ui/src/views/serverlogs/index.jsx @@ -25,6 +25,9 @@ import { useError } from '@/store/context/ErrorContext' import LogsEmptySVG from '@/assets/images/logs_empty.svg' import 'react-datepicker/dist/react-datepicker.css' +// i18n +import { useTranslation } from 'react-i18next' + const DatePickerCustomInput = forwardRef(function DatePickerCustomInput({ value, onClick }, ref) { return ( @@ -39,16 +42,16 @@ DatePickerCustomInput.propTypes = { } const searchTimeRanges = [ - 'Last hour', - 'Last 4 hours', - 'Last 24 hours', - 'Last 2 days', - 'Last 7 days', - 'Last 14 days', - 'Last 1 month', - 'Last 2 months', - 'Last 3 months', - 'Custom' + { id: 'Last hour', label: 'logs.timeRanges.lastHour' }, + { id: 'Last 4 hours', label: 'logs.timeRanges.last4Hours' }, + { id: 'Last 24 hours', label: 'logs.timeRanges.last24hours' }, + { id: 'Last 2 days', label: 'logs.timeRanges.last2Days' }, + { id: 'Last 7 days', label: 'logs.timeRanges.last7Days' }, + { id: 'Last 14 days', label: 'logs.timeRanges.last14Days' }, + { id: 'Last 1 month', label: 'logs.timeRanges.last1Month' }, + { id: 'Last 2 months', label: 'logs.timeRanges.last2Months' }, + { id: 'Last 3 months', label: 'logs.timeRanges.last3Months' }, + { id: 'Custom', label: 'logs.timeRanges.custom' } ] const getDateBefore = (unit, value) => { @@ -99,6 +102,7 @@ const subtractTime = (months, days, hours) => { } const Logs = () => { + const { t } = useTranslation() const colorTheme = useTheme() const customStyle = EditorView.baseTheme({ @@ -207,7 +211,7 @@ const Logs = () => { ) : ( - + {isLoading ? ( @@ -234,8 +238,8 @@ const Logs = () => { onChange={handleTimeSelectionChange} > {searchTimeRanges.map((range) => ( - - {range} + + {t(range.label)} ))} @@ -251,9 +255,9 @@ const Logs = () => { endDate={endDate} maxDate={endDate} showTimeSelect - timeFormat='HH:mm' + timeFormat={t('logs.formats.time')} timeIntervals={60} - dateFormat='yyyy MMMM d, h aa' + dateFormat={t('logs.formats.date')} customInput={} /> @@ -264,13 +268,13 @@ const Logs = () => { onChange={(date) => onEndDateSelected(date)} selectsEnd showTimeSelect - timeFormat='HH:mm' + timeFormat={t('logs.formats.time')} timeIntervals={60} startDate={startDate} endDate={endDate} minDate={startDate} maxDate={new Date()} - dateFormat='yyyy MMMM d, h aa' + dateFormat={t('logs.formats.date')} customInput={} /> @@ -301,7 +305,7 @@ const Logs = () => { alt='LogsEmptySVG' /> -
No Logs Yet
+
{t('logs.notFound')}
)} diff --git a/packages/ui/src/views/settings/index.jsx b/packages/ui/src/views/settings/index.jsx index 9aafd1a7367..8f59341bf39 100644 --- a/packages/ui/src/views/settings/index.jsx +++ b/packages/ui/src/views/settings/index.jsx @@ -18,9 +18,13 @@ import agentsettings from '@/menu-items/agentsettings' import customAssistantSettings from '@/menu-items/customassistant' import { useAuth } from '@/hooks/useAuth' +// i18n +import { useTranslation } from 'react-i18next' + // ==============================|| SETTINGS ||============================== // const Settings = ({ chatflow, isSettingsOpen, isCustomAssistant, anchorEl, isAgentCanvas, onSettingsItemClick, onUploadFile, onClose }) => { + const { t } = useTranslation() const theme = useTheme() const [settingsMenu, setSettingsMenu] = useState([]) const customization = useSelector((state) => state.customization) @@ -100,7 +104,7 @@ const Settings = ({ chatflow, isSettingsOpen, isCustomAssistant, anchorEl, isAge }} > {itemIcon} - {menu.title}} /> + {t(menu.title)}} />
) }) diff --git a/packages/ui/src/views/tools/HowToUseFunctionDialog.jsx b/packages/ui/src/views/tools/HowToUseFunctionDialog.jsx index a542c05a505..e154d188c0a 100644 --- a/packages/ui/src/views/tools/HowToUseFunctionDialog.jsx +++ b/packages/ui/src/views/tools/HowToUseFunctionDialog.jsx @@ -2,7 +2,11 @@ import { createPortal } from 'react-dom' import PropTypes from 'prop-types' import { Dialog, DialogContent, DialogTitle } from '@mui/material' +// i18n +import { useTranslation } from 'react-i18next' + const HowToUseFunctionDialog = ({ show, onCancel }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const component = show ? ( @@ -15,13 +19,13 @@ const HowToUseFunctionDialog = ({ show, onCancel }) => { aria-describedby='alert-dialog-description' > - How To Use Function + {t('tools.dialogs.howToUse.title')}
    -
  • You can use any libraries imported in Flowise
  • +
  • {t('tools.dialogs.howToUse.youCan')}
  • - You can use properties specified in Input Schema as variables with prefix $: + {t('tools.dialogs.howToUse.propUse')}
    • Property = userid @@ -32,7 +36,7 @@ const HowToUseFunctionDialog = ({ show, onCancel }) => {
  • - You can get default flow config: + {t('tools.dialogs.howToUse.deafultConfig')}
    • $flow.sessionId @@ -52,9 +56,9 @@ const HowToUseFunctionDialog = ({ show, onCancel }) => {
  • - You can get custom variables: {`$vars.`} + {t('tools.dialogs.howToUse.variables')} {`$vars.`}
  • -
  • Must return a string value at the end of function
  • +
  • {t('tools.dialogs.howToUse.return')}
diff --git a/packages/ui/src/views/tools/PasteJSONDialog.jsx b/packages/ui/src/views/tools/PasteJSONDialog.jsx index 02778aa1cdf..be4764ceb16 100644 --- a/packages/ui/src/views/tools/PasteJSONDialog.jsx +++ b/packages/ui/src/views/tools/PasteJSONDialog.jsx @@ -5,7 +5,11 @@ import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle } from ' import { StyledButton } from '@/ui-component/button/StyledButton' import { CodeEditor } from '@/ui-component/editor/CodeEditor' +// i18n +import { useTranslation } from 'react-i18next' + const PasteJSONDialog = ({ show, onCancel, onConfirm, customization }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const [jsonInput, setJsonInput] = useState('') const [error, setError] = useState('') @@ -24,7 +28,7 @@ const PasteJSONDialog = ({ show, onCancel, onConfirm, customization }) => { onConfirm(formattedData) setError('') } catch (err) { - setError('Invalid JSON format. Please check your input.') + setError(t('tools.error')) } } @@ -46,12 +50,12 @@ const PasteJSONDialog = ({ show, onCancel, onConfirm, customization }) => { const component = show ? ( - Paste JSON Schema + {t('tools.dialogs.jsonSchema.title')} { - + - Confirm + {t('common.actions.confirm')} diff --git a/packages/ui/src/views/tools/ToolDialog.jsx b/packages/ui/src/views/tools/ToolDialog.jsx index b73b15ec19a..970b2bb7c00 100644 --- a/packages/ui/src/views/tools/ToolDialog.jsx +++ b/packages/ui/src/views/tools/ToolDialog.jsx @@ -5,12 +5,25 @@ import { useDispatch, useSelector } from 'react-redux' import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from '@/store/actions' import { cloneDeep } from 'lodash' -import { Box, Button, Typography, Dialog, DialogActions, DialogContent, DialogTitle, Stack, OutlinedInput } from '@mui/material' +import { + IconButton, + Tooltip, + Box, + Button, + Typography, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Stack, + OutlinedInput +} from '@mui/material' import { StyledButton } from '@/ui-component/button/StyledButton' import { Grid } from '@/ui-component/grid/Grid' import { TooltipWithParser } from '@/ui-component/tooltip/TooltipWithParser' import { GridActionsCellItem } from '@mui/x-data-grid' import DeleteIcon from '@mui/icons-material/Delete' +import { Info } from '@mui/icons-material' import ConfirmDialog from '@/ui-component/dialog/ConfirmDialog' import { CodeEditor } from '@/ui-component/editor/CodeEditor' import HowToUseFunctionDialog from './HowToUseFunctionDialog' @@ -34,6 +47,9 @@ import useNotifier from '@/utils/useNotifier' import { generateRandomGradient, formatDataGridRows } from '@/utils/genericHelper' import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions' +// i18n +import { useTranslation, Trans } from 'react-i18next' + const exampleAPIFunc = `/* * You can use any libraries imported in Flowise * You can use properties specified in Input Schema as variables. Ex: Property = userid, Variable = $userid @@ -60,6 +76,7 @@ try { }` const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, setError }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const customization = useSelector((state) => state.customization) @@ -116,7 +133,7 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set const onSaveAsTemplate = () => { setExportAsTemplateDialogProps({ - title: 'Export As Template', + title: t('tools.dialogs.exportAsTemplate'), tool: { name: toolName, description: toolDesc, @@ -143,23 +160,28 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set const columns = useMemo( () => [ - { field: 'property', headerName: 'Property', editable: true, flex: 1 }, + { field: 'property', headerName: t('tools.columns.property'), editable: true, flex: 1 }, { field: 'type', - headerName: 'Type', + headerName: t('common.labels.type'), type: 'singleSelect', valueOptions: ['string', 'number', 'boolean', 'date'], editable: true, width: 120 }, - { field: 'description', headerName: 'Description', editable: true, flex: 1 }, - { field: 'required', headerName: 'Required', type: 'boolean', editable: true, width: 80 }, + { field: 'description', headerName: t('common.labels.description'), editable: true, flex: 1 }, + { field: 'required', headerName: t('tools.columns.required'), type: 'boolean', editable: true, width: 80 }, { field: 'actions', type: 'actions', width: 80, getActions: (params) => [ - } label='Delete' onClick={deleteItem(params.id)} /> + } + label={t('common.actions.delete')} + onClick={deleteItem(params.id)} + /> ] } ], @@ -258,9 +280,9 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set } } catch (error) { enqueueSnackbar({ - message: `Failed to export Tool: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('tools.messages.export.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -289,7 +311,7 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set const createResp = await toolsApi.createNewTool(obj) if (createResp.data) { enqueueSnackbar({ - message: 'New Tool added', + message: t('tools.messages.add.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -304,9 +326,9 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set } } catch (error) { enqueueSnackbar({ - message: `Failed to add new Tool: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('tools.messages.add.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -333,7 +355,7 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set }) if (saveResp.data) { enqueueSnackbar({ - message: 'Tool saved', + message: t('tools.messages.save.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -348,9 +370,9 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set } } catch (error) { enqueueSnackbar({ - message: `Failed to save Tool: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('tools.messages.save.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -368,10 +390,10 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set const deleteTool = async () => { const confirmPayload = { - title: `Delete Tool`, - description: `Delete tool ${toolName}?`, - confirmButtonName: 'Delete', - cancelButtonName: 'Cancel' + title: t('tools.dialogs.deleteTool.title'), + description: t('tools.dialogs.deleteTool.description', { name: toolName }), + confirmButtonName: t('common.actions.delete'), + cancelButtonName: t('common.actions.cancel') } const isConfirmed = await confirm(confirmPayload) @@ -380,7 +402,7 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set const delResp = await toolsApi.deleteTool(toolId) if (delResp.data) { enqueueSnackbar({ - message: 'Tool deleted', + message: t('tools.messages.delete.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -395,9 +417,9 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set } } catch (error) { enqueueSnackbar({ - message: `Failed to delete Tool: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('tools.messages.delete.error', { + mag: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -442,7 +464,7 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set startIcon={} color='secondary' > - Save As Template + {t('common.actions.saveAsTemplate')} exportTool()} startIcon={} > - Export + {t('common.actions.export')} )} @@ -462,17 +484,17 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set - Tool Name + {t('tools.inputs.toolName.title')}  * - + setToolName(e.target.value)} @@ -481,19 +503,17 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set - Tool description + {t('tools.inputs.toolDescription.title')}  * - + - Tool Icon Source + {t('tools.inputs.toolIconSource')} - Input Schema - + {t('tools.inputs.inputSchema.title')} + {dialogProps.type !== 'TEMPLATE' && ( )} @@ -538,8 +558,39 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set - Javascript Function - + {t('tools.inputs.jsFunction.title')} + {/* Throws a parser error if the TooltipWithParser component is used */} + + ), + c: , + code: + }} + /> + } + placement='right' + > + + + + {dialogProps.type !== 'TEMPLATE' && ( )} @@ -570,13 +621,13 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm, set {dialogProps.type === 'EDIT' && ( deleteTool()}> - Delete + {t('common.actions.delete')} )} {dialogProps.type === 'TEMPLATE' && ( - Use Template + {t('tools.actions.useTemplate')} )} diff --git a/packages/ui/src/views/tools/index.jsx b/packages/ui/src/views/tools/index.jsx index 10dff435140..d3690a29d7c 100644 --- a/packages/ui/src/views/tools/index.jsx +++ b/packages/ui/src/views/tools/index.jsx @@ -26,9 +26,13 @@ import { gridSpacing } from '@/store/constant' import { IconPlus, IconFileUpload, IconLayoutGrid, IconList } from '@tabler/icons-react' import ToolEmptySVG from '@/assets/images/tools_empty.svg' +// i18n +import { useTranslation } from 'react-i18next' + // ==============================|| TOOLS ||============================== // const Tools = () => { + const { t } = useTranslation() const theme = useTheme() const getAllToolsApi = useApi(toolsApi.getAllTools) const { error, setError } = useError() @@ -68,10 +72,10 @@ const Tools = () => { const onUploadFile = (file) => { try { const dialogProp = { - title: 'Add New Tool', + title: t('tools.dialogs.addNewTool'), type: 'IMPORT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Save', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.save'), data: JSON.parse(file) } setDialogProps(dialogProp) @@ -99,10 +103,10 @@ const Tools = () => { const addNew = () => { const dialogProp = { - title: 'Add New Tool', + title: t('tools.dialogs.addNewTool'), type: 'ADD', - cancelButtonName: 'Cancel', - confirmButtonName: 'Add' + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.add') } setDialogProps(dialogProp) setShowDialog(true) @@ -110,10 +114,10 @@ const Tools = () => { const edit = (selectedTool) => { const dialogProp = { - title: 'Edit Tool', + title: t('tools.dialogs.editTool'), type: 'EDIT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Save', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.save'), data: selectedTool } setDialogProps(dialogProp) @@ -161,9 +165,9 @@ const Tools = () => { { }} variant='contained' value='card' - title='Card View' + title={t('common.actions.cardView')} > @@ -193,7 +197,7 @@ const Tools = () => { }} variant='contained' value='list' - title='List View' + title={t('common.actions.listView')} > @@ -206,7 +210,7 @@ const Tools = () => { startIcon={} sx={{ borderRadius: 2, height: 40 }} > - Load + {t('common.actions.load')} { onChange={(e) => handleFileUpload(e)} /> - + { startIcon={} sx={{ borderRadius: 2, height: 40 }} > - Create + {t('tools.actions.create')} @@ -264,7 +268,7 @@ const Tools = () => { alt='ToolEmptySVG' /> -
No Tools Created Yet
+
{t('tools.notFound')}
)} diff --git a/packages/ui/src/views/users/EditUserDialog.jsx b/packages/ui/src/views/users/EditUserDialog.jsx index a40815ea1c8..01addb64de4 100644 --- a/packages/ui/src/views/users/EditUserDialog.jsx +++ b/packages/ui/src/views/users/EditUserDialog.jsx @@ -25,18 +25,22 @@ import useNotifier from '@/utils/useNotifier' import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions' import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from '@/store/actions' +// i18n +import { useTranslation } from 'react-i18next' + const statuses = [ { - label: 'Active', + label: 'users.statuses.active', name: 'active' }, { - label: 'Inactive', + label: 'users.statuses.inactive', name: 'inactive' } ] const EditUserDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const currentUser = useSelector((state) => state.auth.user) @@ -86,7 +90,7 @@ const EditUserDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) => const saveResp = await userApi.updateOrganizationUser(saveObj) if (saveResp.data) { enqueueSnackbar({ - message: 'User Details Updated', + message: t('users.messages.update.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -102,9 +106,9 @@ const EditUserDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) => } catch (error) { setError(err) enqueueSnackbar({ - message: `Failed to update User: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('users.messages.update.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -132,14 +136,15 @@ const EditUserDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =>
- {'Edit User'} + {t('users.dialogs.edit.title')}
- Email * + {t('common.labels.email')} +  *
@@ -157,7 +162,7 @@ const EditUserDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =>
- Name + {t('common.labels.name')}
@@ -175,7 +180,8 @@ const EditUserDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) =>
- Account Status * + {t('users.inputs.accountStatus')} +  *
@@ -183,14 +189,16 @@ const EditUserDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) => key={status} name='status' disabled={dialogProps?.data?.isOrgOwner} - options={statuses} + options={statuses.map((x) => { + return { name: x.name, label: t(x.label) } + })} onSelect={(newValue) => setStatus(newValue)} - value={status ?? 'choose an option'} + value={status ?? t('components.dropdown.chooseOption')} id='dropdown_status' /> {dialogProps?.data?.isOrgOwner && ( - Cannot change status of the organization owner! + {t('users.error')} )}
diff --git a/packages/ui/src/views/users/index.jsx b/packages/ui/src/views/users/index.jsx index ce6ad2e0331..be4818104a5 100644 --- a/packages/ui/src/views/users/index.jsx +++ b/packages/ui/src/views/users/index.jsx @@ -50,7 +50,11 @@ import users_emptySVG from '@/assets/images/users_empty.svg' import { useError } from '@/store/context/ErrorContext' import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from '@/store/actions' +// i18n +import { useTranslation } from 'react-i18next' + function ShowUserRow(props) { + const { t } = useTranslation() const customization = useSelector((state) => state.customization) const [open, setOpen] = useState(false) @@ -134,7 +138,7 @@ function ShowUserRow(props) { <> {' '}
- {' '} + {' '} )} @@ -155,12 +159,14 @@ function ShowUserRow(props) { {'INVITED' === props.row.status.toUpperCase() && } {'INACTIVE' === props.row.status.toUpperCase() && } - {!props.row.lastLogin ? 'Never' : moment(props.row.lastLogin).format('DD/MM/YYYY HH:mm')} + + {!props.row.lastLogin ? 'Never' : moment(props.row.lastLogin).format(t('common.formats.dateDayMonthYearTime24'))} + {props.row.status.toUpperCase() === 'INVITED' && ( props.onEditClick(props.row)} > @@ -174,7 +180,7 @@ function ShowUserRow(props) { ) : ( props.onDeleteClick(props.row.user)} > @@ -186,14 +192,14 @@ function ShowUserRow(props) { setOpen(false)} sx={{ minWidth: 320 }}> - Assigned Roles + {t('users.assignedRoles')} -
+
- Role - Workspace + {t('users.tables.role')} + {t('common.labels.workspace')} @@ -236,6 +242,7 @@ ShowUserRow.propTypes = { // ==============================|| Users ||============================== // const Users = () => { + const { t } = useTranslation() const theme = useTheme() const customization = useSelector((state) => state.customization) const dispatch = useDispatch() @@ -272,8 +279,8 @@ const Users = () => { const addNew = () => { const dialogProp = { type: 'ADD', - cancelButtonName: 'Cancel', - confirmButtonName: 'Send Invite', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('users.actions.sendInvite'), data: null } setInviteDialogProps(dialogProp) @@ -291,8 +298,8 @@ const Users = () => { const editInvite = (user) => { const dialogProp = { type: 'EDIT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Update Invite', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('users.actions.updateInvite'), data: user } setInviteDialogProps(dialogProp) @@ -302,8 +309,8 @@ const Users = () => { const editUser = (user) => { const dialogProp = { type: 'EDIT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Save', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.save'), data: user } setInviteDialogProps(dialogProp) @@ -312,10 +319,10 @@ const Users = () => { const deleteUser = async (user) => { const confirmPayload = { - title: `Delete`, - description: `Remove ${user.name ?? user.email} from organization?`, - confirmButtonName: 'Delete', - cancelButtonName: 'Cancel' + title: t('common.dialogs.delete'), + description: t('users.dialogs.delete.description', { name: user.name ?? user.email }), + confirmButtonName: t('common.actions.delete'), + cancelButtonName: t('common.actions.cancel') } const isConfirmed = await confirm(confirmPayload) @@ -325,7 +332,7 @@ const Users = () => { const deleteResp = await userApi.deleteOrganizationUser(currentUser.activeOrganizationId, user.id) if (deleteResp.data) { enqueueSnackbar({ - message: 'User removed from organization successfully', + message: t('users.messages.delete.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -340,9 +347,9 @@ const Users = () => { } } catch (error) { enqueueSnackbar({ - message: `Failed to delete User: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('users.messages.delete.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -400,7 +407,12 @@ const Users = () => { ) : ( - + { startIcon={} id='btn_createUser' > - Invite User + {t('users.actions.inviteUser')} {!isLoading && users.length === 0 ? ( @@ -421,7 +433,7 @@ const Users = () => { alt='users_emptySVG' /> -
No Users Yet
+
{t('users.notFound')}
) : ( <> @@ -443,10 +455,10 @@ const Users = () => { >   - Email/Name - Assigned Roles - Status - Last Login + {t('users.tables.emailName')} + {t('users.tables.assignedRoles')} + {t('common.labels.status')} + {t('users.tables.lastLogin')} diff --git a/packages/ui/src/views/variables/AddEditVariableDialog.jsx b/packages/ui/src/views/variables/AddEditVariableDialog.jsx index dd82c756061..1c6083bc536 100644 --- a/packages/ui/src/views/variables/AddEditVariableDialog.jsx +++ b/packages/ui/src/views/variables/AddEditVariableDialog.jsx @@ -26,20 +26,24 @@ import useNotifier from '@/utils/useNotifier' import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions' import { Dropdown } from '@/ui-component/dropdown/Dropdown' -const variableTypes = [ +// i18n +import { useTranslation } from 'react-i18next' + +const variableTypes = (t) => [ { - label: 'Static', + label: t('variables.types.static.title'), name: 'static', - description: 'Variable value will be read from the value entered below' + description: t('variables.types.static.description') }, { - label: 'Runtime', + label: t('variables.types.runtime.title'), name: 'runtime', - description: 'Variable value will be read from .env file' + description: t('variables.types.runtime.description') } ] const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm, setError }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const dispatch = useDispatch() @@ -97,7 +101,7 @@ const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm, setErro const createResp = await variablesApi.createVariable(obj) if (createResp.data) { enqueueSnackbar({ - message: 'New Variable added', + message: t('variables.messages.add.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -113,9 +117,9 @@ const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm, setErro } catch (err) { if (setError) setError(err) enqueueSnackbar({ - message: `Failed to add new Variable: ${ - typeof err.response.data === 'object' ? err.response.data.message : err.response.data - }`, + message: t('variables.messages.add.error', { + msg: typeof err.response.data === 'object' ? err.response.data.message : err.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -142,7 +146,7 @@ const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm, setErro const saveResp = await variablesApi.updateVariable(variable.id, saveObj) if (saveResp.data) { enqueueSnackbar({ - message: 'Variable saved', + message: t('variables.messages.save.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -158,9 +162,9 @@ const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm, setErro } catch (err) { if (setError) setError(err) enqueueSnackbar({ - message: `Failed to save Variable: ${ - typeof err.response.data === 'object' ? err.response.data.message : err.response.data - }`, + message: t('variables.messages.save.error', { + msg: typeof err.response.data === 'object' ? err.response.data.message : err.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -188,14 +192,15 @@ const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm, setErro
- {dialogProps.type === 'ADD' ? 'Add Variable' : 'Edit Variable'} + {t(dialogProps.type === 'ADD' ? 'variables.dialogs.add' : 'variables.dialogs.edit')}
- Variable Name * + {t('variables.inputs.variableName')} +  *
@@ -214,16 +219,17 @@ const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm, setErro
- Type * + {t('common.labels.type')} +  *
setVariableType(newValue)} - value={variableType ?? 'choose an option'} + value={variableType ?? t('components.dropdown.chooseOption')} id='dropdown_variableType' />
@@ -231,7 +237,8 @@ const AddEditVariableDialog = ({ show, dialogProps, onCancel, onConfirm, setErro
- Value * + {t('variables.inputs.value')} +  *
diff --git a/packages/ui/src/views/variables/HowToUseVariablesDialog.jsx b/packages/ui/src/views/variables/HowToUseVariablesDialog.jsx index 61a59acbe84..2deabc75e7b 100644 --- a/packages/ui/src/views/variables/HowToUseVariablesDialog.jsx +++ b/packages/ui/src/views/variables/HowToUseVariablesDialog.jsx @@ -3,6 +3,9 @@ import PropTypes from 'prop-types' import { Dialog, DialogContent, DialogTitle } from '@mui/material' import { CodeEditor } from '@/ui-component/editor/CodeEditor' +// i18n +import { useTranslation, Trans } from 'react-i18next' + const overrideConfig = `{ overrideConfig: { vars: { @@ -12,6 +15,7 @@ const overrideConfig = `{ }` const HowToUseVariablesDialog = ({ show, onCancel }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const component = show ? ( @@ -24,12 +28,10 @@ const HowToUseVariablesDialog = ({ show, onCancel }) => { aria-describedby='alert-dialog-description' > - How To Use Variables + {t('variables.help.title')} -

- Variables can be used in Custom Tool, Custom Function, Custom Loader, If Else Function with the $ prefix. -

+

{t('variables.help.ifElse')}

`} @@ -38,9 +40,7 @@ const HowToUseVariablesDialog = ({ show, onCancel }) => { lang={'js'} basicSetup={{ highlightActiveLine: false, highlightActiveLineGutter: false }} /> -

- Variables can also be used in Text Field parameter of any node. For example, in System Message of Agent: -

+

{t('variables.help.sysmessage')}

{ lang={'js'} basicSetup={{ highlightActiveLine: false, highlightActiveLineGutter: false }} /> -

- If variable type is Static, the value will be retrieved as it is. If variable type is Runtime, the value will be - retrieved from .env file. -

-

- You can also override variable values in API overrideConfig using vars: -

+

{t('variables.help.static')}

+

{t('variables.help.override')}

{ basicSetup={{ highlightActiveLine: false, highlightActiveLineGutter: false }} />

- Read more from{' '} - - docs - + + ) + }} + />

diff --git a/packages/ui/src/views/variables/index.jsx b/packages/ui/src/views/variables/index.jsx index 01e1d3225e4..d48f8790e40 100644 --- a/packages/ui/src/views/variables/index.jsx +++ b/packages/ui/src/views/variables/index.jsx @@ -52,6 +52,9 @@ import VariablesEmptySVG from '@/assets/images/variables_empty.svg' // const import { useError } from '@/store/context/ErrorContext' +// i18n +import { useTranslation } from 'react-i18next' + const StyledTableCell = styled(TableCell)(({ theme }) => ({ borderColor: theme.palette.grey[900] + 25, @@ -74,6 +77,7 @@ const StyledTableRow = styled(TableRow)(() => ({ // ==============================|| Credentials ||============================== // const Variables = () => { + const { t } = useTranslation() const theme = useTheme() const customization = useSelector((state) => state.customization) const dispatch = useDispatch() @@ -123,8 +127,8 @@ const Variables = () => { const addNew = () => { const dialogProp = { type: 'ADD', - cancelButtonName: 'Cancel', - confirmButtonName: 'Add', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.add'), customBtnId: 'btn_confirmAddingVariable', data: {} } @@ -135,8 +139,8 @@ const Variables = () => { const edit = (variable) => { const dialogProp = { type: 'EDIT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Save', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.save'), data: variable } setVariableDialogProps(dialogProp) @@ -145,10 +149,10 @@ const Variables = () => { const deleteVariable = async (variable) => { const confirmPayload = { - title: `Delete`, - description: `Delete variable ${variable.name}?`, - confirmButtonName: 'Delete', - cancelButtonName: 'Cancel' + title: t('common.dialogs.delete'), + description: t('variables.dialogs.delete.description', { name: variable.name }), + confirmButtonName: t('common.actions.delete'), + cancelButtonName: t('common.actions.cancel') } const isConfirmed = await confirm(confirmPayload) @@ -157,7 +161,7 @@ const Variables = () => { const deleteResp = await variablesApi.deleteVariable(variable.id) if (deleteResp.data) { enqueueSnackbar({ - message: 'Variable deleted', + message: t('variables.messages.delete.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -172,9 +176,9 @@ const Variables = () => { } } catch (error) { enqueueSnackbar({ - message: `Failed to delete Variable: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('variables.messages.delete.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -222,12 +226,12 @@ const Variables = () => { { startIcon={} id='btn_createVariable' > - Add Variable + {t('variables.actions.addVariable')} {!isLoading && variables.length === 0 ? ( @@ -249,7 +253,7 @@ const Variables = () => { alt='VariablesEmptySVG' />
-
No Variables Yet
+
{t('variables.notFound')}
) : ( <> @@ -257,7 +261,7 @@ const Variables = () => { sx={{ border: 1, borderColor: theme.palette.grey[900] + 25, borderRadius: 2 }} component={Paper} > -
+
{ }} > - Name - Value - Type - Last Updated - Created + {t('common.labels.name')} + {t('variables.tables.value')} + {t('common.labels.type')} + {t('variables.tables.lastUpdated')} + {t('variables.tables.created')} @@ -382,14 +386,22 @@ const Variables = () => { /> - {moment(variable.updatedDate).format('MMMM Do, YYYY HH:mm:ss')} + {moment(variable.updatedDate).format( + t('common.formats.dateMonthDayYearTime24Long') + )} - {moment(variable.createdDate).format('MMMM Do, YYYY HH:mm:ss')} + {moment(variable.createdDate).format( + t('common.formats.dateMonthDayYearTime24Long') + )} - edit(variable)}> + edit(variable)} + > @@ -397,7 +409,7 @@ const Variables = () => { deleteVariable(variable)} > diff --git a/packages/ui/src/views/vectorstore/UpsertHistoryDialog.jsx b/packages/ui/src/views/vectorstore/UpsertHistoryDialog.jsx index 9ecbe5c33bd..a821cb71bdd 100644 --- a/packages/ui/src/views/vectorstore/UpsertHistoryDialog.jsx +++ b/packages/ui/src/views/vectorstore/UpsertHistoryDialog.jsx @@ -49,6 +49,9 @@ import { baseURL } from '@/store/constant' import useNotifier from '@/utils/useNotifier' import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from '@/store/actions' +// i18n +import { useTranslation } from 'react-i18next' + const DatePickerCustomInput = forwardRef(function DatePickerCustomInput({ value, onClick }, ref) { return ( @@ -63,6 +66,7 @@ DatePickerCustomInput.propTypes = { } function UpsertHistoryRow(props) { + const { t } = useTranslation() const [open, setOpen] = useState(false) const [nodeConfigExpanded, setNodeConfigExpanded] = useState({}) @@ -87,13 +91,13 @@ function UpsertHistoryRow(props) { }} /> - {moment(props.upsertHistory.date).format('MMMM Do YYYY, h:mm:ss a')} + {moment(props.upsertHistory.date).format(t('common.formats.dateMonthDayYearTime12Seconds'))} {props.upsertHistory.result?.numAdded ?? '0'} {props.upsertHistory.result?.numUpdated ?? '0'} {props.upsertHistory.result?.numSkipped ?? '0'} {props.upsertHistory.result?.numDeleted ?? '0'} - setOpen(!open)}> + setOpen(!open)}> {open ? : } @@ -186,6 +190,7 @@ UpsertHistoryRow.propTypes = { } const UpsertHistoryDialog = ({ show, dialogProps, onCancel }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const dispatch = useDispatch() const customization = useSelector((state) => state.customization) @@ -250,7 +255,7 @@ const UpsertHistoryDialog = ({ show, dialogProps, onCancel }) => { try { await vectorstoreApi.deleteUpsertHistory(selected) enqueueSnackbar({ - message: 'Succesfully deleted upsert history', + message: t('vectorStore.messages.remove.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -265,9 +270,9 @@ const UpsertHistoryDialog = ({ show, dialogProps, onCancel }) => { setSelected([]) } catch (error) { enqueueSnackbar({ - message: `Failed to delete Upsert History: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('vectorStore.messages.remove.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -327,7 +332,7 @@ const UpsertHistoryDialog = ({ show, dialogProps, onCancel }) => { <>
- From Date + {t('vectorStore.inputs.fromDate')} onStartDateSelected(date)} @@ -338,7 +343,7 @@ const UpsertHistoryDialog = ({ show, dialogProps, onCancel }) => { />
- To Date + {t('vectorStore.inputs.toDate')} onEndDateSelected(date)} @@ -359,7 +364,7 @@ const UpsertHistoryDialog = ({ show, dialogProps, onCancel }) => { color='error' startIcon={} > - Delete {selected.length} {selected.length === 1 ? 'row' : 'rows'} + {t('vectorStore.actions.delete', { count: selected.length })} )} {chatflowUpsertHistory.length <= 0 && ( @@ -371,7 +376,7 @@ const UpsertHistoryDialog = ({ show, dialogProps, onCancel }) => { alt='HistoryEmptySVG' /> -
No Upsert History Yet
+
{t('vectorStore.notFound')}
)} {chatflowUpsertHistory.length > 0 && ( @@ -385,46 +390,40 @@ const UpsertHistoryDialog = ({ show, dialogProps, onCancel }) => { checked={selected.length === chatflowUpsertHistory.length} onChange={onSelectAllClick} inputProps={{ - 'aria-label': 'select all' + 'aria-label': t('common.actions.selectAll') }} /> - Date + {t('vectorStore.tables.date')} - Added{' '} + {t('vectorStore.tables.added.title')}{' '} - Updated{' '} + {t('vectorStore.tables.updated.title')}{' '} - Skipped{' '} + {t('vectorStore.tables.skipped.title')}{' '} - Deleted{' '} + {t('vectorStore.tables.deleted.title')}{' '} - Details + {t('vectorStore.tables.details')} @@ -445,7 +444,7 @@ const UpsertHistoryDialog = ({ show, dialogProps, onCancel }) => { - + ) : null diff --git a/packages/ui/src/views/vectorstore/UpsertResultDialog.jsx b/packages/ui/src/views/vectorstore/UpsertResultDialog.jsx index e2b4a37e302..766003fb7ed 100644 --- a/packages/ui/src/views/vectorstore/UpsertResultDialog.jsx +++ b/packages/ui/src/views/vectorstore/UpsertResultDialog.jsx @@ -8,7 +8,11 @@ import StatsCard from '@/ui-component/cards/StatsCard' import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions' import { IconZoomScan } from '@tabler/icons-react' +// i18n +import { useTranslation } from 'react-i18next' + const UpsertResultDialog = ({ show, dialogProps, onCancel, onGoToRetrievalQuery }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const dispatch = useDispatch() const customization = useSelector((state) => state.customization) @@ -29,7 +33,7 @@ const UpsertResultDialog = ({ show, dialogProps, onCancel, onGoToRetrievalQuery aria-describedby='upsert-result-dialog-description' > - Upsert Record + {t('vectorStore.dialogs.upsertRecord')} <> @@ -46,7 +50,9 @@ const UpsertResultDialog = ({ show, dialogProps, onCancel, onGoToRetrievalQuery
{dialogProps.addedDocs && dialogProps.addedDocs.length > 0 && ( - {dialogProps.numAdded} Added Documents + + {t('vectorStore.addedDocuments', { count: dialogProps.numAdded })} + )} {dialogProps.addedDocs && dialogProps.addedDocs.length > 0 && @@ -94,14 +100,14 @@ const UpsertResultDialog = ({ show, dialogProps, onCancel, onGoToRetrievalQuery startIcon={} onClick={onGoToRetrievalQuery} > - Test Retrieval + {t('vectorStore.actions.testRetrieval')}
)} - {!dialogProps.goToRetrievalQuery && } + {!dialogProps.goToRetrievalQuery && } ) : null diff --git a/packages/ui/src/views/vectorstore/VectorStoreDialog.jsx b/packages/ui/src/views/vectorstore/VectorStoreDialog.jsx index 83277ac5002..2471c5e2a08 100644 --- a/packages/ui/src/views/vectorstore/VectorStoreDialog.jsx +++ b/packages/ui/src/views/vectorstore/VectorStoreDialog.jsx @@ -50,6 +50,9 @@ import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions' import { baseURL } from '@/store/constant' import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from '@/store/actions' +// i18n +import { useTranslation, Trans } from 'react-i18next' + function TabPanel(props) { const { children, value, index, ...other } = props return ( @@ -79,6 +82,7 @@ function a11yProps(index) { } const VectorStoreDialog = ({ show, dialogProps, onCancel, onIndexResult }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const { reactFlowInstance } = useContext(flowContext) const dispatch = useDispatch() @@ -308,7 +312,7 @@ formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")` try { const res = await vectorstoreApi.upsertVectorStore(dialogProps.chatflowid, { stopNodeId: vectorStoreNode.data.id }) enqueueSnackbar({ - message: 'Succesfully upserted vector store. You can start chatting now!', + message: t('vectorStore.messages.upsertClicked.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -491,7 +495,7 @@ formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")` onCheckBoxChanged(data.vectorNode.data.id)} /> @@ -571,18 +575,21 @@ formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")` fontWeight: 500 }} > - { - 'For security reason, override config is disabled by default. You can change this by going into Chatflow Configuration -> Security tab, and enable the property you want to override.' - } -  Refer{' '} - - here - {' '} - for more details + {t('vectorStore.note')} +   + + ) + }} + /> @@ -606,8 +613,7 @@ formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")` > - You can also specify multiple values for a config parameter by - specifying the node id + {t('vectorStore.specify')}
@@ -638,10 +644,10 @@ formData.append("openAIApiKey[openAIEmbeddings_0]", "sk-my-openai-2nd-key")` fullWidth variant='contained' color='teal' - title='Upsert' + title={t('vectorStore.actions.upsert')} onClick={() => onUpsertClicked(data.vectorNode)} > - Upsert + {t('vectorStore.actions.upsert')} )}
diff --git a/packages/ui/src/views/vectorstore/VectorStorePopUp.jsx b/packages/ui/src/views/vectorstore/VectorStorePopUp.jsx index e7fe929fa30..3f2ee934c40 100644 --- a/packages/ui/src/views/vectorstore/VectorStorePopUp.jsx +++ b/packages/ui/src/views/vectorstore/VectorStorePopUp.jsx @@ -8,7 +8,11 @@ import { StyledFab } from '@/ui-component/button/StyledFab' import VectorStoreDialog from './VectorStoreDialog' import UpsertResultDialog from './UpsertResultDialog' +// i18n +import { useTranslation } from 'react-i18next' + const VectorStorePopUp = ({ chatflowid }) => { + const { t } = useTranslation() const [open, setOpen] = useState(false) const [showExpandDialog, setShowExpandDialog] = useState(false) const [expandDialogProps, setExpandDialogProps] = useState({}) @@ -22,7 +26,7 @@ const VectorStorePopUp = ({ chatflowid }) => { setOpen((prevopen) => !prevopen) const props = { open: true, - title: 'Upsert Vector Store', + title: t('vectorStore.dialogs.upsertVectorStore'), chatflowid } setExpandDialogProps(props) @@ -45,8 +49,8 @@ const VectorStorePopUp = ({ chatflowid }) => { ref={anchorRef} size='small' color='teal' - aria-label='upsert' - title='Upsert Vector Database' + aria-label={t('vectorStore.actions.upsert')} + title={t('vectorStore.upsertVectorDatabase')} onClick={handleToggle} > {open ? : } diff --git a/packages/ui/src/views/workspace/AddEditWorkspaceDialog.jsx b/packages/ui/src/views/workspace/AddEditWorkspaceDialog.jsx index 9e49e9c473b..55bc8aa9d27 100644 --- a/packages/ui/src/views/workspace/AddEditWorkspaceDialog.jsx +++ b/packages/ui/src/views/workspace/AddEditWorkspaceDialog.jsx @@ -31,7 +31,11 @@ import { SHOW_CANVAS_DIALOG } from '@/store/actions' +// i18n +import { useTranslation } from 'react-i18next' + const AddEditWorkspaceDialog = ({ show, dialogProps, onCancel, onConfirm }) => { + const { t } = useTranslation() const portalElement = document.getElementById('portal') const dispatch = useDispatch() @@ -79,7 +83,7 @@ const AddEditWorkspaceDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const addNewWorkspace = async () => { if (workspaceName === 'Default Workspace' || workspaceName === 'Personal Workspace') { enqueueSnackbar({ - message: 'Workspace name cannot be Default Workspace or Personal Workspace - this is a reserved name', + message: t('workspace.messages.add.errors.reserved'), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -104,7 +108,7 @@ const AddEditWorkspaceDialog = ({ show, dialogProps, onCancel, onConfirm }) => { const createResp = await workspaceApi.createWorkspace(obj) if (createResp.data) { enqueueSnackbar({ - message: 'New Workspace added', + message: t('workspace.messages.add.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -119,9 +123,9 @@ const AddEditWorkspaceDialog = ({ show, dialogProps, onCancel, onConfirm }) => { } } catch (error) { enqueueSnackbar({ - message: `Failed to add new Workspace: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('workspace.messages.add.errors.failed', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -150,7 +154,7 @@ const AddEditWorkspaceDialog = ({ show, dialogProps, onCancel, onConfirm }) => { if (saveResp.data) { store.dispatch(workspaceNameUpdated(saveResp.data)) enqueueSnackbar({ - message: 'Workspace saved', + message: t('workspace.messages.save.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -165,9 +169,9 @@ const AddEditWorkspaceDialog = ({ show, dialogProps, onCancel, onConfirm }) => { } } catch (error) { enqueueSnackbar({ - message: `Failed to save Workspace: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('workspace.messages.save.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -195,14 +199,15 @@ const AddEditWorkspaceDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
- {dialogProps.type === 'ADD' ? 'Add Workspace' : 'Edit Workspace'} + {t(dialogProps.type === 'ADD' ? 'workspace.dialogs.add' : 'workspace.dialogs.edit')}
- Name * + {t('common.labels.name')} +  *
@@ -218,7 +223,7 @@ const AddEditWorkspaceDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
- Description + {t('common.labels.description')}
{ + const { t } = useTranslation() const portalElement = document.getElementById('portal') const currentUser = useSelector((state) => state.auth.user) @@ -118,7 +122,7 @@ const EditWorkspaceUserRoleDialog = ({ show, dialogProps, onCancel, onConfirm }) const saveResp = await workspaceApi.updateWorkspaceUserRole(saveObj) if (saveResp.data) { enqueueSnackbar({ - message: 'WorkspaceUser Details Updated', + message: t('workspace.messages.update.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -133,9 +137,9 @@ const EditWorkspaceUserRoleDialog = ({ show, dialogProps, onCancel, onConfirm }) } } catch (error) { enqueueSnackbar({ - message: `Failed to update WorkspaceUser: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('workspace.messages.update.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -166,14 +170,15 @@ const EditWorkspaceUserRoleDialog = ({ show, dialogProps, onCancel, onConfirm })
- {'Change Workspace Role - '} {userEmail || ''} {user.name ? `(${user.name})` : ''} + {t('workspace.change', { email: userEmail || '', name: user.name ? `(${user.name})` : '' })}
- New Role to Assign * + {t('workspace.inputs.assign.title')} +  *
@@ -183,7 +188,9 @@ const EditWorkspaceUserRoleDialog = ({ show, dialogProps, onCancel, onConfirm }) onChange={handleRoleChange} getOptionLabel={(option) => option.label || ''} options={availableRoles} - renderInput={(params) => } + renderInput={(params) => ( + + )} value={selectedRole} PopperComponent={StyledPopper} /> diff --git a/packages/ui/src/views/workspace/WorkspaceUsers.jsx b/packages/ui/src/views/workspace/WorkspaceUsers.jsx index e1024ca8cd8..34ae702a323 100644 --- a/packages/ui/src/views/workspace/WorkspaceUsers.jsx +++ b/packages/ui/src/views/workspace/WorkspaceUsers.jsx @@ -48,7 +48,11 @@ import { IconEdit, IconX, IconUnlink, IconUserPlus } from '@tabler/icons-react' import { useError } from '@/store/context/ErrorContext' import { closeSnackbar as closeSnackbarAction, enqueueSnackbar as enqueueSnackbarAction } from '@/store/actions' +// i18n +import { useTranslation } from 'react-i18next' + const WorkspaceDetails = () => { + const { t } = useTranslation() const theme = useTheme() const customization = useSelector((state) => state.customization) const currentUser = useSelector((state) => state.auth.user) @@ -120,8 +124,8 @@ const WorkspaceDetails = () => { const addUser = () => { const dialogProp = { type: 'ADD', - cancelButtonName: 'Cancel', - confirmButtonName: 'Send Invite', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('workspace.actions.sendInvite'), data: workspace } setDialogProps(dialogProp) @@ -139,8 +143,8 @@ const WorkspaceDetails = () => { const editInvite = (user) => { const dialogProp = { type: 'EDIT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Update Invite', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('workspace.actions.updateInvite'), data: { ...user, isWorkspaceUser: true @@ -164,8 +168,8 @@ const WorkspaceDetails = () => { } const dialogProp = { type: 'EDIT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Update Role', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('workspace.actions.updateRole'), data: userObj } setWorkspaceUserRoleDialogProps(dialogProp) @@ -176,10 +180,10 @@ const WorkspaceDetails = () => { const userList = usersSelected.map((user) => (user.name ? `${user.name} (${user.email})` : user.email)).join(', ') const confirmPayload = { - title: `Remove Users`, - description: `Remove the following users from the workspace?\n${userList}`, - confirmButtonName: 'Remove', - cancelButtonName: 'Cancel' + title: t('workspace.dialogs.remove.title'), + description: t('workspace.dialogs.remove.description', { users: userList }), + confirmButtonName: t('workspace.actions.remove'), + cancelButtonName: t('common.actions.cancel') } const orgOwner = workspaceUsers.find( @@ -187,7 +191,7 @@ const WorkspaceDetails = () => { ) if (orgOwner) { enqueueSnackbar({ - message: `Organization owner cannot be removed from workspace.`, + message: t('workspace.messages.remove.errors.failed'), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -209,7 +213,7 @@ const WorkspaceDetails = () => { await Promise.all(deletePromises) enqueueSnackbar({ - message: `${usersSelected.length} User(s) removed from workspace.`, + message: t('workspace.messages.remove.success', { count: usersSelected.length }), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -231,9 +235,9 @@ const WorkspaceDetails = () => { onConfirm() } catch (error) { enqueueSnackbar({ - message: `Failed to unlink users: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('workspace.messages.remove.errors.link', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -316,9 +320,9 @@ const WorkspaceDetails = () => { onBack={() => window.history.back()} search={workspaceUsers.length > 0} onSearchChange={onSearchChange} - searchPlaceholder={'Search Users'} - title={(workspace?.name || '') + ': Workspace Users'} - description={'Manage workspace users and permissions.'} + searchPlaceholder={t('workspace.dialogs.workspaceUsers.searchPlaceholder')} + title={t('workspace.dialogs.workspaceUsers.title', { name: workspace?.name || '' })} + description={t('workspace.dialogs.workspaceUsers.description')} > {workspaceUsers.length > 0 && ( <> @@ -331,7 +335,7 @@ const WorkspaceDetails = () => { color='error' startIcon={} > - Remove Users + {t('workspace.actions.removeUsers')} { onClick={addUser} startIcon={} > - Add User + {t('workspace.actions.addUser')} )} @@ -354,7 +358,7 @@ const WorkspaceDetails = () => { alt='empty_datasetSVG' />
-
No Assigned Users Yet
+
{t('workspace.dialogs.workspaceUsers.notFound')}
{ startIcon={} onClick={addUser} > - Add User + {t('workspace.actions.addUser')} ) : ( @@ -387,14 +391,14 @@ const WorkspaceDetails = () => { checked={usersSelected.length === (workspaceUsers || []).length - 1} onChange={onUsersSelectAllClick} inputProps={{ - 'aria-label': 'select all' + 'aria-label': t('common.actions.selectAll') }} />
- Email/Name - Role - Status - Last Login + {t('workspace.tables.emailName')} + {t('workspace.tables.role')} + {t('common.labels.status')} + {t('workspace.tables.lastLogin')}
@@ -473,7 +477,7 @@ const WorkspaceDetails = () => { {item.isOrgOwner ? ( - + ) : ( item.role.name )} @@ -498,12 +502,14 @@ const WorkspaceDetails = () => { {!item.lastLogin ? 'Never' - : moment(item.lastLogin).format('DD/MM/YYYY HH:mm')} + : moment(item.lastLogin).format( + t('common.formats.dateDayMonthYearTime24') + )} {!item.isOrgOwner && item.status.toUpperCase() === 'INVITED' && ( onEditClick(item)} > @@ -512,7 +518,7 @@ const WorkspaceDetails = () => { )} {!item.isOrgOwner && item.status.toUpperCase() === 'ACTIVE' && ( onEditClick(item)} > diff --git a/packages/ui/src/views/workspace/index.jsx b/packages/ui/src/views/workspace/index.jsx index 3c12764dda3..6dc86170a1b 100644 --- a/packages/ui/src/views/workspace/index.jsx +++ b/packages/ui/src/views/workspace/index.jsx @@ -59,7 +59,11 @@ import { useError } from '@/store/context/ErrorContext' import { workspaceSwitchSuccess } from '@/store/reducers/authSlice' import { Link } from 'react-router-dom' +// i18n +import { useTranslation } from 'react-i18next' + function ShowWorkspaceRow(props) { + const { t } = useTranslation() const customization = useSelector((state) => state.customization) const currentUser = useSelector((state) => state.auth.user) const [open, setOpen] = useState(false) @@ -116,7 +120,7 @@ function ShowWorkspaceRow(props) { {props.workspace.userCount}{' '} {props.workspace.userCount > 0 && ( handleViewWorkspaceUsers(props.workspace.id)} @@ -125,12 +129,14 @@ function ShowWorkspaceRow(props) { )} - {moment(props.workspace.updatedDate).format('MMMM Do YYYY, hh:mm A')} + + {moment(props.workspace.updatedDate).format(t('common.formats.dateMonthDayYearTime12Short'))} + {props.workspace.name !== 'Default Workspace' && ( props.onEditClick(props.workspace)} > @@ -138,19 +144,24 @@ function ShowWorkspaceRow(props) { )} - + {props.workspace.name !== 'Default Workspace' && (props.workspace.userCount > 1 || props.workspace.isOrgDefault === true ? ( - props.onDeleteClick(props.workspace)}> + props.onDeleteClick(props.workspace)} + > ) : ( props.onDeleteClick(props.workspace)} > @@ -162,14 +173,14 @@ function ShowWorkspaceRow(props) { setOpen(false)} sx={{ minWidth: 320 }}> - Users + {t('workspace.users')} -
+
- User - Role + {t('common.labels.user')} + {t('workspace.tables.role')} @@ -189,9 +200,9 @@ function ShowWorkspaceRow(props) { {item.user.name || item.user.email} {item.isOrgOwner ? ( - + ) : item.role.name === 'personal workspace' ? ( - + ) : ( item.role.name )} @@ -220,6 +231,7 @@ ShowWorkspaceRow.propTypes = { // ==============================|| Workspaces ||============================== // const Workspaces = () => { + const { t } = useTranslation() const navigate = useNavigate() const theme = useTheme() const { confirm } = useConfirm() @@ -254,8 +266,8 @@ const Workspaces = () => { const addNew = () => { const dialogProp = { type: 'ADD', - cancelButtonName: 'Cancel', - confirmButtonName: 'Add', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.add'), data: {} } setWorkspaceDialogProps(dialogProp) @@ -265,8 +277,8 @@ const Workspaces = () => { const edit = (workspace) => { const dialogProp = { type: 'EDIT', - cancelButtonName: 'Cancel', - confirmButtonName: 'Save', + cancelButtonName: t('common.actions.cancel'), + confirmButtonName: t('common.actions.save'), data: workspace } setWorkspaceDialogProps(dialogProp) @@ -275,10 +287,10 @@ const Workspaces = () => { const deleteWorkspace = async (workspace) => { const confirmPayload = { - title: `Delete Workspace ${workspace.name}`, - description: `This is irreversible and will remove all associated data inside the workspace. Are you sure you want to delete?`, - confirmButtonName: 'Delete', - cancelButtonName: 'Cancel' + title: t('workspace.dialogs.delete.title', { name: workspace.name }), + description: t('workspace.dialogs.delete.description'), + confirmButtonName: t('common.actions.delete'), + cancelButtonName: t('common.actions.cancel') } const isConfirmed = await confirm(confirmPayload) @@ -289,7 +301,7 @@ const Workspaces = () => { const deleteResp = await workspaceApi.deleteWorkspace(deleteWorkspaceId) if (deleteResp.data) { enqueueSnackbar({ - message: 'Workspace deleted', + message: t('workspace.messages.delete.success'), options: { key: new Date().getTime() + Math.random(), variant: 'success', @@ -305,9 +317,9 @@ const Workspaces = () => { } catch (error) { console.error('Failed to delete workspace:', error) enqueueSnackbar({ - message: `Failed to delete workspace: ${ - typeof error.response.data === 'object' ? error.response.data.message : error.response.data - }`, + message: t('workspace.messages.delete.error', { + msg: typeof error.response.data === 'object' ? error.response.data.message : error.response.data + }), options: { key: new Date().getTime() + Math.random(), variant: 'error', @@ -407,8 +419,8 @@ const Workspaces = () => { isEditButton={false} onSearchChange={onSearchChange} search={true} - title='Workspaces' - searchPlaceholder='Search Workspaces' + title={t('workspace.title')} + searchPlaceholder={t('workspace.searchPlaceholder')} > { onClick={addNew} startIcon={} > - Add New + {t('common.actions.addNew')} {!isLoading && workspaces.length <= 0 ? ( @@ -429,7 +441,7 @@ const Workspaces = () => { alt='workspaces_emptySVG' /> -
No Workspaces Yet
+
{t('workspace.notFound')}
) : ( { }} > - Name - Description - Users - Last Updated + {t('common.labels.name')} + {t('common.labels.description')} + {t('workspace.tables.user_other')} + {t('workspace.tables.lastUpdated')} @@ -526,7 +538,7 @@ const Workspaces = () => { - Switching workspace... + {t('workspace.loading')} @@ -536,7 +548,7 @@ const Workspaces = () => { - Deleting workspace... + {t('workspace.deleting')} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b1397be3198..3a19b3c520c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1297,6 +1297,15 @@ importers: html-react-parser: specifier: ^3.0.4 version: 3.0.16(react@18.2.0) + i18next: + specifier: ^26.0.4 + version: 26.0.4(typescript@5.5.2) + i18next-browser-languagedetector: + specifier: ^8.2.1 + version: 8.2.1 + i18next-http-backend: + specifier: ^3.0.4 + version: 3.0.4(encoding@0.1.13) lodash: specifier: ^4.17.21 version: 4.17.21 @@ -1330,6 +1339,9 @@ importers: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + react-i18next: + specifier: ^17.0.2 + version: 17.0.2(i18next@26.0.4(typescript@5.5.2))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.5.2) react-markdown: specifier: ^8.0.6 version: 8.0.7(@types/react@18.2.65)(react@18.2.0) @@ -3540,6 +3552,10 @@ packages: resolution: {integrity: sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + '@babel/template@7.25.9': resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} engines: {node: '>=6.9.0'} @@ -4429,79 +4445,67 @@ packages: resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-arm@1.0.5': resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-s390x@1.0.4': resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-x64@1.0.4': resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.0.4': resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.0.4': resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-linux-arm64@0.33.5': resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-linux-arm@0.33.5': resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-linux-s390x@0.33.5': resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-linux-x64@0.33.5': resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-linuxmusl-arm64@0.33.5': resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-linuxmusl-x64@0.33.5': resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-wasm32@0.33.5': resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} @@ -6307,35 +6311,30 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@napi-rs/canvas-linux-arm64-musl@0.1.73': resolution: {integrity: sha512-lX0z2bNmnk1PGZ+0a9OZwI2lPPvWjRYzPqvEitXX7lspyLFrOzh2kcQiLL7bhyODN23QvfriqwYqp5GreSzVvA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@napi-rs/canvas-linux-riscv64-gnu@0.1.73': resolution: {integrity: sha512-QDQgMElwxAoADsSR3UYvdTTQk5XOyD9J5kq15Z8XpGwpZOZsSE0zZ/X1JaOtS2x+HEZL6z1S6MF/1uhZFZb5ig==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] - libc: [glibc] '@napi-rs/canvas-linux-x64-gnu@0.1.73': resolution: {integrity: sha512-wbzLJrTalQrpyrU1YRrO6w6pdr5vcebbJa+Aut5QfTaW9eEmMb1WFG6l1V+cCa5LdHmRr8bsvl0nJDU/IYDsmw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@napi-rs/canvas-linux-x64-musl@0.1.73': resolution: {integrity: sha512-xbfhYrUufoTAKvsEx2ZUN4jvACabIF0h1F5Ik1Rk4e/kQq6c+Dwa5QF0bGrfLhceLpzHT0pCMGMDeQKQrcUIyA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@napi-rs/canvas-win32-x64-msvc@0.1.73': resolution: {integrity: sha512-YQmHXBufFBdWqhx+ympeTPkMfs3RNxaOgWm59vyjpsub7Us07BwCcmu1N5kildhO8Fm0syoI2kHnzGkJBLSvsg==} @@ -7200,67 +7199,56 @@ packages: resolution: {integrity: sha512-hLrmRl53prCcD+YXTfNvXd776HTxNh8wPAMllusQ+amcQmtgo3V5i/nkhPN6FakW+QVLoUUr2AsbtIRPFU3xIA==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.45.0': resolution: {integrity: sha512-XBKGSYcrkdiRRjl+8XvrUR3AosXU0NvF7VuqMsm7s5nRy+nt58ZMB19Jdp1RdqewLcaYnpk8zeVs/4MlLZEJxw==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.45.0': resolution: {integrity: sha512-fRvZZPUiBz7NztBE/2QnCS5AtqLVhXmUOPj9IHlfGEXkapgImf4W9+FSkL8cWqoAjozyUzqFmSc4zh2ooaeF6g==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.45.0': resolution: {integrity: sha512-Btv2WRZOcUGi8XU80XwIvzTg4U6+l6D0V6sZTrZx214nrwxw5nAi8hysaXj/mctyClWgesyuxbeLylCBNauimg==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loongarch64-gnu@4.45.0': resolution: {integrity: sha512-Li0emNnwtUZdLwHjQPBxn4VWztcrw/h7mgLyHiEI5Z0MhpeFGlzaiBHpSNVOMB/xucjXTTcO+dhv469Djr16KA==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-powerpc64le-gnu@4.45.0': resolution: {integrity: sha512-sB8+pfkYx2kvpDCfd63d5ScYT0Fz1LO6jIb2zLZvmK9ob2D8DeVqrmBDE0iDK8KlBVmsTNzrjr3G1xV4eUZhSw==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.45.0': resolution: {integrity: sha512-5GQ6PFhh7E6jQm70p1aW05G2cap5zMOvO0se5JMecHeAdj5ZhWEHbJ4hiKpfi1nnnEdTauDXxPgXae/mqjow9w==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.45.0': resolution: {integrity: sha512-N/euLsBd1rekWcuduakTo/dJw6U6sBP3eUq+RXM9RNfPuWTvG2w/WObDkIvJ2KChy6oxZmOSC08Ak2OJA0UiAA==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.45.0': resolution: {integrity: sha512-2l9sA7d7QdikL0xQwNMO3xURBUNEWyHVHfAsHsUdq+E/pgLTUcCE+gih5PCdmyHmfTDeXUWVhqL0WZzg0nua3g==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.45.0': resolution: {integrity: sha512-XZdD3fEEQcwG2KrJDdEQu7NrHonPxxaV0/w2HpvINBdcqebz1aL+0vM2WFJq4DeiAVT6F5SUQas65HY5JDqoPw==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.45.0': resolution: {integrity: sha512-7ayfgvtmmWgKWBkCGg5+xTQ0r5V1owVm67zTrsEY1008L5ro7mCyGYORomARt/OquB9KY7LpxVBZes+oSniAAQ==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.45.0': resolution: {integrity: sha512-B+IJgcBnE2bm93jEW5kHisqvPITs4ddLOROAcOc/diBgrEiQJJ6Qcjby75rFSmH5eMGrqJryUgJDhrfj942apQ==} @@ -8244,28 +8232,24 @@ packages: engines: {node: '>=10'} cpu: [arm64] os: [linux] - libc: [glibc] '@swc/core-linux-arm64-musl@1.4.6': resolution: {integrity: sha512-LGQsKJ8MA9zZ8xHCkbGkcPSmpkZL2O7drvwsGKynyCttHhpwVjj9lguhD4DWU3+FWIsjvho5Vu0Ggei8OYi/Lw==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - libc: [musl] '@swc/core-linux-x64-gnu@1.4.6': resolution: {integrity: sha512-10JL2nLIreMQDKvq2TECnQe5fCuoqBHu1yW8aChqgHUyg9d7gfZX/kppUsuimqcgRBnS0AjTDAA+JF6UsG/2Yg==} engines: {node: '>=10'} cpu: [x64] os: [linux] - libc: [glibc] '@swc/core-linux-x64-musl@1.4.6': resolution: {integrity: sha512-EGyjFVzVY6Do89x8sfah7I3cuP4MwtwzmA6OlfD/KASqfCFf5eIaEBMbajgR41bVfMV7lK72lwAIea5xEyq1AQ==} engines: {node: '>=10'} cpu: [x64] os: [linux] - libc: [musl] '@swc/core-win32-arm64-msvc@1.4.6': resolution: {integrity: sha512-gfW9AuXvwSyK07Vb8Y8E9m2oJZk21WqcD+X4BZhkbKB0TCZK0zk1j/HpS2UFlr1JB2zPKPpSWLU3ll0GEHRG2A==} @@ -9356,49 +9340,41 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] - libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] - libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] - libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -10578,14 +10554,12 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] chromadb-js-bindings-linux-x64-gnu@1.1.1: resolution: {integrity: sha512-RcvBcECbUcFXlFM2httdGc+3wmkI76tD9iGS0H9npEhz4LyLvdXKBK8CZtw67hsHCH09KklKw4ITkSS85pHVbA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] chromadb-js-bindings-win32-x64-msvc@1.1.1: resolution: {integrity: sha512-296SxWNwsmvP+1Ggkl72norTFLOivoXhGu0t5mXNIbd7yd2UodntvAvG2cheTHDCL/ILbox4u+6KHNvRxHgm6A==} @@ -11094,6 +11068,9 @@ packages: cross-fetch@4.0.0: resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==} + cross-fetch@4.1.0: + resolution: {integrity: sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==} + cross-spawn-async@2.2.5: resolution: {integrity: sha512-snteb3aVrxYYOX9e8BabYFK9WhCDhTlw1YQktfTthBogxri4/2r9U2nQc0ffY73ZAxezDc+U8gvHAeU1wy1ubQ==} deprecated: cross-spawn no longer requires a build toolchain, use it instead @@ -13333,6 +13310,9 @@ packages: engines: {node: '>=12'} hasBin: true + html-parse-stringify@3.0.1: + resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + html-react-parser@3.0.16: resolution: {integrity: sha512-ysQZtRFPcg+McVb4B05oNWSnqM14zagpvTgGcI5e1/BvCl38YwzWzKibrbBmXeemg70olN1bAoeixo7o06G5Eg==} peerDependencies: @@ -13451,6 +13431,20 @@ packages: resolution: {integrity: sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==} engines: {node: '>=10.18'} + i18next-browser-languagedetector@8.2.1: + resolution: {integrity: sha512-bZg8+4bdmaOiApD7N7BPT9W8MLZG+nPTOFlLiJiT8uzKXFjhxw4v2ierCXOwB5sFDMtuA5G4kgYZ0AznZxQ/cw==} + + i18next-http-backend@3.0.4: + resolution: {integrity: sha512-udwrBIE6cNpqn1gRAqRULq3+7MzIIuaiKRWrz++dVz5SqWW2VwXmPJtAgkI0JtMLFaADC9qNmnZAxWAhsxXx2g==} + + i18next@26.0.4: + resolution: {integrity: sha512-gXF7U9bfioXPLv7mw8Qt2nfO7vij5MyINvPgVv99pX3fL1Y01pw2mKBFrlYpRxRCl2wz3ISenj6VsMJT2isfuA==} + peerDependencies: + typescript: ^5 || ^6 + peerDependenciesMeta: + typescript: + optional: true + ibm-cloud-sdk-core@5.4.8: resolution: {integrity: sha512-tLMlZv13cV6S1UPj/bhv8XfV9Z1BDDs/4DxHKWnCw7QlJMzmGdHLPX386x9nrFMQMPZ48eAH+Thsa06tzUZkaA==} engines: {node: '>=20'} @@ -17492,6 +17486,22 @@ packages: react: '>= 16.3' react-dom: '>= 16.3' + react-i18next@17.0.2: + resolution: {integrity: sha512-shBftH2vaTWK2Bsp7FiL+cevx3xFJlvFxmsDFQSrJc+6twHkP0tv/bGa01VVWzpreUVVwU+3Hev5iFqRg65RwA==} + peerDependencies: + i18next: '>= 26.0.1' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + typescript: ^5 || ^6 + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + typescript: + optional: true + react-inspector@6.0.2: resolution: {integrity: sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==} peerDependencies: @@ -19940,6 +19950,10 @@ packages: engines: {node: '>=6.0'} hasBin: true + void-elements@3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + vue-template-compiler@2.7.16: resolution: {integrity: sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==} @@ -24951,6 +24965,8 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 + '@babel/runtime@7.29.2': {} + '@babel/template@7.25.9': dependencies: '@babel/code-frame': 7.26.2 @@ -33931,6 +33947,12 @@ snapshots: transitivePeerDependencies: - encoding + cross-fetch@4.1.0(encoding@0.1.13): + dependencies: + node-fetch: 2.7.0(encoding@0.1.13) + transitivePeerDependencies: + - encoding + cross-spawn-async@2.2.5: dependencies: lru-cache: 4.1.5 @@ -37003,6 +37025,10 @@ snapshots: relateurl: 0.2.7 terser: 5.29.1 + html-parse-stringify@3.0.1: + dependencies: + void-elements: 3.1.0 + html-react-parser@3.0.16(react@18.2.0): dependencies: domhandler: 5.0.3 @@ -37166,6 +37192,22 @@ snapshots: hyperdyperid@1.2.0: {} + i18next-browser-languagedetector@8.2.1: + dependencies: + '@babel/runtime': 7.26.10 + + i18next-http-backend@3.0.4(encoding@0.1.13): + dependencies: + cross-fetch: 4.1.0(encoding@0.1.13) + transitivePeerDependencies: + - encoding + + i18next@26.0.4(typescript@5.5.2): + dependencies: + '@babel/runtime': 7.29.2 + optionalDependencies: + typescript: 5.5.2 + ibm-cloud-sdk-core@5.4.8: dependencies: '@types/debug': 4.1.12 @@ -42246,6 +42288,17 @@ snapshots: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + react-i18next@17.0.2(i18next@26.0.4(typescript@5.5.2))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@5.5.2): + dependencies: + '@babel/runtime': 7.29.2 + html-parse-stringify: 3.0.1 + i18next: 26.0.4(typescript@5.5.2) + react: 18.2.0 + use-sync-external-store: 1.6.0(react@18.2.0) + optionalDependencies: + react-dom: 18.2.0(react@18.2.0) + typescript: 5.5.2 + react-inspector@6.0.2(react@18.2.0): dependencies: react: 18.2.0 @@ -45269,6 +45322,8 @@ snapshots: acorn: 8.16.0 acorn-walk: 8.3.5 + void-elements@3.1.0: {} + vue-template-compiler@2.7.16: dependencies: de-indent: 1.0.2