feat(ui): i18n for policies impact views and main page

This commit is contained in:
吹雪 2026-06-10 23:54:02 +09:00
parent 6f3463d207
commit 1505d4477d
5 changed files with 262 additions and 100 deletions

View file

@ -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<any>(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 ? (
<div className="p-2 text-center">
<Spin size="small" /> Loading...
<Spin size="small" /> {t("common.loading")}
</div>
) : impact ? (
<div className="text-xs" style={{ maxWidth: 280 }}>
{impact.affected_keys_count === -1 ? (
<p className="font-medium text-amber-600">Global scope — affects all keys and teams</p>
<p className="font-medium text-amber-600">{t("policies.impactPopover.globalScope")}</p>
) : (
<>
<p className="mb-1">
<strong>{impact.affected_keys_count}</strong> key{impact.affected_keys_count !== 1 ? "s" : ""},{" "}
<strong>{impact.affected_teams_count}</strong> team{impact.affected_teams_count !== 1 ? "s" : ""} affected
<Trans
i18nKey="policies.impactPopover.keysTeamsAffected"
values={{
keysCount: impact.affected_keys_count,
keysWord: t("policies.impactPopover.keyWord", { count: impact.affected_keys_count }),
teamsCount: impact.affected_teams_count,
teamsWord: t("policies.impactPopover.teamWord", { count: impact.affected_teams_count }),
}}
components={[<strong key="keys" />, <strong key="teams" />]}
/>
</p>
{impact.sample_keys.length > 0 && (
<div className="mb-1">
<span className="text-gray-500">Keys: </span>
<span className="text-gray-500">{t("policies.impactPopover.keysLabel")} </span>
{impact.sample_keys.map((k: string) => (
<Tag key={k} style={{ fontSize: 10, margin: 1 }}>
{k}
@ -60,34 +70,34 @@ const ImpactPopover: React.FC<{ attachment: PolicyAttachment; accessToken: strin
)}
{impact.sample_teams.length > 0 && (
<div>
<span className="text-gray-500">Teams: </span>
{impact.sample_teams.map((t: string) => (
<Tag key={t} style={{ fontSize: 10, margin: 1 }}>
{t}
<span className="text-gray-500">{t("policies.impactPopover.teamsLabel")} </span>
{impact.sample_teams.map((teamName: string) => (
<Tag key={teamName} style={{ fontSize: 10, margin: 1 }}>
{teamName}
</Tag>
))}
</div>
)}
{impact.affected_keys_count === 0 && impact.affected_teams_count === 0 && (
<p className="text-gray-400">No keys or teams currently affected</p>
<p className="text-gray-400">{t("policies.impactPopover.noKeysOrTeams")}</p>
)}
</>
)}
</div>
) : (
<p className="text-xs text-gray-400">Click to load</p>
<p className="text-xs text-gray-400">{t("policies.impactPopover.clickToLoad")}</p>
);
return (
<Popover
content={content}
title="Blast Radius"
title={t("policies.impactPopover.blastRadius")}
trigger="click"
onOpenChange={(open) => {
if (open) loadImpact();
}}
>
<Tooltip title="View blast radius">
<Tooltip title={t("policies.impactPopover.viewBlastRadius")}>
<Icon icon={EyeIcon} size="sm" className="cursor-pointer hover:text-blue-500" />
</Tooltip>
</Popover>

View file

@ -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<ImpactPreviewAlertProps> = ({ impactResult }) => {
const { t } = useTranslation();
return (
<Alert
type={impactResult.affected_keys_count === -1 ? "warning" : "info"}
showIcon
className="mb-4"
message="Impact Preview"
message={t("policies.impactPreviewAlert.message")}
description={
impactResult.affected_keys_count === -1 ? (
<Text>
Global scope — this will affect <strong>all keys and teams</strong>.
<Trans i18nKey="policies.impactPreviewAlert.globalScopeDesc" components={{ strong: <strong /> }} />
</Text>
) : (
<div>
<Text>
This attachment would affect{" "}
<strong>
{impactResult.affected_keys_count} key{impactResult.affected_keys_count !== 1 ? "s" : ""}
</strong>{" "}
and{" "}
<strong>
{impactResult.affected_teams_count} team{impactResult.affected_teams_count !== 1 ? "s" : ""}
</strong>
.
<Trans
i18nKey="policies.impactPreviewAlert.attachmentAffects"
values={{
keysCount: impactResult.affected_keys_count,
keysWord: t("policies.impactPreviewAlert.keyWord", { count: impactResult.affected_keys_count }),
teamsCount: impactResult.affected_teams_count,
teamsWord: t("policies.impactPreviewAlert.teamWord", { count: impactResult.affected_teams_count }),
}}
components={[<strong key="keys" />, <strong key="teams" />]}
/>
</Text>
{impactResult.sample_keys.length > 0 && (
<div className="mt-1">
<Text type="secondary" style={{ fontSize: 12 }}>
Keys:{" "}
{t("policies.impactPreviewAlert.keysLabel")}{" "}
</Text>
{impactResult.sample_keys.slice(0, 5).map((k: string) => (
<Tag key={k} style={{ fontSize: 11 }}>
@ -51,7 +55,7 @@ const ImpactPreviewAlert: React.FC<ImpactPreviewAlertProps> = ({ impactResult })
))}
{impactResult.affected_keys_count > 5 && (
<Text type="secondary" style={{ fontSize: 11 }}>
and {impactResult.affected_keys_count - 5} more...
{t("policies.impactPreviewAlert.andMore", { count: impactResult.affected_keys_count - 5 })}
</Text>
)}
</div>
@ -59,16 +63,16 @@ const ImpactPreviewAlert: React.FC<ImpactPreviewAlertProps> = ({ impactResult })
{impactResult.sample_teams.length > 0 && (
<div className="mt-1">
<Text type="secondary" style={{ fontSize: 12 }}>
Teams:{" "}
{t("policies.impactPreviewAlert.teamsLabel")}{" "}
</Text>
{impactResult.sample_teams.slice(0, 5).map((t: string) => (
<Tag key={t} style={{ fontSize: 11 }}>
{t}
{impactResult.sample_teams.slice(0, 5).map((teamName: string) => (
<Tag key={teamName} style={{ fontSize: 11 }}>
{teamName}
</Tag>
))}
{impactResult.affected_teams_count > 5 && (
<Text type="secondary" style={{ fontSize: 11 }}>
and {impactResult.affected_teams_count - 5} more...
{t("policies.impactPreviewAlert.andMore", { count: impactResult.affected_teams_count - 5 })}
</Text>
)}
</div>

View file

@ -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<PoliciesPanelProps> = ({ accessToken, userRole }) => {
const { t } = useTranslation();
const [policiesList, setPoliciesList] = useState<Policy[]>([]);
const [attachmentsList, setAttachmentsList] = useState<PolicyAttachment[]>([]);
const [guardrailsList, setGuardrailsList] = useState<Guardrail[]>([]);
@ -78,11 +80,11 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ 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<PoliciesPanelProps> = ({ 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<PoliciesPanelProps> = ({ 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<PoliciesPanelProps> = ({ 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<PoliciesPanelProps> = ({ 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<PoliciesPanelProps> = ({ 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<PoliciesPanelProps> = ({ 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<PoliciesPanelProps> = ({ 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<PoliciesPanelProps> = ({ accessToken, userRole })
<div className="w-full mx-auto flex-auto overflow-y-auto m-8 p-2">
<TabGroup index={activeTab} onIndexChange={setActiveTab}>
<TabList className="mb-4">
<Tab>Templates</Tab>
<Tab>Policies</Tab>
<Tab>Attachments</Tab>
<Tab>Policy Simulator</Tab>
<Tab>{t("policies.policiesView.tabTemplates")}</Tab>
<Tab>{t("policies.policiesView.tabPolicies")}</Tab>
<Tab>{t("policies.policiesView.tabAttachments")}</Tab>
<Tab>{t("policies.policiesView.tabPolicySimulator")}</Tab>
</TabList>
<TabPanels>
<TabPanel>
<Alert
message="About Policies"
message={t("policies.policiesView.aboutPoliciesTitle")}
description={
<div>
<p className="mb-3">
Use policies to group guardrails and control which ones run for specific teams, keys, or models.
</p>
<p className="mb-2 font-semibold">Why use policies?</p>
<p className="mb-3">{t("policies.policiesView.aboutPoliciesDesc")}</p>
<p className="mb-2 font-semibold">{t("policies.policiesView.whyUsePolicies")}</p>
<ul className="list-disc list-inside mb-3 space-y-1 ml-2">
<li>Enable/disable specific guardrails for teams, keys, or models</li>
<li>Group guardrails into a single policy</li>
<li>Inherit from existing policies and override what you need</li>
<li>{t("policies.policiesView.whyPoliciesBullet1")}</li>
<li>{t("policies.policiesView.whyPoliciesBullet2")}</li>
<li>{t("policies.policiesView.whyPoliciesBullet3")}</li>
</ul>
<a
href="https://docs.litellm.ai/docs/proxy/guardrails/guardrail_policies"
@ -390,7 +391,7 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ 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")}
</a>
</div>
}
@ -410,17 +411,15 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
<TabPanel>
<Alert
message="About Policies"
message={t("policies.policiesView.aboutPoliciesTitle")}
description={
<div>
<p className="mb-3">
Use policies to group guardrails and control which ones run for specific teams, keys, or models.
</p>
<p className="mb-2 font-semibold">Why use policies?</p>
<p className="mb-3">{t("policies.policiesView.aboutPoliciesDesc")}</p>
<p className="mb-2 font-semibold">{t("policies.policiesView.whyUsePolicies")}</p>
<ul className="list-disc list-inside mb-3 space-y-1 ml-2">
<li>Enable/disable specific guardrails for teams, keys, or models</li>
<li>Group guardrails into a single policy</li>
<li>Inherit from existing policies and override what you need</li>
<li>{t("policies.policiesView.whyPoliciesBullet1")}</li>
<li>{t("policies.policiesView.whyPoliciesBullet2")}</li>
<li>{t("policies.policiesView.whyPoliciesBullet3")}</li>
</ul>
<a
href="https://docs.litellm.ai/docs/proxy/guardrails/guardrail_policies"
@ -428,7 +427,7 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ 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")}
</a>
</div>
}
@ -441,7 +440,7 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
<div className="flex justify-between items-center mb-4">
<Button onClick={handleAddPolicy} disabled={!accessToken}>
+ Add New Policy
{t("policies.policiesView.addNewPolicy")}
</Button>
</div>
@ -490,14 +489,16 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
<DeleteResourceModal
isOpen={isDeleteModalOpen}
title="Delete Policy"
message={`Are you sure you want to delete policy: ${policyToDelete?.policy_name}? This action cannot be undone.`}
resourceInformationTitle="Policy Information"
title={t("policies.policiesView.deletePolicyTitle")}
message={t("policies.policiesView.deletePolicyMessage", {
name: policyToDelete?.policy_name,
})}
resourceInformationTitle={t("policies.policiesView.policyInformationTitle")}
resourceInformation={[
{ label: "Name", value: policyToDelete?.policy_name },
{ label: "ID", value: policyToDelete?.policy_id, code: true },
{ label: "Description", value: policyToDelete?.description || "-" },
{ label: "Inherits From", value: policyToDelete?.inherit || "-" },
{ label: t("common.name"), value: policyToDelete?.policy_name },
{ label: t("policies.policiesView.labelId"), value: policyToDelete?.policy_id, code: true },
{ label: t("common.description"), value: policyToDelete?.description || "-" },
{ label: t("policies.policiesView.labelInheritsFrom"), value: policyToDelete?.inherit || "-" },
]}
onCancel={handleDeleteCancel}
onOk={handleDeleteConfirm}
@ -526,32 +527,29 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
<TabPanel>
<Alert
message="About Policy Attachments"
message={t("policies.policiesView.aboutAttachmentsTitle")}
description={
<div>
<p className="mb-3">
Policy attachments control where your policies apply. Policies don&apos;t do anything until you
attach them to specific teams, keys, models, tags, or globally.
</p>
<p className="mb-2 font-semibold">Attachment Scopes:</p>
<p className="mb-3">{t("policies.policiesView.aboutAttachmentsDesc")}</p>
<p className="mb-2 font-semibold">{t("policies.policiesView.attachmentScopes")}</p>
<ul className="list-disc list-inside mb-3 space-y-1 ml-2">
<li>
<strong>Global (*)</strong> - Applies to all requests
<Trans i18nKey="policies.policiesView.scopeGlobal" components={{ strong: <strong /> }} />
</li>
<li>
<strong>Teams</strong> - Applies only to specific teams
<Trans i18nKey="policies.policiesView.scopeTeams" components={{ strong: <strong /> }} />
</li>
<li>
<strong>Keys</strong> - Applies only to specific API keys (supports wildcards like dev-*)
<Trans i18nKey="policies.policiesView.scopeKeys" components={{ strong: <strong /> }} />
</li>
<li>
<strong>Models</strong> - Applies only when specific models are used
<Trans i18nKey="policies.policiesView.scopeModels" components={{ strong: <strong /> }} />
</li>
<li>
<strong>Tags</strong> - Matches tags from key/team <code>metadata.tags</code> or tags passed
dynamically in the request body (<code>metadata.tags</code>). Use this to enforce policies across
groups, e.g. &quot;all keys tagged <code>healthcare</code> get HIPAA guardrails.&quot; Supports
wildcards (<code>prod-*</code>).
<Trans
i18nKey="policies.policiesView.scopeTags"
components={{ strong: <strong />, code: <code /> }}
/>
</li>
</ul>
<a
@ -560,7 +558,7 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ 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")}
</a>
</div>
}
@ -572,8 +570,8 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
/>
<Alert
message="Enterprise Feature Notice"
description="Parts of policy attachments will be on LiteLLM Enterprise in subsequent releases."
message={t("policies.policiesView.enterpriseFeatureTitle")}
description={t("policies.policiesView.enterpriseFeatureDesc")}
type="warning"
showIcon
closable
@ -585,7 +583,7 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
onClick={() => setIsAddAttachmentModalVisible(true)}
disabled={!accessToken || policiesList.length === 0}
>
+ Add New Attachment
{t("policies.policiesView.addNewAttachment")}
</Button>
</div>
@ -615,13 +613,13 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
<DeleteResourceModal
isOpen={isDeleteAttachmentModalOpen}
title="Delete Attachment"
message="Are you sure you want to delete this attachment? This action cannot be undone."
resourceInformationTitle="Attachment Information"
title={t("policies.policiesView.deleteAttachmentTitle")}
message={t("policies.policiesView.deleteAttachmentMessage")}
resourceInformationTitle={t("policies.policiesView.attachmentInformationTitle")}
resourceInformation={[
{ label: "Attachment ID", value: attachmentToDelete?.attachment_id, code: true },
{ label: "Policy", value: attachmentToDelete?.policy_name ?? "-" },
{ label: "Scope", value: attachmentToDelete?.scope ?? "-" },
{ label: t("policies.policiesView.labelAttachmentId"), value: attachmentToDelete?.attachment_id, code: true },
{ label: t("policies.policiesView.labelPolicy"), value: attachmentToDelete?.policy_name ?? "-" },
{ label: t("policies.policiesView.labelScope"), value: attachmentToDelete?.scope ?? "-" },
]}
onCancel={handleAttachmentDeleteCancel}
onOk={handleAttachmentDeleteConfirm}

View file

@ -4823,6 +4823,81 @@
"createAndUseTemplate_one": "Create {{count}} Guardrail & Use Template",
"createAndUseTemplate_other": "Create {{count}} Guardrails & Use Template",
"useTemplate": "Use Template"
},
"impactPopover": {
"blastRadius": "Blast Radius",
"viewBlastRadius": "View blast radius",
"globalScope": "Global scope — affects all keys and teams",
"keysTeamsAffected": "<0>{{keysCount}}</0> {{keysWord}}, <1>{{teamsCount}}</1> {{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 <strong>all keys and teams</strong>.",
"attachmentAffects": "This attachment would affect <0>{{keysCount}}</0> {{keysWord}} and <1>{{teamsCount}}</1> {{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": "<strong>Global (*)</strong> - Applies to all requests",
"scopeTeams": "<strong>Teams</strong> - Applies only to specific teams",
"scopeKeys": "<strong>Keys</strong> - Applies only to specific API keys (supports wildcards like dev-*)",
"scopeModels": "<strong>Models</strong> - Applies only when specific models are used",
"scopeTags": "<strong>Tags</strong> - Matches tags from key/team <code>metadata.tags</code> or tags passed dynamically in the request body (<code>metadata.tags</code>). Use this to enforce policies across groups, e.g. \"all keys tagged <code>healthcare</code> get HIPAA guardrails.\" Supports wildcards (<code>prod-*</code>).",
"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."
}
}
}

View file

@ -4823,6 +4823,81 @@
"createAndUseTemplate_one": "创建 {{count}} 个护栏并使用模板",
"createAndUseTemplate_other": "创建 {{count}} 个护栏并使用模板",
"useTemplate": "使用模板"
},
"impactPopover": {
"blastRadius": "影响范围",
"viewBlastRadius": "查看影响范围",
"globalScope": "全局范围 — 影响所有密钥和团队",
"keysTeamsAffected": "<0>{{keysCount}}</0> {{keysWord}},<1>{{teamsCount}}</1> {{teamsWord}}受到影响",
"keyWord_one": "个密钥",
"keyWord_other": "个密钥",
"teamWord_one": "个团队",
"teamWord_other": "个团队",
"keysLabel": "密钥:",
"teamsLabel": "团队:",
"noKeysOrTeams": "当前没有密钥或团队受到影响",
"clickToLoad": "点击加载"
},
"impactPreviewAlert": {
"message": "影响预览",
"globalScopeDesc": "全局范围 — 将影响<strong>所有密钥和团队</strong>。",
"attachmentAffects": "此附件将影响 <0>{{keysCount}}</0> {{keysWord}}和 <1>{{teamsCount}}</1> {{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": "<strong>全局 (*)</strong> - 适用于所有请求",
"scopeTeams": "<strong>团队</strong> - 仅适用于特定团队",
"scopeKeys": "<strong>密钥</strong> - 仅适用于特定 API 密钥(支持通配符,如 dev-*)",
"scopeModels": "<strong>模型</strong> - 仅在使用特定模型时适用",
"scopeTags": "<strong>标签</strong> - 匹配密钥/团队 <code>metadata.tags</code> 中的标签,或在请求体中动态传入的标签(<code>metadata.tags</code>)。用于跨组强制执行策略,例如:所有标记为 <code>healthcare</code> 的密钥使用 HIPAA 护栏。支持通配符(<code>prod-*</code>)。",
"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": "创建护栏失败,请重试。"
}
}
}