From d3de601946ce45aaba262f868b4a0e96336c4e39 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Mon, 2 Nov 2026 15:56:49 -0800 Subject: [PATCH] refactor(ui): move guardrails text onto i18n (#49524) Moves the text the /guardrails page writes itself onto react-i18next under a new top-level `guardrails` block in en.json, placed right before `guardrailsMonitor`. Catalog data (garden cards, custom code templates, option lists) stores ParseKeys and translates at render time. Provider and vendor names, API values, code samples and test payloads stay raw guardrail_info.tsx and custom_code/CustomCodeModal.tsx cross the 800-line max-lines cap only because Prettier wraps the new t() calls, so each gets a max-lines suppression entry instead of a split. The react/no-unescaped-entities entry for add_guardrail_form.tsx is pruned because its apostrophes moved into en.json --- ui/litellm-dashboard/eslint-suppressions.json | 9 +- .../_components/GuardrailDeleteModal.tsx | 32 +- .../_components/GuardrailFormField.tsx | 34 +- .../_components/GuardrailModeDisplay.tsx | 79 +- .../_components/GuardrailReadOnlyDetails.tsx | 131 +- .../_components/GuardrailSettingsActions.tsx | 8 +- .../_components/GuardrailTestPanel.tsx | 50 +- .../_components/GuardrailTestPlayground.tsx | 34 +- .../_components/GuardrailTestResults.tsx | 36 +- .../_components/GuardrailsPanel.tsx | 16 +- .../_components/StreamScopeFields.tsx | 64 +- .../_components/TeamGuardrailsTab.tsx | 341 +++-- .../_components/add_guardrail_form.tsx | 185 ++- .../content_filter/CategoryTable.tsx | 38 +- .../CompetitorIntentConfiguration.tsx | 151 +- .../ContentCategoryConfiguration.tsx | 73 +- .../ContentFilterConfiguration.tsx | 40 +- .../content_filter/ContentFilterDisplay.tsx | 23 +- .../content_filter/ContentFilterManager.tsx | 8 +- .../content_filter/CustomPatternModal.tsx | 40 +- .../content_filter/KeywordModal.tsx | 29 +- .../content_filter/KeywordTable.tsx | 27 +- .../content_filter/PatternModal.tsx | 30 +- .../content_filter/PatternTable.tsx | 37 +- .../content_filter/action_options.ts | 28 +- .../CustomCodeLoggingOnlyScope.tsx | 60 +- .../custom_code/CustomCodeModal.tsx | 205 ++- .../custom_code/custom_code_catalog.ts | 191 ++- .../decision_model/DecisionModelFields.tsx | 94 +- .../decision_model/DecisionTestSection.tsx | 44 +- .../buildDecisionModelParams.ts | 14 +- .../_components/guardrailTableColumns.tsx | 52 +- .../_components/guardrail_garden.tsx | 41 +- .../_components/guardrail_garden_card.tsx | 15 +- .../_components/guardrail_garden_data.ts | 186 ++- .../_components/guardrail_garden_detail.tsx | 133 +- .../guardrails/_components/guardrail_info.tsx | 126 +- .../_components/guardrail_info_helpers.tsx | 44 +- .../_components/guardrail_optional_params.tsx | 45 +- .../_components/guardrail_provider_fields.tsx | 50 +- .../_components/guardrail_table.tsx | 13 +- .../_components/llm_judge/LLMJudgeFields.tsx | 77 +- .../guardrails/_components/pii_components.tsx | 31 +- .../_components/pii_configuration.tsx | 8 +- .../ToolPermissionRulesEditor.tsx | 85 +- ui/litellm-dashboard/src/i18n/locales/en.json | 1317 +++++++++++++++++ 46 files changed, 3242 insertions(+), 1132 deletions(-) diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json index d4ac60e501b..0cadc13a213 100644 --- a/ui/litellm-dashboard/eslint-suppressions.json +++ b/ui/litellm-dashboard/eslint-suppressions.json @@ -215,9 +215,6 @@ }, "max-lines": { "count": 1 - }, - "react/no-unescaped-entities": { - "count": 2 } }, "src/app/(dashboard)/guardrails/_components/content_filter/CompetitorIntentConfiguration.tsx": { @@ -247,6 +244,9 @@ } }, "src/app/(dashboard)/guardrails/_components/custom_code/CustomCodeModal.tsx": { + "max-lines": { + "count": 1 + }, "no-nested-ternary": { "count": 6 }, @@ -281,6 +281,9 @@ "local/no-complex-jsx-arrow": { "count": 1 }, + "max-lines": { + "count": 1 + }, "max-params": { "count": 1 }, diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/GuardrailDeleteModal.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/GuardrailDeleteModal.tsx index a3ed15b630f..8dfe9442d7c 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/GuardrailDeleteModal.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/GuardrailDeleteModal.tsx @@ -1,12 +1,10 @@ import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; import DeleteResourceModal from "@/components/common_components/DeleteResourceModal"; import { deleteGuardrailCall } from "@/components/networking"; import { toast } from "@/lib/toast"; import { formatGuardrailMode, getGuardrailLogoAndName } from "./guardrail_info_helpers"; -const DANGLING_REFERENCE_WARNING = - "Keys, teams, and policies that list this guardrail will silently stop running it. Policy pipeline steps that use it will block every request with a 400 error, unless the step's on_error is next or allow (or its on_fail, when on_error is not set)."; - export interface DeletableGuardrail { guardrail_id: string; guardrail_name?: string | null; @@ -25,6 +23,7 @@ interface GuardrailDeleteModalProps { } export function GuardrailDeleteModal({ guardrail, accessToken, onClose, onDeleted }: GuardrailDeleteModalProps) { + const { t } = useTranslation(); const [isDeleting, setIsDeleting] = useState(false); const guardrailName = guardrail ? guardrail.guardrail_name || guardrail.guardrail_id : ""; const providerDisplayName = getGuardrailLogoAndName(guardrail?.litellm_params?.guardrail || "").displayName; @@ -34,11 +33,11 @@ export function GuardrailDeleteModal({ guardrail, accessToken, onClose, onDelete setIsDeleting(true); try { await deleteGuardrailCall(accessToken, guardrail.guardrail_id); - toast.success(`Guardrail "${guardrailName}" deleted successfully`); + toast.success(t("guardrails.deleteModal.toast.deleted", { name: guardrailName })); onClose(); onDeleted(); } catch { - toast.fromError("Failed to delete guardrail"); + toast.fromError(t("guardrails.deleteModal.toast.deleteFailed")); onClose(); } finally { setIsDeleting(false); @@ -48,16 +47,21 @@ export function GuardrailDeleteModal({ guardrail, accessToken, onClose, onDelete return ( = ({ ); }; -const SKIP_MESSAGE_ITEMS = [ - { label: "Use global default", value: "inherit" }, - { label: "Yes — exclude from guardrail scan", value: "yes" }, - { label: "No — always include in scan", value: "no" }, +const SKIP_MESSAGE_ITEMS: { labelKey: ParseKeys; value: string }[] = [ + { labelKey: "guardrails.formField.skipMessage.options.inherit", value: "inherit" }, + { labelKey: "guardrails.formField.skipMessage.options.yes", value: "yes" }, + { labelKey: "guardrails.formField.skipMessage.options.no", value: "no" }, ]; export const SkipMessageSelect: React.FC<{ control: GuardrailFieldControlProps }> = ({ control }) => { + const { t } = useTranslation(); const { id, value, onChange, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy } = control; + const items = SKIP_MESSAGE_ITEMS.map(({ labelKey, value: itemValue }) => ({ label: t(labelKey), value: itemValue })); return ( - - + - {SKIP_MESSAGE_ITEMS.map((item) => ( + {items.map((item) => ( {item.label} @@ -150,8 +154,9 @@ export const LoggingOnlyScopeSelect: React.FC<{ control: GuardrailFieldControlProps; directionalScopeSupported: boolean; }> = ({ control, directionalScopeSupported }) => { + const { t } = useTranslation(); const { id, value, onChange, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy } = control; - const items = getLoggingOnlyScopeOptions(directionalScopeSupported); + const items = getLoggingOnlyScopeOptions(directionalScopeSupported, t); useEffect(() => { const currentChoice = isLoggingOnlyScopeChoice(value) ? value : "default"; @@ -162,7 +167,7 @@ export const LoggingOnlyScopeSelect: React.FC<{ return (