diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/CompareUI.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/CompareUI.tsx index 1259f2daeae..e2fc05ada05 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/CompareUI.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/CompareUI.tsx @@ -498,7 +498,7 @@ export default function CompareUI({ accessToken, disabledPersonalKeyCreation }: } // Validate selection based on endpoint type if (targetComparisons.some((comparison) => !hasValidSelection(comparison, selectedEndpoint))) { - toast.fromError(endpointConfig.validationMessage); + toast.fromError(t(endpointConfig.validationMessage)); return; } @@ -825,10 +825,10 @@ export default function CompareUI({ accessToken, disabledPersonalKeyCreation }: ) : isAnyComparisonLoading ? ( - {endpointConfig.loadingMessage} + {t(endpointConfig.loadingMessage)} ) : ( - {endpointConfig.inputPlaceholder} + {t(endpointConfig.inputPlaceholder)} )} {uploadedFile && ( diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/components/UnifiedSelector.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/components/UnifiedSelector.test.tsx index d471a6ddaf8..a92ab1f2604 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/components/UnifiedSelector.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/components/UnifiedSelector.test.tsx @@ -1,6 +1,7 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { describe, expect, it, vi } from "vitest"; +import i18n from "@/i18n/i18n"; import { UnifiedSelector } from "./UnifiedSelector"; import { EndpointId, ENDPOINT_CONFIGS } from "../endpoint_config"; @@ -32,19 +33,19 @@ describe("UnifiedSelector", () => { it("prompts with the endpoint's own selector copy", () => { render(); - expect(promptIsVisible(CHAT.selectorPlaceholder)).toBe(true); + expect(promptIsVisible(i18n.t(CHAT.selectorPlaceholder))).toBe(true); }); it("prompts with the agent endpoint's copy when configured for agents", () => { render(); - expect(promptIsVisible(AGENTS.selectorPlaceholder)).toBe(true); + expect(promptIsVisible(i18n.t(AGENTS.selectorPlaceholder))).toBe(true); }); it("swaps the prompt for a loading message while options are in flight", () => { render(); - expect(promptIsVisible(`Loading ${CHAT.selectorLabel.toLowerCase()}s...`)).toBe(true); + expect(promptIsVisible(`Loading ${i18n.t(CHAT.selectorLabel).toLowerCase()}s...`)).toBe(true); }); it("reports the chosen option's value, not its label", async () => { @@ -116,6 +117,6 @@ describe("UnifiedSelector", () => { await openList(user); - expect(await screen.findByText(`No ${CHAT.selectorLabel.toLowerCase()}s available`)).toBeInTheDocument(); + expect(await screen.findByText(`No ${i18n.t(CHAT.selectorLabel).toLowerCase()}s available`)).toBeInTheDocument(); }); }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/components/UnifiedSelector.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/components/UnifiedSelector.tsx index 07518c3b656..8e042b8e9e3 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/components/UnifiedSelector.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/components/UnifiedSelector.tsx @@ -29,7 +29,7 @@ const matchesQuery = (option: SelectorOption, query: string): boolean => export function UnifiedSelector({ value, options, loading, config, onChange }: UnifiedSelectorProps) { const { t } = useTranslation(); const selected = options.find((option) => option.value === value) ?? null; - const noun = config.selectorLabel.toLowerCase(); + const noun = t(config.selectorLabel).toLowerCase(); return ( diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/endpoint_config.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/endpoint_config.test.ts index 6463b2f9e5e..4172dcf6e4e 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/endpoint_config.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/endpoint_config.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from "vitest"; +import i18n from "@/i18n/i18n"; import { EndpointId, ENDPOINT_CONFIGS, @@ -44,7 +45,7 @@ describe("endpoint_config", () => { const config = getEndpointConfig(EndpointId.CHAT_COMPLETIONS); expect(config.id).toBe(EndpointId.CHAT_COMPLETIONS); expect(config.selectorType).toBe("model"); - expect(config.selectorLabel).toBe("Model"); + expect(i18n.t(config.selectorLabel)).toBe("Model"); }); it("should check if endpoint is agent endpoint", () => { diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/endpoint_config.ts b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/endpoint_config.ts index 2d1ebdaedd6..ab30e90ae28 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/endpoint_config.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/endpoint_config.ts @@ -3,6 +3,7 @@ * Add new endpoints here to extend the comparison functionality. */ +import type { ParseKeys } from "i18next"; import { Agent } from "../../llm_calls/fetch_agents"; // Endpoint identifiers @@ -23,11 +24,11 @@ export interface EndpointConfig { id: EndpointIdType; label: string; selectorType: SelectorType; - selectorLabel: string; - selectorPlaceholder: string; - inputPlaceholder: string; - loadingMessage: string; - validationMessage: string; + selectorLabel: ParseKeys; + selectorPlaceholder: ParseKeys; + inputPlaceholder: ParseKeys; + loadingMessage: ParseKeys; + validationMessage: ParseKeys; } // Endpoint configurations @@ -36,21 +37,21 @@ export const ENDPOINT_CONFIGS: Record = { id: EndpointId.CHAT_COMPLETIONS, label: "/v1/chat/completions", selectorType: "model", - selectorLabel: "Model", - selectorPlaceholder: "Select a model", - inputPlaceholder: "Send a prompt to compare models", - loadingMessage: "Gathering responses from all models...", - validationMessage: "Select a model before sending a message.", + selectorLabel: "playground.compare.endpoints.chatCompletions.selectorLabel", + selectorPlaceholder: "playground.compare.endpoints.chatCompletions.selectorPlaceholder", + inputPlaceholder: "playground.compare.endpoints.chatCompletions.inputPlaceholder", + loadingMessage: "playground.compare.endpoints.chatCompletions.loadingMessage", + validationMessage: "playground.compare.endpoints.chatCompletions.validationMessage", }, [EndpointId.A2A_AGENTS]: { id: EndpointId.A2A_AGENTS, label: "/a2a (Agents)", selectorType: "agent", - selectorLabel: "Agent", - selectorPlaceholder: "Select an agent", - inputPlaceholder: "Send a message to compare agents", - loadingMessage: "Gathering responses from all agents...", - validationMessage: "Select an agent before sending a message.", + selectorLabel: "playground.compare.endpoints.a2aAgents.selectorLabel", + selectorPlaceholder: "playground.compare.endpoints.a2aAgents.selectorPlaceholder", + inputPlaceholder: "playground.compare.endpoints.a2aAgents.inputPlaceholder", + loadingMessage: "playground.compare.endpoints.a2aAgents.loadingMessage", + validationMessage: "playground.compare.endpoints.a2aAgents.validationMessage", }, }; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/complianceUI/ComplianceUI.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/complianceUI/ComplianceUI.tsx index 15e21176c48..b9eb313ed55 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/complianceUI/ComplianceUI.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/complianceUI/ComplianceUI.tsx @@ -1,6 +1,8 @@ "use client"; import { + COMPLIANCE_CATEGORY_LABEL_KEYS, + COMPLIANCE_FRAMEWORK_LABEL_KEYS, getFrameworks, type ComplianceCategory, type ComplianceFramework, @@ -46,6 +48,7 @@ import { Upload, X, } from "lucide-react"; +import type { ParseKeys, TFunction } from "i18next"; import Papa from "papaparse"; import { Trans, useTranslation } from "react-i18next"; import React, { useCallback, useEffect, useRef, useState } from "react"; @@ -67,6 +70,21 @@ const CATEGORY_ICON_MAP: Record> = { + ...COMPLIANCE_FRAMEWORK_LABEL_KEYS, + Custom: "playground.compliance.library.customFramework", + "CSV Upload": "playground.compliance.library.csvFramework", +}; + +const CATEGORY_LABEL_KEYS: Readonly> = { + ...COMPLIANCE_CATEGORY_LABEL_KEYS, + "Custom Prompts": "playground.compliance.library.customCategory", + "Uploaded Prompts": "playground.compliance.library.csvCategory", +}; + +const labelFor = (labelKeys: Readonly>, id: string, t: TFunction): string => + Object.hasOwn(labelKeys, id) ? t(labelKeys[id]) : id; + function CategoryIcon({ iconKey, className = "w-4 h-4 text-muted-foreground", @@ -1085,7 +1103,9 @@ export default function ComplianceUI({ )}
- {fw.name} + + {labelFor(FRAMEWORK_LABEL_KEYS, fw.name, t)} + {t("playground.compliance.library.promptCount", { total: fwPromptCount })} @@ -1137,7 +1157,7 @@ export default function ComplianceUI({ /> - {category.name} + {labelFor(CATEGORY_LABEL_KEYS, category.name, t)} {category.prompts.length} @@ -1153,7 +1173,9 @@ export default function ComplianceUI({

- {category.description} + {category.descriptionKey + ? term(t(category.descriptionKey)) + : category.description}

)} diff --git a/ui/litellm-dashboard/src/components/chat/ConversationList.tsx b/ui/litellm-dashboard/src/components/chat/ConversationList.tsx index ad759c8dc67..daef8610867 100644 --- a/ui/litellm-dashboard/src/components/chat/ConversationList.tsx +++ b/ui/litellm-dashboard/src/components/chat/ConversationList.tsx @@ -19,6 +19,7 @@ import { import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { ScrollArea } from "@/components/ui/scroll-area"; import dayjs from "dayjs"; +import type { ParseKeys } from "i18next"; import { useTranslation } from "react-i18next"; import { Conversation } from "./types"; @@ -47,6 +48,13 @@ const getDateGroup = (timestamp: number): DateGroup => { const DATE_GROUP_ORDER: DateGroup[] = ["Recents", "Yesterday", "Last 7 Days", "Older"]; +const DATE_GROUP_LABEL_KEYS: Readonly> = { + Recents: "chat.conversationList.recent", + Yesterday: "chat.conversationList.yesterday", + "Last 7 Days": "chat.conversationList.lastSevenDays", + Older: "chat.conversationList.older", +}; + interface GroupedConversations { group: DateGroup; items: Conversation[]; @@ -350,7 +358,7 @@ const ConversationList: React.FC = ({
{items.map((conv) => ( >({}); const toggleKey = (key: string, open: boolean) => { @@ -72,7 +74,7 @@ function MCPEventsPanels({ toolsEvent, mcpCallEvents }: MCPEventsPanelsProps) { {toolsEvent && ( toggleKey("list-tools", open)} > @@ -95,13 +97,15 @@ function MCPEventsPanels({ toolsEvent, mcpCallEvents }: MCPEventsPanelsProps) { toggleKey(key, open)} >
-
Request
+
+ {t("chatUi.mcpEvents.request")} +
{callEvent.item?.arguments && (
@@ -116,13 +120,15 @@ function MCPEventsPanels({ toolsEvent, mcpCallEvents }: MCPEventsPanelsProps) {
                     
-                    Approved
+                    {t("chatUi.mcpEvents.approved")}
                   
{callEvent.item?.output && (
-
Response
+
+ {t("chatUi.mcpEvents.response")} +
{callEvent.item.output}
diff --git a/ui/litellm-dashboard/src/components/chat_ui/ReasoningContent.tsx b/ui/litellm-dashboard/src/components/chat_ui/ReasoningContent.tsx index 73c952eab3a..c621c80ee64 100644 --- a/ui/litellm-dashboard/src/components/chat_ui/ReasoningContent.tsx +++ b/ui/litellm-dashboard/src/components/chat_ui/ReasoningContent.tsx @@ -1,4 +1,5 @@ import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; import ReactMarkdown from "react-markdown"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { coy } from "react-syntax-highlighter/dist/esm/styles/prism"; @@ -13,6 +14,7 @@ interface ReasoningContentProps { } const ReasoningContent: React.FC = ({ reasoningContent }) => { + const { t } = useTranslation(); const syntaxTheme = useSyntaxTheme(coy); const [isExpanded, setIsExpanded] = useState(true); @@ -32,7 +34,7 @@ const ReasoningContent: React.FC = ({ reasoningContent }) } > - {isExpanded ? "Hide reasoning" : "Show reasoning"} + {isExpanded ? t("chatUi.reasoning.hide") : t("chatUi.reasoning.show")} {isExpanded ? : } diff --git a/ui/litellm-dashboard/src/components/chat_ui/ResponseMetrics.tsx b/ui/litellm-dashboard/src/components/chat_ui/ResponseMetrics.tsx index abd0bacf0c9..f8cf9350309 100644 --- a/ui/litellm-dashboard/src/components/chat_ui/ResponseMetrics.tsx +++ b/ui/litellm-dashboard/src/components/chat_ui/ResponseMetrics.tsx @@ -101,6 +101,7 @@ function PromptCacheChips({ usage }: { usage?: TokenUsage }) { } const ResponseMetrics: React.FC = ({ timeToFirstToken, totalLatency, usage, toolName }) => { + const { t } = useTranslation(); if (!timeToFirstToken && !totalLatency && !usage) return null; return ( @@ -108,7 +109,7 @@ const ResponseMetrics: React.FC = ({ timeToFirstToken, tot {timeToFirstToken !== undefined && (
@@ -226,10 +228,10 @@ const ModelFilters: React.FC = ({ const filtersContent = (
-

Search Models:

+

{t("modelFilters.searchModels")}

setSearchTerm(e.target.value)} className="border rounded-sm px-3 py-2 w-64 h-10 text-sm" @@ -237,14 +239,14 @@ const ModelFilters: React.FC = ({
{showProviderFilter && (
-

Provider:

+

{t("modelFilters.provider")}

changeMode(e.target.value)} className="border rounded-sm px-3 py-2 text-sm text-muted-foreground w-32 h-10" > {modelHubData && getUniqueModes(modelHubData).map((mode) => ( @@ -274,14 +276,14 @@ const ModelFilters: React.FC = ({
-

Features:

+

{t("modelFilters.features")}