diff --git a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/CompliancePanel.tsx b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/CompliancePanel.tsx index cfa2c146300..0d043005db3 100644 --- a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/CompliancePanel.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/CompliancePanel.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from "react"; import { Tooltip } from "antd"; +import { useTranslation } from "react-i18next"; import { checkEuAiActCompliance, checkGdprCompliance, @@ -54,6 +55,7 @@ const ComplianceCard = ({ loading: boolean; error: string | null; }) => { + const { t } = useTranslation(); const [expanded, setExpanded] = useState(false); return ( @@ -85,12 +87,12 @@ const ComplianceCard = ({ : "bg-red-100 text-red-700 border border-red-200" }`} > - {data.compliant ? "COMPLIANT" : "NON-COMPLIANT"} + {data.compliant ? t("viewLogs.compliancePanel.compliant") : t("viewLogs.compliancePanel.nonCompliant")} )} {error && ( - UNAVAILABLE + {t("viewLogs.compliancePanel.unavailable")} )} - {loading &&

Checking compliance...

} + {loading &&

{t("viewLogs.compliancePanel.checkingCompliance")}

} {error &&

{error}

} {data && (
@@ -134,6 +136,7 @@ const ComplianceCard = ({ // -- Main Component -- const CompliancePanel: React.FC = ({ accessToken, logEntry }) => { + const { t } = useTranslation(); const [euAiActData, setEuAiActData] = useState(null); const [gdprData, setGdprData] = useState(null); const [euAiActLoading, setEuAiActLoading] = useState(false); @@ -156,20 +159,22 @@ const CompliancePanel: React.FC = ({ accessToken, logEntry setEuAiActError(null); checkEuAiActCompliance(accessToken, payload) .then(setEuAiActData) - .catch((err) => setEuAiActError(err.message || "Failed to check EU AI Act compliance")) + .catch((err) => setEuAiActError(err.message || t("viewLogs.compliancePanel.euAiActCheckFailed"))) .finally(() => setEuAiActLoading(false)); setGdprLoading(true); setGdprError(null); checkGdprCompliance(accessToken, payload) .then(setGdprData) - .catch((err) => setGdprError(err.message || "Failed to check GDPR compliance")) + .catch((err) => setGdprError(err.message || t("viewLogs.compliancePanel.gdprCheckFailed"))) .finally(() => setGdprLoading(false)); }, [accessToken, logEntry]); return (
-

Regulatory Compliance

+

+ {t("viewLogs.compliancePanel.regulatoryCompliance")} +

diff --git a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/ContentFilterDetails.tsx b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/ContentFilterDetails.tsx index 10efed16b69..82e6c140509 100644 --- a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/ContentFilterDetails.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/ContentFilterDetails.tsx @@ -1,4 +1,5 @@ import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; export interface ContentFilterDetection { type: "pattern" | "blocked_word" | "category_keyword"; @@ -74,13 +75,15 @@ const KV: React.FC = ({ label, children, mono }) => ( ); const ContentFilterDetails: React.FC = ({ response }) => { + const { t } = useTranslation(); + // Handle case where response is a string (error message) or null if (!response || typeof response === "string") { if (typeof response === "string" && response) { return (
-
Error
+
{t("viewLogs.contentFilterDetails.errorTitle")}

{response}

@@ -95,7 +98,7 @@ const ContentFilterDetails: React.FC = ({ response }) if (detections.length === 0) { return (
-
No detections found
+
{t("viewLogs.contentFilterDetails.noDetectionsFound")}
); } @@ -118,23 +121,31 @@ const ContentFilterDetails: React.FC = ({ response })
- + {totalDetections} - +
- {blockedCount > 0 && chip(`${blockedCount} blocked`, "red")} - {maskedCount > 0 && chip(`${maskedCount} masked`, "blue")} - {blockedCount === 0 && maskedCount === 0 && chip("passed", "green")} + {blockedCount > 0 && + chip(t("viewLogs.contentFilterDetails.blocked_other", { count: blockedCount }), "red")} + {maskedCount > 0 && + chip(t("viewLogs.contentFilterDetails.masked_other", { count: maskedCount }), "blue")} + {blockedCount === 0 && maskedCount === 0 && chip(t("viewLogs.contentFilterDetails.passed"), "green")}
- +
- {patterns.length > 0 && chip(`${patterns.length} patterns`, "slate")} - {blockedWords.length > 0 && chip(`${blockedWords.length} keywords`, "slate")} - {categoryKeywords.length > 0 && chip(`${categoryKeywords.length} categories`, "slate")} + {patterns.length > 0 && + chip(t("viewLogs.contentFilterDetails.patterns_other", { count: patterns.length }), "slate")} + {blockedWords.length > 0 && + chip(t("viewLogs.contentFilterDetails.keywords_other", { count: blockedWords.length }), "slate")} + {categoryKeywords.length > 0 && + chip( + t("viewLogs.contentFilterDetails.categories_other", { count: categoryKeywords.length }), + "slate", + )}
@@ -143,16 +154,20 @@ const ContentFilterDetails: React.FC = ({ response }) {/* Patterns Section */} {patterns.length > 0 && ( -
+
{patterns.map((detection, idx) => (
- {detection.pattern_name || "unknown"} + + {detection.pattern_name || t("viewLogs.contentFilterDetails.unknown")} +
- {chip(detection.action, detection.action === "BLOCK" ? "red" : "blue")} + + {chip(detection.action, detection.action === "BLOCK" ? "red" : "blue")} +
@@ -163,19 +178,27 @@ const ContentFilterDetails: React.FC = ({ response }) {/* Blocked Words Section */} {blockedWords.length > 0 && ( -
+
{blockedWords.map((detection, idx) => (
- - {detection.keyword || "unknown"} + + {detection.keyword || t("viewLogs.contentFilterDetails.unknown")} - {detection.description && {detection.description}} + {detection.description && ( + {detection.description} + )}
- {chip(detection.action, detection.action === "BLOCK" ? "red" : "blue")} + + {chip(detection.action, detection.action === "BLOCK" ? "red" : "blue")} +
@@ -186,18 +209,24 @@ const ContentFilterDetails: React.FC = ({ response }) {/* Category Keywords Section */} {categoryKeywords.length > 0 && ( -
+
{categoryKeywords.map((detection, idx) => (
- {detection.category || "unknown"} - - {detection.keyword || "unknown"} + + {detection.category || t("viewLogs.contentFilterDetails.unknown")} + + + {detection.keyword || t("viewLogs.contentFilterDetails.unknown")} {detection.severity && ( - + {chip( detection.severity, detection.severity === "high" ? "red" : detection.severity === "medium" ? "amber" : "slate", @@ -206,7 +235,9 @@ const ContentFilterDetails: React.FC = ({ response }) )}
- {chip(detection.action, detection.action === "BLOCK" ? "red" : "blue")} + + {chip(detection.action, detection.action === "BLOCK" ? "red" : "blue")} +
@@ -216,7 +247,7 @@ const ContentFilterDetails: React.FC = ({ response }) )} {/* Raw JSON (for debugging) */} -
+
{JSON.stringify(detections, null, 2)}
diff --git a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.tsx b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.tsx index fc2e4f092b3..e5180610381 100644 --- a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.tsx @@ -1,5 +1,6 @@ import React, { useState, useMemo } from "react"; import { Tooltip } from "antd"; +import { useTranslation } from "react-i18next"; import PresidioDetectedEntities from "./PresidioDetectedEntities"; import BedrockGuardrailDetails, { BedrockGuardrailResponse, @@ -250,19 +251,23 @@ const ExternalLinkIcon = () => ( // ── Sub-components ────────────────────────────────────────────────────────── const MatchDetailsTable = ({ matchDetails }: { matchDetails: MatchDetail[] }) => { + const { t } = useTranslation(); + if (!matchDetails || matchDetails.length === 0) return null; return (
-
Match Details ({matchDetails.length})
+
+ {t("viewLogs.guardrailViewer.matchDetails", { count: matchDetails.length })} +
- - - - + + + + @@ -297,6 +302,7 @@ const MatchDetailsTable = ({ matchDetails }: { matchDetails: MatchDetail[] }) => }; const GenericGuardrailResponse = ({ response }: { response: any }) => { + const { t } = useTranslation(); const [showRaw, setShowRaw] = useState(false); return (
@@ -307,7 +313,7 @@ const GenericGuardrailResponse = ({ response }: { response: any }) => { >
-
Raw Guardrail Response
+
{t("viewLogs.guardrailViewer.rawGuardrailResponse")}
{showRaw && ( @@ -331,6 +337,7 @@ interface TimelineEntry { } const RequestLifecycle = ({ entries }: { entries: GuardrailInformation[] }) => { + const { t } = useTranslation(); const sorted = useMemo(() => [...entries].sort((a, b) => (a.start_time ?? 0) - (b.start_time ?? 0)), [entries]); const timeline = useMemo(() => { @@ -340,7 +347,7 @@ const RequestLifecycle = ({ entries }: { entries: GuardrailInformation[] }) => { const items: TimelineEntry[] = []; // Request received - items.push({ type: "request", label: "Request received", offsetMs: 0 }); + items.push({ type: "request", label: t("viewLogs.guardrailViewer.requestReceived"), offsetMs: 0 }); // Pre-call guardrails — use modeMatches so array modes (e.g. ["pre_call", "post_call"]) // place the entry in every matching bucket. @@ -354,9 +361,9 @@ const RequestLifecycle = ({ entries }: { entries: GuardrailInformation[] }) => { const offsetMs = Math.round((e.end_time - baseTime) * 1000); items.push({ type: "guardrail", - label: `Pre-call guardrail: ${getDisplayName(e)}`, + label: t("viewLogs.guardrailViewer.preCallGuardrail", { name: getDisplayName(e) }), offsetMs, - status: isEntrySuccess(e) ? "PASSED" : "FAILED", + status: isEntrySuccess(e) ? t("viewLogs.guardrailViewer.passed") : t("viewLogs.guardrailViewer.failed"), isSuccess: isEntrySuccess(e), }); } @@ -369,7 +376,7 @@ const RequestLifecycle = ({ entries }: { entries: GuardrailInformation[] }) => { items.push({ type: "llm", - label: "LLM call", + label: t("viewLogs.guardrailViewer.llmCall"), offsetMs: llmOffsetMs, }); @@ -378,9 +385,9 @@ const RequestLifecycle = ({ entries }: { entries: GuardrailInformation[] }) => { const offsetMs = Math.round((e.end_time - baseTime) * 1000); items.push({ type: "guardrail", - label: `During-call guardrail: ${getDisplayName(e)}`, + label: t("viewLogs.guardrailViewer.duringCallGuardrail", { name: getDisplayName(e) }), offsetMs, - status: isEntrySuccess(e) ? "PASSED" : "FAILED", + status: isEntrySuccess(e) ? t("viewLogs.guardrailViewer.passed") : t("viewLogs.guardrailViewer.failed"), isSuccess: isEntrySuccess(e), }); } @@ -390,9 +397,9 @@ const RequestLifecycle = ({ entries }: { entries: GuardrailInformation[] }) => { const offsetMs = Math.round((e.end_time - baseTime) * 1000); items.push({ type: "guardrail", - label: `Post-call guardrail: ${getDisplayName(e)}`, + label: t("viewLogs.guardrailViewer.postCallGuardrail", { name: getDisplayName(e) }), offsetMs, - status: isEntrySuccess(e) ? "PASSED" : "FAILED", + status: isEntrySuccess(e) ? t("viewLogs.guardrailViewer.passed") : t("viewLogs.guardrailViewer.failed"), isSuccess: isEntrySuccess(e), }); } @@ -400,14 +407,16 @@ const RequestLifecycle = ({ entries }: { entries: GuardrailInformation[] }) => { // Response returned const maxEnd = Math.max(...sorted.map((e) => e.end_time)); const responseOffsetMs = Math.round((maxEnd - baseTime) * 1000) + 1; - items.push({ type: "response", label: "Response returned", offsetMs: responseOffsetMs }); + items.push({ type: "response", label: t("viewLogs.guardrailViewer.responseReturned"), offsetMs: responseOffsetMs }); return items; - }, [sorted]); + }, [sorted, t]); return (
-

Request Lifecycle

+

+ {t("viewLogs.guardrailViewer.requestLifecycle")} +

{timeline.map((item, idx) => (
@@ -457,6 +466,7 @@ const RequestLifecycle = ({ entries }: { entries: GuardrailInformation[] }) => { // ── Evaluation Card ───────────────────────────────────────────────────────── const EvaluationCard = ({ entry }: { entry: GuardrailInformation }) => { + const { t } = useTranslation(); const [expanded, setExpanded] = useState(false); const success = isEntrySuccess(entry); const totalMasked = getTotalMasked(entry); @@ -509,7 +519,7 @@ const EvaluationCard = ({ entry }: { entry: GuardrailInformation }) => { : "bg-red-100 text-red-700 border border-red-200" }`} > - {success ? "PASSED" : "FAILED"} + {success ? t("viewLogs.guardrailViewer.passed") : t("viewLogs.guardrailViewer.failed")} {matchCountStr && ( @@ -526,16 +536,18 @@ const EvaluationCard = ({ entry }: { entry: GuardrailInformation }) => { {entry.confidence_score != null && ( - {(entry.confidence_score * 100).toFixed(0)}% conf + {t("viewLogs.guardrailViewer.confidencePercent", { + percent: (entry.confidence_score * 100).toFixed(0), + })} )} {riskScore != null && success && ( - + - Risk {riskScore}/10 + {t("viewLogs.guardrailViewer.riskScore", { score: riskScore })} )} @@ -559,28 +571,36 @@ const EvaluationCard = ({ entry }: { entry: GuardrailInformation }) => { {/* Classification details for llm-judge */} {entry.classification && (
-
Classification
+
{t("viewLogs.guardrailViewer.classification")}
{entry.classification.category && (
- Category: + + {t("viewLogs.guardrailViewer.classificationCategory")} + {entry.classification.category}
)} {entry.classification.article_reference && (
- Reference: + + {t("viewLogs.guardrailViewer.classificationReference")} + {entry.classification.article_reference}
)} {entry.classification.confidence != null && (
- Confidence: + + {t("viewLogs.guardrailViewer.classificationConfidence")} + {(entry.classification.confidence * 100).toFixed(0)}%
)} {entry.classification.reason && (
- Reason: + + {t("viewLogs.guardrailViewer.classificationReason")} + {entry.classification.reason}
)} @@ -595,7 +615,7 @@ const EvaluationCard = ({ entry }: { entry: GuardrailInformation }) => { {/* Masked entity summary */} {totalMasked > 0 && (
-
Masked Entities
+
{t("viewLogs.guardrailViewer.maskedEntities")}
{Object.entries(entry.masked_entity_count || {}).map(([entityType, count]) => ( @@ -634,6 +654,7 @@ const EvaluationCard = ({ entry }: { entry: GuardrailInformation }) => { // ── Main Component ────────────────────────────────────────────────────────── const GuardrailViewer = ({ data, accessToken, logEntry }: GuardrailViewerProps) => { + const { t } = useTranslation(); const guardrailEntries = useMemo(() => { return Array.isArray(data) ? data.filter((entry): entry is GuardrailInformation => Boolean(entry)) @@ -676,10 +697,10 @@ const GuardrailViewer = ({ data, accessToken, logEntry }: GuardrailViewerProps)
-

Guardrails & Policy Compliance

+

{t("viewLogs.guardrailViewer.headerTitle")}

- {guardrailEntries.length} guardrail{guardrailEntries.length !== 1 ? "s" : ""} evaluated + {t("viewLogs.guardrailViewer.guardrailsEvaluated_other", { count: guardrailEntries.length })} | ) : null} - {passedCount} Passed + {t("viewLogs.guardrailViewer.passedCount", { count: passedCount })}
@@ -708,7 +729,9 @@ const GuardrailViewer = ({ data, accessToken, logEntry }: GuardrailViewerProps)
-
Total: {totalOverheadMs}ms overhead
+
+ {t("viewLogs.guardrailViewer.totalOverhead", { ms: totalOverheadMs })} +
@@ -737,7 +760,9 @@ const GuardrailViewer = ({ data, accessToken, logEntry }: GuardrailViewerProps) {/* Evaluation Details */}
-

Evaluation Details

+

+ {t("viewLogs.guardrailViewer.evaluationDetails")} +

{guardrailEntries.map((entry, index) => ( diff --git a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/PresidioDetectedEntities.tsx b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/PresidioDetectedEntities.tsx index 016c440e47f..1fd0716fb8a 100644 --- a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/PresidioDetectedEntities.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/PresidioDetectedEntities.tsx @@ -1,4 +1,5 @@ import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; interface RecognitionMetadata { recognizer_name: string; @@ -24,6 +25,7 @@ const getScoreColor = (score: number): string => { }; const PresidioDetectedEntities = ({ entities }: PresidioDetectedEntitiesProps) => { + const { t } = useTranslation(); const [entityListExpanded, setEntityListExpanded] = useState(true); const [expandedEntities, setExpandedEntities] = useState>({}); @@ -47,7 +49,9 @@ const PresidioDetectedEntities = ({ entities }: PresidioDetectedEntitiesProps) = > -

Detected Entities ({entities.length})

+

+ {t("viewLogs.presidioDetectedEntities.detectedEntities", { count: entities.length })} +

{entityListExpanded && ( @@ -71,10 +75,15 @@ const PresidioDetectedEntities = ({ entities }: PresidioDetectedEntitiesProps) = {entity.entity_type} - Score: {entity.score.toFixed(2)} + + {t("viewLogs.presidioDetectedEntities.scoreLabel", { score: entity.score.toFixed(2) })} +
- Position: {entity.start}-{entity.end} + {t("viewLogs.presidioDetectedEntities.positionRange", { + start: entity.start, + end: entity.end, + })}
@@ -83,17 +92,26 @@ const PresidioDetectedEntities = ({ entities }: PresidioDetectedEntitiesProps) =
- Entity Type: + + {t("viewLogs.presidioDetectedEntities.entityTypeLabel")} + {entity.entity_type}
- Position: + + {t("viewLogs.presidioDetectedEntities.positionLabel")} + - Characters {entity.start}-{entity.end} + {t("viewLogs.presidioDetectedEntities.characters", { + start: entity.start, + end: entity.end, + })}
- Confidence: + + {t("viewLogs.presidioDetectedEntities.confidenceLabel")} + {entity.score.toFixed(2)}
@@ -102,11 +120,15 @@ const PresidioDetectedEntities = ({ entities }: PresidioDetectedEntitiesProps) = {entity.recognition_metadata && ( <>
- Recognizer: + + {t("viewLogs.presidioDetectedEntities.recognizerLabel")} + {entity.recognition_metadata.recognizer_name}
- Identifier: + + {t("viewLogs.presidioDetectedEntities.identifierLabel")} + {entity.recognition_metadata.recognizer_identifier} @@ -115,7 +137,9 @@ const PresidioDetectedEntities = ({ entities }: PresidioDetectedEntitiesProps) = )} {entity.analysis_explanation && (
- Explanation: + + {t("viewLogs.presidioDetectedEntities.explanationLabel")} + {entity.analysis_explanation}
)} diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/CollapsibleMessage.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/CollapsibleMessage.tsx index 9aa13c3832a..fa2822820b8 100644 --- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/CollapsibleMessage.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/CollapsibleMessage.tsx @@ -6,6 +6,7 @@ import { useState } from "react"; import { Typography } from "antd"; import { DownOutlined, RightOutlined } from "@ant-design/icons"; +import { useTranslation } from "react-i18next"; const { Text } = Typography; @@ -16,6 +17,7 @@ interface CollapsibleMessageProps { } export function CollapsibleMessage({ label, content, defaultExpanded = false }: CollapsibleMessageProps) { + const { t } = useTranslation(); const [isExpanded, setIsExpanded] = useState(defaultExpanded); const [isHovered, setIsHovered] = useState(false); const charCount = content?.length || 0; @@ -52,7 +54,7 @@ export function CollapsibleMessage({ label, content, defaultExpanded = false }: {label} - ({charCount.toLocaleString()} chars) + {t("viewLogs.collapsibleMessage.charCount", { count: charCount.toLocaleString() })}
diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/DrawerHeader.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/DrawerHeader.tsx index 8c0b679a846..4a1d2471471 100644 --- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/DrawerHeader.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/DrawerHeader.tsx @@ -1,6 +1,7 @@ import { Button, Space, Tag, Tooltip, Typography } from "antd"; import { CloseOutlined, UpOutlined, DownOutlined } from "@ant-design/icons"; import moment from "moment"; +import { useTranslation } from "react-i18next"; import { LogEntry } from "../columns"; import { getProviderLogoAndName } from "../../provider_info_helpers"; import { @@ -40,6 +41,7 @@ export function DrawerHeader({ statusColor, environment, }: DrawerHeaderProps) { + const { t } = useTranslation(); const provider = log.custom_llm_provider || ""; const providerInfo = provider ? getProviderLogoAndName(provider) : null; @@ -87,12 +89,13 @@ function ModelProviderSection({ providerLogo?: string; providerName?: string; }) { + const { t } = useTranslation(); return ( {providerLogo && ( {providerName { const target = e.target as HTMLImageElement; @@ -118,12 +121,16 @@ function ModelProviderSection({ * Request ID display with copy functionality */ function RequestIdSection({ requestId }: { requestId: string }) { + const { t } = useTranslation(); return (
void; onClose: () => void; }) { + const { t } = useTranslation(); const keyboardShortcutStyle = { border: "1px solid #d9d9d9", borderRadius: 4, @@ -173,7 +181,7 @@ function NavigationSection({ J - +
diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.tsx index 6463abf89d4..2ffb3f7df97 100644 --- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.tsx @@ -1,6 +1,7 @@ import { Typography } from "antd"; import { JsonView, defaultStyles } from "react-json-view-lite"; import "react-json-view-lite/dist/index.css"; +import { useTranslation } from "react-i18next"; import { JSON_MAX_HEIGHT, COLOR_BG_LIGHT, SPACING_LARGE } from "./constants"; const { Text } = Typography; @@ -15,7 +16,8 @@ interface JsonViewerProps { * Uses an interactive tree component for easy navigation. */ export function JsonViewer({ data }: JsonViewerProps) { - if (!data) return No data; + const { t } = useTranslation(); + if (!data) return {t("common.noData")}; return (
TypeMethodActionDetail{t("viewLogs.guardrailViewer.matchDetailsHeader.type")}{t("viewLogs.guardrailViewer.matchDetailsHeader.method")}{t("viewLogs.guardrailViewer.matchDetailsHeader.action")}{t("viewLogs.guardrailViewer.matchDetailsHeader.detail")}