diff --git a/app/[locale]/help/page.tsx b/app/[locale]/help/page.tsx index c1753f460b..1d96d20b31 100644 --- a/app/[locale]/help/page.tsx +++ b/app/[locale]/help/page.tsx @@ -1,7 +1,15 @@ -export default function HelpPage() { +import initTranslations from "@/lib/i18n" + +export default async function HelpPage({ + params: { locale } +}: { + params: { locale: string } +}) { + const { t } = await initTranslations(locale, ["translation"]) + return (
-
Help under construction.
+
{t("Help under construction.")}
) } diff --git a/app/[locale]/login/page.tsx b/app/[locale]/login/page.tsx index 527422f65a..14fe72d40c 100644 --- a/app/[locale]/login/page.tsx +++ b/app/[locale]/login/page.tsx @@ -1,7 +1,3 @@ -import { Brand } from "@/components/ui/brand" -import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" -import { SubmitButton } from "@/components/ui/submit-button" import { createClient } from "@/lib/supabase/server" import { Database } from "@/supabase/types" import { createServerClient } from "@supabase/ssr" @@ -9,9 +5,19 @@ import { get } from "@vercel/edge-config" import { Metadata } from "next" import { cookies, headers } from "next/headers" import { redirect } from "next/navigation" +import initTranslations from "@/lib/i18n" +import { LoginForm } from "@/components/login/login-form" -export const metadata: Metadata = { - title: "Login" +export async function generateMetadata({ + params: { locale } +}: { + params: { locale: string } +}): Promise { + const { t } = await initTranslations(locale, ["translation"]) + + return { + title: t("Login") + } } export default async function Login({ @@ -162,60 +168,11 @@ export default async function Login({ } return ( -
-
- - - - - - - - - - Login - - - - Sign Up - - -
- Forgot your password? - -
- - {searchParams?.message && ( -

- {searchParams.message} -

- )} - -
+ ) } diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx index a5a0fc7daf..7e5ba8621e 100644 --- a/app/[locale]/page.tsx +++ b/app/[locale]/page.tsx @@ -4,9 +4,11 @@ import { ChatbotUISVG } from "@/components/icons/chatbotui-svg" import { IconArrowRight } from "@tabler/icons-react" import { useTheme } from "next-themes" import Link from "next/link" +import { useTranslation } from "react-i18next" export default function HomePage() { const { theme } = useTheme() + const { t } = useTranslation() return (
@@ -14,13 +16,13 @@ export default function HomePage() {
-
Chatbot UI
+
{t("Chatbot UI")}
- Start Chatting + {t("Start Chatting")} diff --git a/app/api/chat/google/route.ts b/app/api/chat/google/route.ts index ad79139646..cb9820ee88 100644 --- a/app/api/chat/google/route.ts +++ b/app/api/chat/google/route.ts @@ -44,7 +44,6 @@ export async function POST(request: Request) { return new Response(readableStream, { headers: { "Content-Type": "text/plain" } }) - } catch (error: any) { let errorMessage = error.message || "An unexpected error occurred" const errorCode = error.status || 500 diff --git a/components/chat/chat-files-display.tsx b/components/chat/chat-files-display.tsx index a0675053c8..10ab98e415 100644 --- a/components/chat/chat-files-display.tsx +++ b/components/chat/chat-files-display.tsx @@ -1,3 +1,5 @@ +"use client" + import { ChatbotUIContext } from "@/context/context" import { getFileFromStorage } from "@/db/storage/files" import useHotkey from "@/lib/hooks/use-hotkey" @@ -17,6 +19,7 @@ import { } from "@tabler/icons-react" import Image from "next/image" import { FC, useContext, useState } from "react" +import { useTranslation } from "react-i18next" import { Button } from "../ui/button" import { FilePreview } from "../ui/file-preview" import { WithTooltip } from "../ui/with-tooltip" @@ -28,6 +31,8 @@ export const ChatFilesDisplay: FC = ({}) => { useHotkey("f", () => setShowFilesDisplay(prev => !prev)) useHotkey("e", () => setUseRetrieval(prev => !prev)) + const { t } = useTranslation() + const { files, newMessageImages, @@ -106,7 +111,7 @@ export const ChatFilesDisplay: FC = ({}) => { > -
Hide files
+
{t("Hide files")}
e.stopPropagation()}> @@ -131,7 +136,7 @@ export const ChatFilesDisplay: FC = ({}) => { maxWidth: "56px" }} src={image.base64} // Preview images will always be base64 - alt="File image" + alt={t("File image")} width={56} height={56} onClick={() => { @@ -235,8 +240,11 @@ export const ChatFilesDisplay: FC = ({}) => {
{" "} - View {combinedMessageFiles.length} file - {combinedMessageFiles.length > 1 ? "s" : ""} + {combinedMessageFiles.length > 1 + ? t("View {{count}} files", { + count: combinedMessageFiles.length + }) + : "View file"}
e.stopPropagation()}> @@ -249,6 +257,7 @@ export const ChatFilesDisplay: FC = ({}) => { } const RetrievalToggle = ({}) => { + const { t } = useTranslation() const { useRetrieval, setUseRetrieval } = useContext(ChatbotUIContext) return ( @@ -259,8 +268,12 @@ const RetrievalToggle = ({}) => { display={
{useRetrieval - ? "File retrieval is enabled on the selected files for this message. Click the indicator to disable." - : "Click the indicator to enable file retrieval for this message."} + ? t( + "File retrieval is enabled on the selected files for this message. Click the indicator to disable." + ) + : t( + "Click the indicator to enable file retrieval for this message." + )}
} trigger={ diff --git a/components/chat/chat-help.tsx b/components/chat/chat-help.tsx index 4895ea4eeb..cec4780270 100644 --- a/components/chat/chat-help.tsx +++ b/components/chat/chat-help.tsx @@ -7,6 +7,7 @@ import { } from "@tabler/icons-react" import Link from "next/link" import { FC, useState } from "react" +import { useTranslation } from "react-i18next" import { DropdownMenu, DropdownMenuContent, @@ -20,6 +21,7 @@ import { Announcements } from "../utility/announcements" interface ChatHelpProps {} export const ChatHelp: FC = ({}) => { + const { t } = useTranslation() useHotkey("/", () => setIsOpen(prevState => !prevState)) const [isOpen, setIsOpen] = useState(false) @@ -69,7 +71,7 @@ export const ChatHelp: FC = ({}) => { -
Show Help
+
{t("Show Help")}
⌘ @@ -84,7 +86,7 @@ export const ChatHelp: FC = ({}) => { -
Show Workspaces
+
{t("Show Workspaces")}
⌘ @@ -99,7 +101,7 @@ export const ChatHelp: FC = ({}) => { -
New Chat
+
{t("New Chat")}
⌘ @@ -114,7 +116,7 @@ export const ChatHelp: FC = ({}) => { -
Focus Chat
+
{t("Focus Chat")}
⌘ @@ -129,7 +131,7 @@ export const ChatHelp: FC = ({}) => { -
Toggle Files
+
{t("Toggle Files")}
⌘ @@ -144,7 +146,7 @@ export const ChatHelp: FC = ({}) => { -
Toggle Retrieval
+
{t("Toggle Retrieval")}
⌘ @@ -159,7 +161,7 @@ export const ChatHelp: FC = ({}) => { -
Open Settings
+
{t("Open Settings")}
⌘ @@ -174,7 +176,7 @@ export const ChatHelp: FC = ({}) => { -
Open Quick Settings
+
{t("Open Quick Settings")}
⌘ @@ -189,7 +191,7 @@ export const ChatHelp: FC = ({}) => { -
Toggle Sidebar
+
{t("Toggle Sidebar")}
⌘ diff --git a/components/chat/chat-helpers/index.ts b/components/chat/chat-helpers/index.ts index 17a2089638..ec4a548cdc 100644 --- a/components/chat/chat-helpers/index.ts +++ b/components/chat/chat-helpers/index.ts @@ -208,9 +208,12 @@ export const handleHostedChat = async ( let draftMessages = await buildFinalMessages(payload, profile, chatImages) - let formattedMessages : any[] = [] + let formattedMessages: any[] = [] if (provider === "google") { - formattedMessages = await adaptMessagesForGoogleGemini(payload, draftMessages) + formattedMessages = await adaptMessagesForGoogleGemini( + payload, + draftMessages + ) } else { formattedMessages = draftMessages } diff --git a/components/chat/chat-input.tsx b/components/chat/chat-input.tsx index 761c6cdcf0..429a7dd232 100644 --- a/components/chat/chat-input.tsx +++ b/components/chat/chat-input.tsx @@ -151,7 +151,9 @@ export const ChatInput: FC = ({}) => { if (item.type.indexOf("image") === 0) { if (!imagesAllowed) { toast.error( - `Images are not supported for this model. Use models like GPT-4 Vision instead.` + t( + "Images are not supported for this model. Use models like GPT-4 Vision instead." + ) ) return } @@ -205,7 +207,9 @@ export const ChatInput: FC = ({}) => { )}
- Talking to {selectedAssistant.name} + {t("Talking to {{selectedAssistantName}}", { + selectedAssistantName: selectedAssistant.name + })}
)} @@ -239,10 +243,7 @@ export const ChatInput: FC = ({}) => { = ({}) => { + const { t } = useTranslation() const { sourceCount, setSourceCount } = useContext(ChatbotUIContext) const [isOpen, setIsOpen] = useState(false) @@ -25,7 +29,7 @@ export const ChatRetrievalSettings: FC = ({}) => { Adjust retrieval settings.
} + display={
{t("Adjust retrieval settings.")}
} trigger={ = ({}) => {
@@ -56,7 +60,7 @@ export const ChatRetrievalSettings: FC = ({}) => { diff --git a/components/chat/chat-secondary-buttons.tsx b/components/chat/chat-secondary-buttons.tsx index 780f7654e4..b7040edb55 100644 --- a/components/chat/chat-secondary-buttons.tsx +++ b/components/chat/chat-secondary-buttons.tsx @@ -1,12 +1,16 @@ +"use client" + import { useChatHandler } from "@/components/chat/chat-hooks/use-chat-handler" import { ChatbotUIContext } from "@/context/context" import { IconInfoCircle, IconMessagePlus } from "@tabler/icons-react" import { FC, useContext } from "react" +import { useTranslation } from "react-i18next" import { WithTooltip } from "../ui/with-tooltip" interface ChatSecondaryButtonsProps {} export const ChatSecondaryButtons: FC = ({}) => { + const { t } = useTranslation() const { selectedChat } = useContext(ChatbotUIContext) const { handleNewChat } = useChatHandler() @@ -19,31 +23,40 @@ export const ChatSecondaryButtons: FC = ({}) => { delayDuration={200} display={
-
Chat Info
+
{t("Chat Info")}
-
Model: {selectedChat.model}
-
Prompt: {selectedChat.prompt}
+
+ {t("Model")}: {selectedChat.model} +
+
+ {t("Prompt")}: {selectedChat.prompt} +
-
Temperature: {selectedChat.temperature}
-
Context Length: {selectedChat.context_length}
+
+ {t("Temperature")}: {selectedChat.temperature} +
+
+ {t("Context Length")}: {selectedChat.context_length} +
- Profile Context:{" "} + {t("Profile Context")}:{" "} {selectedChat.include_profile_context - ? "Enabled" - : "Disabled"} + ? t("Enabled") + : t("Disabled")}
{" "} - Workspace Instructions:{" "} + {t("Workspace Instructions")}:{" "} {selectedChat.include_workspace_instructions - ? "Enabled" - : "Disabled"} + ? t("Enabled") + : t("Disabled")}
- Embeddings Provider: {selectedChat.embeddings_provider} + {t("Embeddings Provider")}:{" "} + {selectedChat.embeddings_provider}
@@ -60,7 +73,7 @@ export const ChatSecondaryButtons: FC = ({}) => { Start a new chat
} + display={
{t("Start a new chat")}
} trigger={
= ({}) => { + const { t } = useTranslation() + const { prompts, isPromptPickerOpen, @@ -144,7 +149,7 @@ export const PromptPicker: FC = ({}) => { > - Enter Prompt Variables + {t("Enter Prompt Variables")}
@@ -153,7 +158,9 @@ export const PromptPicker: FC = ({}) => { { const newPromptVariables = [...promptVariables] @@ -175,18 +182,18 @@ export const PromptPicker: FC = ({}) => { size="sm" onClick={handleCancelPromptVariables} > - Cancel + {t("Cancel")}
) : filteredPrompts.length === 0 ? (
- No matching prompts. + {t("No matching prompts.")}
) : ( filteredPrompts.map((prompt, index) => ( diff --git a/components/chat/quick-setting-option.tsx b/components/chat/quick-setting-option.tsx index 6ddd48b3bd..3f670906e2 100644 --- a/components/chat/quick-setting-option.tsx +++ b/components/chat/quick-setting-option.tsx @@ -1,8 +1,11 @@ +"use client" + import { LLM_LIST } from "@/lib/models/llm/llm-list" import { Tables } from "@/supabase/types" import { IconCircleCheckFilled, IconRobotFace } from "@tabler/icons-react" import Image from "next/image" import { FC } from "react" +import { useTranslation } from "react-i18next" import { ModelIcon } from "../models/model-icon" import { DropdownMenuItem } from "../ui/dropdown-menu" @@ -21,6 +24,7 @@ export const QuickSettingOption: FC = ({ onSelect, image }) => { + const { t } = useTranslation() const modelDetails = LLM_LIST.find(model => model.modelId === item.model) return ( @@ -41,7 +45,7 @@ export const QuickSettingOption: FC = ({ style={{ width: "32px", height: "32px" }} className="rounded" src={image} - alt="Assistant" + alt={t("Assistant")} width={32} height={32} /> diff --git a/components/chat/quick-settings.tsx b/components/chat/quick-settings.tsx index 6eb5cabef4..54cbfa5c3b 100644 --- a/components/chat/quick-settings.tsx +++ b/components/chat/quick-settings.tsx @@ -205,7 +205,7 @@ export const QuickSettings: FC = ({}) => { Assistant @@ -217,13 +217,13 @@ export const QuickSettings: FC = ({}) => { ))} {loading ? ( -
Loading assistant...
+
{t("Loading assistant...")}
) : ( <>
{isModified && (selectedPreset || selectedAssistant) && - "Modified "} + t("Modified ")} {selectedPreset?.name || selectedAssistant?.name || @@ -241,13 +241,13 @@ export const QuickSettings: FC = ({}) => { align="start" > {presets.length === 0 && assistants.length === 0 ? ( -
No items found.
+
{t("No items found.")}
) : ( <> setSearch(e.target.value)} onKeyDown={e => e.stopPropagation()} diff --git a/components/login/login-form.tsx b/components/login/login-form.tsx new file mode 100644 index 0000000000..8964de5e9f --- /dev/null +++ b/components/login/login-form.tsx @@ -0,0 +1,80 @@ +"use client" + +import { Brand } from "@/components/ui/brand" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { SubmitButton } from "@/components/ui/submit-button" + +import { useTranslation } from "react-i18next" + +export const LoginForm = ({ + signIn, + signUp, + handleResetPassword, + message +}: { + signIn: (formData: FormData) => void + signUp: (formData: FormData) => void + handleResetPassword: (formData: FormData) => void + message?: string +}) => { + const { t } = useTranslation() + + return ( +
+
+ + + + + + + + + + {t("Login")} + + + + {t("Sign Up")} + + +
+ {t("Forgot your password?")} + +
+ + {message && ( +

+ {t(message)} +

+ )} + +
+ ) +} diff --git a/components/messages/message-replies.tsx b/components/messages/message-replies.tsx index e9dd75b67d..9d4bd8c3f6 100644 --- a/components/messages/message-replies.tsx +++ b/components/messages/message-replies.tsx @@ -1,3 +1,5 @@ +"use client" + import { IconMessage } from "@tabler/icons-react" import { FC, useState } from "react" import { @@ -10,10 +12,12 @@ import { } from "../ui/sheet" import { WithTooltip } from "../ui/with-tooltip" import { MESSAGE_ICON_SIZE } from "./message-actions" +import { useTranslation } from "react-i18next" interface MessageRepliesProps {} export const MessageReplies: FC = ({}) => { + const { t } = useTranslation() const [isOpen, setIsOpen] = useState(false) return ( @@ -22,7 +26,7 @@ export const MessageReplies: FC = ({}) => { View Replies
} + display={
{t("View Replies")}
} trigger={
= ({}) => { - Are you sure absolutely sure? + {t("Are you sure absolutely sure?")} - This action cannot be undone. This will permanently delete your - account and remove your data from our servers. + {t( + "This action cannot be undone. This will permanently delete your account and remove your data from our servers." + )} diff --git a/components/messages/message.tsx b/components/messages/message.tsx index d0867d68ab..ed1c720c74 100644 --- a/components/messages/message.tsx +++ b/components/messages/message.tsx @@ -1,3 +1,5 @@ +"use client" + import { useChatHandler } from "@/components/chat/chat-hooks/use-chat-handler" import { ChatbotUIContext } from "@/context/context" import { LLM_LIST } from "@/lib/models/llm/llm-list" @@ -15,6 +17,7 @@ import { } from "@tabler/icons-react" import Image from "next/image" import { FC, useContext, useEffect, useRef, useState } from "react" +import { useTranslation } from "react-i18next" import { ModelIcon } from "../models/model-icon" import { Button } from "../ui/button" import { FileIcon } from "../ui/file-icon" @@ -45,6 +48,8 @@ export const Message: FC = ({ onCancelEdit, onSubmitEdit }) => { + const { t } = useTranslation() + const { assistants, profile, @@ -209,7 +214,7 @@ export const Message: FC = ({ size={ICON_SIZE} /> -
Prompt
+
{t("Prompt")}
) : (
@@ -222,7 +227,7 @@ export const Message: FC = ({ }} className="rounded" src={messageAssistantImage} - alt="assistant image" + alt={t("assistant image")} height={ICON_SIZE} width={ICON_SIZE} /> @@ -244,7 +249,7 @@ export const Message: FC = ({ src={profile?.image_url} height={32} width={32} - alt="user image" + alt={t("user image")} /> ) : ( = ({
-
Searching files...
+
{t("Searching files...")}
) default: @@ -290,7 +295,11 @@ export const Message: FC = ({
-
Using {toolInUse}...
+
+ {t("Using {toolInUse}...", { + toolInUse + })} +
) } @@ -316,10 +325,13 @@ export const Message: FC = ({ className="flex cursor-pointer items-center text-lg hover:opacity-50" onClick={() => setViewSources(true)} > - {fileItems.length} - {fileItems.length > 1 ? " Sources " : " Source "} - from {Object.keys(fileSummary).length}{" "} - {Object.keys(fileSummary).length > 1 ? "Files" : "File"}{" "} + {t( + "{{fileItemsLength}} Sources from {{fileSummaryLength}} Files", + { + fileItemsLength: fileItems.length, + fileSummaryLength: Object.keys(fileSummary).length + } + )}
) : ( @@ -328,10 +340,13 @@ export const Message: FC = ({ className="flex cursor-pointer items-center text-lg hover:opacity-50" onClick={() => setViewSources(false)} > - {fileItems.length} - {fileItems.length > 1 ? " Sources " : " Source "} - from {Object.keys(fileSummary).length}{" "} - {Object.keys(fileSummary).length > 1 ? "Files" : "File"}{" "} + {t( + "{{fileItemsLength}} Sources from {{fileSummaryLength}} Files", + { + fileItemsLength: fileItems.length, + fileSummaryLength: Object.keys(fileSummary).length + } + )}
@@ -385,7 +400,7 @@ export const Message: FC = ({ key={index} className="cursor-pointer rounded hover:opacity-50" src={path.startsWith("data") ? path : item?.base64} - alt="message image" + alt={t("message image")} width={300} height={300} onClick={() => { @@ -407,11 +422,11 @@ export const Message: FC = ({ {isEditing && (
)} diff --git a/components/models/model-icon.tsx b/components/models/model-icon.tsx index 27ca7b42c4..5f1e790284 100644 --- a/components/models/model-icon.tsx +++ b/components/models/model-icon.tsx @@ -1,3 +1,5 @@ +"use client" + import { cn } from "@/lib/utils" import mistral from "@/public/providers/mistral.png" import groq from "@/public/providers/groq.png" @@ -7,6 +9,7 @@ import { IconSparkles } from "@tabler/icons-react" import { useTheme } from "next-themes" import Image from "next/image" import { FC, HTMLAttributes } from "react" +import { useTranslation } from "react-i18next" import { AnthropicSVG } from "../icons/anthropic-svg" import { GoogleSVG } from "../icons/google-svg" import { OpenAISVG } from "../icons/openai-svg" @@ -23,6 +26,7 @@ export const ModelIcon: FC = ({ width, ...props }) => { + const { t } = useTranslation() const { theme } = useTheme() switch (provider as ModelProvider) { @@ -46,7 +50,7 @@ export const ModelIcon: FC = ({ theme === "dark" ? "bg-white" : "border-DEFAULT border-black" )} src={mistral.src} - alt="Mistral" + alt={"Mistral"} width={width} height={height} /> @@ -59,7 +63,7 @@ export const ModelIcon: FC = ({ theme === "dark" ? "bg-white" : "border-DEFAULT border-black" )} src={groq.src} - alt="Groq" + alt={"Groq"} width={width} height={height} /> @@ -96,7 +100,7 @@ export const ModelIcon: FC = ({ theme === "dark" ? "bg-white" : "border-DEFAULT border-black" )} src={perplexity.src} - alt="Mistral" + alt={t("Mistral")} width={width} height={height} /> diff --git a/components/models/model-select.tsx b/components/models/model-select.tsx index d25d6de507..02ec012fcb 100644 --- a/components/models/model-select.tsx +++ b/components/models/model-select.tsx @@ -1,7 +1,10 @@ +"use client" + import { ChatbotUIContext } from "@/context/context" import { LLM, LLMID, ModelProvider } from "@/types" import { IconCheck, IconChevronDown } from "@tabler/icons-react" import { FC, useContext, useEffect, useRef, useState } from "react" +import { useTranslation } from "react-i18next" import { Button } from "../ui/button" import { DropdownMenu, @@ -22,6 +25,8 @@ export const ModelSelect: FC = ({ selectedModelId, onSelectModel }) => { + const { t } = useTranslation() + const { profile, models, @@ -97,7 +102,7 @@ export const ModelSelect: FC = ({ > {allModels.length === 0 ? (
- Unlock models by entering API keys in your profile settings. + {t("Unlock models by entering API keys in your profile settings.")}
) : (
) : ( -
Select a model
+
{t("Select a model")}
)}
@@ -135,9 +140,9 @@ export const ModelSelect: FC = ({ setTab(value)}> {availableLocalModels.length > 0 && ( - Hosted + {t("Hosted")} - Local + {t("Local")} )} @@ -145,7 +150,7 @@ export const ModelSelect: FC = ({ setSearch(e.target.value)} /> @@ -169,7 +174,7 @@ export const ModelSelect: FC = ({
{provider === "openai" && profile.use_azure_openai - ? "AZURE OPENAI" + ? t("AZURE OPENAI") : provider.toLocaleUpperCase()}
diff --git a/components/setup/api-step.tsx b/components/setup/api-step.tsx index 16393be500..87fc026971 100644 --- a/components/setup/api-step.tsx +++ b/components/setup/api-step.tsx @@ -1,6 +1,7 @@ import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { FC } from "react" +import { useTranslation } from "react-i18next" import { Button } from "../ui/button" interface APIStepProps { @@ -68,12 +69,14 @@ export const APIStep: FC = ({ onUseAzureOpenaiChange, onOpenrouterAPIKeyChange }) => { + const { t } = useTranslation() + return ( <>
= ({ {useAzureOpenai ? ( <>
- + onAzureOpenaiEndpointChange(e.target.value)} @@ -115,10 +118,10 @@ export const APIStep: FC = ({
- + onAzureOpenai35TurboIDChange(e.target.value)} @@ -126,10 +129,10 @@ export const APIStep: FC = ({
- + onAzureOpenai45TurboIDChange(e.target.value)} @@ -137,10 +140,10 @@ export const APIStep: FC = ({
- + onAzureOpenai45VisionIDChange(e.target.value)} @@ -148,10 +151,10 @@ export const APIStep: FC = ({
- + onAzureOpenaiEmbeddingsIDChange(e.target.value)} @@ -161,10 +164,10 @@ export const APIStep: FC = ({ ) : ( <>
- + onOpenaiOrgIDChange(e.target.value)} @@ -175,10 +178,10 @@ export const APIStep: FC = ({
- + onAnthropicAPIKeyChange(e.target.value)} @@ -186,10 +189,10 @@ export const APIStep: FC = ({
- + onGoogleGeminiAPIKeyChange(e.target.value)} @@ -197,10 +200,10 @@ export const APIStep: FC = ({
- + onMistralAPIKeyChange(e.target.value)} @@ -208,10 +211,10 @@ export const APIStep: FC = ({
- + onGroqAPIKeyChange(e.target.value)} @@ -219,20 +222,20 @@ export const APIStep: FC = ({
- + onPerplexityAPIKeyChange(e.target.value)} />
- + onOpenrouterAPIKeyChange(e.target.value)} diff --git a/components/setup/finish-step.tsx b/components/setup/finish-step.tsx index d0747d32e4..f61d6a87a2 100644 --- a/components/setup/finish-step.tsx +++ b/components/setup/finish-step.tsx @@ -1,18 +1,21 @@ import { FC } from "react" +import { useTranslation } from "react-i18next" interface FinishStepProps { displayName: string } export const FinishStep: FC = ({ displayName }) => { + const { t } = useTranslation() + return (
- Welcome to Chatbot UI + {t("Welcome to Chatbot UI")} {displayName.length > 0 ? `, ${displayName.split(" ")[0]}` : null}!
-
Click next to start chatting.
+
{t("Click next to start chatting.")}
) } diff --git a/components/setup/profile-step.tsx b/components/setup/profile-step.tsx index eaf8faf813..1430a85db1 100644 --- a/components/setup/profile-step.tsx +++ b/components/setup/profile-step.tsx @@ -11,6 +11,7 @@ import { IconLoader2 } from "@tabler/icons-react" import { FC, useCallback, useState } from "react" +import { useTranslation } from "react-i18next" import { LimitDisplay } from "../ui/limit-display" import { toast } from "sonner" @@ -31,6 +32,7 @@ export const ProfileStep: FC = ({ onUsernameChange, onDisplayNameChange }) => { + const { t } = useTranslation() const [loading, setLoading] = useState(false) const debounce = (func: (...args: any[]) => void, wait: number) => { @@ -65,7 +67,9 @@ export const ProfileStep: FC = ({ if (!usernameRegex.test(username)) { onUsernameAvailableChange(false) toast.error( - "Username must be letters, numbers, or underscores only - no other characters or spacing allowed." + t( + "Username must be letters, numbers, or underscores only - no other characters or spacing allowed." + ) ) return } @@ -91,13 +95,13 @@ export const ProfileStep: FC = ({ <>
- +
{usernameAvailable ? ( -
AVAILABLE
+
{t("AVAILABLE")}
) : ( -
UNAVAILABLE
+
{t("UNAVAILABLE")}
)}
@@ -105,7 +109,7 @@ export const ProfileStep: FC = ({
{ onUsernameChange(e.target.value) @@ -130,10 +134,10 @@ export const ProfileStep: FC = ({
- + onDisplayNameChange(e.target.value)} maxLength={PROFILE_DISPLAY_NAME_MAX} diff --git a/components/setup/step-container.tsx b/components/setup/step-container.tsx index 2a9ed6972a..7395358a77 100644 --- a/components/setup/step-container.tsx +++ b/components/setup/step-container.tsx @@ -8,6 +8,7 @@ import { CardTitle } from "@/components/ui/card" import { FC, useRef } from "react" +import { useTranslation } from "react-i18next" export const SETUP_STEP_COUNT = 3 @@ -30,6 +31,7 @@ export const StepContainer: FC = ({ showBackButton = false, showNextButton = true }) => { + const { t } = useTranslation() const buttonRef = useRef(null) const handleKeyDown = (e: React.KeyboardEvent) => { @@ -67,7 +69,7 @@ export const StepContainer: FC = ({ variant="outline" onClick={() => onShouldProceed(false)} > - Back + {t("Back")} )}
@@ -79,7 +81,7 @@ export const StepContainer: FC = ({ size="sm" onClick={() => onShouldProceed(true)} > - Next + {t("Next")} )}
diff --git a/components/sidebar/items/all/sidebar-update-item.tsx b/components/sidebar/items/all/sidebar-update-item.tsx index 1f8b346107..5bf361c62d 100644 --- a/components/sidebar/items/all/sidebar-update-item.tsx +++ b/components/sidebar/items/all/sidebar-update-item.tsx @@ -1,3 +1,5 @@ +"use client" + import { Button } from "@/components/ui/button" import { Label } from "@/components/ui/label" import { @@ -81,6 +83,7 @@ import { convertBlobToBase64 } from "@/lib/blob-to-b64" import { Tables, TablesUpdate } from "@/supabase/types" import { CollectionFile, ContentType, DataItemType } from "@/types" import { FC, useContext, useEffect, useRef, useState } from "react" +import { useTranslation } from "react-i18next" import profile from "react-syntax-highlighter/dist/esm/languages/hljs/profile" import { toast } from "sonner" import { SidebarDeleteItem } from "./sidebar-delete-item" @@ -102,6 +105,8 @@ export const SidebarUpdateItem: FC = ({ updateState, isTyping }) => { + const { t } = useTranslation() + const { workspaces, selectedWorkspace, @@ -600,9 +605,19 @@ export const SidebarUpdateItem: FC = ({ setIsOpen(false) - toast.success(`${contentType.slice(0, -1)} updated successfully`) + toast.success( + t("{{contentType}} updated successfully", { + contentType: contentType.slice(0, -1) + }) + ) } catch (error) { - toast.error(`Error updating ${contentType.slice(0, -1)}. ${error}`) + toast.error( + t("Error updating {{contentType}}.", { + contentType: contentType.slice(0, -1) + }) + + " " + + error + ) } } @@ -641,14 +656,16 @@ export const SidebarUpdateItem: FC = ({
- Edit {contentType.slice(0, -1)} + {t("Edit {{contentType}}", { + contentType: contentType.slice(0, -1) + })}
{workspaces.length > 1 && (
- + = ({
diff --git a/components/sidebar/items/assistants/assistant-item.tsx b/components/sidebar/items/assistants/assistant-item.tsx index de807fdc98..fafa22af6f 100644 --- a/components/sidebar/items/assistants/assistant-item.tsx +++ b/components/sidebar/items/assistants/assistant-item.tsx @@ -1,3 +1,5 @@ +"use client" + import { ChatSettingsForm } from "@/components/ui/chat-settings-form" import ImagePicker from "@/components/ui/image-picker" import { Input } from "@/components/ui/input" @@ -8,6 +10,7 @@ import { Tables } from "@/supabase/types" import { IconRobotFace } from "@tabler/icons-react" import Image from "next/image" import { FC, useContext, useEffect, useState } from "react" +import { useTranslation } from "react-i18next" import profile from "react-syntax-highlighter/dist/esm/languages/hljs/profile" import { SidebarItem } from "../all/sidebar-display-item" import { AssistantRetrievalSelect } from "./assistant-retrieval-select" @@ -18,6 +21,7 @@ interface AssistantItemProps { } export const AssistantItem: FC = ({ assistant }) => { + const { t } = useTranslation() const { selectedWorkspace, assistantImages } = useContext(ChatbotUIContext) const [name, setName] = useState(assistant.name) @@ -167,10 +171,10 @@ export const AssistantItem: FC = ({ assistant }) => { }) => ( <>
- + setName(e.target.value)} maxLength={ASSISTANT_NAME_MAX} @@ -178,10 +182,10 @@ export const AssistantItem: FC = ({ assistant }) => {
- + setDescription(e.target.value)} maxLength={ASSISTANT_DESCRIPTION_MAX} @@ -189,7 +193,7 @@ export const AssistantItem: FC = ({ assistant }) => {
- + = ({ assistant }) => { />
- + = ({ assistant }) => {
- + = ({ isOpen, onOpenChange }) => { + const { t } = useTranslation() const { profile, selectedWorkspace } = useContext(ChatbotUIContext) const [name, setName] = useState("") @@ -136,10 +140,10 @@ export const CreateAssistant: FC = ({ renderInputs={() => ( <>
- + setName(e.target.value)} maxLength={ASSISTANT_NAME_MAX} @@ -147,10 +151,10 @@ export const CreateAssistant: FC = ({
- + setDescription(e.target.value)} maxLength={ASSISTANT_DESCRIPTION_MAX} @@ -159,9 +163,9 @@ export const CreateAssistant: FC = ({
= ({ />
- + = ({ {checkIfModelIsToolCompatible() ? (
- + = ({
) : (
- Model is not compatible with tools. + {t("Model is not compatible with tools.")}
)} diff --git a/components/sidebar/items/chat/chat-item.tsx b/components/sidebar/items/chat/chat-item.tsx index 62c2d98c54..e7e0d8737a 100644 --- a/components/sidebar/items/chat/chat-item.tsx +++ b/components/sidebar/items/chat/chat-item.tsx @@ -1,3 +1,5 @@ +"use client" + import { ModelIcon } from "@/components/models/model-icon" import { WithTooltip } from "@/components/ui/with-tooltip" import { ChatbotUIContext } from "@/context/context" @@ -9,6 +11,7 @@ import { IconRobotFace } from "@tabler/icons-react" import Image from "next/image" import { useParams, useRouter } from "next/navigation" import { FC, useContext, useRef } from "react" +import { useTranslation } from "react-i18next" import { DeleteChat } from "./delete-chat" import { UpdateChat } from "./update-chat" @@ -17,6 +20,8 @@ interface ChatItemProps { } export const ChatItem: FC = ({ chat }) => { + const { t } = useTranslation() + const { selectedWorkspace, selectedChat, @@ -70,7 +75,7 @@ export const ChatItem: FC = ({ chat }) => { style={{ width: "30px", height: "30px" }} className="rounded" src={assistantImage} - alt="Assistant image" + alt={t("Assistant image")} width={30} height={30} /> diff --git a/components/sidebar/items/collections/create-collection.tsx b/components/sidebar/items/collections/create-collection.tsx index d2c24370ee..c29db7b7be 100644 --- a/components/sidebar/items/collections/create-collection.tsx +++ b/components/sidebar/items/collections/create-collection.tsx @@ -1,3 +1,5 @@ +"use client" + import { SidebarCreateItem } from "@/components/sidebar/items/all/sidebar-create-item" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" @@ -6,6 +8,7 @@ import { COLLECTION_DESCRIPTION_MAX, COLLECTION_NAME_MAX } from "@/db/limits" import { TablesInsert } from "@/supabase/types" import { CollectionFile } from "@/types" import { FC, useContext, useState } from "react" +import { useTranslation } from "react-i18next" import { CollectionFileSelect } from "./collection-file-select" interface CreateCollectionProps { @@ -17,6 +20,7 @@ export const CreateCollection: FC = ({ isOpen, onOpenChange }) => { + const { t } = useTranslation() const { profile, selectedWorkspace } = useContext(ChatbotUIContext) const [name, setName] = useState("") @@ -64,7 +68,7 @@ export const CreateCollection: FC = ({ renderInputs={() => ( <>
- + = ({
- + setName(e.target.value)} maxLength={COLLECTION_NAME_MAX} @@ -84,10 +88,10 @@ export const CreateCollection: FC = ({
- + setDescription(e.target.value)} maxLength={COLLECTION_DESCRIPTION_MAX} diff --git a/components/sidebar/items/models/create-model.tsx b/components/sidebar/items/models/create-model.tsx index c50ea63dc3..44e00c42e0 100644 --- a/components/sidebar/items/models/create-model.tsx +++ b/components/sidebar/items/models/create-model.tsx @@ -1,3 +1,5 @@ +"use client" + import { SidebarCreateItem } from "@/components/sidebar/items/all/sidebar-create-item" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" @@ -5,6 +7,7 @@ import { ChatbotUIContext } from "@/context/context" import { MODEL_NAME_MAX } from "@/db/limits" import { TablesInsert } from "@/supabase/types" import { FC, useContext, useState } from "react" +import { useTranslation } from "react-i18next" interface CreateModelProps { isOpen: boolean @@ -12,6 +15,7 @@ interface CreateModelProps { } export const CreateModel: FC = ({ isOpen, onOpenChange }) => { + const { t } = useTranslation() const { profile, selectedWorkspace } = useContext(ChatbotUIContext) const [isTyping, setIsTyping] = useState(false) @@ -45,19 +49,19 @@ export const CreateModel: FC = ({ isOpen, onOpenChange }) => { renderInputs={() => ( <>
-
Create a custom model.
+
{t("Create a custom model.")}
- Your API *must* be compatible - with the OpenAI SDK. + {t("Your API")} {t("*must*")}{" "} + {t("be compatible with the OpenAI SDK.")}
- + setName(e.target.value)} maxLength={MODEL_NAME_MAX} @@ -65,46 +69,46 @@ export const CreateModel: FC = ({ isOpen, onOpenChange }) => {
- + setModelId(e.target.value)} />
- + setBaseUrl(e.target.value)} />
- Your API must be compatible with the OpenAI SDK. + {t("Your API must be compatible with the OpenAI SDK.")}
- + setApiKey(e.target.value)} />
- + setContextLength(parseInt(e.target.value))} diff --git a/components/sidebar/items/models/model-item.tsx b/components/sidebar/items/models/model-item.tsx index b6f6f74d1b..de3c92b934 100644 --- a/components/sidebar/items/models/model-item.tsx +++ b/components/sidebar/items/models/model-item.tsx @@ -1,9 +1,12 @@ +"use client" + import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { MODEL_NAME_MAX } from "@/db/limits" import { Tables, TablesUpdate } from "@/supabase/types" import { IconSparkles } from "@tabler/icons-react" import { FC, useState } from "react" +import { useTranslation } from "react-i18next" import { SidebarItem } from "../all/sidebar-display-item" interface ModelItemProps { @@ -11,6 +14,8 @@ interface ModelItemProps { } export const ModelItem: FC = ({ model }) => { + const { t } = useTranslation() + const [isTyping, setIsTyping] = useState(false) const [apiKey, setApiKey] = useState(model.api_key) @@ -39,10 +44,10 @@ export const ModelItem: FC = ({ model }) => { renderInputs={() => ( <>
- + setName(e.target.value)} maxLength={MODEL_NAME_MAX} @@ -50,46 +55,46 @@ export const ModelItem: FC = ({ model }) => {
- + setModelId(e.target.value)} />
- + setBaseUrl(e.target.value)} />
- Your API must be compatible with the OpenAI SDK. + {t("Your API must be compatible with the OpenAI SDK.")}
- + setApiKey(e.target.value)} />
- + setContextLength(parseInt(e.target.value))} diff --git a/components/sidebar/sidebar-create-buttons.tsx b/components/sidebar/sidebar-create-buttons.tsx index 934365471f..dddd7300d1 100644 --- a/components/sidebar/sidebar-create-buttons.tsx +++ b/components/sidebar/sidebar-create-buttons.tsx @@ -12,6 +12,7 @@ import { CreateModel } from "./items/models/create-model" import { CreatePreset } from "./items/presets/create-preset" import { CreatePrompt } from "./items/prompts/create-prompt" import { CreateTool } from "./items/tools/create-tool" +import { useTranslation } from "react-i18next" interface SidebarCreateButtonsProps { contentType: ContentType @@ -34,6 +35,8 @@ export const SidebarCreateButtons: FC = ({ const [isCreatingTool, setIsCreatingTool] = useState(false) const [isCreatingModel, setIsCreatingModel] = useState(false) + const { t } = useTranslation() + const handleCreateFolder = async () => { if (!profile) return if (!selectedWorkspace) return @@ -41,7 +44,7 @@ export const SidebarCreateButtons: FC = ({ const createdFolder = await createFolder({ user_id: profile.user_id, workspace_id: selectedWorkspace.id, - name: "New Folder", + name: t("New Folder"), description: "", type: contentType }) @@ -95,13 +98,17 @@ export const SidebarCreateButtons: FC = ({ } } + const translatedContentType = t(contentType) + return (
{hasData && ( diff --git a/components/sidebar/sidebar-data-list.tsx b/components/sidebar/sidebar-data-list.tsx index ee9ba44e0f..6341cd7154 100644 --- a/components/sidebar/sidebar-data-list.tsx +++ b/components/sidebar/sidebar-data-list.tsx @@ -21,6 +21,7 @@ import { ModelItem } from "./items/models/model-item" import { PresetItem } from "./items/presets/preset-item" import { PromptItem } from "./items/prompts/prompt-item" import { ToolItem } from "./items/tools/tool-item" +import { useTranslation } from "react-i18next" interface SidebarDataListProps { contentType: ContentType @@ -216,6 +217,7 @@ export const SidebarDataList: FC = ({ const dataWithFolders = data.filter(item => item.folder_id) const dataWithoutFolders = data.filter(item => item.folder_id === null) + const { t } = useTranslation() return ( <> @@ -227,7 +229,7 @@ export const SidebarDataList: FC = ({ {data.length === 0 && (
- No {contentType}. + {t("No {{contentType}}.", { contentType: t(contentType) })}
)} @@ -278,7 +280,7 @@ export const SidebarDataList: FC = ({ sortedData.length > 0 && (
- {dateCategory} + {t(dateCategory)}
= ({ searchTerm, setSearchTerm }) => { + const { t } = useTranslation() + return ( setSearchTerm(e.target.value)} /> diff --git a/components/sidebar/sidebar-switch-item.tsx b/components/sidebar/sidebar-switch-item.tsx index 2ccc92f440..605ad1a274 100644 --- a/components/sidebar/sidebar-switch-item.tsx +++ b/components/sidebar/sidebar-switch-item.tsx @@ -2,6 +2,7 @@ import { ContentType } from "@/types" import { FC } from "react" import { TabsTrigger } from "../ui/tabs" import { WithTooltip } from "../ui/with-tooltip" +import { useTranslation } from "react-i18next" interface SidebarSwitchItemProps { contentType: ContentType @@ -14,10 +15,15 @@ export const SidebarSwitchItem: FC = ({ icon, onContentTypeChange }) => { + const { t } = useTranslation() + const translatedContentType = t(contentType) return ( {contentType[0].toUpperCase() + contentType.substring(1)}
+
+ {translatedContentType[0].toUpperCase() + + translatedContentType.substring(1)} +
} trigger={ = ({ children }) => { + const { t } = useTranslation() + const [isOpen, setIsOpen] = useState( false // localStorage.getItem("advanced-settings-open") === "true" @@ -25,7 +30,7 @@ export const AdvancedSettings: FC = ({ children }) => {
-
Advanced Settings
+
{t("Advanced Settings")}
{isOpen ? ( ) : ( diff --git a/components/ui/chat-settings-form.tsx b/components/ui/chat-settings-form.tsx index 60d6b510da..1a0eca5044 100644 --- a/components/ui/chat-settings-form.tsx +++ b/components/ui/chat-settings-form.tsx @@ -5,6 +5,7 @@ import { CHAT_SETTING_LIMITS } from "@/lib/chat-setting-limits" import { ChatSettings } from "@/types" import { IconInfoCircle } from "@tabler/icons-react" import { FC, useContext } from "react" +import { useTranslation } from "react-i18next" import { ModelSelect } from "../models/model-select" import { AdvancedSettings } from "./advanced-settings" import { Checkbox } from "./checkbox" @@ -33,6 +34,7 @@ export const ChatSettingsForm: FC = ({ useAdvancedDropdown = true, showTooltip = true }) => { + const { t } = useTranslation() const { profile, models } = useContext(ChatbotUIContext) if (!profile) return null @@ -40,7 +42,7 @@ export const ChatSettingsForm: FC = ({ return (
- + = ({
- + { onChangeChatSettings({ ...chatSettings, prompt }) }} @@ -97,6 +99,7 @@ const AdvancedContent: FC = ({ onChangeChatSettings, showTooltip }) => { + const { t } = useTranslation() const { profile, selectedWorkspace, availableOpenRouterModels, models } = useContext(ChatbotUIContext) @@ -119,7 +122,7 @@ const AdvancedContent: FC = ({
@@ -140,7 +143,7 @@ const AdvancedContent: FC = ({
@@ -175,14 +178,14 @@ const AdvancedContent: FC = ({ } /> - + {showTooltip && ( - {profile?.profile_context || "No profile context."} + {profile?.profile_context || t("No profile context.")}
} trigger={ @@ -203,7 +206,7 @@ const AdvancedContent: FC = ({ } /> - + {showTooltip && ( = ({ display={
{selectedWorkspace?.instructions || - "No workspace instructions."} + t("No workspace instructions.")}
} trigger={ @@ -222,7 +225,7 @@ const AdvancedContent: FC = ({
- + diff --git a/components/ui/file-preview.tsx b/components/ui/file-preview.tsx index 2bfbc70378..c798ff19a6 100644 --- a/components/ui/file-preview.tsx +++ b/components/ui/file-preview.tsx @@ -1,9 +1,12 @@ +"use client" + import { cn } from "@/lib/utils" import { Tables } from "@/supabase/types" import { ChatFile, MessageImage } from "@/types" import { IconFileFilled } from "@tabler/icons-react" import Image from "next/image" import { FC } from "react" +import { useTranslation } from "react-i18next" import { DrawingCanvas } from "../utility/drawing-canvas" import { Dialog, DialogContent } from "./dialog" @@ -20,6 +23,8 @@ export const FilePreview: FC = ({ isOpen, onOpenChange }) => { + const { t } = useTranslation() + return ( = ({ File image = ({ width = 200, height = 200 }) => { + const { t } = useTranslation() + const [previewSrc, setPreviewSrc] = useState(src) const [previewImage, setPreviewImage] = useState(image) @@ -28,7 +33,7 @@ const ImagePicker: FC = ({ const file = e.target.files[0] if (file.size > 6000000) { - toast.error("Image must be less than 6MB!") + toast.error(t("Image must be less than 6MB!")) return } @@ -42,7 +47,7 @@ const ImagePicker: FC = ({ const ctx = canvas.getContext("2d") if (!ctx) { - toast.error("Unable to create canvas context.") + toast.error(t("Unable to create canvas context.")) return } @@ -81,7 +86,7 @@ const ImagePicker: FC = ({ height={width} width={width} src={previewSrc} - alt={"Image"} + alt={t("Image")} /> )} diff --git a/components/utility/command-k.tsx b/components/utility/command-k.tsx index 1f13c53748..e4979a0ed5 100644 --- a/components/utility/command-k.tsx +++ b/components/utility/command-k.tsx @@ -1,7 +1,10 @@ +"use client" + import { ChatbotUIContext } from "@/context/context" import useHotkey from "@/lib/hooks/use-hotkey" import { IconLoader2, IconSend } from "@tabler/icons-react" import { FC, useContext, useState } from "react" +import { useTranslation } from "react-i18next" import { Dialog, DialogContent } from "../ui/dialog" import { TextareaAutosize } from "../ui/textarea-autosize" @@ -10,6 +13,7 @@ interface CommandKProps {} export const CommandK: FC = ({}) => { useHotkey("k", () => setIsOpen(prevState => !prevState)) + const { t } = useTranslation() const { profile } = useContext(ChatbotUIContext) const [isOpen, setIsOpen] = useState(false) @@ -51,15 +55,15 @@ export const CommandK: FC = ({}) => {
{content}
-
turn dark mode on.
-
find my sql chat
-
i need a new assistant
-
start a chat with my 2024 resolutions file
+
{t("turn dark mode on.")}
+
{t("find my sql chat")}
+
{t("i need a new assistant")}
+
{t("start a chat with my 2024 resolutions file")}
@@ -78,7 +82,9 @@ export const CommandK: FC = ({}) => {
) : ( -
Add your OpenAI API key in the settings to unlock CMD+K.
+
+ {t("Add your OpenAI API key in the settings to unlock CMD+K.")} +
)}
diff --git a/components/utility/profile-settings.tsx b/components/utility/profile-settings.tsx index 576cee0eb3..f0ef3612c2 100644 --- a/components/utility/profile-settings.tsx +++ b/components/utility/profile-settings.tsx @@ -1,3 +1,5 @@ +"use client" + import { ChatbotUIContext } from "@/context/context" import { PROFILE_CONTEXT_MAX, @@ -24,6 +26,7 @@ import { import Image from "next/image" import { useRouter } from "next/navigation" import { FC, useCallback, useContext, useRef, useState } from "react" +import { useTranslation } from "react-i18next" import { toast } from "sonner" import { SIDEBAR_ICON_SIZE } from "../sidebar/sidebar-switcher" import { Button } from "../ui/button" @@ -46,6 +49,8 @@ import { ThemeSwitcher } from "./theme-switcher" interface ProfileSettingsProps {} export const ProfileSettings: FC = ({}) => { + const { t } = useTranslation() + const { profile, setProfile, @@ -319,7 +324,7 @@ export const ProfileSettings: FC = ({}) => {
-
User Settings
+
{t("User Settings")}
- Profile - API Keys + {t("Profile")} + {t("API Keys")}
- +
{username !== profile.username ? ( usernameAvailable ? ( -
AVAILABLE
+
{t("AVAILABLE")}
) : ( -
UNAVAILABLE
+
{t("UNAVAILABLE")}
) ) : null}
@@ -358,7 +363,7 @@ export const ProfileSettings: FC = ({}) => {
{ setUsername(e.target.value) @@ -388,7 +393,7 @@ export const ProfileSettings: FC = ({}) => {
- + = ({}) => {
- + setDisplayName(e.target.value)} maxLength={PROFILE_DISPLAY_NAME_MAX} @@ -413,14 +418,15 @@ export const ProfileSettings: FC = ({}) => {
@@ -438,10 +444,10 @@ export const ProfileSettings: FC = ({}) => { {useAzureOpenai ? envKeyMap["azure"] ? "" - : "Azure OpenAI API Key" + : t("Azure OpenAI API Key") : envKeyMap["openai"] ? "" - : "OpenAI API Key"} + : t("OpenAI API Key")} {useAzureOpenai ? ( <> {envKeyMap["azure"] ? ( - + ) : ( setAzureOpenaiAPIKey(e.target.value)} @@ -475,10 +481,10 @@ export const ProfileSettings: FC = ({}) => { ) : ( <> {envKeyMap["openai"] ? ( - + ) : ( setOpenaiAPIKey(e.target.value)} @@ -495,11 +501,11 @@ export const ProfileSettings: FC = ({}) => {
{envKeyMap["azure_openai_endpoint"] ? ( ) : ( <> - + = ({}) => {
{envKeyMap["azure_gpt_35_turbo_name"] ? ( ) : ( <> - + setAzureOpenai35TurboID(e.target.value) @@ -539,14 +551,20 @@ export const ProfileSettings: FC = ({}) => {
{envKeyMap["azure_gpt_45_turbo_name"] ? ( ) : ( <> - + setAzureOpenai45TurboID(e.target.value) @@ -561,14 +579,20 @@ export const ProfileSettings: FC = ({}) => {
{envKeyMap["azure_gpt_45_vision_name"] ? ( ) : ( <> - + setAzureOpenai45VisionID(e.target.value) @@ -583,14 +607,20 @@ export const ProfileSettings: FC = ({}) => {
{envKeyMap["azure_embeddings_name"] ? ( ) : ( <> - + setAzureEmbeddingsID(e.target.value) @@ -606,14 +636,14 @@ export const ProfileSettings: FC = ({}) => {
{envKeyMap["openai_organization_id"] ? ( ) : ( <> - + = ({}) => {
{envKeyMap["anthropic"] ? ( - + ) : ( <> - + setAnthropicAPIKey(e.target.value)} @@ -646,12 +676,12 @@ export const ProfileSettings: FC = ({}) => {
{envKeyMap["google"] ? ( - + ) : ( <> - + setGoogleGeminiAPIKey(e.target.value)} @@ -662,12 +692,12 @@ export const ProfileSettings: FC = ({}) => {
{envKeyMap["mistral"] ? ( - + ) : ( <> - + setMistralAPIKey(e.target.value)} @@ -678,12 +708,12 @@ export const ProfileSettings: FC = ({}) => {
{envKeyMap["groq"] ? ( - + ) : ( <> - + setGroqAPIKey(e.target.value)} @@ -694,12 +724,12 @@ export const ProfileSettings: FC = ({}) => {
{envKeyMap["perplexity"] ? ( - + ) : ( <> - + setPerplexityAPIKey(e.target.value)} @@ -710,12 +740,12 @@ export const ProfileSettings: FC = ({}) => {
{envKeyMap["openrouter"] ? ( - + ) : ( <> - + setOpenrouterAPIKey(e.target.value)} @@ -734,7 +764,9 @@ export const ProfileSettings: FC = ({}) => { - Download Chatbot UI 1.0 data as JSON. Import coming soon! + {t( + "Download Chatbot UI 1.0 data as JSON. Import coming soon!" + )}
} trigger={ @@ -749,11 +781,11 @@ export const ProfileSettings: FC = ({}) => {
diff --git a/components/utility/workspace-switcher.tsx b/components/utility/workspace-switcher.tsx index 261dc27c5b..2730beaed8 100644 --- a/components/utility/workspace-switcher.tsx +++ b/components/utility/workspace-switcher.tsx @@ -14,6 +14,7 @@ import { ChevronsUpDown } from "lucide-react" import Image from "next/image" import { useRouter } from "next/navigation" import { FC, useContext, useEffect, useState } from "react" +import { useTranslation } from "react-i18next" import { Button } from "../ui/button" import { Input } from "../ui/input" @@ -22,6 +23,8 @@ interface WorkspaceSwitcherProps {} export const WorkspaceSwitcher: FC = ({}) => { useHotkey(";", () => setOpen(prevState => !prevState)) + const { t } = useTranslation() + const { workspaces, workspaceImages, @@ -124,7 +127,7 @@ export const WorkspaceSwitcher: FC = ({}) => {
)} - {getWorkspaceName(value) || "Select workspace..."} + {getWorkspaceName(value) || t("Select workspace...")}
@@ -138,11 +141,11 @@ export const WorkspaceSwitcher: FC = ({}) => { onClick={handleCreateWorkspace} > -
New Workspace
+
{t("New Workspace")}
setSearch(e.target.value)} diff --git a/components/workspace/delete-workspace.tsx b/components/workspace/delete-workspace.tsx index ff76e35695..cadf47e6dd 100644 --- a/components/workspace/delete-workspace.tsx +++ b/components/workspace/delete-workspace.tsx @@ -1,3 +1,5 @@ +"use client" + import { useChatHandler } from "@/components/chat/chat-hooks/use-chat-handler" import { Button } from "@/components/ui/button" import { @@ -12,9 +14,10 @@ import { import { ChatbotUIContext } from "@/context/context" import { deleteWorkspace } from "@/db/workspaces" import { Tables } from "@/supabase/types" +import { useRouter } from "next/navigation" import { FC, useContext, useRef, useState } from "react" +import { useTranslation } from "react-i18next" import { Input } from "../ui/input" -import { useRouter } from "next/navigation" interface DeleteWorkspaceProps { workspace: Tables<"workspaces"> @@ -28,6 +31,7 @@ export const DeleteWorkspace: FC = ({ const { setWorkspaces, setSelectedWorkspace } = useContext(ChatbotUIContext) const { handleNewChat } = useChatHandler() const router = useRouter() + const { t } = useTranslation() const buttonRef = useRef(null) @@ -66,28 +70,30 @@ export const DeleteWorkspace: FC = ({ return ( - + - Delete {workspace.name} + + {t("Delete")} {workspace.name} + - WARNING: Deleting a workspace will delete all of its data. + {t("WARNING: Deleting a workspace will delete all of its data.")} setName(e.target.value)} /> diff --git a/components/workspace/workspace-settings.tsx b/components/workspace/workspace-settings.tsx index 083c7e9d89..220b88bc69 100644 --- a/components/workspace/workspace-settings.tsx +++ b/components/workspace/workspace-settings.tsx @@ -1,3 +1,5 @@ +"use client" + import { ChatbotUIContext } from "@/context/context" import { WORKSPACE_INSTRUCTIONS_MAX } from "@/db/limits" import { @@ -9,6 +11,7 @@ import { convertBlobToBase64 } from "@/lib/blob-to-b64" import { LLMID } from "@/types" import { IconHome, IconSettings } from "@tabler/icons-react" import { FC, useContext, useEffect, useRef, useState } from "react" +import { useTranslation } from "react-i18next" import { toast } from "sonner" import { Button } from "../ui/button" import { ChatSettingsForm } from "../ui/chat-settings-form" @@ -31,6 +34,8 @@ import { DeleteWorkspace } from "./delete-workspace" interface WorkspaceSettingsProps {} export const WorkspaceSettings: FC = ({}) => { + const { t } = useTranslation() + const { profile, selectedWorkspace, @@ -168,7 +173,7 @@ export const WorkspaceSettings: FC = ({}) => { Workspace Settings
} + display={
{t("Workspace Settings")}
} trigger={ = ({}) => {
- Workspace Settings + {t("Workspace Settings")} {selectedWorkspace?.is_home && } {selectedWorkspace?.is_home && (
- This is your home workspace for personal use. + {t("This is your home workspace for personal use.")}
)}
- Main - Defaults + {t("Main")} + {t("Defaults")} <>
- + setName(e.target.value)} /> @@ -227,7 +232,7 @@ export const WorkspaceSettings: FC = ({}) => {
*/}
- + = ({}) => {
= ({}) => {
- These are the settings your workspace begins with when selected. + {t( + "These are the settings your workspace begins with when selected." + )}
= ({}) => {
diff --git a/db/files.ts b/db/files.ts index 68ad01e343..0b69625e68 100644 --- a/db/files.ts +++ b/db/files.ts @@ -94,7 +94,10 @@ export const createFile = async ( let validFilename = fileRecord.name.replace(/[^a-z0-9.]/gi, "_").toLowerCase() const extension = file.name.split(".").pop() const extensionIndex = validFilename.lastIndexOf(".") - const baseName = validFilename.substring(0, (extensionIndex < 0) ? undefined : extensionIndex) + const baseName = validFilename.substring( + 0, + extensionIndex < 0 ? undefined : extensionIndex + ) const maxBaseNameLength = 100 - (extension?.length || 0) - 1 if (baseName.length > maxBaseNameLength) { fileRecord.name = baseName.substring(0, maxBaseNameLength) + "." + extension diff --git a/lib/build-prompt.ts b/lib/build-prompt.ts index ddb0e76377..8bda846f9f 100644 --- a/lib/build-prompt.ts +++ b/lib/build-prompt.ts @@ -184,36 +184,35 @@ function buildRetrievalText(fileItems: Tables<"file_items">[]) { } function adaptSingleMessageForGoogleGemini(message: any) { - let adaptedParts = [] let rawParts = [] - if(!Array.isArray(message.content)) { - rawParts.push({type: 'text', text: message.content}) + if (!Array.isArray(message.content)) { + rawParts.push({ type: "text", text: message.content }) } else { rawParts = message.content } - for(let i = 0; i < rawParts.length; i++) { + for (let i = 0; i < rawParts.length; i++) { let rawPart = rawParts[i] - if(rawPart.type == 'text') { - adaptedParts.push({text: rawPart.text}) - } else if(rawPart.type === 'image_url') { + if (rawPart.type == "text") { + adaptedParts.push({ text: rawPart.text }) + } else if (rawPart.type === "image_url") { adaptedParts.push({ inlineData: { data: getBase64FromDataURL(rawPart.image_url.url), - mimeType: getMediaTypeFromDataURL(rawPart.image_url.url), + mimeType: getMediaTypeFromDataURL(rawPart.image_url.url) } }) } } - let role = 'user' - if(["user", "system"].includes(message.role)) { - role = 'user' - } else if(message.role === 'assistant') { - role = 'model' + let role = "user" + if (["user", "system"].includes(message.role)) { + role = "user" + } else if (message.role === "assistant") { + role = "model" } return { @@ -222,29 +221,29 @@ function adaptSingleMessageForGoogleGemini(message: any) { } } -function adaptMessagesForGeminiVision( - messages: any[] -) { +function adaptMessagesForGeminiVision(messages: any[]) { // Gemini Pro Vision cannot process multiple messages // Reformat, using all texts and last visual only const basePrompt = messages[0].parts[0].text const baseRole = messages[0].role - const lastMessage = messages[messages.length-1] - const visualMessageParts = lastMessage.parts; - let visualQueryMessages = [{ - role: "user", - parts: [ - `${baseRole}:\n${basePrompt}\n\nuser:\n${visualMessageParts[0].text}\n\n`, - visualMessageParts.slice(1) - ] - }] + const lastMessage = messages[messages.length - 1] + const visualMessageParts = lastMessage.parts + let visualQueryMessages = [ + { + role: "user", + parts: [ + `${baseRole}:\n${basePrompt}\n\nuser:\n${visualMessageParts[0].text}\n\n`, + visualMessageParts.slice(1) + ] + } + ] return visualQueryMessages } export async function adaptMessagesForGoogleGemini( payload: ChatPayload, - messages: any[] + messages: any[] ) { let geminiMessages = [] for (let i = 0; i < messages.length; i++) { @@ -252,9 +251,8 @@ export async function adaptMessagesForGoogleGemini( geminiMessages.push(adaptedMessage) } - if(payload.chatSettings.model === "gemini-pro-vision") { + if (payload.chatSettings.model === "gemini-pro-vision") { geminiMessages = adaptMessagesForGeminiVision(geminiMessages) } return geminiMessages } - diff --git a/lib/chat-setting-limits.ts b/lib/chat-setting-limits.ts index c802bd657b..1dcde08490 100644 --- a/lib/chat-setting-limits.ts +++ b/lib/chat-setting-limits.ts @@ -47,7 +47,7 @@ export const CHAT_SETTING_LIMITS: Record = { }, // GOOGLE MODELS - + "gemini-1.5-flash": { MIN_TEMPERATURE: 0.0, MAX_TEMPERATURE: 1.0, diff --git a/lib/models/llm/google-llm-list.ts b/lib/models/llm/google-llm-list.ts index d039eda4af..833eb7ec2b 100644 --- a/lib/models/llm/google-llm-list.ts +++ b/lib/models/llm/google-llm-list.ts @@ -44,4 +44,9 @@ const GEMINI_PRO_VISION: LLM = { imageInput: true } -export const GOOGLE_LLM_LIST: LLM[] = [GEMINI_PRO, GEMINI_PRO_VISION, GEMINI_1_5_PRO, GEMINI_1_5_FLASH] +export const GOOGLE_LLM_LIST: LLM[] = [ + GEMINI_PRO, + GEMINI_PRO_VISION, + GEMINI_1_5_PRO, + GEMINI_1_5_FLASH +] diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index b6238ed079..c1019ae296 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -1,3 +1,211 @@ { - "Ask anything. Type \"/\" for prompts, \"@\" for files, and \"#\" for tools.": "Ask anything. Type \"/\" for prompts, \"@\" for files, and \"#\" for tools." + "Ask anything. Type \"/\" for prompts, \"@\" for files, and \"#\" for tools.": "Ask anything. Type \"/\" for prompts, \"@\" for files, and \"#\" for tools.", + "Login": "Login", + "Email": "Email", + "you@example.com": "you@example.com", + "Password": "Password", + "Sign Up": "Sign Up", + "Forgot your password?": "Forgot your password?", + "Reset": "Reset", + "Check email to reset password": "Check email to reset password", + "Email {email} is not allowed to sign up.": "Email {email} is not allowed to sign up.", + "Check email to continue sign in process": "Check email to continue sign in process", + "Let's create your profile.": "Let's create your profile.", + "Welcome to Chatbot UI": "Welcome to Chatbot UI", + "Enter API keys for each service you'd like to use.": "Enter API keys for each service you'd like to use.", + "Set API Keys (optional)": "Set API Keys (optional)", + "You are all set up!": "You are all set up!", + "Setup Complete": "Setup Complete", + "Back": "Back", + "Next": "Next", + "Username": "Username", + "AVAILABLE": "AVAILABLE", + "UNAVAILABLE": "UNAVAILABLE", + "username": "username", + "Username must be letters, numbers, or underscores only - no other characters or spacing allowed.": "Username must be letters, numbers, or underscores only - no other characters or spacing allowed.", + "Chat Display Name": "Chat Display Name", + "Your Name": "Your Name", + "Azure OpenAI API Key": "Azure OpenAI API Key", + "OpenAI API Key": "OpenAI API Key", + "Switch To Standard OpenAI": "Switch To Standard OpenAI", + "Switch To Azure OpenAI": "Switch To Azure OpenAI", + "Azure OpenAI Endpoint": "Azure OpenAI Endpoint", + "https://your-endpoint.openai.azure.com": "https://your-endpoint.openai.azure.com", + "Azure OpenAI GPT-3.5 Turbo ID": "Azure OpenAI GPT-3.5 Turbo ID", + "Azure OpenAI GPT-4.5 Turbo ID": "Azure OpenAI GPT-4.5 Turbo ID", + "Azure OpenAI GPT-4.5 Vision ID": "Azure OpenAI GPT-4.5 Vision ID", + "Azure OpenAI Embeddings ID": "Azure OpenAI Embeddings ID", + "OpenAI Organization ID": "OpenAI Organization ID", + "OpenAI Organization ID (optional)": "OpenAI Organization ID (optional)", + "Anthropic API Key": "Anthropic API Key", + "Google Gemini API Key": "Google Gemini API Key", + "Mistral API Key": "Mistral API Key", + "Groq API Key": "Groq API Key", + "Perplexity API Key": "Perplexity API Key", + "OpenRouter API Key": "OpenRouter API Key", + "Click next to start chatting.": "Click next to start chatting.", + "Show Help": "Show Help", + "Show Workspaces": "Show Workspaces", + "New Chat": "New Chat", + "Focus Chat": "Focus Chat", + "Toggle Files": "Toggle Files", + "Toggle Retrieval": "Toggle Retrieval", + "Open Settings": "Open Settings", + "Open Quick Settings": "Open Quick Settings", + "Toggle Sidebar": "Toggle Sidebar", + "Ask anything. Type @ / # !": "Ask anything. Type @ / # !", + "Images are not supported for this model. Use models like GPT-4 Vision instead.": "Images are not supported for this model. Use models like GPT-4 Vision instead.", + "Talking to {{selectedAssistantName}}": "Talking to {{selectedAssistantName}}", + "Model": "Model", + "Prompt": "Prompt", + "You are a helpful AI assistant.": "You are a helpful AI assistant.", + "Temperature:": "Temperature:", + "Context Length:": "Context Length:", + "Chats Include Profile Context": "Chats Include Profile Context", + "No profile context.": "No profile context.", + "Chats Include Workspace Instructions": "Chats Include Workspace Instructions", + "No workspace instructions.": "No workspace instructions.", + "Embeddings Provider": "Embeddings Provider", + "Azure OpenAI": "Azure OpenAI", + "OpenAI": "OpenAI", + "Local": "Local", + "Advanced Settings": "Advanced Settings", + "View Replies": "View Replies", + "Are you sure absolutely sure?": "Are you sure absolutely sure?", + "This action cannot be undone. This will permanently delete your account and remove your data from our servers.": "This action cannot be undone. This will permanently delete your account and remove your data from our servers.", + "Edit {{contentType}}": "Edit {{contentType}}", + "Assigned Workspaces": "Assigned Workspaces", + "Cancel": "Cancel", + "Save": "Save", + "{{contentType}} updated successfully": "{{contentType}} updated successfully", + "Error updating {{contentType}}.": "Error updating {{contentType}}.", + "User Settings": "User Settings", + "Logout": "Logout", + "Profile": "Profile", + "API Keys": "API Keys", + "Username...": "Username...", + "Profile Image": "Profile Image", + "Chat display name...": "Chat display name...", + "What would you like the AI to know about you to provide better responses?": "What would you like the AI to know about you to provide better responses?", + "Profile context... (optional)": "Profile context... (optional)", + "Azure OpenAI API key set by admin.": "Azure OpenAI API key set by admin.", + "OpenAI API key set by admin.": "OpenAI API key set by admin.", + "Azure endpoint set by admin.": "Azure endpoint set by admin.", + "Azure GPT-3.5 Turbo deployment name set by admin.": "Azure GPT-3.5 Turbo deployment name set by admin.", + "Azure GPT-4.5 Turbo deployment name set by admin.": "Azure GPT-4.5 Turbo deployment name set by admin.", + "Azure GPT-4.5 Vision deployment name set by admin.": "Azure GPT-4.5 Vision deployment name set by admin.", + "Azure Embeddings deployment name set by admin.": "Azure Embeddings deployment name set by admin.", + "OpenAI Organization ID set by admin.": "OpenAI Organization ID set by admin.", + "Anthropic API key set by admin.": "Anthropic API key set by admin.", + "Google Gemini API key set by admin.": "Google Gemini API key set by admin.", + "Mistral API key set by admin.": "Mistral API key set by admin.", + "Groq API key set by admin.": "Groq API key set by admin.", + "Perplexity API key set by admin.": "Perplexity API key set by admin.", + "OpenRouter API key set by admin.": "OpenRouter API key set by admin.", + "Download Chatbot UI 1.0 data as JSON. Import coming soon!": "Download Chatbot UI 1.0 data as JSON. Import coming soon!", + "Adjust retrieval settings.": "Adjust retrieval settings.", + "Source Count:": "Source Count:", + "Save & Close": "Save & Close", + "Create a custom model.": "Create a custom model.", + "Your API": "Your API", + "*must*": "*must*", + "be compatible with the OpenAI SDK.": "be compatible with the OpenAI SDK.", + "Model name...": "Model name...", + "Model ID": "Model ID", + "Model ID...": "Model ID...", + "Base URL": "Base URL", + "Base URL...": "Base URL...", + "Your API must be compatible with the OpenAI SDK.": "Your API must be compatible with the OpenAI SDK.", + "API Key...": "API Key...", + "Max Context Length": "Max Context Length", + "4096": "4096", + "turn dark mode on.": "turn dark mode on.", + "find my sql chat": "find my sql chat", + "i need a new assistant": "i need a new assistant", + "start a chat with my 2024 resolutions file": "start a chat with my 2024 resolutions file", + "create a prompt for writing sql code": "create a prompt for writing sql code", + "Add your OpenAI API key in the settings to unlock CMD+K.": "Add your OpenAI API key in the settings to unlock CMD+K.", + "Help under construction.": "Help under construction.", + "Chatbot UI": "Chatbot UI", + "Start Chatting": "Start Chatting", + "Hide files": "Hide files", + "File image": "File image", + "View {{count}} files": "View {{count}} files", + "View file": "View file", + "File retrieval is enabled on the selected files for this message. Click the indicator to disable.": "File retrieval is enabled on the selected files for this message. Click the indicator to disable.", + "Click the indicator to enable file retrieval for this message.": "Click the indicator to enable file retrieval for this message.", + "Chat Info": "Chat Info", + "Temperature": "Temperature", + "Context Length": "Context Length", + "Profile Context": "Profile Context", + "Enabled": "Enabled", + "Disabled": "Disabled", + "Workspace Instructions": "Workspace Instructions", + "Start a new chat": "Start a new chat", + "Enter Prompt Variables": "Enter Prompt Variables", + "Enter a value for {variableName}...": "Enter a value for {variableName}...", + "Submit": "Submit", + "No matching prompts.": "No matching prompts.", + "Assistant": "Assistant", + "Loading assistant...": "Loading assistant...", + "Modified ": "Modified ", + "No items found.": "No items found.", + "Search...": "Search...", + "New Workspace": "New Workspace", + "Select workspace...": "Select workspace...", + "Search workspaces...": "Search workspaces...", + "Delete": "Delete", + "WARNING: Deleting a workspace will delete all of its data.": "WARNING: Deleting a workspace will delete all of its data.", + "Type the name of this workspace to confirm": "Type the name of this workspace to confirm", + "Workspace Settings": "Workspace Settings", + "This is your home workspace for personal use.": "This is your home workspace for personal use.", + "Main": "Main", + "Defaults": "Defaults", + "Workspace Name": "Workspace Name", + "Name...": "Name...", + "Workspace Image": "Workspace Image", + "How would you like the AI to respond in this workspace?": "How would you like the AI to respond in this workspace?", + "Instructions... (optional)": "Instructions... (optional)", + "Type the name of this workspace to confirm": "Type the name of this workspace to confirm", + "Unlock models by entering API keys in your profile settings.": "Unlock models by entering API keys in your profile settings.", + "Select a model": "Select a model", + "Hosted": "Hosted", + "Search models...": "Search models...", + "AZURE OPENAI": "AZURE OPENAI", + "Assistant name...": "Assistant name...", + "Assistant description...": "Assistant description...", + "Image": "Image", + "Files & Collections": "Files & Collections", + "Tools": "Tools", + "assistant image": "assistant image", + "user image": "user image", + "message image": "message image", + "Searching files...": "Searching files...", + "Using {toolInUse}...": "Using {toolInUse}...", + "{{fileItemsLength}} Sources from {{fileSummaryLength}} Files": "{{fileItemsLength}} Sources from {{fileSummaryLength}} Files", + "Save & Send": "Save & Send", + "(optional)": "(optional)", + "Description": "Description", + "Model is not compatible with tools.": "Model is not compatible with tools.", + "Image must be less than 6MB!": "Image must be less than 6MB!", + "Unable to create canvas context.": "Unable to create canvas context.", + "Files": "Files", + "Collection name...": "Collection name...", + "Collection description...": "Collection description...", + "Search {{contentType}}...": "Search {{contentType}}...", + "New Folder": "New Folder", + "New {{contentType}}": "New {{contentType}}", + "Today": "Today", + "Yesterday": "Yesterday", + "Previous Week": "Previous Week", + "Older": "Older", + "No {{contentType}}.": "No {{contentType}}.", + "chats": "chats", + "presets": "presets", + "prompts": "prompts", + "files": "files", + "collections": "collections", + "assistants": "assistants", + "tools": "tools", + "models": "models" } diff --git a/public/locales/es/translation.json b/public/locales/es/translation.json new file mode 100644 index 0000000000..83b0b6a8de --- /dev/null +++ b/public/locales/es/translation.json @@ -0,0 +1,211 @@ +{ + "Ask anything. Type \"/\" for prompts, \"@\" for files, and \"#\" for tools.": "Pregunta cualquier cosa. Escribe \"/\" para prompts, \"@\" para archivos y \"#\" para herramientas.", + "Login": "Iniciar sesión", + "Email": "Correo electrónico", + "you@example.com": "tu@ejemplo.com", + "Password": "Contraseña", + "Sign Up": "Registrarse", + "Forgot your password?": "¿Olvidaste tu contraseña?", + "Reset": "Restablecer", + "Check email to reset password": "Revisa tu correo electrónico para restablecer la contraseña", + "Email {email} is not allowed to sign up.": "El correo electrónico {email} no tiene permitido registrarse.", + "Check email to continue sign in process": "Revisa tu correo electrónico para continuar el proceso de inicio de sesión", + "Let's create your profile.": "Vamos a crear tu perfil.", + "Welcome to Chatbot UI": "Bienvenido a Chatbot UI", + "Enter API keys for each service you'd like to use.": "Introduce las claves de API para cada servicio que quieras usar.", + "Set API Keys (optional)": "Configurar claves de API (opcional)", + "You are all set up!": "¡Todo listo!", + "Setup Complete": "Configuración completa", + "Back": "Atrás", + "Next": "Siguiente", + "Username": "Nombre de usuario", + "AVAILABLE": "DISPONIBLE", + "UNAVAILABLE": "NO DISPONIBLE", + "username": "nombre de usuario", + "Username must be letters, numbers, or underscores only - no other characters or spacing allowed.": "El nombre de usuario solo debe contener letras, números o guiones bajos, sin otros caracteres o espacios.", + "Chat Display Name": "Nombre a mostrar en el chat", + "Your Name": "Tu nombre", + "Azure OpenAI API Key": "Clave de API de Azure OpenAI", + "OpenAI API Key": "Clave de API de OpenAI", + "Switch To Standard OpenAI": "Cambiar a OpenAI estándar", + "Switch To Azure OpenAI": "Cambiar a Azure OpenAI", + "Azure OpenAI Endpoint": "Punto de conexión de Azure OpenAI", + "https://your-endpoint.openai.azure.com": "https://tu-punto-de-conexion.openai.azure.com", + "Azure OpenAI GPT-3.5 Turbo ID": "ID de Azure OpenAI GPT-3.5 Turbo", + "Azure OpenAI GPT-4.5 Turbo ID": "ID de Azure OpenAI GPT-4.5 Turbo", + "Azure OpenAI GPT-4.5 Vision ID": "ID de Azure OpenAI GPT-4.5 Vision", + "Azure OpenAI Embeddings ID": "ID de embeddings de Azure OpenAI", + "OpenAI Organization ID": "ID de organización de OpenAI", + "OpenAI Organization ID (optional)": "ID de organización de OpenAI (opcional)", + "Anthropic API Key": "Clave de API de Anthropic", + "Google Gemini API Key": "Clave de API de Google Gemini", + "Mistral API Key": "Clave de API de Mistral", + "Groq API Key": "Clave de API de Groq", + "Perplexity API Key": "Clave de API de Perplexity", + "OpenRouter API Key": "Clave de API de OpenRouter", + "Click next to start chatting.": "Haz clic en siguiente para empezar a chatear.", + "Show Help": "Mostrar ayuda", + "Show Workspaces": "Mostrar espacios de trabajo", + "New Chat": "Nuevo chat", + "Focus Chat": "Enfocar chat", + "Toggle Files": "Alternar archivos", + "Toggle Retrieval": "Alternar recuperación", + "Open Settings": "Abrir configuración", + "Open Quick Settings": "Abrir configuración rápida", + "Toggle Sidebar": "Alternar barra lateral", + "Ask anything. Type @ / # !": "Pregunta cualquier cosa. Escribe @ / # !", + "Images are not supported for this model. Use models like GPT-4 Vision instead.": "Las imágenes no son compatibles con este modelo. Usa modelos como GPT-4 Vision en su lugar.", + "Talking to {{selectedAssistantName}}": "Hablando con {{selectedAssistantName}}", + "Model": "Modelo", + "Prompt": "Prompt", + "You are a helpful AI assistant.": "Eres un asistente de IA útil.", + "Temperature:": "Temperatura:", + "Context Length:": "Longitud del contexto:", + "Chats Include Profile Context": "Los chats incluyen el contexto del perfil", + "No profile context.": "Sin contexto de perfil.", + "Chats Include Workspace Instructions": "Los chats incluyen instrucciones del espacio de trabajo", + "No workspace instructions.": "Sin instrucciones del espacio de trabajo.", + "Embeddings Provider": "Proveedor de embeddings", + "Azure OpenAI": "Azure OpenAI", + "OpenAI": "OpenAI", + "Local": "Local", + "Advanced Settings": "Configuraciones avanzadas", + "View Replies": "Ver respuestas", + "Are you sure absolutely sure?": "¿Estás absolutamente seguro?", + "This action cannot be undone. This will permanently delete your account and remove your data from our servers.": "Esta acción no se puede deshacer. Esto eliminará permanentemente tu cuenta y tus datos de nuestros servidores.", + "Edit {{contentType}}": "Editar {{contentType}}", + "Assigned Workspaces": "Espacios de trabajo asignados", + "Cancel": "Cancelar", + "Save": "Guardar", + "{{contentType}} updated successfully": "{{contentType}} actualizado correctamente", + "Error updating {{contentType}}.": "Error al actualizar {{contentType}}.", + "User Settings": "Configuración de usuario", + "Logout": "Cerrar sesión", + "Profile": "Perfil", + "API Keys": "Claves de API", + "Username...": "Nombre de usuario...", + "Profile Image": "Imagen de perfil", + "Chat display name...": "Nombre a mostrar en el chat...", + "What would you like the AI to know about you to provide better responses?": "¿Qué te gustaría que la IA sepa de ti para darte mejores respuestas?", + "Profile context... (optional)": "Contexto del perfil... (opcional)", + "Azure OpenAI API key set by admin.": "Clave de API de Azure OpenAI configurada por el administrador.", + "OpenAI API key set by admin.": "Clave de API de OpenAI configurada por el administrador.", + "Azure endpoint set by admin.": "Punto de conexión de Azure configurado por el administrador.", + "Azure GPT-3.5 Turbo deployment name set by admin.": "Nombre de despliegue de Azure GPT-3.5 Turbo configurado por el administrador.", + "Azure GPT-4.5 Turbo deployment name set by admin.": "Nombre de despliegue de Azure GPT-4.5 Turbo configurado por el administrador.", + "Azure GPT-4.5 Vision deployment name set by admin.": "Nombre de despliegue de Azure GPT-4.5 Vision configurado por el administrador.", + "Azure Embeddings deployment name set by admin.": "Nombre de despliegue de Azure Embeddings configurado por el administrador.", + "OpenAI Organization ID set by admin.": "ID de organización de OpenAI configurado por el administrador.", + "Anthropic API key set by admin.": "Clave de API de Anthropic configurada por el administrador.", + "Google Gemini API key set by admin.": "Clave de API de Google Gemini configurada por el administrador.", + "Mistral API key set by admin.": "Clave de API de Mistral configurada por el administrador.", + "Groq API key set by admin.": "Clave de API de Groq configurada por el administrador.", + "Perplexity API key set by admin.": "Clave de API de Perplexity configurada por el administrador.", + "OpenRouter API key set by admin.": "Clave de API de OpenRouter configurada por el administrador.", + "Download Chatbot UI 1.0 data as JSON. Import coming soon!": "Descargar datos de Chatbot UI 1.0 como JSON. ¡Próximamente la importación!", + "Adjust retrieval settings.": "Ajustar la configuración de recuperación.", + "Source Count:": "Número de fuentes:", + "Save & Close": "Guardar y cerrar", + "Create a custom model.": "Crear un modelo personalizado.", + "Your API": "Tu API", + "*must*": "*debe*", + "be compatible with the OpenAI SDK.": "ser compatible con el SDK de OpenAI.", + "Model name...": "Nombre del modelo...", + "Model ID": "ID del modelo", + "Model ID...": "ID del modelo...", + "Base URL": "URL base", + "Base URL...": "URL base...", + "Your API must be compatible with the OpenAI SDK.": "Tu API debe ser compatible con el SDK de OpenAI.", + "API Key...": "Clave de API...", + "Max Context Length": "Longitud máxima de contexto", + "4096": "4096", + "turn dark mode on.": "activar modo oscuro.", + "find my sql chat": "encontrar mi chat de sql", + "i need a new assistant": "necesito un nuevo asistente", + "start a chat with my 2024 resolutions file": "iniciar un chat con mi archivo de resoluciones de 2024", + "create a prompt for writing sql code": "crear un prompt para escribir código sql", + "Add your OpenAI API key in the settings to unlock CMD+K.": "Agrega tu clave de API de OpenAI en la configuración para desbloquear CMD+K.", + "Help under construction.": "Ayuda en construcción.", + "Chatbot UI": "Chatbot UI", + "Start Chatting": "Empezar a chatear", + "Hide files": "Ocultar archivos", + "File image": "Imagen de archivo", + "View {{count}} file": "Ver {{count}} archivos", + "View file": "Ver archivo", + "File retrieval is enabled on the selected files for this message. Click the indicator to disable.": "La recuperación de archivos está habilitada en los archivos seleccionados para este mensaje. Haz clic en el indicador para deshabilitarla.", + "Click the indicator to enable file retrieval for this message.": "Haz clic en el indicador para habilitar la recuperación de archivos para este mensaje.", + "Chat Info": "Información del chat", + "Temperature": "Temperatura", + "Context Length": "Longitud del contexto", + "Profile Context": "Contexto del perfil", + "Enabled": "Habilitado", + "Disabled": "Deshabilitado", + "Workspace Instructions": "Instrucciones del espacio de trabajo", + "Start a new chat": "Empezar un nuevo chat", + "Enter Prompt Variables": "Introduce las variables del prompt", + "Enter a value for {variableName}...": "Introduce un valor para {variableName}...", + "Submit": "Enviar", + "No matching prompts.": "No hay prompts que coincidan.", + "Assistant": "Asistente", + "Loading assistant...": "Cargando asistente...", + "Modified ": "Modificado ", + "No items found.": "No se encontraron elementos.", + "Search...": "Buscar...", + "New Workspace": "Nuevo espacio de trabajo", + "Select workspace...": "Seleccionar espacio de trabajo...", + "Search workspaces...": "Buscar espacios de trabajo...", + "Delete": "Eliminar", + "WARNING: Deleting a workspace will delete all of its data.": "ADVERTENCIA: Eliminar un espacio de trabajo eliminará todos sus datos.", + "Type the name of this workspace to confirm": "Escribe el nombre de este espacio de trabajo para confirmar", + "Workspace Settings": "Configuración del espacio de trabajo", + "This is your home workspace for personal use.": "Este es tu espacio de trabajo personal.", + "Main": "Principal", + "Defaults": "Valores por defecto", + "Workspace Name": "Nombre del espacio de trabajo", + "Name...": "Nombre...", + "Workspace Image": "Imagen del espacio de trabajo", + "How would you like the AI to respond in this workspace?": "¿Cómo te gustaría que la IA respondiera en este espacio de trabajo?", + "Instructions... (optional)": "Instrucciones... (opcional)", + "Type the name of this workspace to confirm": "Escribe el nombre de este espacio de trabajo para confirmar", + "Unlock models by entering API keys in your profile settings.": "Desbloquea modelos introduciendo claves de API en la configuración de tu perfil.", + "Select a model": "Selecciona un modelo", + "Hosted": "Alojado", + "Search models...": "Buscar modelos...", + "AZURE OPENAI": "AZURE OPENAI", + "Assistant name...": "Nombre del asistente...", + "Assistant description...": "Descripción del asistente...", + "Image": "Imagen", + "Files & Collections": "Archivos y colecciones", + "Tools": "Herramientas", + "assistant image": "imagen de asistente", + "user image": "imagen de usuario", + "message image": "imagen de mensaje", + "Searching files...": "Buscando archivos...", + "Using {toolInUse}...": "Usando {toolInUse}...", + "{{fileItemsLength}} Sources from {{fileSummaryLength}} Files": "{{fileItemsLength}} Fuentes de {{fileSummaryLength}} Archivos", + "Save & Send": "Guardar y enviar", + "(optional)": "(opcional)", + "Description": "Descripción", + "Model is not compatible with tools.": "El modelo no es compatible con las herramientas.", + "Image must be less than 6MB!": "¡La imagen debe pesar menos de 6MB!", + "Unable to create canvas context.": "No se puede crear el contexto del lienzo.", + "Files": "Archivos", + "Collection name...": "Nombre de la colección...", + "Collection description...": "Descripción de la colección...", + "Search {{contentType}}...": "Buscar {{contentType}}...", + "New Folder": "Nueva Carpeta", + "New {{contentType}}": "Nuevo {{contentType}}", + "Today": "Hoy", + "Yesterday": "Ayer", + "Previous Week": "La Semana Pasada", + "Older": "Anteriores", + "No {{contentType}}.": "Sin {{contentType}}.", + "chats": "chats", + "presets": "preestablecidos", + "prompts": "prompts", + "files": "archivos", + "collections": "colecciones", + "assistants": "asistentes", + "tools": "herramientas", + "models": "modelos" +}