diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailContent.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailContent.tsx
index 1dca2715e36..2045ecf3bc0 100644
--- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailContent.tsx
+++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailContent.tsx
@@ -1,4 +1,5 @@
import { useState } from "react";
+import { useTranslation } from "react-i18next";
import { Typography, Descriptions, Card, Tag, Tabs, Alert, Collapse, Radio, Space, Spin } from "antd";
import moment from "moment";
import { LogEntry } from "../columns";
@@ -52,6 +53,7 @@ export interface LogDetailContentProps {
* be reused for both single-log and session-mode views.
*/
export function LogDetailContent({ logEntry, isLoadingDetails = false, accessToken }: LogDetailContentProps) {
+ const { t } = useTranslation();
const metadata = logEntry.metadata || {};
const hasError = metadata.status === "failure";
const errorInfo = hasError ? metadata.error_information : null;
@@ -83,9 +85,9 @@ export function LogDetailContent({ logEntry, isLoadingDetails = false, accessTok
if (hasError && errorInfo) {
return {
error: {
- message: errorInfo.error_message || "An error occurred",
- type: errorInfo.error_class || "error",
- code: errorInfo.error_code || "unknown",
+ message: errorInfo.error_message || t("common.somethingWentWrong"),
+ type: errorInfo.error_class || t("common.error"),
+ code: errorInfo.error_code || t("common.unknown"),
param: null,
},
};
@@ -100,7 +102,7 @@ export function LogDetailContent({ logEntry, isLoadingDetails = false, accessTok
}
className="mb-6"
/>
@@ -113,22 +115,33 @@ export function LogDetailContent({ logEntry, isLoadingDetails = false, accessTok
{/* Request Details */}
-
+
- {logEntry.model}
- {logEntry.custom_llm_provider || "-"}
- {logEntry.call_type}
-
+ {logEntry.model}
+
+ {logEntry.custom_llm_provider || "-"}
+
+
+ {logEntry.call_type}
+
+
-
+
{logEntry.requester_ip_address && (
- {logEntry.requester_ip_address}
+
+ {logEntry.requester_ip_address}
+
)}
{hasGuardrailData && (
-
+
)}
@@ -165,7 +178,7 @@ export function LogDetailContent({ logEntry, isLoadingDetails = false, accessTok
{isLoadingDetails ? (
-
Loading request & response data...
+
{t("viewLogs.logDetailContent.loadingRequestResponse")}
) : (
{errorInfo.error_code && (
- Error Code: {errorInfo.error_code}
+ {t("viewLogs.logDetailContent.errorCode")} {errorInfo.error_code}
)}
{errorInfo.error_message && (
- Message: {errorInfo.error_message}
+ {t("viewLogs.logDetailContent.errorMessage")} {errorInfo.error_message}
)}
@@ -233,10 +247,11 @@ function ErrorDescription({ errorInfo }: { errorInfo: any }) {
}
function TagsSection({ tags }: { tags: Record }) {
+ const { t } = useTranslation();
return (
- Tags
+ {t("viewLogs.logDetailContent.tags")}
{Object.entries(tags).map(([key, value]) => (
@@ -250,6 +265,7 @@ function TagsSection({ tags }: { tags: Record }) {
}
function GuardrailLabel({ label, maskedCount }: { label: string; maskedCount: number }) {
+ const { t } = useTranslation();
const handleClick = () => {
const el = document.getElementById("guardrail-section");
if (el) el.scrollIntoView({ behavior: "smooth" });
@@ -260,7 +276,7 @@ function GuardrailLabel({ label, maskedCount }: { label: string; maskedCount: nu
{label}
- {maskedCount > 0 && {maskedCount} masked}
+ {maskedCount > 0 && {t("viewLogs.logDetailContent.masked", { count: maskedCount })}}
);
}
@@ -279,6 +295,7 @@ function getUncachedInputTextTokens(metadata: Record
): number | und
}
function MetricsSection({ logEntry, metadata }: { logEntry: LogEntry; metadata: Record }) {
+ const { t } = useTranslation();
const completionStartTime = logEntry.completionStartTime;
const ttftMs =
completionStartTime && completionStartTime !== logEntry.endTime
@@ -300,17 +317,19 @@ function MetricsSection({ logEntry, metadata }: { logEntry: LogEntry; metadata:
return (
-
+
{showAnthropicMessagesInputOutput ? (
<>
- {formatNumberWithCommas(uncachedInputTokens)}
-
+
+ {formatNumberWithCommas(uncachedInputTokens)}
+
+
{formatNumberWithCommas(logEntry.completion_tokens)}
>
) : (
-
+
)}
- ${formatNumberWithCommas(logEntry.spend || 0, 8)}
-
+
+ ${formatNumberWithCommas(logEntry.spend || 0, 8)}
+
+
{logEntry.request_duration_ms != null ? (logEntry.request_duration_ms / 1000).toFixed(3) : "-"} s
{ttftMs != null && ttftMs > 0 && (
- {(ttftMs / 1000).toFixed(3)} s
+
+ {(ttftMs / 1000).toFixed(3)} s
+
)}
{hasCacheActivity && (
<>
-
+
{cacheHitValue}
{metadata?.additional_usage_values?.cache_read_input_tokens > 0 && (
-
+
{formatNumberWithCommas(metadata.additional_usage_values.cache_read_input_tokens)}
)}
{metadata?.additional_usage_values?.cache_creation_input_tokens > 0 && (
-
+
{formatNumberWithCommas(metadata.additional_usage_values.cache_creation_input_tokens)}
)}
@@ -345,12 +368,12 @@ function MetricsSection({ logEntry, metadata }: { logEntry: LogEntry; metadata:
)}
{metadata?.litellm_overhead_time_ms !== undefined && metadata.litellm_overhead_time_ms !== null && (
-
+
{metadata.litellm_overhead_time_ms.toFixed(2)} ms
)}
-
+
{metadata?.attempted_retries !== undefined && metadata?.attempted_retries !== null ? (
metadata.attempted_retries > 0 ? (
<>
@@ -360,17 +383,17 @@ function MetricsSection({ logEntry, metadata }: { logEntry: LogEntry; metadata:
: ""}
>
) : (
- None
+ {t("viewLogs.logDetailContent.retriesNone")}
)
) : (
"-"
)}
-
+
{moment(logEntry.startTime).format("YYYY-MM-DDTHH:mm:ss.SSS[Z]")}
-
+
{moment(logEntry.endTime).format("YYYY-MM-DDTHH:mm:ss.SSS[Z]")}
@@ -394,6 +417,7 @@ function RequestResponseSection({
getFormattedResponse,
logEntry,
}: RequestResponseSectionProps) {
+ const { t } = useTranslation();
const [activeTab, setActiveTab] = useState(TAB_REQUEST);
const [viewMode, setViewMode] = useState<"pretty" | "json">("pretty");
@@ -438,11 +462,11 @@ function RequestResponseSection({
}}
>
- Request & Response
+ {t("viewLogs.logDetailContent.requestAndResponse")}
setViewMode(e.target.value)}>
- Pretty
- JSON
+ {t("viewLogs.logDetailContent.viewPretty")}
+ {t("viewLogs.logDetailContent.viewJson")}
),
@@ -467,7 +491,10 @@ function RequestResponseSection({
@@ -475,7 +502,7 @@ function RequestResponseSection({
items={[
{
key: TAB_REQUEST,
- label: "Request",
+ label: t("viewLogs.logDetailContent.tabRequest"),
children: (
@@ -484,14 +511,14 @@ function RequestResponseSection({
},
{
key: TAB_RESPONSE,
- label: "Response",
+ label: t("viewLogs.logDetailContent.tabResponse"),
children: (
{hasResponse || hasError ? (
) : (
- Response data not available
+ {t("viewLogs.logDetailContent.responseNotAvailable")}
)}
@@ -510,6 +537,7 @@ function RequestResponseSection({
}
export function GuardrailJumpLink({ guardrailEntries }: { guardrailEntries: any[] }) {
+ const { t } = useTranslation();
const allPassed = guardrailEntries.every((e) => {
const status = e?.guardrail_status || e?.status;
return status === "pass" || status === "passed" || status === "success";
@@ -538,8 +566,8 @@ export function GuardrailJumpLink({ guardrailEntries }: { guardrailEntries: any[
border: `1px solid ${allPassed ? "#bbf7d0" : "#fecaca"}`,
}}
>
- {allPassed ? "\u2713" : "\u2717"} {guardrailEntries.length} guardrail{guardrailEntries.length !== 1 ? "s" : ""}{" "}
- evaluated
+ {allPassed ? "\u2713" : "\u2717"}{" "}
+ {t("viewLogs.logDetailContent.guardrailsEvaluated", { count: guardrailEntries.length })}
{"\u2193"}
@@ -547,6 +575,7 @@ export function GuardrailJumpLink({ guardrailEntries }: { guardrailEntries: any[
}
function MetadataSection({ metadata }: { metadata: Record }) {
+ const { t } = useTranslation();
return (
}) {
items={[
{
key: "1",
- label: Metadata
,
+ label: {t("viewLogs.logDetailContent.metadata")}
,
children: (
diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailsDrawer.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailsDrawer.tsx
index 029a9814b81..bc748cdf93e 100644
--- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailsDrawer.tsx
+++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailsDrawer.tsx
@@ -1,4 +1,5 @@
import { useEffect, useMemo, useState } from "react";
+import { useTranslation } from "react-i18next";
import { Button, Drawer } from "antd";
import { CheckOutlined, CopyOutlined, LeftOutlined, RightOutlined } from "@ant-design/icons";
import { Bot, Sparkles, Wrench } from "lucide-react";
@@ -234,6 +235,8 @@ export function LogDetailsDrawer({
}
};
+ const { t } = useTranslation();
+
if (!currentLog || !enrichedLog) return null;
return (
@@ -259,7 +262,7 @@ export function LogDetailsDrawer({
icon={
}
onClick={() => setIsSidebarCollapsed(true)}
className="absolute top-2 left-2 z-20 !bg-white !border !border-slate-200 !rounded-md"
- aria-label="Collapse trace sidebar"
+ aria-label={t("viewLogs.logDetailsDrawer.collapseTraceSidebar")}
/>
) : (
}
onClick={() => setIsSidebarCollapsed(false)}
className="absolute top-2 left-2 z-20 !bg-white !border !border-slate-200 !rounded-md"
- aria-label="Expand trace sidebar"
+ aria-label={t("viewLogs.logDetailsDrawer.expandTraceSidebar")}
/>
)}
{!isSidebarCollapsed && (
@@ -277,7 +280,9 @@ export function LogDetailsDrawer({
- {isSessionMode ? "Session" : "Trace"}
+ {isSessionMode
+ ? t("viewLogs.logDetailsDrawer.labelSession")
+ : t("viewLogs.logDetailsDrawer.labelTrace")}
{leftPanelDisplayId}
@@ -285,7 +290,7 @@ export function LogDetailsDrawer({
type="button"
onClick={handleCopyLeftPanelId}
className="text-slate-400 hover:text-slate-600"
- aria-label="Copy trace id"
+ aria-label={t("viewLogs.logDetailsDrawer.copyTraceId")}
>
{copiedLeftPanelId ? (
@@ -297,7 +302,7 @@ export function LogDetailsDrawer({
- {logsForList.length} req
+ {t("viewLogs.logDetailsDrawer.labelReqCount", { count: logsForList.length })}
{[
isSessionMode
? llmCount
@@ -309,7 +314,11 @@ export function LogDetailsDrawer({
: logsForList.filter((row) => AGENT_CALL_TYPES.includes(row.call_type)).length,
isSessionMode ? mcpCount : logsForList.filter((row) => MCP_CALL_TYPES.includes(row.call_type)).length,
].map((count, i) => {
- const label = [" LLM", " Agent", " MCP"][i];
+ const label = [
+ t("viewLogs.logDetailsDrawer.labelLlm"),
+ t("viewLogs.logDetailsDrawer.labelAgent"),
+ t("viewLogs.logDetailsDrawer.labelMcp"),
+ ][i];
return count > 0 ? (
·
diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/OutputCard.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/OutputCard.tsx
index 9362d249874..bb0e07f6e91 100644
--- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/OutputCard.tsx
+++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/OutputCard.tsx
@@ -4,6 +4,7 @@
*/
import { useState } from "react";
+import { useTranslation } from "react-i18next";
import { Typography } from "antd";
import MessageManager from "@/components/molecules/message_manager";
import { ParsedMessage } from "./prettyMessagesTypes";
@@ -19,6 +20,7 @@ interface OutputCardProps {
}
export function OutputCard({ message, completionTokens, outputCost }: OutputCardProps) {
+ const { t } = useTranslation();
const [isCollapsed, setIsCollapsed] = useState(false);
const handleCopy = () => {
@@ -26,7 +28,7 @@ export function OutputCard({ message, completionTokens, outputCost }: OutputCard
const content = message.content || "";
navigator.clipboard.writeText(content);
- MessageManager.success("Output copied");
+ MessageManager.success(t("viewLogs.outputCard.outputCopied"));
};
if (!message) {
@@ -56,7 +58,7 @@ export function OutputCard({ message, completionTokens, outputCost }: OutputCard
>
- No response data available
+ {t("viewLogs.outputCard.noResponseData")}
diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/RealtimePrettyView.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/RealtimePrettyView.tsx
index 5552c6decbe..f26a3754c71 100644
--- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/RealtimePrettyView.tsx
+++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/RealtimePrettyView.tsx
@@ -5,6 +5,7 @@
*/
import { useState } from "react";
+import { useTranslation } from "react-i18next";
import { Typography, Tag, Tooltip } from "antd";
import {
SoundOutlined,
@@ -90,6 +91,7 @@ export function isRealtimeResponse(response: any): boolean {
}
export function RealtimePrettyView({ response, metrics }: RealtimePrettyViewProps) {
+ const { t } = useTranslation();
const events: RealtimeEvent[] = response?.results || [];
const usage = response?.usage;
@@ -122,7 +124,7 @@ export function RealtimePrettyView({ response, metrics }: RealtimePrettyViewProp
fontSize: 13,
}}
>
- No recognized realtime events found
+ {t("viewLogs.realtimePrettyView.noEventsFound")}
)}
@@ -130,6 +132,7 @@ export function RealtimePrettyView({ response, metrics }: RealtimePrettyViewProp
}
function SessionCard({ session, turnCount }: { session: RealtimeSession; turnCount: number }) {
+ const { t } = useTranslation();
const [isCollapsed, setIsCollapsed] = useState(true);
return (
@@ -170,14 +173,14 @@ function SessionCard({ session, turnCount }: { session: RealtimeSession; turnCou
- Session
+ {t("viewLogs.realtimePrettyView.labelSession")}
{session.model}
{turnCount > 0 && (
- {turnCount} {turnCount === 1 ? "turn" : "turns"}
+ {t("viewLogs.realtimePrettyView.turnCount", { count: turnCount })}
)}
{session.voice && (
@@ -214,15 +217,32 @@ function SessionCard({ session, turnCount }: { session: RealtimeSession; turnCou
fontSize: 13,
}}
>
-
-
-
-
-
-
- {session.turn_detection && }
+
+
+
+
+
+
+ {session.turn_detection && (
+
+ )}
{session.tools && session.tools.length > 0 && (
-
+
)}
@@ -238,7 +258,7 @@ function SessionCard({ session, turnCount }: { session: RealtimeSession; turnCou
marginBottom: 4,
}}
>
- Instructions
+ {t("viewLogs.realtimePrettyView.labelInstructions")}
{usage && (
- {usage.input_tokens ?? 0} in / {usage.output_tokens ?? 0} out tokens
+ {t("viewLogs.realtimePrettyView.tokenUsage", {
+ input: usage.input_tokens ?? 0,
+ output: usage.output_tokens ?? 0,
+ })}
)}
{response.conversation_id && (
@@ -367,8 +391,18 @@ function ResponseTurn({ response, index }: { response: RealtimeResponse; index:
))}
{/* Token breakdown if available */}
- {usage?.input_token_details && }
- {usage?.output_token_details && }
+ {usage?.input_token_details && (
+
+ )}
+ {usage?.output_token_details && (
+
+ )}
);
}
diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/SectionHeader.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/SectionHeader.tsx
index 817928f170c..acd9e92d124 100644
--- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/SectionHeader.tsx
+++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/SectionHeader.tsx
@@ -2,6 +2,7 @@
* SectionHeader - Datadog-style header with icon, label, metrics, and copy
*/
+import { useTranslation } from "react-i18next";
import { Typography, Button, Tooltip } from "antd";
import { MessageOutlined, CopyOutlined, DownOutlined, UpOutlined } from "@ant-design/icons";
@@ -26,6 +27,7 @@ export function SectionHeader({
onToggleCollapse,
turnCount,
}: SectionHeaderProps) {
+ const { t } = useTranslation();
return (
✨
)}
- {type === "input" ? "Input" : "Output"}
+
+ {type === "input" ? t("viewLogs.sectionHeader.labelInput") : t("viewLogs.sectionHeader.labelOutput")}
+
{/* Tokens */}
{tokens !== undefined && (
- Tokens: {tokens.toLocaleString()}
+ {t("viewLogs.sectionHeader.tokens", { count: tokens.toLocaleString() })}
)}
{/* Cost */}
{cost !== undefined && (
- Cost: ${cost.toFixed(6)}
+ {t("viewLogs.sectionHeader.cost", { amount: cost.toFixed(6) })}
)}
{/* Turn count */}
{turnCount !== undefined && turnCount > 0 && (
- Turns: {turnCount}
+ {t("viewLogs.sectionHeader.turns", { count: turnCount })}
)}
{/* Copy Button */}
-
+