diff --git a/ui/litellm-dashboard/src/components/guardrails/TeamGuardrailsTab.tsx b/ui/litellm-dashboard/src/components/guardrails/TeamGuardrailsTab.tsx index d9d9f9ac1c4..789eec5c5f9 100644 --- a/ui/litellm-dashboard/src/components/guardrails/TeamGuardrailsTab.tsx +++ b/ui/litellm-dashboard/src/components/guardrails/TeamGuardrailsTab.tsx @@ -1,6 +1,9 @@ "use client"; import React, { useState, useEffect, useCallback } from "react"; +import { useTranslation } from "react-i18next"; +import { Trans } from "react-i18next"; +import type { TFunction } from "i18next"; import { SearchIcon, PlusIcon, @@ -109,26 +112,30 @@ function submissionToTeamGuardrail(item: GuardrailSubmissionItem): TeamGuardrail }; } -const STATUS_CONFIG: Record = { - active: { - label: "Active", - bg: "bg-green-50", - text: "text-green-700", - dot: "bg-green-500", - }, - pending: { - label: "Pending Review", - bg: "bg-yellow-50", - text: "text-yellow-700", - dot: "bg-yellow-500", - }, - rejected: { - label: "Rejected", - bg: "bg-red-50", - text: "text-red-700", - dot: "bg-red-500", - }, -}; +type StatusConfig = { label: string; bg: string; text: string; dot: string }; + +function getStatusConfig(t: TFunction): Record { + return { + active: { + label: t("guardrails.teamGuardrailsTab.statusActive"), + bg: "bg-green-50", + text: "text-green-700", + dot: "bg-green-500", + }, + pending: { + label: t("guardrails.teamGuardrailsTab.statusPendingReview"), + bg: "bg-yellow-50", + text: "text-yellow-700", + dot: "bg-yellow-500", + }, + rejected: { + label: t("guardrails.teamGuardrailsTab.statusRejected"), + bg: "bg-red-50", + text: "text-red-700", + dot: "bg-red-500", + }, + }; +} const TEAM_COLORS: Record = { "ML Platform": "bg-purple-100 text-purple-700", @@ -227,7 +234,9 @@ function GuardrailCard({ onApprove, onReject, }: GuardrailCardProps) { - const status = STATUS_CONFIG[g.status]; + const { t } = useTranslation(); + const statusConfig = getStatusConfig(t); + const status = statusConfig[g.status]; const teamColor = TEAM_COLORS[g.team] ?? "bg-gray-100 text-gray-700"; return (
- Team: {g.team} + + {t("guardrails.teamGuardrailsTab.teamLabel", { team: g.team })} + @@ -254,16 +265,20 @@ function GuardrailCard({
- Model: {g.model} + {t("guardrails.teamGuardrailsTab.modelLabel")}{" "} + {g.model} - Submitted: {g.submittedAt} + {t("guardrails.teamGuardrailsTab.submittedLabel")}{" "} + {g.submittedAt}
- Forward API Key + + {t("guardrails.teamGuardrailsTab.forwardApiKey")} +
@@ -272,7 +287,7 @@ function GuardrailCard({ onClick={onSelect} className="text-xs border border-gray-300 text-gray-600 hover:bg-gray-50 px-3 py-1.5 rounded-md transition-colors font-medium" > - {isSelected ? "Close" : "Review"} + {isSelected ? t("common.close") : t("guardrails.teamGuardrailsTab.review")} {g.status === "pending" && ( <> @@ -281,14 +296,14 @@ function GuardrailCard({ onClick={onApprove} className="text-xs bg-green-500 hover:bg-green-600 text-white px-3 py-1.5 rounded-md transition-colors font-medium" > - Approve + {t("guardrails.teamGuardrailsTab.approve")} )} @@ -302,7 +317,7 @@ function GuardrailCard({ className="flex items-center gap-1.5 text-xs text-gray-500 hover:text-gray-700 transition-colors" > {isHeadersExpanded ? : } - Static headers + {t("guardrails.teamGuardrailsTab.staticHeaders")} {g.customHeaders.length > 0 && ( {g.customHeaders.length} @@ -312,7 +327,7 @@ function GuardrailCard({ {isHeadersExpanded && (
{g.customHeaders.length === 0 ? ( -

No static headers configured.

+

{t("guardrails.teamGuardrailsTab.noStaticHeaders")}

) : (
{g.customHeaders.map((h, i) => ( @@ -361,11 +376,13 @@ function DetailPanel({ onUpdateCustomHeaders, onUpdateExtraHeaders, }: DetailPanelProps) { + const { t } = useTranslation(); const [configExpanded, setConfigExpanded] = useState(false); const [newExtraHeader, setNewExtraHeader] = useState(""); const [newStaticHeaderKey, setNewStaticHeaderKey] = useState(""); const [newStaticHeaderValue, setNewStaticHeaderValue] = useState(""); - const status = STATUS_CONFIG[g.status]; + const statusConfig = getStatusConfig(t); + const status = statusConfig[g.status]; const teamColor = TEAM_COLORS[g.team] ?? "bg-gray-100 text-gray-700"; return (
@@ -373,7 +390,9 @@ function DetailPanel({
- Team: {g.team} + + {t("guardrails.teamGuardrailsTab.teamLabel", { team: g.team })} + @@ -383,21 +402,21 @@ function DetailPanel({

{g.name}

- Submitted by {g.submittedBy} on {g.submittedAt} + {t("guardrails.teamGuardrailsTab.submittedBy", { by: g.submittedBy, at: g.submittedAt })}

{g.description}

- Static headers + + {t("guardrails.teamGuardrailsTab.staticHeaders")} + {g.customHeaders.length > 0 && ( {g.customHeaders.length} )}
-

Sent with every request to the guardrail.

+

{t("guardrails.teamGuardrailsTab.staticHeadersDesc")}

{g.customHeaders.length === 0 ? ( -

No static headers configured.

+

{t("guardrails.teamGuardrailsTab.noStaticHeaders")}

) : (
    {g.customHeaders.map((h, i) => ( @@ -456,7 +479,7 @@ function DetailPanel({ type="button" onClick={() => onUpdateCustomHeaders(g.customHeaders.filter((_, idx) => idx !== i))} className="text-gray-400 hover:text-red-600 flex-shrink-0" - aria-label={`Remove ${h.key}`} + aria-label={t("guardrails.teamGuardrailsTab.removeHeader", { name: h.key })} > @@ -469,7 +492,7 @@ function DetailPanel({ type="text" value={newStaticHeaderKey} onChange={(e) => setNewStaticHeaderKey(e.target.value)} - placeholder="Header name (e.g. X-API-Key)" + placeholder={t("guardrails.teamGuardrailsTab.headerNamePlaceholder")} className="flex-1 min-w-0 text-xs font-mono border border-gray-200 rounded px-2 py-1.5 text-gray-700 placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-blue-500" onKeyDown={(e) => { if (e.key === "Enter") { @@ -488,7 +511,7 @@ function DetailPanel({ type="text" value={newStaticHeaderValue} onChange={(e) => setNewStaticHeaderValue(e.target.value)} - placeholder="Value" + placeholder={t("guardrails.teamGuardrailsTab.headerValuePlaceholder")} className="flex-1 min-w-0 text-xs font-mono border border-gray-200 rounded px-2 py-1.5 text-gray-700 placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-blue-500" onKeyDown={(e) => { if (e.key === "Enter") { @@ -516,24 +539,26 @@ function DetailPanel({ }} className="text-xs font-medium text-blue-600 hover:text-blue-700 border border-blue-200 bg-blue-50 hover:bg-blue-100 px-2 py-1.5 rounded transition-colors flex-shrink-0" > - Add + {t("common.add")}
- Forward client headers + + {t("guardrails.teamGuardrailsTab.forwardClientHeaders")} + {g.extraHeaders.length > 0 && ( {g.extraHeaders.length} )}
-

- Allowed header names to forward from the client request to the guardrail (e.g. x-request-id). -

+

{t("guardrails.teamGuardrailsTab.forwardClientHeadersDesc")}

{g.extraHeaders.length === 0 ? ( -

No forward client headers configured.

+

+ {t("guardrails.teamGuardrailsTab.noForwardClientHeaders")} +

) : (
    {g.extraHeaders.map((name, i) => ( @@ -546,7 +571,7 @@ function DetailPanel({ type="button" onClick={() => onUpdateExtraHeaders(g.extraHeaders.filter((_, idx) => idx !== i))} className="text-gray-400 hover:text-red-600 flex-shrink-0" - aria-label={`Remove ${name}`} + aria-label={t("guardrails.teamGuardrailsTab.removeHeader", { name })} > @@ -559,7 +584,7 @@ function DetailPanel({ type="text" value={newExtraHeader} onChange={(e) => setNewExtraHeader(e.target.value)} - placeholder="e.g. x-request-id" + placeholder={t("guardrails.teamGuardrailsTab.extraHeaderPlaceholder")} className="flex-1 min-w-0 text-xs font-mono border border-gray-200 rounded px-2 py-1.5 text-gray-700 placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-blue-500" onKeyDown={(e) => { if (e.key === "Enter") { @@ -583,7 +608,7 @@ function DetailPanel({ }} className="text-xs font-medium text-blue-600 hover:text-blue-700 border border-blue-200 bg-blue-50 hover:bg-blue-100 px-2 py-1.5 rounded transition-colors" > - Add + {t("common.add")}
@@ -593,7 +618,7 @@ function DetailPanel({ onClick={() => setConfigExpanded(!configExpanded)} className="w-full flex items-center justify-between px-3 py-2 text-left text-xs font-semibold text-gray-700 bg-gray-50 hover:bg-gray-100 transition-colors" > - Equivalent config + {t("guardrails.teamGuardrailsTab.equivalentConfig")} {configExpanded ? ( ) : ( @@ -609,17 +634,19 @@ function DetailPanel({
@@ -629,7 +656,7 @@ function DetailPanel({ className="w-full flex items-center justify-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 text-sm font-medium py-2 rounded-md transition-colors" > - Test Endpoint + {t("guardrails.teamGuardrailsTab.testEndpoint")} {g.status === "pending" && (
@@ -639,7 +666,7 @@ function DetailPanel({ className="flex-1 flex items-center justify-center gap-1.5 bg-green-500 hover:bg-green-600 text-white text-sm font-medium py-2 rounded-md transition-colors" > - Approve + {t("guardrails.teamGuardrailsTab.approve")}
)} @@ -665,6 +692,7 @@ type ConfirmDialogProps = { }; function ConfirmDialog({ action, guardrailName, onConfirm, onCancel }: ConfirmDialogProps) { + const { t } = useTranslation(); const isApprove = action === "approve"; return (
@@ -681,14 +709,14 @@ function ConfirmDialog({ action, guardrailName, onConfirm, onCancel }: ConfirmDi )}

- {isApprove ? "Approve Guardrail" : "Reject Guardrail"} + {isApprove + ? t("guardrails.teamGuardrailsTab.approveGuardrailTitle") + : t("guardrails.teamGuardrailsTab.rejectGuardrailTitle")}

- Are you sure you want to {action}{" "} - "{guardrailName}"?{" "} {isApprove - ? "This will make it active and available for use." - : "This will mark it as rejected and notify the team."} + ? t("guardrails.teamGuardrailsTab.approveGuardrailConfirm", { name: guardrailName }) + : t("guardrails.teamGuardrailsTab.rejectGuardrailConfirm", { name: guardrailName })}

@@ -718,6 +746,7 @@ interface TeamGuardrailsTabProps { } export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) { + const { t } = useTranslation(); const [guardrails, setGuardrails] = useState([]); const [summary, setSummary] = useState({ total: 0, @@ -741,8 +770,8 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) { const registerGuardrail = useRegisterGuardrail(); useEffect(() => { - const t = setTimeout(() => setSearchDebounced(search), 300); - return () => clearTimeout(t); + const timer = setTimeout(() => setSearchDebounced(search), 300); + return () => clearTimeout(timer); }, [search]); const fetchSubmissions = useCallback(async () => { @@ -762,12 +791,12 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) { setGuardrails(res.submissions.map(submissionToTeamGuardrail)); setSummary(res.summary); } catch (err) { - setError(err instanceof Error ? err.message : "Failed to load submissions"); + setError(err instanceof Error ? err.message : t("guardrails.teamGuardrailsTab.failedToLoadSubmissions")); setGuardrails([]); } finally { setIsLoading(false); } - }, [accessToken, statusFilter, searchDebounced]); + }, [accessToken, statusFilter, searchDebounced, t]); useEffect(() => { fetchSubmissions(); @@ -790,9 +819,13 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) { litellm_params: { forward_api_key: newValue }, }); setGuardrails((prev) => prev.map((x) => (x.id === id ? { ...x, forwardKey: newValue } : x))); - NotificationsManager.success(newValue ? "Forward API key enabled" : "Forward API key disabled"); + NotificationsManager.success( + newValue + ? t("guardrails.teamGuardrailsTab.forwardApiKeyEnabled") + : t("guardrails.teamGuardrailsTab.forwardApiKeyDisabled"), + ); } catch { - NotificationsManager.fromBackend("Failed to update forward API key"); + NotificationsManager.fromBackend(t("guardrails.teamGuardrailsTab.failedToUpdateForwardApiKey")); } } @@ -816,9 +849,9 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) { : x, ), ); - NotificationsManager.success("Static headers updated"); + NotificationsManager.success(t("guardrails.teamGuardrailsTab.staticHeadersUpdated")); } catch { - NotificationsManager.fromBackend("Failed to update static headers"); + NotificationsManager.fromBackend(t("guardrails.teamGuardrailsTab.failedToUpdateStaticHeaders")); } } @@ -829,9 +862,9 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) { litellm_params: { extra_headers: extraHeaders }, }); setGuardrails((prev) => prev.map((x) => (x.id === id ? { ...x, extraHeaders } : x))); - NotificationsManager.success("Forward client headers updated"); + NotificationsManager.success(t("guardrails.teamGuardrailsTab.forwardClientHeadersUpdated")); } catch { - NotificationsManager.fromBackend("Failed to update forward client headers"); + NotificationsManager.fromBackend(t("guardrails.teamGuardrailsTab.failedToUpdateForwardClientHeaders")); } } @@ -842,9 +875,9 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) { setConfirmAction(null); if (selectedId === id) setSelectedId(null); await fetchSubmissions(); - NotificationsManager.success("Guardrail approved"); + NotificationsManager.success(t("guardrails.teamGuardrailsTab.guardrailApproved")); } catch { - NotificationsManager.fromBackend("Failed to approve guardrail"); + NotificationsManager.fromBackend(t("guardrails.teamGuardrailsTab.failedToApproveGuardrail")); } } @@ -855,9 +888,9 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) { setConfirmAction(null); if (selectedId === id) setSelectedId(null); await fetchSubmissions(); - NotificationsManager.success("Guardrail rejected"); + NotificationsManager.success(t("guardrails.teamGuardrailsTab.guardrailRejected")); } catch { - NotificationsManager.fromBackend("Failed to reject guardrail"); + NotificationsManager.fromBackend(t("guardrails.teamGuardrailsTab.failedToRejectGuardrail")); } } @@ -874,17 +907,25 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
- - - - + + + +
setSearch(e.target.value)} className="w-full pl-9 pr-4 py-2 border border-gray-200 rounded-md text-sm text-gray-700 placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500" @@ -895,10 +936,10 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) { onChange={(e) => setStatusFilter(e.target.value as typeof statusFilter)} className="border border-gray-200 rounded-md px-3 py-2 text-sm text-gray-700 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-blue-500 bg-white" > - - - - + + + +
- {isLoading &&
Loading submissions…
} + {isLoading && ( +
+ {t("guardrails.teamGuardrailsTab.loadingSubmissions")} +
+ )} {error &&
{error}
} {!isLoading && !error && filtered.length === 0 && ( -
No guardrails match your filters.
+
+ {t("guardrails.teamGuardrailsTab.noGuardrailsMatch")} +
)} {!isLoading && !error && @@ -955,17 +1002,17 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) { )} { setIsSubmitModalOpen(false); submitForm.resetFields(); }} onOk={() => submitForm.submit()} - okText="Submit for Review" + okText={t("guardrails.teamGuardrailsTab.submitForReview")} >
- Your guardrail will be sent for admin review before it becomes active. + {t("guardrails.teamGuardrailsTab.submitCallout")}
- + - + - + - + { @@ -1032,11 +1087,11 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) { try { const parsed = JSON.parse(value); if (typeof parsed !== "object" || Array.isArray(parsed)) { - return Promise.reject("Must be a JSON object"); + return Promise.reject(t("guardrails.teamGuardrailsTab.formMustBeJsonObject")); } return Promise.resolve(); } catch { - return Promise.reject("Invalid JSON"); + return Promise.reject(t("guardrails.teamGuardrailsTab.formInvalidJson")); } }, }, @@ -1045,11 +1100,11 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) { diff --git a/ui/litellm-dashboard/src/locales/en.json b/ui/litellm-dashboard/src/locales/en.json index 0c0ba629b77..ccb09787772 100644 --- a/ui/litellm-dashboard/src/locales/en.json +++ b/ui/litellm-dashboard/src/locales/en.json @@ -1222,7 +1222,85 @@ "labelMode": "Mode", "labelDefaultOn": "Default On", "deleteSuccess": "Guardrail \"{{name}}\" deleted successfully", - "deleteFailed": "Failed to delete guardrail" + "deleteFailed": "Failed to delete guardrail", + "teamGuardrailsTab": { + "statusActive": "Active", + "statusPendingReview": "Pending Review", + "statusRejected": "Rejected", + "teamLabel": "Team: {{team}}", + "modelLabel": "Model:", + "submittedLabel": "Submitted:", + "forwardApiKey": "Forward API Key", + "review": "Review", + "approve": "Approve", + "reject": "Reject", + "staticHeaders": "Static headers", + "noStaticHeaders": "No static headers configured.", + "submittedBy": "Submitted by {{by}} on {{at}}", + "closeDetailPanel": "Close detail panel", + "endpoint": "Endpoint", + "method": "Method", + "forwardLiteLLMApiKey": "Forward LiteLLM API Key", + "forwardKeyDesc": "When enabled, the caller's LiteLLM API key is forwarded as an Authorization header to your guardrail endpoint. This allows your guardrail to authenticate model calls using the original caller's credentials.", + "staticHeadersDesc": "Sent with every request to the guardrail.", + "removeHeader": "Remove {{name}}", + "headerNamePlaceholder": "Header name (e.g. X-API-Key)", + "headerValuePlaceholder": "Value", + "forwardClientHeaders": "Forward client headers", + "forwardClientHeadersDesc": "Allowed header names to forward from the client request to the guardrail (e.g. x-request-id).", + "noForwardClientHeaders": "No forward client headers configured.", + "extraHeaderPlaceholder": "e.g. x-request-id", + "equivalentConfig": "Equivalent config", + "guardrailInfoNote": "This guardrail runs on a separate instance. It receives the user request and forwards the result to the next step in the pipeline. See LiteLLM Generic Guardrail API docs for configuration details.", + "testEndpoint": "Test Endpoint", + "approveGuardrailTitle": "Approve Guardrail", + "rejectGuardrailTitle": "Reject Guardrail", + "approveGuardrailConfirm": "Are you sure you want to approve \"{{name}}\"? This will make it active and available for use.", + "rejectGuardrailConfirm": "Are you sure you want to reject \"{{name}}\"? This will mark it as rejected and notify the team.", + "totalSubmitted": "Total Submitted", + "searchPlaceholder": "Search guardrails...", + "filterAllStatus": "All Status", + "addGuardrail": "Add Guardrail", + "loadingSubmissions": "Loading submissions…", + "noGuardrailsMatch": "No guardrails match your filters.", + "failedToLoadSubmissions": "Failed to load submissions", + "forwardApiKeyEnabled": "Forward API key enabled", + "forwardApiKeyDisabled": "Forward API key disabled", + "failedToUpdateForwardApiKey": "Failed to update forward API key", + "staticHeadersUpdated": "Static headers updated", + "failedToUpdateStaticHeaders": "Failed to update static headers", + "forwardClientHeadersUpdated": "Forward client headers updated", + "failedToUpdateForwardClientHeaders": "Failed to update forward client headers", + "guardrailApproved": "Guardrail approved", + "failedToApproveGuardrail": "Failed to approve guardrail", + "guardrailRejected": "Guardrail rejected", + "failedToRejectGuardrail": "Failed to reject guardrail", + "submitModalTitle": "Submit Guardrail for Review", + "submitForReview": "Submit for Review", + "submitCallout": "Your guardrail will be sent for admin review before it becomes active.", + "guardrailSubmitted": "Guardrail submitted for review", + "formTeam": "Team", + "formTeamRequired": "Select a team", + "formGuardrailName": "Guardrail Name", + "formGuardrailNameRequired": "Enter a guardrail name", + "formGuardrailNamePlaceholder": "e.g. pii-detection", + "formMode": "Mode", + "formModeRequired": "Select a mode", + "modePreCall": "Pre Call", + "modePostCall": "Post Call", + "modeDuringCall": "During Call", + "formApiBaseUrl": "API Base URL", + "formApiBaseUrlRequired": "Enter the API base URL", + "formApiBaseUrlInvalid": "Must be a valid URL", + "formApiBaseUrlPlaceholder": "https://your-guardrail-api.com/v1/check", + "formExtraParams": "Additional litellm_params (optional)", + "formExtraParamsTooltip": "JSON object merged into litellm_params. e.g. forward_api_key, headers, model, unreachable_fallback", + "formExtraParamsPlaceholder": "{\"forward_api_key\": true, \"headers\": {\"X-Custom\": \"value\"}}", + "formMustBeJsonObject": "Must be a JSON object", + "formInvalidJson": "Invalid JSON", + "formGuardrailInfo": "Guardrail Info (optional)", + "formGuardrailInfoPlaceholder": "{\"description\": \"Detects PII in requests\"}" + } }, "keyValueInput": { "headerNamePlaceholder": "Header Name", diff --git a/ui/litellm-dashboard/src/locales/zh-CN.json b/ui/litellm-dashboard/src/locales/zh-CN.json index 5b33d7d06e8..b104119f3c3 100644 --- a/ui/litellm-dashboard/src/locales/zh-CN.json +++ b/ui/litellm-dashboard/src/locales/zh-CN.json @@ -1208,6 +1208,84 @@ "statusNotSet": "未设置" }, "guardrails": { + "teamGuardrailsTab": { + "statusActive": "已激活", + "statusPendingReview": "待审核", + "statusRejected": "已拒绝", + "teamLabel": "团队:{{team}}", + "modelLabel": "模型:", + "submittedLabel": "提交时间:", + "forwardApiKey": "转发 API 密钥", + "review": "审阅", + "approve": "批准", + "reject": "拒绝", + "staticHeaders": "静态请求头", + "noStaticHeaders": "未配置静态请求头。", + "submittedBy": "由 {{by}} 于 {{at}} 提交", + "closeDetailPanel": "关闭详情面板", + "endpoint": "端点", + "method": "方法", + "forwardLiteLLMApiKey": "转发 LiteLLM API 密钥", + "forwardKeyDesc": "启用后,调用方的 LiteLLM API 密钥将作为 Authorization 请求头转发到您的护栏端点,使护栏能够使用原始调用方凭据验证模型调用。", + "staticHeadersDesc": "随每次护栏请求一同发送。", + "removeHeader": "移除 {{name}}", + "headerNamePlaceholder": "请求头名称(如 X-API-Key)", + "headerValuePlaceholder": "值", + "forwardClientHeaders": "转发客户端请求头", + "forwardClientHeadersDesc": "允许从客户端请求转发到护栏的请求头名称(如 x-request-id)。", + "noForwardClientHeaders": "未配置转发客户端请求头。", + "extraHeaderPlaceholder": "如 x-request-id", + "equivalentConfig": "等效配置", + "guardrailInfoNote": "此护栏运行在独立实例上,接收用户请求并将结果转发到流水线下一步。请参阅 LiteLLM Generic Guardrail API 文档了解配置详情。", + "testEndpoint": "测试端点", + "approveGuardrailTitle": "批准护栏", + "rejectGuardrailTitle": "拒绝护栏", + "approveGuardrailConfirm": "确定要批准「{{name}}」吗?批准后将使其激活并可供使用。", + "rejectGuardrailConfirm": "确定要拒绝「{{name}}」吗?拒绝后将标记为已拒绝并通知团队。", + "totalSubmitted": "已提交总数", + "searchPlaceholder": "搜索护栏...", + "filterAllStatus": "所有状态", + "addGuardrail": "添加护栏", + "loadingSubmissions": "加载提交记录中…", + "noGuardrailsMatch": "没有护栏匹配您的筛选条件。", + "failedToLoadSubmissions": "加载提交记录失败", + "forwardApiKeyEnabled": "转发 API 密钥已启用", + "forwardApiKeyDisabled": "转发 API 密钥已禁用", + "failedToUpdateForwardApiKey": "更新转发 API 密钥失败", + "staticHeadersUpdated": "静态请求头已更新", + "failedToUpdateStaticHeaders": "更新静态请求头失败", + "forwardClientHeadersUpdated": "转发客户端请求头已更新", + "failedToUpdateForwardClientHeaders": "更新转发客户端请求头失败", + "guardrailApproved": "护栏已批准", + "failedToApproveGuardrail": "批准护栏失败", + "guardrailRejected": "护栏已拒绝", + "failedToRejectGuardrail": "拒绝护栏失败", + "submitModalTitle": "提交护栏审核", + "submitForReview": "提交审核", + "submitCallout": "您的护栏将在激活前发送给管理员审核。", + "guardrailSubmitted": "护栏已提交审核", + "formTeam": "团队", + "formTeamRequired": "请选择团队", + "formGuardrailName": "护栏名称", + "formGuardrailNameRequired": "请输入护栏名称", + "formGuardrailNamePlaceholder": "如 pii-detection", + "formMode": "模式", + "formModeRequired": "请选择模式", + "modePreCall": "调用前", + "modePostCall": "调用后", + "modeDuringCall": "调用中", + "formApiBaseUrl": "API 基础 URL", + "formApiBaseUrlRequired": "请输入 API 基础 URL", + "formApiBaseUrlInvalid": "请输入有效的 URL", + "formApiBaseUrlPlaceholder": "https://your-guardrail-api.com/v1/check", + "formExtraParams": "附加 litellm_params(可选)", + "formExtraParamsTooltip": "合并到 litellm_params 的 JSON 对象,如 forward_api_key、headers、model、unreachable_fallback", + "formExtraParamsPlaceholder": "{\"forward_api_key\": true, \"headers\": {\"X-Custom\": \"value\"}}", + "formMustBeJsonObject": "必须为 JSON 对象", + "formInvalidJson": "JSON 格式无效", + "formGuardrailInfo": "护栏信息(可选)", + "formGuardrailInfoPlaceholder": "{\"description\": \"Detects PII in requests\"}" + }, "guardrailGardenTab": "护栏库", "guardrailsTab": "护栏", "testPlaygroundTab": "测试 Playground",