diff --git a/ui/litellm-dashboard/src/components/policies/impact_popover.tsx b/ui/litellm-dashboard/src/components/policies/impact_popover.tsx index 632d1fab1c9..eee3f70c4ba 100644 --- a/ui/litellm-dashboard/src/components/policies/impact_popover.tsx +++ b/ui/litellm-dashboard/src/components/policies/impact_popover.tsx @@ -2,6 +2,7 @@ import React, { useState } from "react"; import { Icon } from "@tremor/react"; import { EyeIcon } from "@heroicons/react/outline"; import { Tooltip, Tag, Popover, Spin } from "antd"; +import { useTranslation, Trans } from "react-i18next"; import { PolicyAttachment } from "./types"; import { estimateAttachmentImpactCall } from "../networking"; @@ -9,6 +10,7 @@ const ImpactPopover: React.FC<{ attachment: PolicyAttachment; accessToken: strin attachment, accessToken, }) => { + const { t } = useTranslation(); const [impact, setImpact] = useState(null); const [loading, setLoading] = useState(false); const [loaded, setLoaded] = useState(false); @@ -36,21 +38,29 @@ const ImpactPopover: React.FC<{ attachment: PolicyAttachment; accessToken: strin const content = loading ? (
- Loading... + {t("common.loading")}
) : impact ? (
{impact.affected_keys_count === -1 ? ( -

Global scope — affects all keys and teams

+

{t("policies.impactPopover.globalScope")}

) : ( <>

- {impact.affected_keys_count} key{impact.affected_keys_count !== 1 ? "s" : ""},{" "} - {impact.affected_teams_count} team{impact.affected_teams_count !== 1 ? "s" : ""} affected + , ]} + />

{impact.sample_keys.length > 0 && (
- Keys: + {t("policies.impactPopover.keysLabel")} {impact.sample_keys.map((k: string) => ( {k} @@ -60,34 +70,34 @@ const ImpactPopover: React.FC<{ attachment: PolicyAttachment; accessToken: strin )} {impact.sample_teams.length > 0 && (
- Teams: - {impact.sample_teams.map((t: string) => ( - - {t} + {t("policies.impactPopover.teamsLabel")} + {impact.sample_teams.map((teamName: string) => ( + + {teamName} ))}
)} {impact.affected_keys_count === 0 && impact.affected_teams_count === 0 && ( -

No keys or teams currently affected

+

{t("policies.impactPopover.noKeysOrTeams")}

)} )}
) : ( -

Click to load

+

{t("policies.impactPopover.clickToLoad")}

); return ( { if (open) loadImpact(); }} > - + diff --git a/ui/litellm-dashboard/src/components/policies/impact_preview_alert.tsx b/ui/litellm-dashboard/src/components/policies/impact_preview_alert.tsx index 29cd37a7093..5402a82ea4f 100644 --- a/ui/litellm-dashboard/src/components/policies/impact_preview_alert.tsx +++ b/ui/litellm-dashboard/src/components/policies/impact_preview_alert.tsx @@ -1,5 +1,6 @@ import React from "react"; import { Alert, Tag, Typography } from "antd"; +import { useTranslation, Trans } from "react-i18next"; const { Text } = Typography; @@ -15,34 +16,37 @@ interface ImpactPreviewAlertProps { } const ImpactPreviewAlert: React.FC = ({ impactResult }) => { + const { t } = useTranslation(); + return ( - Global scope — this will affect all keys and teams. + }} /> ) : (
- This attachment would affect{" "} - - {impactResult.affected_keys_count} key{impactResult.affected_keys_count !== 1 ? "s" : ""} - {" "} - and{" "} - - {impactResult.affected_teams_count} team{impactResult.affected_teams_count !== 1 ? "s" : ""} - - . + , ]} + /> {impactResult.sample_keys.length > 0 && (
- Keys:{" "} + {t("policies.impactPreviewAlert.keysLabel")}{" "} {impactResult.sample_keys.slice(0, 5).map((k: string) => ( @@ -51,7 +55,7 @@ const ImpactPreviewAlert: React.FC = ({ impactResult }) ))} {impactResult.affected_keys_count > 5 && ( - and {impactResult.affected_keys_count - 5} more... + {t("policies.impactPreviewAlert.andMore", { count: impactResult.affected_keys_count - 5 })} )}
@@ -59,16 +63,16 @@ const ImpactPreviewAlert: React.FC = ({ impactResult }) {impactResult.sample_teams.length > 0 && (
- Teams:{" "} + {t("policies.impactPreviewAlert.teamsLabel")}{" "} - {impactResult.sample_teams.slice(0, 5).map((t: string) => ( - - {t} + {impactResult.sample_teams.slice(0, 5).map((teamName: string) => ( + + {teamName} ))} {impactResult.affected_teams_count > 5 && ( - and {impactResult.affected_teams_count - 5} more... + {t("policies.impactPreviewAlert.andMore", { count: impactResult.affected_teams_count - 5 })} )}
diff --git a/ui/litellm-dashboard/src/components/policies/index.tsx b/ui/litellm-dashboard/src/components/policies/index.tsx index 4462bbdd9b9..15331df53b0 100644 --- a/ui/litellm-dashboard/src/components/policies/index.tsx +++ b/ui/litellm-dashboard/src/components/policies/index.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect, useCallback } from "react"; import { Button, TabGroup, TabList, Tab, TabPanels, TabPanel } from "@tremor/react"; import { Alert } from "antd"; +import { useTranslation, Trans } from "react-i18next"; import MessageManager from "@/components/molecules/message_manager"; import { InfoCircleOutlined } from "@ant-design/icons"; @@ -39,6 +40,7 @@ interface PoliciesPanelProps { } const PoliciesPanel: React.FC = ({ accessToken, userRole }) => { + const { t } = useTranslation(); const [policiesList, setPoliciesList] = useState([]); const [attachmentsList, setAttachmentsList] = useState([]); const [guardrailsList, setGuardrailsList] = useState([]); @@ -78,11 +80,11 @@ const PoliciesPanel: React.FC = ({ accessToken, userRole }) setPoliciesList(response.policies || []); } catch (error) { console.error("Error fetching policies:", error); - MessageManager.error("Failed to fetch policies"); + MessageManager.error(t("policies.policiesView.fetchPoliciesFailed")); } finally { setIsLoading(false); } - }, [accessToken]); + }, [accessToken, t]); const fetchAttachments = useCallback(async () => { if (!accessToken) return; @@ -93,11 +95,11 @@ const PoliciesPanel: React.FC = ({ accessToken, userRole }) setAttachmentsList(response.attachments || []); } catch (error) { console.error("Error fetching attachments:", error); - MessageManager.error("Failed to fetch attachments"); + MessageManager.error(t("policies.policiesView.fetchAttachmentsFailed")); } finally { setIsAttachmentsLoading(false); } - }, [accessToken]); + }, [accessToken, t]); const fetchGuardrails = useCallback(async () => { if (!accessToken) return; @@ -146,11 +148,11 @@ const PoliciesPanel: React.FC = ({ accessToken, userRole }) setIsDeleting(true); try { await deletePolicyCall(accessToken, policyToDelete.policy_id); - MessageManager.success(`Policy "${policyToDelete.policy_name}" deleted successfully`); + MessageManager.success(t("policies.policiesView.policyDeletedSuccess", { name: policyToDelete.policy_name })); await fetchPolicies(); } catch (error) { console.error("Error deleting policy:", error); - MessageManager.error("Failed to delete policy"); + MessageManager.error(t("policies.policiesView.deletePolicyFailed")); } finally { setIsDeleting(false); setIsDeleteModalOpen(false); @@ -195,7 +197,7 @@ const PoliciesPanel: React.FC = ({ accessToken, userRole }) const handleUseTemplate = async (template: any) => { if (!accessToken) { - MessageManager.error("Authentication required"); + MessageManager.error(t("policies.policiesView.authenticationRequired")); return; } @@ -223,7 +225,7 @@ const PoliciesPanel: React.FC = ({ accessToken, userRole }) setIsGuardrailSelectionModalOpen(true); } catch (error) { console.error("Error fetching guardrails:", error); - MessageManager.error("Failed to load guardrails. Please try again."); + MessageManager.error(t("policies.policiesView.loadGuardrailsFailed")); } }; @@ -273,7 +275,7 @@ const PoliciesPanel: React.FC = ({ accessToken, userRole }) await proceedWithTemplate(enrichedTemplate); } catch (error) { console.error("Error enriching template:", error); - MessageManager.error("Failed to configure template. Please try again."); + MessageManager.error(t("policies.policiesView.configureTemplateFailed")); setIsEnrichingTemplate(false); } }; @@ -320,16 +322,17 @@ const PoliciesPanel: React.FC = ({ accessToken, userRole }) // Show success message if (createdGuardrails.length > 0) { - MessageManager.success( - `Created ${createdGuardrails.length} guardrail${createdGuardrails.length > 1 ? "s" : ""}! Complete the policy form to save.`, - ); + MessageManager.success(t("policies.policiesView.guardrailsCreated", { count: createdGuardrails.length })); } else { - MessageManager.success("Template ready! Complete the policy form to save."); + MessageManager.success(t("policies.policiesView.templateReady")); } if (failedGuardrails.length > 0) { MessageManager.warning( - `Failed to create ${failedGuardrails.length} guardrail(s): ${failedGuardrails.join(", ")}. You may need to create them manually.`, + t("policies.policiesView.guardrailsCreateFailed", { + count: failedGuardrails.length, + list: failedGuardrails.join(", "), + }), ); } @@ -348,7 +351,7 @@ const PoliciesPanel: React.FC = ({ accessToken, userRole }) setTemplateQueue([]); setTemplateQueueProgress(null); console.error("Error creating guardrails:", error); - MessageManager.error("Failed to create guardrails. Please try again."); + MessageManager.error(t("policies.policiesView.createGuardrailsFailed")); } }; @@ -363,26 +366,24 @@ const PoliciesPanel: React.FC = ({ accessToken, userRole })
- Templates - Policies - Attachments - Policy Simulator + {t("policies.policiesView.tabTemplates")} + {t("policies.policiesView.tabPolicies")} + {t("policies.policiesView.tabAttachments")} + {t("policies.policiesView.tabPolicySimulator")} -

- Use policies to group guardrails and control which ones run for specific teams, keys, or models. -

-

Why use policies?

+

{t("policies.policiesView.aboutPoliciesDesc")}

+

{t("policies.policiesView.whyUsePolicies")}

    -
  • Enable/disable specific guardrails for teams, keys, or models
  • -
  • Group guardrails into a single policy
  • -
  • Inherit from existing policies and override what you need
  • +
  • {t("policies.policiesView.whyPoliciesBullet1")}
  • +
  • {t("policies.policiesView.whyPoliciesBullet2")}
  • +
  • {t("policies.policiesView.whyPoliciesBullet3")}
= ({ accessToken, userRole }) rel="noopener noreferrer" className="text-blue-600 hover:text-blue-800 underline inline-block mt-1" > - Learn more in the documentation → + {t("policies.policiesView.learnMoreDocs")}
} @@ -410,17 +411,15 @@ const PoliciesPanel: React.FC = ({ accessToken, userRole }) -

- Use policies to group guardrails and control which ones run for specific teams, keys, or models. -

-

Why use policies?

+

{t("policies.policiesView.aboutPoliciesDesc")}

+

{t("policies.policiesView.whyUsePolicies")}

    -
  • Enable/disable specific guardrails for teams, keys, or models
  • -
  • Group guardrails into a single policy
  • -
  • Inherit from existing policies and override what you need
  • +
  • {t("policies.policiesView.whyPoliciesBullet1")}
  • +
  • {t("policies.policiesView.whyPoliciesBullet2")}
  • +
  • {t("policies.policiesView.whyPoliciesBullet3")}
= ({ accessToken, userRole }) rel="noopener noreferrer" className="text-blue-600 hover:text-blue-800 underline inline-block mt-1" > - Learn more in the documentation → + {t("policies.policiesView.learnMoreDocs")}
} @@ -441,7 +440,7 @@ const PoliciesPanel: React.FC = ({ accessToken, userRole })
@@ -490,14 +489,16 @@ const PoliciesPanel: React.FC = ({ accessToken, userRole }) = ({ accessToken, userRole }) -

- Policy attachments control where your policies apply. Policies don't do anything until you - attach them to specific teams, keys, models, tags, or globally. -

-

Attachment Scopes:

+

{t("policies.policiesView.aboutAttachmentsDesc")}

+

{t("policies.policiesView.attachmentScopes")}

  • - Global (*) - Applies to all requests + }} />
  • - Teams - Applies only to specific teams + }} />
  • - Keys - Applies only to specific API keys (supports wildcards like dev-*) + }} />
  • - Models - Applies only when specific models are used + }} />
  • - Tags - Matches tags from key/team metadata.tags or tags passed - dynamically in the request body (metadata.tags). Use this to enforce policies across - groups, e.g. "all keys tagged healthcare get HIPAA guardrails." Supports - wildcards (prod-*). + , code: }} + />
= ({ accessToken, userRole }) rel="noopener noreferrer" className="text-blue-600 hover:text-blue-800 underline inline-block mt-1" > - Learn more about attachments → + {t("policies.policiesView.learnMoreAttachments")}
} @@ -572,8 +570,8 @@ const PoliciesPanel: React.FC = ({ accessToken, userRole }) /> = ({ accessToken, userRole }) onClick={() => setIsAddAttachmentModalVisible(true)} disabled={!accessToken || policiesList.length === 0} > - + Add New Attachment + {t("policies.policiesView.addNewAttachment")} @@ -615,13 +613,13 @@ const PoliciesPanel: React.FC = ({ accessToken, userRole }) {{keysCount}} {{keysWord}}, <1>{{teamsCount}} {{teamsWord}} affected", + "keyWord_one": "key", + "keyWord_other": "keys", + "teamWord_one": "team", + "teamWord_other": "teams", + "keysLabel": "Keys:", + "teamsLabel": "Teams:", + "noKeysOrTeams": "No keys or teams currently affected", + "clickToLoad": "Click to load" + }, + "impactPreviewAlert": { + "message": "Impact Preview", + "globalScopeDesc": "Global scope — this will affect all keys and teams.", + "attachmentAffects": "This attachment would affect <0>{{keysCount}} {{keysWord}} and <1>{{teamsCount}} {{teamsWord}}.", + "keyWord_one": "key", + "keyWord_other": "keys", + "teamWord_one": "team", + "teamWord_other": "teams", + "keysLabel": "Keys:", + "teamsLabel": "Teams:", + "andMore": "and {{count}} more..." + }, + "policiesView": { + "tabTemplates": "Templates", + "tabPolicies": "Policies", + "tabAttachments": "Attachments", + "tabPolicySimulator": "Policy Simulator", + "aboutPoliciesTitle": "About Policies", + "aboutPoliciesDesc": "Use policies to group guardrails and control which ones run for specific teams, keys, or models.", + "whyUsePolicies": "Why use policies?", + "whyPoliciesBullet1": "Enable/disable specific guardrails for teams, keys, or models", + "whyPoliciesBullet2": "Group guardrails into a single policy", + "whyPoliciesBullet3": "Inherit from existing policies and override what you need", + "learnMoreDocs": "Learn more in the documentation →", + "addNewPolicy": "+ Add New Policy", + "aboutAttachmentsTitle": "About Policy Attachments", + "aboutAttachmentsDesc": "Policy attachments control where your policies apply. Policies don't do anything until you attach them to specific teams, keys, models, tags, or globally.", + "attachmentScopes": "Attachment Scopes:", + "scopeGlobal": "Global (*) - Applies to all requests", + "scopeTeams": "Teams - Applies only to specific teams", + "scopeKeys": "Keys - Applies only to specific API keys (supports wildcards like dev-*)", + "scopeModels": "Models - Applies only when specific models are used", + "scopeTags": "Tags - Matches tags from key/team metadata.tags or tags passed dynamically in the request body (metadata.tags). Use this to enforce policies across groups, e.g. \"all keys tagged healthcare get HIPAA guardrails.\" Supports wildcards (prod-*).", + "learnMoreAttachments": "Learn more about attachments →", + "enterpriseFeatureTitle": "Enterprise Feature Notice", + "enterpriseFeatureDesc": "Parts of policy attachments will be on LiteLLM Enterprise in subsequent releases.", + "addNewAttachment": "+ Add New Attachment", + "deletePolicyTitle": "Delete Policy", + "deletePolicyMessage": "Are you sure you want to delete policy: {{name}}? This action cannot be undone.", + "policyInformationTitle": "Policy Information", + "labelId": "ID", + "labelInheritsFrom": "Inherits From", + "deleteAttachmentTitle": "Delete Attachment", + "deleteAttachmentMessage": "Are you sure you want to delete this attachment? This action cannot be undone.", + "attachmentInformationTitle": "Attachment Information", + "labelAttachmentId": "Attachment ID", + "labelPolicy": "Policy", + "labelScope": "Scope", + "fetchPoliciesFailed": "Failed to fetch policies", + "fetchAttachmentsFailed": "Failed to fetch attachments", + "authenticationRequired": "Authentication required", + "loadGuardrailsFailed": "Failed to load guardrails. Please try again.", + "configureTemplateFailed": "Failed to configure template. Please try again.", + "policyDeletedSuccess": "Policy \"{{name}}\" deleted successfully", + "deletePolicyFailed": "Failed to delete policy", + "guardrailsCreated_one": "Created {{count}} guardrail! Complete the policy form to save.", + "guardrailsCreated_other": "Created {{count}} guardrails! Complete the policy form to save.", + "templateReady": "Template ready! Complete the policy form to save.", + "guardrailsCreateFailed": "Failed to create {{count}} guardrail(s): {{list}}. You may need to create them manually.", + "createGuardrailsFailed": "Failed to create guardrails. Please try again." } } } diff --git a/ui/litellm-dashboard/src/locales/zh-CN.json b/ui/litellm-dashboard/src/locales/zh-CN.json index 5184c9661bc..8556f39ec86 100644 --- a/ui/litellm-dashboard/src/locales/zh-CN.json +++ b/ui/litellm-dashboard/src/locales/zh-CN.json @@ -4823,6 +4823,81 @@ "createAndUseTemplate_one": "创建 {{count}} 个护栏并使用模板", "createAndUseTemplate_other": "创建 {{count}} 个护栏并使用模板", "useTemplate": "使用模板" + }, + "impactPopover": { + "blastRadius": "影响范围", + "viewBlastRadius": "查看影响范围", + "globalScope": "全局范围 — 影响所有密钥和团队", + "keysTeamsAffected": "<0>{{keysCount}} {{keysWord}},<1>{{teamsCount}} {{teamsWord}}受到影响", + "keyWord_one": "个密钥", + "keyWord_other": "个密钥", + "teamWord_one": "个团队", + "teamWord_other": "个团队", + "keysLabel": "密钥:", + "teamsLabel": "团队:", + "noKeysOrTeams": "当前没有密钥或团队受到影响", + "clickToLoad": "点击加载" + }, + "impactPreviewAlert": { + "message": "影响预览", + "globalScopeDesc": "全局范围 — 将影响所有密钥和团队。", + "attachmentAffects": "此附件将影响 <0>{{keysCount}} {{keysWord}}和 <1>{{teamsCount}} {{teamsWord}}。", + "keyWord_one": "个密钥", + "keyWord_other": "个密钥", + "teamWord_one": "个团队", + "teamWord_other": "个团队", + "keysLabel": "密钥:", + "teamsLabel": "团队:", + "andMore": "及另外 {{count}} 个..." + }, + "policiesView": { + "tabTemplates": "模板", + "tabPolicies": "策略", + "tabAttachments": "附件", + "tabPolicySimulator": "策略模拟器", + "aboutPoliciesTitle": "关于策略", + "aboutPoliciesDesc": "使用策略对护栏进行分组,并控制哪些护栏针对特定团队、密钥或模型运行。", + "whyUsePolicies": "为什么使用策略?", + "whyPoliciesBullet1": "为团队、密钥或模型启用/禁用特定护栏", + "whyPoliciesBullet2": "将多个护栏组合到一个策略中", + "whyPoliciesBullet3": "继承现有策略并按需覆盖", + "learnMoreDocs": "在文档中了解更多 →", + "addNewPolicy": "+ 添加新策略", + "aboutAttachmentsTitle": "关于策略附件", + "aboutAttachmentsDesc": "策略附件控制策略的应用范围。策略在附加到特定团队、密钥、模型、标签或全局之前不会生效。", + "attachmentScopes": "附件范围:", + "scopeGlobal": "全局 (*) - 适用于所有请求", + "scopeTeams": "团队 - 仅适用于特定团队", + "scopeKeys": "密钥 - 仅适用于特定 API 密钥(支持通配符,如 dev-*)", + "scopeModels": "模型 - 仅在使用特定模型时适用", + "scopeTags": "标签 - 匹配密钥/团队 metadata.tags 中的标签,或在请求体中动态传入的标签(metadata.tags)。用于跨组强制执行策略,例如:所有标记为 healthcare 的密钥使用 HIPAA 护栏。支持通配符(prod-*)。", + "learnMoreAttachments": "了解更多关于附件的信息 →", + "enterpriseFeatureTitle": "企业版功能通知", + "enterpriseFeatureDesc": "策略附件的部分功能将在后续版本中纳入 LiteLLM 企业版。", + "addNewAttachment": "+ 添加新附件", + "deletePolicyTitle": "删除策略", + "deletePolicyMessage": "确定要删除策略:{{name}}?此操作无法撤销。", + "policyInformationTitle": "策略信息", + "labelId": "ID", + "labelInheritsFrom": "继承自", + "deleteAttachmentTitle": "删除附件", + "deleteAttachmentMessage": "确定要删除此附件吗?此操作无法撤销。", + "attachmentInformationTitle": "附件信息", + "labelAttachmentId": "附件 ID", + "labelPolicy": "策略", + "labelScope": "范围", + "fetchPoliciesFailed": "获取策略失败", + "fetchAttachmentsFailed": "获取附件失败", + "authenticationRequired": "需要身份验证", + "loadGuardrailsFailed": "加载护栏失败,请重试。", + "configureTemplateFailed": "配置模板失败,请重试。", + "policyDeletedSuccess": "策略「{{name}}」已成功删除", + "deletePolicyFailed": "删除策略失败", + "guardrailsCreated_one": "已创建 {{count}} 个护栏!请完成策略表单以保存。", + "guardrailsCreated_other": "已创建 {{count}} 个护栏!请完成策略表单以保存。", + "templateReady": "模板已就绪!请完成策略表单以保存。", + "guardrailsCreateFailed": "{{count}} 个护栏创建失败:{{list}}。您可能需要手动创建。", + "createGuardrailsFailed": "创建护栏失败,请重试。" } } }