diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat/ByokConnect.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat/ByokConnect.tsx index f94c43b720a..4e337a88add 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat/ByokConnect.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat/ByokConnect.tsx @@ -2,6 +2,8 @@ import { useId, useState } from "react"; import { Check, ExternalLink, KeyRound } from "lucide-react"; +import type { TFunction } from "i18next"; +import { useTranslation } from "react-i18next"; import { PasswordInput } from "@/components/shared/PasswordInput"; import { Button } from "@/components/ui/button"; import { InputGroupButton } from "@/components/ui/input-group"; @@ -13,13 +15,14 @@ import type { MCPServer } from "@/components/mcp_tools/types"; import { fetchClient } from "@/lib/http/api"; import { ApiError } from "@/lib/http/client"; import { toast } from "@/lib/toast"; +import i18n from "@/i18n/i18n"; -const saveErrorMessage = (error: unknown): string => { +const saveErrorMessage = (error: unknown, t: TFunction = i18n.t.bind(i18n)): string => { if (error instanceof ApiError) { const detail = (error.body as { detail?: { error?: string } } | null)?.detail?.error; if (detail) return detail; } - return error instanceof Error && error.message ? error.message : "Failed to connect"; + return error instanceof Error && error.message ? error.message : t("playground.chat.byok.failed"); }; export function ByokConnect({ @@ -33,6 +36,7 @@ export function ByokConnect({ onConnected: () => void; onCancel?: () => void; }) { + const { t } = useTranslation(); const [credential, setCredential] = useState(""); const [save, setSave] = useState(true); const [isSaving, setIsSaving] = useState(false); @@ -41,7 +45,7 @@ export function ByokConnect({ const connect = async () => { if (!credential.trim()) { - toast.error("Enter your API key"); + toast.error(t("playground.chat.byok.enterKey")); return; } setIsSaving(true); @@ -50,11 +54,11 @@ export function ByokConnect({ params: { path: { server_id: server.server_id } }, body: { credential: credential.trim(), save }, }); - toast.success(`Connected to ${name}`); + toast.success(t("playground.chat.byok.connected", { name })); setCredential(""); onConnected(); } catch (error) { - toast.error(saveErrorMessage(error)); + toast.error(saveErrorMessage(error, t)); } finally { setIsSaving(false); } @@ -65,12 +69,14 @@ export function ByokConnect({
{server.has_user_credential - ? `The new key replaces the one the gateway uses for ${name}.` - : `The gateway calls ${name} with your own key. Connect it to list and use its tools.`} + ? t("playground.chat.byok.replaceDescription", { name }) + : t("playground.chat.byok.connectDescription", { name })}
Pick a server and a tool above to fill in its arguments
+{t("playground.chat.composer.pickTool")}
)} ); @@ -148,9 +157,9 @@ export function Composer({ value={draft.text} onChange={(event) => draft.setText(event.target.value)} onKeyDown={handleKeyDown} - placeholder={capabilities.placeholder} + placeholder={t(capabilities.placeholder)} rows={2} - aria-label="Message" + aria-label={t("playground.chat.composer.message")} className="max-h-60" /> ); @@ -168,21 +177,31 @@ export function Composer({
{JSON.stringify(message.mcpToolCall.arguments, null, 2)}
@@ -190,7 +230,8 @@ function AssistantReply({
inspected: boolean;
onInspect: () => void;
}) {
- const model = replyModel(message);
+ const { t } = useTranslation();
+ const model = replyModel(message, t);
const showMcpEvents = isLast && extras.mcpEvents.length > 0 && MCP_EVENT_ENDPOINTS.includes(extras.endpoint);
const showCode = isLast && extras.codeInterpreter && extras.endpoint === EndpointType.RESPONSES;
return (
@@ -201,7 +242,7 @@ function AssistantReply({
{fellBack(message) && }
@@ -220,7 +261,7 @@ function AssistantReply({
)}
{message.isError ? (
- The request failed
+ {t("playground.chat.conversation.requestFailed")}
{textOf(message)}
) : (
@@ -239,17 +280,19 @@ function AssistantReply({
)}
- {!message.isImage && !message.isAudio && }
+ {!message.isImage && !message.isAudio && (
+
+ )}
{message.trace && (
- Inspect
+ {t("playground.chat.conversation.inspect")}
)}
@@ -260,12 +303,17 @@ function AssistantReply({
}
function WaitingTurn({ target }: { target: string | undefined }) {
+ const { t } = useTranslation();
return (
- {target ? `Waiting for ${target}` : "Waiting for a reply"}
+
+ {target
+ ? t("playground.chat.conversation.waitingFor", { target })
+ : t("playground.chat.conversation.waitingForReply")}
+
@@ -273,12 +321,13 @@ function WaitingTurn({ target }: { target: string | undefined }) {
}
function SwitchMarker({ model }: { model: string }) {
+ const { t } = useTranslation();
return (
- Switched to {model}
+ {t("playground.chat.conversation.switchedTo", { model })}
);
}
@@ -290,16 +339,15 @@ function EmptyConversation({
suggestions: readonly string[];
onPick: (text: string) => void;
}) {
+ const { t } = useTranslation();
return (
- Send a test request
-
- Pick a model and anything the request should go through, then inspect what the gateway did with it
-
+ {t("playground.chat.conversation.empty.title")}
+ {t("playground.chat.conversation.empty.description")}
{suggestions.length > 0 && (
@@ -340,13 +388,14 @@ export function Conversation({
suggestions: readonly string[];
onPickSuggestion: (text: string) => void;
}) {
+ const { t } = useTranslation();
if (messages.length === 0) return ;
const lastIndex = messages.length - 1;
const waiting = isLoading && messages[lastIndex]?.role === "user";
return (
-
+
{messages.flatMap((message, index) => {
const switched = switchedModel(messages, index);
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat/Inspector.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat/Inspector.tsx
index 3a9919ce38f..d294c859a18 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat/Inspector.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat/Inspector.tsx
@@ -14,6 +14,9 @@ import {
Shuffle,
Timer,
} from "lucide-react";
+import type { TFunction } from "i18next";
+import { useTranslation } from "react-i18next";
+import i18n from "@/i18n/i18n";
import type { MessageType } from "@/components/chat_ui/types";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
import { Badge } from "@/components/ui/badge";
@@ -80,130 +83,175 @@ const millis = (value: number | undefined): string | undefined =>
value === undefined ? undefined : `${Math.round(value)}ms`;
function Routing({ message }: { message: MessageType }) {
+ const { t } = useTranslation();
const trace = message.trace;
const served = servedModel(message);
const fellBack = (trace?.attemptedFallbacks ?? 0) > 0;
const reported = Boolean(trace?.callId || trace?.modelGroup || trace?.providerModel);
return (
-
+
{trace?.requestedModel && (
-
-
+
+
)}
{served && (
-
+
{fellBack && }
-
+
)}
{trace?.providerModel && (
-
+
)}
{trace?.apiBase && (
-
+
)}
{trace?.callId && (
-
+
{trace.callId}
-
+
)}
{trace?.responseId && (
-
+
{trace.responseId}
-
+
)}
, value: count(trace?.attemptedFallbacks) },
- { label: "Retries", icon: , value: count(trace?.attemptedRetries) },
- { label: "Overhead", icon: , value: millis(trace?.overheadMs) },
+ {
+ label: t("playground.chat.inspector.routing.fallbacks"),
+ icon: ,
+ value: count(trace?.attemptedFallbacks),
+ },
+ {
+ label: t("playground.chat.inspector.routing.retries"),
+ icon: ,
+ value: count(trace?.attemptedRetries),
+ },
+ { label: t("playground.chat.inspector.routing.overhead"), icon: , value: millis(trace?.overheadMs) },
]}
/>
- {!reported && (
-
- The gateway did not share routing details with this page. Open the dashboard from the proxy to see them
-
- )}
+ {!reported && {t("playground.chat.inspector.routing.notReported")} }
);
}
-const noneRan = (policies: readonly string[] | undefined): string | null => {
- if (policies === undefined) return "No guardrails ran on this request";
- return policies.length === 0 ? "No guardrails or policies ran on this request" : null;
+const noneRan = (policies: readonly string[] | undefined, t: TFunction = i18n.t.bind(i18n)): string | null => {
+ if (policies === undefined) return t("playground.chat.inspector.guardrails.noGuardrailsRan");
+ return policies.length === 0 ? t("playground.chat.inspector.guardrails.noneRan") : null;
};
function Guardrails({ message }: { message: MessageType }) {
+ const { t } = useTranslation();
const guardrails = message.trace?.appliedGuardrails;
const policies = message.trace?.appliedPolicies;
if (guardrails === undefined) {
return (
-
- This endpoint does not report which guardrails or policies ran
+
+ {t("playground.chat.inspector.guardrails.unsupported")}
);
}
- const emptyNote = guardrails.length === 0 ? noneRan(policies) : null;
+ const emptyNote = guardrails.length === 0 ? noneRan(policies, t) : null;
return (
-
+
{guardrails.map((name) => (
-
- Ran
+
+ {t("playground.chat.inspector.guardrails.ran")}
))}
{policies?.map((name) => (
-
- Ran
+
+ {t("playground.chat.inspector.guardrails.ran")}
))}
{emptyNote && {emptyNote} }
- {policies === undefined && Policies are only reported when the dashboard is opened from the proxy }
+ {policies === undefined && {t("playground.chat.inspector.guardrails.policiesUnreported")} }
);
}
function SpeedAndCost({ message }: { message: MessageType }) {
+ const { t } = useTranslation();
const usage = message.usage;
const cost = usage?.cost ?? message.trace?.cost;
return (
-
+
,
value: message.timeToFirstToken === undefined ? undefined : formatSeconds(message.timeToFirstToken),
},
{
- label: "Total",
+ label: t("playground.chat.inspector.performance.total"),
icon: ,
value: message.totalLatency === undefined ? undefined : formatSeconds(message.totalLatency),
},
- { label: "Cost", icon: , value: cost === undefined ? undefined : formatCost(cost) },
- { label: "Tokens in", icon: , value: count(usage?.promptTokens) },
- { label: "Tokens out", icon: , value: count(usage?.completionTokens) },
- { label: "Cached", icon: , value: count(usage?.cacheReadTokens) },
- { label: "Reasoning", icon: , value: count(usage?.reasoningTokens) },
- { label: "Gateway time", icon: , value: millis(message.trace?.responseMs) },
+ {
+ label: t("playground.chat.inspector.performance.cost"),
+ icon: ,
+ value: cost === undefined ? undefined : formatCost(cost),
+ },
+ {
+ label: t("playground.chat.inspector.performance.tokensIn"),
+ icon: ,
+ value: count(usage?.promptTokens),
+ },
+ {
+ label: t("playground.chat.inspector.performance.tokensOut"),
+ icon: ,
+ value: count(usage?.completionTokens),
+ },
+ {
+ label: t("playground.chat.inspector.performance.cached"),
+ icon: ,
+ value: count(usage?.cacheReadTokens),
+ },
+ {
+ label: t("playground.chat.inspector.performance.reasoning"),
+ icon: ,
+ value: count(usage?.reasoningTokens),
+ },
+ {
+ label: t("playground.chat.inspector.performance.gatewayTime"),
+ icon: ,
+ value: millis(message.trace?.responseMs),
+ },
]}
/>
@@ -211,13 +259,12 @@ function SpeedAndCost({ message }: { message: MessageType }) {
}
function NothingYet() {
+ const { t } = useTranslation();
return (
- Nothing to inspect yet
-
- Each reply shows here with the deployment that served it, fallbacks, guardrails, timing and cost
-
+ {t("playground.chat.inspector.empty.title")}
+ {t("playground.chat.inspector.empty.description")}
);
@@ -235,6 +282,7 @@ function Trace({ message }: { message: MessageType | null }) {
}
function CodeTab({ code }: { code: (sdk: CodeSdk) => string }) {
+ const { t } = useTranslation();
const [sdk, setSdk] = useState("openai");
const snippet = code(sdk);
return (
@@ -246,21 +294,21 @@ function CodeTab({ code }: { code: (sdk: CodeSdk) => string }) {
variant="outline"
size="sm"
spacing={0}
- aria-label="SDK"
+ aria-label={t("playground.chat.inspector.code.sdk")}
>
- OpenAI SDK
+ {t("playground.chat.inspector.code.openai")}
- Azure SDK
+ {t("playground.chat.inspector.code.azure")}
-
+
- Send this request from your own app. Replace YOUR_LITELLM_API_KEY with a virtual key
+ {t("playground.chat.inspector.code.description")}
{snippet}
@@ -271,12 +319,13 @@ function CodeTab({ code }: { code: (sdk: CodeSdk) => string }) {
}
export function InspectorPanel({ message, code }: { message: MessageType | null; code: (sdk: CodeSdk) => string }) {
+ const { t } = useTranslation();
return (
- Trace
- Code
+ {t("playground.chat.inspector.tabs.trace")}
+ {t("playground.chat.inspector.tabs.code")}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat/McpDialog.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat/McpDialog.tsx
index e5e0828825d..2a72a5facc9 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat/McpDialog.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat/McpDialog.tsx
@@ -2,6 +2,7 @@
import { useId, useState, type ReactNode } from "react";
import { Blocks, KeyRound, Search } from "lucide-react";
+import { Trans, useTranslation } from "react-i18next";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
@@ -84,9 +85,14 @@ function MasterRow({
onSelect: () => void;
summary?: string;
}) {
+ const { t } = useTranslation();
return (
-
+