From 105b7cba16414fa32206ba3dbbc6620b5bd08afa Mon Sep 17 00:00:00 2001
From: yuneng-jiang
Date: Mon, 2 Nov 2026 15:56:59 -0800
Subject: [PATCH] refactor(ui): move agents and knowledge text onto i18n
(#49530)
Move the page-authored English on the agents, vector stores, prompts,
search tools, memory and skills pages, plus the shared skill detail,
make-skill-public and model selector components, into en.json behind t().
The English on screen is unchanged. Keys are added under agents,
vectorStores, prompts, searchTools, memory, skills, claudeCodePlugins and
modelSelector, inserted at the top of en.json
Strings that could only move with a structural change stay English,
along with API values, code samples, paths and names
---
.../agents/_components/AgentFormKit.tsx | 5 +-
.../_components/AgentIdentityDetails.tsx | 50 +-
.../_components/AgentIdentityFields.tsx | 143 +-
.../_components/AgentKillSwitchDangerZone.tsx | 44 +-
.../agents/_components/AgentsPanel.tsx | 31 +-
.../agents/_components/AgentsTable.tsx | 25 +-
.../agents/_components/AgentsTableColumns.tsx | 42 +-
.../_components/KillSwitchFormFields.tsx | 74 +-
.../agents/_components/add_agent_form.tsx | 258 ++-
.../_components/agent_card_discovery.tsx | 84 +-
.../agents/_components/agent_config.ts | 42 +-
.../agents/_components/agent_cost_view.tsx | 10 +-
.../agents/_components/agent_form_fields.tsx | 110 +-
.../agents/_components/agent_info.tsx | 168 +-
.../agents/_components/agent_virtual_keys.tsx | 12 +-
.../_components/dynamic_agent_form_fields.tsx | 39 +-
.../agents/_components/kill_switch_config.ts | 16 +-
.../memory/_components/MemoryDetailDrawer.tsx | 24 +-
.../memory/_components/MemoryEditModal.tsx | 40 +-
.../memory/_components/MemoryTable.tsx | 17 +-
.../memory/_components/MemoryTableColumns.tsx | 39 +-
.../memory/_components/MemoryView.tsx | 49 +-
.../src/app/(dashboard)/memory/page.tsx | 4 +-
.../prompts/_components/PromptTable.tsx | 13 +-
.../_components/PromptTableColumns.tsx | 45 +-
.../prompts/_components/add_prompt_form.tsx | 43 +-
.../(dashboard)/prompts/_components/index.tsx | 45 +-
.../DeveloperMessageCard.tsx | 8 +-
.../prompt_editor_view/DotpromptViewTab.tsx | 6 +-
.../prompt_editor_view/ModelConfigCard.tsx | 10 +-
.../prompt_editor_view/PromptCodeSnippets.tsx | 20 +-
.../prompt_editor_view/PromptEditorHeader.tsx | 26 +-
.../prompt_editor_view/PromptMessagesCard.tsx | 30 +-
.../prompt_editor_view/PublishModal.tsx | 18 +-
.../prompt_editor_view/ToolsCard.tsx | 12 +-
.../VersionHistorySidePanel.tsx | 18 +-
.../conversation_panel/EmptyState.tsx | 6 +-
.../conversation_panel/MessageInput.tsx | 8 +-
.../conversation_panel/MessageList.tsx | 7 +-
.../conversation_panel/VariableInput.tsx | 6 +-
.../conversation_panel/VariableWarning.tsx | 8 +-
.../conversation_panel/index.tsx | 4 +-
.../conversation_panel/useConversation.ts | 10 +-
.../_components/prompt_editor_view/index.tsx | 22 +-
.../_components/prompt_editor_view/utils.ts | 3 +-
.../prompts/_components/prompt_info.tsx | 101 +-
.../prompts/_components/tool_modal.tsx | 14 +-
.../prompts/_components/variable_textarea.tsx | 12 +-
.../_components/CreateSearchTools.tsx | 76 +-
.../_components/SearchConnectionTest.tsx | 62 +-
.../_components/SearchToolTable.tsx | 13 +-
.../_components/SearchToolTableColumns.tsx | 52 +-
.../_components/SearchToolTester.tsx | 50 +-
.../_components/SearchToolView.tsx | 42 +-
.../search-tools/_components/SearchTools.tsx | 97 +-
.../_components/ClaudeCodePluginsPanel.tsx | 31 +-
.../skills/_components/PluginTable.tsx | 9 +-
.../skills/_components/PluginTableColumns.tsx | 53 +-
.../skills/_components/add_plugin_form.tsx | 130 +-
.../_components/CreateVectorStore.tsx | 90 +-
.../_components/DocumentsTable.tsx | 9 +-
.../_components/DocumentsTableColumns.tsx | 26 +-
.../vector-stores/_components/IndexesTab.tsx | 47 +-
.../_components/IndexesTable.tsx | 13 +-
.../_components/IndexesTableColumns.tsx | 31 +-
.../_components/S3VectorsConfig.tsx | 68 +-
.../_components/TestVectorStoreTab.tsx | 14 +-
.../_components/VectorStoreForm.tsx | 229 +--
.../_components/VectorStoreTable.tsx | 16 +-
.../_components/VectorStoreTableColumns.tsx | 79 +-
.../_components/VectorStoreTester.tsx | 48 +-
.../vector-stores/_components/index.tsx | 49 +-
.../_components/vector_store_info.tsx | 114 +-
.../MakeSkillPublicForm.tsx | 63 +-
.../components/claude_code_plugins/helpers.ts | 17 +-
.../claude_code_plugins/skill_detail.tsx | 136 +-
.../common_components/ModelSelector.tsx | 11 +-
.../src/components/vector_store_providers.tsx | 169 +-
ui/litellm-dashboard/src/i18n/locales/en.json | 1803 +++++++++++++++++
79 files changed, 3942 insertions(+), 1526 deletions(-)
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentFormKit.tsx b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentFormKit.tsx
index 3d863036234..70c7d2fdb92 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentFormKit.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentFormKit.tsx
@@ -27,6 +27,7 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/component
import { Input } from "@/components/ui/input";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { Field, FieldDescription, FieldError, FieldGroup, FieldLabel } from "@/components/ui/field";
+import i18n from "@/i18n/i18n";
import type { KeyValueFormValue, KillSwitchConfig, KillSwitchFormValue } from "./kill_switch_config";
export interface AgentSkillFormValue {
@@ -282,7 +283,7 @@ export const AgentTagsInput = ({
value,
onValueChange,
placeholder,
- emptyText = "No matching options",
+ emptyText = i18n.t("agents.formKit.noMatchingOptions"),
...props
}: AgentTagsInputProps) => {
const anchor = useComboboxAnchor();
@@ -396,7 +397,7 @@ export const AgentMultiSelect = ({
value,
onValueChange,
placeholder,
- emptyText = "No matching options",
+ emptyText = i18n.t("agents.formKit.noMatchingOptions"),
...props
}: AgentMultiSelectProps) => {
const anchor = useComboboxAnchor();
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentIdentityDetails.tsx b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentIdentityDetails.tsx
index 12465c6e861..e891195d137 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentIdentityDetails.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentIdentityDetails.tsx
@@ -1,14 +1,17 @@
import React from "react";
+import type { TFunction } from "i18next";
+import { useTranslation } from "react-i18next";
import type { components } from "@/lib/http/schema";
import { useQuery } from "@tanstack/react-query";
import { apiClient } from "@/components/networking";
import { Button } from "@/components/ui/button";
import { readAgentIdentity } from "./agent_identity";
-const authenticationMessage = (error: boolean, lastAuthenticated?: string | null): string => {
- if (error) return "Could not load authentication evidence";
- if (lastAuthenticated) return `Last authenticated identity match: ${new Date(lastAuthenticated).toLocaleString()}`;
- return "Configured, awaiting an authenticated request";
+const authenticationMessage = (t: TFunction, error: boolean, lastAuthenticated?: string | null): string => {
+ if (error) return t("agents.identityDetails.evidence.loadFailed");
+ if (lastAuthenticated)
+ return t("agents.identityDetails.evidence.lastMatch", { date: new Date(lastAuthenticated).toLocaleString() });
+ return t("agents.identityDetails.evidence.awaiting");
};
export const AgentIdentityDetails = ({
@@ -22,6 +25,7 @@ export const AgentIdentityDetails = ({
accessToken: string | null;
isAdmin: boolean;
}) => {
+ const { t } = useTranslation();
const identity = readAgentIdentity(value);
const { data, isError, isFetching, refetch } = useQuery({
queryKey: ["agent-identity", agentId, identity],
@@ -36,31 +40,39 @@ export const AgentIdentityDetails = ({
});
if (!identity || !isAdmin) return null;
- const executionLabel = data?.enabled ? "Enabled" : "Disabled";
+ const executionLabel = data?.enabled
+ ? t("agents.identityDetails.execution.enabled")
+ : t("agents.identityDetails.execution.disabled");
return (
-
- Agent Identity: Microsoft Entra ID
+
+ {t("agents.identityDetails.title")}
- Tenant: {identity.tenant_id}
+ {t("agents.identityDetails.tenant")} {identity.tenant_id}
<>
- Application (Client) ID: {identity.client_id}
+ {t("agents.identityDetails.clientId")} {identity.client_id}
+
+
+ {t("agents.identityDetails.servicePrincipal")}{" "}
+ {identity.service_principal_id || t("agents.identityDetails.notConfigured")}
- Enterprise application Object ID: {identity.service_principal_id || "Not configured"}
>
- Execution: {data ? executionLabel : "Loading"} · Mode: {data?.execution_mode ?? "Loading"}
+ {t("agents.identityDetails.executionSummary", {
+ execution: data ? executionLabel : t("agents.identityDetails.loading"),
+ mode: data?.execution_mode ?? t("agents.identityDetails.loading"),
+ })}
{data?.identity?.active === false
- ? "Identity unbound; execution is disabled"
- : authenticationMessage(isError, data?.last_authenticated_at)}
-
-
- Recent evidence comes from a validated Entra token matching this binding. It is persisted across restarts and
- cleared when the binding changes. Tool and model permissions are checked separately.
+ ? t("agents.identityDetails.evidence.unbound")
+ : authenticationMessage(t, isError, data?.last_authenticated_at)}
+ {t("agents.identityDetails.evidenceNote")}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentIdentityFields.tsx b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentIdentityFields.tsx
index 50c60776ff3..d4af3ff3833 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentIdentityFields.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentIdentityFields.tsx
@@ -1,26 +1,30 @@
import React, { useEffect, useState } from "react";
import { useWatch } from "react-hook-form";
+import type { ParseKeys } from "i18next";
+import { Trans, useTranslation } from "react-i18next";
+import i18n from "@/i18n/i18n";
import { apiClient } from "@/components/networking";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { AgentFormField, type AgentFormValues } from "./AgentFormKit";
import { entraTenantFromIssuer, IDENTITY_UUID_PATTERN } from "./agent_identity";
-const PROVIDER_OPTIONS = [
- { value: "none", label: "No explicit identity binding" },
- { value: "microsoft_entra", label: "Microsoft Entra ID" },
+const PROVIDER_OPTIONS: { value: string; labelKey: ParseKeys }[] = [
+ { value: "none", labelKey: "agents.identityFields.providers.none" },
+ { value: "microsoft_entra", labelKey: "agents.identityFields.providers.microsoftEntra" },
];
-const EXECUTION_MODE_OPTIONS = [
- { value: "autonomous", label: "Autonomous" },
- { value: "delegated", label: "On behalf of a user" },
- { value: "both", label: "Both" },
+const EXECUTION_MODE_OPTIONS: { value: string; labelKey: ParseKeys }[] = [
+ { value: "autonomous", labelKey: "agents.identityFields.executionModes.autonomous" },
+ { value: "delegated", labelKey: "agents.identityFields.executionModes.delegated" },
+ { value: "both", labelKey: "agents.identityFields.executionModes.both" },
];
-const EXECUTION_OPTIONS = [
- { value: "enabled", label: "Enabled" },
- { value: "disabled", label: "Disabled" },
+const EXECUTION_OPTIONS: { value: string; labelKey: ParseKeys }[] = [
+ { value: "enabled", labelKey: "agents.identityFields.execution.enabled" },
+ { value: "disabled", labelKey: "agents.identityFields.execution.disabled" },
];
export const AgentIdentityFields = ({ accessToken }: { accessToken: string | null }) => {
+ const { t } = useTranslation();
const provider = useWatch({ name: "identity_provider" });
const mode = useWatch({ name: "execution_mode" });
const showScopes = mode !== "autonomous" && mode !== undefined;
@@ -44,7 +48,7 @@ export const AgentIdentityFields = ({ accessToken }: { accessToken: string | nul
}
})
.catch(() => {
- if (active) setError("Could not load the gateway's trusted identity providers");
+ if (active) setError(i18n.t("agents.identityFields.errors.providersLoadFailed"));
});
return () => {
active = false;
@@ -53,18 +57,18 @@ export const AgentIdentityFields = ({ accessToken }: { accessToken: string | nul
return (
<>
-
+
-
Agent Identity
-
- Connect an existing identity provider application to this agent. Its name and runtime address can change
- independently.
-
+
{t("agents.identityFields.title")}
+
{t("agents.identityFields.description")}
-
+
{({ value, onChange, id }) => (
({ value: option.value, label: t(option.labelKey) }))}
value={typeof value === "string" ? value : "none"}
onValueChange={onChange}
>
@@ -74,7 +78,7 @@ export const AgentIdentityFields = ({ accessToken }: { accessToken: string | nul
{PROVIDER_OPTIONS.map((option) => (
- {option.label}
+ {t(option.labelKey)}
))}
@@ -85,13 +89,13 @@ export const AgentIdentityFields = ({ accessToken }: { accessToken: string | nul
<>
{({ value, onChange, id }) => (
-
+
{tenants.map((tenant) => (
@@ -109,26 +113,24 @@ export const AgentIdentityFields = ({ accessToken }: { accessToken: string | nul
)}
{!error && tenants.length === 0 && (
-
- No trusted Entra tenant is available. Configure JWT issuer and audience validation on the gateway first.
- Dashboard Microsoft SSO is configured separately.
-
+ {t("agents.identityFields.tenant.noneAvailable")}
)}
- Find this under{" "}
-
- Entra App registrations
-
- , select your agent application, then Overview. No client secret is required here.
- >
+
+ ),
+ }}
+ />
}
>
{({ value, onChange, ref, ...control }) => (
@@ -141,10 +143,14 @@ export const AgentIdentityFields = ({ accessToken }: { accessToken: string | nul
/>
)}
-
+
{({ value, onChange, id }) => (
({ value: option.value, label: t(option.labelKey) }))}
value={typeof value === "string" ? value : "autonomous"}
onValueChange={onChange}
>
@@ -154,7 +160,7 @@ export const AgentIdentityFields = ({ accessToken }: { accessToken: string | nul
{EXECUTION_MODE_OPTIONS.map((option) => (
- {option.label}
+ {t(option.labelKey)}
))}
@@ -163,25 +169,25 @@ export const AgentIdentityFields = ({ accessToken }: { accessToken: string | nul
- Open{" "}
-
- Entra Enterprise applications
-
- , select this application, and copy its Object ID. The App registrations Object ID is a different
- value.
- >
+
+ ),
+ }}
+ />
}
>
{({ value, onChange, ref, ...control }) => (
@@ -191,8 +197,8 @@ export const AgentIdentityFields = ({ accessToken }: { accessToken: string | nul
{({ value, onChange, ref, ...control }) => (
{({ value, onChange, ref, ...control }) => (
)}
-
- Users must first sign in through this gateway's Microsoft SSO. Subsequent delegated calls must
- satisfy both user and agent permissions.
-
+ {t("agents.identityFields.requiredScopes.note")}
>
)}
-
+
{({ value, onChange, id }) => (
({ value: option.value, label: t(option.labelKey) }))}
value={value === false ? "disabled" : "enabled"}
onValueChange={(next) => onChange(next === "enabled")}
>
@@ -241,18 +244,14 @@ export const AgentIdentityFields = ({ accessToken }: { accessToken: string | nul
{EXECUTION_OPTIONS.map((option) => (
- {option.label}
+ {t(option.labelKey)}
))}
)}
-
- LiteLLM verifies the agent's Entra token before matching this identity. Saving these fields
- configures the binding; an authenticated request provides verification. Runtime authentication headers are
- configured separately.
-
+ {t("agents.identityFields.verificationNote")}
>
)}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentKillSwitchDangerZone.tsx b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentKillSwitchDangerZone.tsx
index 8d68a8f921b..e733a564576 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentKillSwitchDangerZone.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentKillSwitchDangerZone.tsx
@@ -1,5 +1,6 @@
import { CircleAlert } from "lucide-react";
import React, { useState } from "react";
+import { Trans, useTranslation } from "react-i18next";
import { Alert, AlertDescription, AlertTitle } from "@/components/shared/Alert";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
@@ -24,6 +25,7 @@ const AgentKillSwitchDangerZone: React.FC = ({
accessToken,
isAdmin,
}) => {
+ const { t } = useTranslation();
const [isConfirmOpen, setIsConfirmOpen] = useState(false);
const [confirmationInput, setConfirmationInput] = useState("");
const [isFiring, setIsFiring] = useState(false);
@@ -44,9 +46,9 @@ const AgentKillSwitchDangerZone: React.FC = ({
const result = await triggerAgentKillSwitchCall(accessToken, agentId);
setLastResult(result);
setIsConfirmOpen(false);
- toast.success(`Kill switch fired (HTTP ${result.status_code})`);
+ toast.success(t("agents.dangerZone.toasts.fired", { status: result.status_code }));
} catch (error) {
- toast.error(error instanceof Error ? error.message : "Failed to fire kill switch");
+ toast.error(error instanceof Error ? error.message : t("agents.dangerZone.toasts.fireFailed"));
} finally {
setIsFiring(false);
}
@@ -55,38 +57,33 @@ const AgentKillSwitchDangerZone: React.FC = ({
return (
- Danger Zone
+ {t("agents.dangerZone.title")}
-
Kill switch
+
{t("agents.dangerZone.killSwitch")}
{killSwitch ? (
<>
-
- Calls the configured webhook to stop this agent's upstream runtime. This can cause an outage for
- everyone using the agent and cannot be undone from LiteLLM
-
+
{t("agents.dangerZone.configuredDescription")}
{killSwitch.method ?? "POST"} {killSwitch.url}
>
) : (
-
- Not configured. Add a kill switch webhook under Settings to enable this action
-
+
{t("agents.dangerZone.notConfigured")}
)}
{killSwitch && (
{isFiring && }
- Fire Kill Switch
+ {t("agents.dangerZone.fire")}
)}
{lastResult && (
- Last result: HTTP {lastResult.status_code}
+ {t("agents.dangerZone.lastResult", { status: lastResult.status_code })}
{lastResult.response_body ? ` ${lastResult.response_body}` : ""}
)}
@@ -95,27 +92,32 @@ const AgentKillSwitchDangerZone: React.FC
= ({
!open && !isFiring && setIsConfirmOpen(false)}>
- Fire kill switch for {agentName}?
+ {t("agents.dangerZone.confirm.title", { name: agentName })}
- This can cause an outage
+ {t("agents.dangerZone.confirm.outageTitle")}
- LiteLLM will call {killSwitch?.method ?? "POST"} {killSwitch?.url} immediately. Whatever that webhook
- does to the agent is outside LiteLLM's control and cannot be reverted here
+ {t("agents.dangerZone.confirm.outageDescription", {
+ method: killSwitch?.method ?? "POST",
+ url: killSwitch?.url,
+ })}
- Type {agentName} to confirm:
+ {agentName} }}
+ />
setConfirmationInput(e.target.value)}
placeholder={agentName}
@@ -126,7 +128,7 @@ const AgentKillSwitchDangerZone: React.FC = ({
setIsConfirmOpen(false)} disabled={isFiring}>
- Cancel
+ {t("agents.dangerZone.confirm.cancel")}
= ({
aria-busy={isFiring}
>
{isFiring && }
- {isFiring ? "Firing..." : "Fire Kill Switch"}
+ {isFiring ? t("agents.dangerZone.confirm.firing") : t("agents.dangerZone.fire")}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentsPanel.tsx b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentsPanel.tsx
index 3f599c38976..308cefec844 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentsPanel.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentsPanel.tsx
@@ -1,6 +1,7 @@
import { useTerminology } from "@/contexts/TerminologyContext";
import React, { useState, useEffect } from "react";
import { Info, Plus } from "lucide-react";
+import { useTranslation } from "react-i18next";
import { getAgentsList, deleteAgentCall } from "@/components/networking";
import AddAgentForm from "./add_agent_form";
import { isAdminRole } from "@/utils/roles";
@@ -33,6 +34,7 @@ interface AgentsResponse {
const AgentsPanel: React.FC
= ({ accessToken, userRole, teams }) => {
const term = useTerminology();
+ const { t } = useTranslation();
const [agentsList, setAgentsList] = useState([]);
const [isAddModalVisible, setIsAddModalVisible] = useState(false);
const [isLoading, setIsLoading] = useState(true);
@@ -122,11 +124,11 @@ const AgentsPanel: React.FC = ({ accessToken, userRole, teams
setIsDeleting(true);
try {
await deleteAgentCall(accessToken, agentToDelete.id);
- toast.success(`Agent "${agentToDelete.name}" deleted successfully`);
+ toast.success(t("agents.panel.toasts.deleted", { name: agentToDelete.name }));
await refetchAgents(healthCheckEnabled);
} catch (error) {
console.error("Error deleting agent:", error);
- toast.fromError("Failed to delete agent");
+ toast.fromError(t("agents.panel.toasts.deleteFailed"));
} finally {
setIsDeleting(false);
setAgentToDelete(null);
@@ -140,25 +142,18 @@ const AgentsPanel: React.FC = ({ accessToken, userRole, teams
return (
-
Agents
-
- {term(
- "List of A2A-spec agents that are available to be used in your organization. Go to AI Hub, to make agents public.",
- )}
-
+
{t("agents.panel.title")}
+
{term(t("agents.panel.description"))}
- How do agents authenticate?
-
- Agents can authenticate with a virtual key or a trusted identity provider using JWT. Configure an identity
- binding when adding or editing an agent. JWT authentication does not require a virtual key.
-
+ {t("agents.panel.authAlert.title")}
+ {t("agents.panel.authAlert.description")}
{isAdmin && (
- Add New Agent
+ {t("agents.panel.addAgent")}
)}
@@ -201,15 +196,15 @@ const AgentsPanel: React.FC
= ({ accessToken, userRole, teams
>
- Delete Agent
+ {t("agents.panel.deleteDialog.title")}
- Are you sure you want to delete agent: {agentToDelete.name}? This action cannot be undone.
+ {t("agents.panel.deleteDialog.description", { name: agentToDelete.name })}
- Cancel
+ {t("agents.panel.deleteDialog.cancel")}
- Delete
+ {t("agents.panel.deleteDialog.confirm")}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentsTable.tsx b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentsTable.tsx
index d737a9250eb..421207e72d0 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentsTable.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentsTable.tsx
@@ -3,6 +3,7 @@
import { SortingState } from "@tanstack/react-table";
import { Bot, CircleCheck, Search as SearchIcon, X } from "lucide-react";
import React, { useMemo, useState } from "react";
+import { useTranslation } from "react-i18next";
import { Agent } from "@/components/agents/types";
import { DataTable } from "@/components/shared/DataTable";
@@ -27,16 +28,17 @@ interface AgentsTableProps {
const DEFAULT_SORTING: SortingState = [{ id: "created_at", desc: true }];
function EmptyState({ isFiltered }: { isFiltered: boolean }) {
+ const { t } = useTranslation();
return (
-
{isFiltered ? "No matching agents" : "No agents yet"}
+
+ {isFiltered ? t("agents.table.empty.filteredTitle") : t("agents.table.empty.title")}
+
- {isFiltered
- ? "Adjust the search to see more agents."
- : "Add an agent to make it available in your organization."}
+ {isFiltered ? t("agents.table.empty.filteredDescription") : t("agents.table.empty.description")}
);
@@ -52,6 +54,7 @@ const AgentsTable: React.FC = ({
onAgentClick,
onDeleteClick,
}) => {
+ const { t } = useTranslation();
const [sorting, setSorting] = useState(DEFAULT_SORTING);
const [searchTerm, setSearchTerm] = useState("");
const filteredAgents = useMemo(
@@ -79,7 +82,7 @@ const AgentsTable: React.FC = ({
sorting={sorting}
onSortingChange={setSorting}
isLoading={isLoading}
- loadingMessage="Loading agents…"
+ loadingMessage={t("agents.table.loading")}
noDataMessage={ 0} />}
size="compact"
toolbar={() => (
@@ -89,13 +92,17 @@ const AgentsTable: React.FC = ({
setSearchTerm(e.target.value)}
/>
{searchTerm && (
- setSearchTerm("")}>
+ setSearchTerm("")}
+ >
@@ -109,7 +116,7 @@ const AgentsTable: React.FC = ({
- Health Check
+ {t("agents.table.healthCheck.label")}
= ({
}
/>
-
When enabled, only agents with reachable URLs are shown
+
{t("agents.table.healthCheck.hint")}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentsTableColumns.tsx b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentsTableColumns.tsx
index 002219f5478..a0e8751e682 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentsTableColumns.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/AgentsTableColumns.tsx
@@ -1,6 +1,7 @@
"use client";
import { ColumnDef } from "@tanstack/react-table";
+import { useTranslation } from "react-i18next";
import { MoreHorizontal, Trash2 } from "lucide-react";
import { Agent } from "@/components/agents/types";
@@ -14,6 +15,7 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
+import i18n from "@/i18n/i18n";
import { cn } from "@/lib/cva.config";
interface AgentRowActionsProps {
@@ -22,10 +24,11 @@ interface AgentRowActionsProps {
}
function AgentRowActions({ agent, onDeleteClick }: AgentRowActionsProps) {
+ const { t } = useTranslation();
return (
@@ -38,7 +41,7 @@ function AgentRowActions({ agent, onDeleteClick }: AgentRowActionsProps) {
onClick={() => onDeleteClick(agent.agent_id, agent.agent_name)}
>
- Delete
+ {t("agents.table.actions.delete")}
@@ -59,8 +62,8 @@ export const getAgentsTableColumns = ({
{
id: "agent_name",
accessorKey: "agent_name",
- meta: { title: "Agent Name" },
- header: ({ column }) => ,
+ meta: { title: i18n.t("agents.table.columns.name") },
+ header: ({ column }) => ,
size: 200,
enableSorting: true,
cell: ({ row }) => {
@@ -75,8 +78,8 @@ export const getAgentsTableColumns = ({
{
id: "agent_id",
accessorKey: "agent_id",
- meta: { title: "Agent ID" },
- header: ({ column }) => ,
+ meta: { title: i18n.t("agents.table.columns.id") },
+ header: ({ column }) => ,
size: 200,
enableSorting: true,
cell: ({ row }) => (
@@ -90,22 +93,22 @@ export const getAgentsTableColumns = ({
{
id: "spend",
accessorKey: "spend",
- meta: { title: "Spend (USD)", numeric: true },
- header: ({ column }) => ,
+ meta: { title: i18n.t("agents.table.columns.spend"), numeric: true },
+ header: ({ column }) => ,
size: 130,
enableSorting: true,
cell: ({ row }) => ,
},
{
id: "model",
- meta: { title: "Model" },
- header: "Model",
+ meta: { title: i18n.t("agents.table.columns.model") },
+ header: i18n.t("agents.table.columns.model"),
size: 170,
enableSorting: false,
cell: ({ row }) => {
const model = row.original.litellm_params?.model;
if (!model) {
- return N/A ;
+ return {i18n.t("agents.table.noModel")} ;
}
return (
@@ -122,25 +125,26 @@ export const getAgentsTableColumns = ({
const timestamp = agent.created_at ? new Date(agent.created_at).getTime() : 0;
return Number.isNaN(timestamp) ? 0 : timestamp;
},
- meta: { title: "Created" },
- header: ({ column }) => ,
+ meta: { title: i18n.t("agents.table.columns.created") },
+ header: ({ column }) => ,
size: 150,
enableSorting: true,
cell: ({ row }) => ,
},
{
id: "status",
- meta: { title: "Status" },
- header: "Status",
+ meta: { title: i18n.t("agents.table.columns.status") },
+ header: i18n.t("agents.table.columns.status"),
size: 130,
enableSorting: false,
cell: ({ row }) => {
const hasKeys = (row.original.keys?.length ?? 0) > 0;
- if (row.original.jwt_auth_configured) return ;
+ if (row.original.jwt_auth_configured)
+ return ;
return hasKeys ? (
-
+
) : (
-
+
);
},
},
@@ -149,7 +153,7 @@ export const getAgentsTableColumns = ({
{
id: "actions",
meta: { className: "text-right", headerClassName: "text-right" },
- header: () => Actions ,
+ header: () => {i18n.t("agents.table.columns.actions")} ,
size: 64,
enableSorting: false,
enableHiding: false,
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/KillSwitchFormFields.tsx b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/KillSwitchFormFields.tsx
index 578d80da6ed..2b9a0edb70d 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/KillSwitchFormFields.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/KillSwitchFormFields.tsx
@@ -1,5 +1,6 @@
import React from "react";
import { useFieldArray, useFormContext, useWatch } from "react-hook-form";
+import { useTranslation } from "react-i18next";
import { Plus, Trash2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@@ -7,6 +8,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
import { Textarea } from "@/components/ui/textarea";
import { Field, FieldTitle } from "@/components/ui/field";
import { PasswordInput } from "@/components/shared/PasswordInput";
+import i18n from "@/i18n/i18n";
import { AgentFormField, AgentFormValues, labelWithHint } from "./AgentFormKit";
import { KILL_SWITCH_AUTH_TYPES, KILL_SWITCH_METHODS, validateKillSwitchBody } from "./kill_switch_config";
@@ -21,6 +23,7 @@ const KeyValueFieldArray = ({
keyPlaceholder: string;
valuePlaceholder: string;
}) => {
+ const { t } = useTranslation();
const { control } = useFormContext();
const { fields, append, remove } = useFieldArray({ control, name });
@@ -28,7 +31,10 @@ const KeyValueFieldArray = ({
{fields.map((item, index) => (
-
+
{({ value, onChange, ref, ...control }) => (
remove(index)}
>
@@ -109,24 +115,50 @@ const TextField = ({
);
const KillSwitchAuthFields = () => {
+ const { t } = useTranslation();
const { control } = useFormContext();
const authType = useWatch({ control, name: "kill_switch.auth_type" });
switch (authType) {
case "bearer":
- return ;
+ return (
+
+ );
case "api_key":
return (
<>
-
-
+
+
>
);
case "basic":
return (
<>
-
-
+
+
>
);
default:
@@ -139,13 +171,13 @@ const KillSwitchFormFields = () => (
-
+
{({ value, onChange, ref: _ref, ...control }) => (
@@ -163,28 +195,28 @@ const KillSwitchFormFields = () => (
- Headers
+ {i18n.t("agents.killSwitchFields.headers.title")}
- Query Parameters
+ {i18n.t("agents.killSwitchFields.queryParams.title")}
validateKillSwitchBody(typeof value === "string" ? value : "") }}
>
{({ value, onChange, ref, ...control }) => (
@@ -199,7 +231,7 @@ const KillSwitchFormFields = () => (
)}
-
+
{({ value, onChange, ref: _ref, ...control }) => (
@@ -208,7 +240,7 @@ const KillSwitchFormFields = () => (
{KILL_SWITCH_AUTH_TYPES.map((option) => (
- {option.label}
+ {i18n.t(option.labelKey)}
))}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx
index 188d3349beb..715f2516e66 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx
@@ -5,6 +5,7 @@ import { FormProvider, useForm, useWatch } from "react-hook-form";
import { toast } from "@/lib/toast";
import { Logo } from "@/components/molecules/logo/Logo";
import { Bot, Check, CircleCheck, Key, LayoutGrid } from "lucide-react";
+import { Trans, useTranslation } from "react-i18next";
import CreatedKeyDisplay from "@/components/shared/CreatedKeyDisplay";
import { Badge } from "@/components/ui/badge";
import { StatusBadge } from "@/components/shared/table_cells/status_badge";
@@ -55,10 +56,17 @@ import MCPToolPermissions from "@/components/mcp_server_management/MCPToolPermis
import AccessGroupSelector from "@/components/common_components/AccessGroupSelector";
import GuardrailSelector from "@/components/guardrails/GuardrailSelector";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
+import i18n from "@/i18n/i18n";
const CUSTOM_AGENT_TYPE = "custom";
-const STEP_TITLES = ["Configure", "Entitlements", "Governance", "Agent Management", "Ready"] as const;
+const STEP_TITLES = [
+ "agents.addForm.steps.configure",
+ "agents.addForm.steps.entitlements",
+ "agents.addForm.steps.governance",
+ "agents.addForm.steps.agentManagement",
+ "agents.addForm.steps.ready",
+] as const;
const stepMarkerClass = (index: number, current: number): string => {
if (index < current) return "border-primary text-primary";
@@ -73,11 +81,12 @@ const stepTitleClass = (index: number, current: number): string => {
};
const AgentTypeLabel: React.FC<{ agentType: string; info: AgentCreateInfo | undefined }> = ({ agentType, info }) => {
+ const { t } = useTranslation();
if (agentType === CUSTOM_AGENT_TYPE) {
return (
- Custom / Other
+ {t("agents.addForm.agentType.custom")}
);
}
@@ -91,7 +100,7 @@ const AgentTypeLabel: React.FC<{ agentType: string; info: AgentCreateInfo | unde
};
const StepProgress: React.FC<{ current: number }> = ({ current }) => (
-
+
{STEP_TITLES.map((title, index) => (
= ({ current }) => (
>
{index < current ? : index + 1}
- {title}
+ {i18n.t(title)}
{index < STEP_TITLES.length - 1 && }
))}
@@ -132,6 +141,7 @@ interface AddAgentFormProps {
}
const AddAgentForm: React.FC = ({ visible, onClose, accessToken, onSuccess, teams }) => {
+ const { t } = useTranslation();
const { userId, userRole } = useAuthorized();
const form = useForm({ defaultValues: buildInitialValues("a2a") });
const panels = useCollapsiblePanels([AGENT_FORM_CONFIG.basic.key]);
@@ -339,12 +349,12 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
const handleCreateAgent = async () => {
if (!accessToken) {
- toast.error("No access token available");
+ toast.error(t("agents.addForm.toasts.noAccessToken"));
return;
}
if (keyAssignOption === "existing_key" && !selectedExistingKey) {
- toast.error("Please select an existing key to assign");
+ toast.error(t("agents.addForm.toasts.selectExistingKey"));
return;
}
@@ -358,7 +368,7 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
const values = form.getValues();
const built = buildAgentData(values);
if (!built) {
- toast.error("Failed to build agent data");
+ toast.error(t("agents.addForm.toasts.buildFailed"));
setIsSubmitting(false);
return;
}
@@ -434,7 +444,11 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
} catch (error) {
console.error("Error creating agent:", error);
const errorMessage = error instanceof Error ? error.message : String(error);
- toast.error(errorMessage ? `Failed to create agent: ${errorMessage}` : "Failed to create agent");
+ toast.error(
+ errorMessage
+ ? t("agents.addForm.toasts.createFailedWithError", { error: errorMessage })
+ : t("agents.addForm.toasts.createFailed"),
+ );
} finally {
setIsSubmitting(false);
}
@@ -461,17 +475,14 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
const renderEntitlementsStep = () => (
-
- Configure which models, agents, and MCP tools this agent is allowed to use. Leave fields empty to allow all
- (subject to key/team permissions).
-
+
{t("agents.addForm.entitlements.description")}
{({ id, value, onChange }) => (
@@ -479,7 +490,11 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
id={id}
value={Array.isArray(value) ? (value as string[]) : []}
onValueChange={onChange}
- placeholder={loadingModels ? "Loading models..." : "Select models (leave empty for all)"}
+ placeholder={
+ loadingModels
+ ? t("agents.addForm.entitlements.models.loading")
+ : t("agents.addForm.entitlements.models.placeholder")
+ }
options={availableModels.map((m) => ({ label: getModelDisplayName(m), value: m }))}
/>
)}
@@ -488,8 +503,8 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
{({ id, value, onChange }) => (
@@ -497,7 +512,11 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
id={id}
value={Array.isArray(value) ? (value as string[]) : []}
onValueChange={onChange}
- placeholder={loadingAgents ? "Loading agents..." : "Select agents (leave empty for all)"}
+ placeholder={
+ loadingAgents
+ ? t("agents.addForm.entitlements.agents.loading")
+ : t("agents.addForm.entitlements.agents.placeholder")
+ }
options={availableAgents.map((a) => ({ label: a.agent_name, value: a.agent_id }))}
/>
)}
@@ -506,15 +525,15 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
{({ value, onChange }) => (
)}
@@ -524,8 +543,8 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
{({ value, onChange }) => (
@@ -536,7 +555,7 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
accessGroups: (value as McpServerSelection | undefined)?.accessGroups ?? [],
}}
accessToken={accessToken ?? ""}
- placeholder="Select MCP servers or access groups (optional)"
+ placeholder={t("agents.addForm.entitlements.mcpServers.placeholder")}
/>
)}
@@ -574,15 +593,15 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
const renderObservabilityStep = () => (
-
Tracing
+
{t("agents.addForm.governance.tracing.title")}
- Require x-litellm-trace-id on calls TO this agent
+ {t("agents.addForm.governance.tracing.inbound.label", { header: "x-litellm-trace-id" })}
- Only accept this agent being invoked with a trace-id (e.g. when used as a sub-agent).
+ {t("agents.addForm.governance.tracing.inbound.description")}
@@ -591,10 +610,10 @@ const AddAgentForm: React.FC
= ({ visible, onClose, accessTok
- Require x-litellm-trace-id on calls BY this agent
+ {t("agents.addForm.governance.tracing.outbound.label", { header: "x-litellm-trace-id" })}
- Requires LLM/MCP calls made by this agent to include x-litellm-trace-id for session tracking.
+ {t("agents.addForm.governance.tracing.outbound.description", { header: "x-litellm-trace-id" })}
= ({ visible, onClose, accessTok
-
Budgets & Rate Limits
+
{t("agents.addForm.governance.budgets.title")}
{!requireTraceIdOutbound && (
- Enable "Require x-litellm-trace-id on calls BY this agent" in Tracing to configure budgets and
- rate limits.
+ {t("agents.addForm.governance.budgets.requiresOutbound", { header: "x-litellm-trace-id" })}
)}
-
Session Budgets
+
+ {t("agents.addForm.governance.budgets.session.title")}
+
-
Agent Rate Limits
-
Global rate limits applied across all callers of this agent.
+
+ {t("agents.addForm.governance.rateLimits.agent.title")}
+
+
{t("agents.addForm.governance.rateLimits.agent.description")}
- {rateLimitField("tpm_limit", "TPM Limit", "e.g. 100000")}
- {rateLimitField("rpm_limit", "RPM Limit", "e.g. 100")}
+ {rateLimitField(
+ "tpm_limit",
+ t("agents.addForm.governance.rateLimits.agent.tpm.label"),
+ t("agents.addForm.governance.rateLimits.agent.tpm.placeholder"),
+ )}
+ {rateLimitField(
+ "rpm_limit",
+ t("agents.addForm.governance.rateLimits.agent.rpm.label"),
+ t("agents.addForm.governance.rateLimits.agent.rpm.placeholder"),
+ )}
-
Per-Session Rate Limits
+
+ {t("agents.addForm.governance.rateLimits.session.title")}
+
- Rate limits per session (x-litellm-trace-id). Each session gets its own counters.
+ {t("agents.addForm.governance.rateLimits.session.description", { header: "x-litellm-trace-id" })}
- {rateLimitField("session_tpm_limit", "Session TPM Limit", "e.g. 10000")}
- {rateLimitField("session_rpm_limit", "Session RPM Limit", "e.g. 20")}
+ {rateLimitField(
+ "session_tpm_limit",
+ t("agents.addForm.governance.rateLimits.session.tpm.label"),
+ t("agents.addForm.governance.rateLimits.session.tpm.placeholder"),
+ )}
+ {rateLimitField(
+ "session_rpm_limit",
+ t("agents.addForm.governance.rateLimits.session.rpm.label"),
+ t("agents.addForm.governance.rateLimits.session.rpm.placeholder"),
+ )}
@@ -684,10 +732,8 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
-
Guardrails
-
- Apply guardrails to this agent. Selected guardrails will run on all calls made by this agent.
-
+
{t("agents.addForm.governance.guardrails.title")}
+
{t("agents.addForm.governance.guardrails.description")}
{({ value, onChange }) => (
= ({ visible, onClose, accessTok
<>
- {labelWithHint("Agent Type", "Select the type of agent you want to create")}
+ {labelWithHint(t("agents.addForm.configure.agentType.label"), t("agents.addForm.configure.agentType.hint"))}
value !== null && handleAgentTypeChange(value)}>
@@ -785,18 +831,22 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
))}
- Not listed?
+ {t("agents.addForm.configure.agentType.notListed")}
- Custom / Other
-
+ {t("agents.addForm.agentType.custom")}
+
- For outbound agents using an identity provider or virtual key
+ {t("agents.addForm.configure.agentType.customDescription")}
@@ -810,24 +860,28 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
{agentType === CUSTOM_AGENT_TYPE ? (
-
+
{({ value, onChange, ref, ...control }) => (
)}
-
+
{({ value, onChange, ref, ...control }) => (
@@ -842,7 +896,7 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
{selectedAgentTypeInfo.credential_fields.length > 0 && (
- {selectedAgentTypeInfo.agent_type_display_name} Settings
+ {t("agents.addForm.configure.typeSettings", { type: selectedAgentTypeInfo.agent_type_display_name })}
{selectedAgentTypeInfo.credential_fields.map((field) => (
@@ -851,7 +905,11 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
name={field.key}
label={field.tooltip ? labelWithHint(field.label, field.tooltip) : field.label}
defaultValue={field.default_value ?? undefined}
- rules={field.required ? { required: `Please enter ${field.label}` } : undefined}
+ rules={
+ field.required
+ ? { required: t("agents.addForm.configure.enterField", { field: field.label }) }
+ : undefined
+ }
>
{({ value, onChange, ref, ...control }) =>
field.field_type === "password" ? (
@@ -901,7 +959,7 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
);
const renderAssignKeyStep = () => {
- const agentName = form.getValues("agent_name") || "your-agent";
+ const agentName = form.getValues("agent_name") || t("agents.addForm.assignKey.agentNameFallback");
return (
{/* Agent name chip */}
@@ -914,10 +972,7 @@ const AddAgentForm: React.FC
= ({ visible, onClose, accessTok
{({ value, onChange }) => (
@@ -927,9 +982,7 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
{form.getValues("identity_provider") === "microsoft_entra" && (
-
- This agent will authenticate with Microsoft Entra ID. You can skip virtual key creation.
-
+ {t("agents.addForm.assignKey.entraNotice")}
)}
= ({ visible, onClose, accessTok
>
-
+
- Create a new key for this agent
+ {t("agents.addForm.assignKey.createNew.title")}
-
A dedicated key scoped to this agent.
+
+ {t("agents.addForm.assignKey.createNew.description")}
+
{keyAssignOption === "create_new" && (
e.stopPropagation()}>
- Key Name
+
+ {t("agents.addForm.assignKey.createNew.keyName.label")}
+
setNewKeyName(e.target.value)}
- placeholder="e.g. my-agent-key"
+ placeholder={t("agents.addForm.assignKey.createNew.keyName.placeholder")}
/>
)}
-
+
@@ -983,18 +1040,24 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
onClick={() => setKeyAssignOption("existing_key")}
>
-
+
- Assign an existing key
+ {t("agents.addForm.assignKey.existing.title")}
-
Re-assign a key you already have to this agent.
+
+ {t("agents.addForm.assignKey.existing.description")}
+
{keyAssignOption === "existing_key" && (
e.stopPropagation()}>
({
@@ -1016,8 +1079,8 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
onClick={() => setKeyAssignOption("skip")}
>
{form.getValues("identity_provider") === "microsoft_entra"
- ? "Use Entra JWT authentication"
- : "Skip for now, I’ll assign a key later"}
+ ? t("agents.addForm.assignKey.skip.entra")
+ : t("agents.addForm.assignKey.skip.later")}
@@ -1027,7 +1090,7 @@ const AddAgentForm: React.FC
= ({ visible, onClose, accessTok
const renderReadyStep = () => (
-
Agent Created!
+
{t("agents.addForm.ready.title")}
@@ -1041,14 +1104,17 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
)}
{assignedKeyAlias && (
- Key {assignedKeyAlias} has been assigned to this agent.
+ {assignedKeyAlias} }}
+ />
)}
{!createdKeyValue && !assignedKeyAlias && keyAssignOption === "skip" && (
{form.getValues("identity_provider") === "microsoft_entra"
- ? "Microsoft Entra ID is configured. Send an authenticated agent request to verify the connection."
- : "No key assigned. You can create one from the Virtual Keys page."}
+ ? t("agents.addForm.ready.entraConfigured")
+ : t("agents.addForm.ready.noKey")}
)}
@@ -1060,9 +1126,15 @@ const AddAgentForm: React.FC
= ({ visible, onClose, accessTok
{selectedLogo && currentStep < 1 && (
-
+
)}
- Add New Agent
+
+ {t("agents.addForm.dialog.title")}
+
@@ -1083,24 +1155,24 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
{currentStep > 0 && currentStep < 4 && (
- ← Back
+ {t("agents.addForm.dialog.back")}
)}
{currentStep < 4 && (
- Cancel
+ {t("agents.addForm.dialog.cancel")}
)}
- {currentStep < 3 && Next → }
+ {currentStep < 3 && {t("agents.addForm.dialog.next")} }
{currentStep === 3 && (
{isSubmitting && }
- {isSubmitting ? "Creating..." : "Create Agent →"}
+ {isSubmitting ? t("agents.addForm.dialog.creating") : t("agents.addForm.dialog.create")}
)}
- {currentStep === 4 && Done }
+ {currentStep === 4 && {t("agents.addForm.dialog.done")} }
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_card_discovery.tsx b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_card_discovery.tsx
index aba16a50508..35df5d2ff3d 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_card_discovery.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_card_discovery.tsx
@@ -2,6 +2,8 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { ChevronDown, CircleAlert, CircleCheck, Info, Link as LinkIcon, RotateCw, Search, X } from "lucide-react";
+import { Trans, useTranslation } from "react-i18next";
+import i18n from "@/i18n/i18n";
import { useDebouncedCallback } from "@tanstack/react-pacer/debouncer";
import { DiscoveredAgentCard, discoverAgentCardCall } from "@/components/networking";
@@ -63,6 +65,7 @@ const AgentCardDiscovery: React.FC = ({
discoveryRequest,
savedAgentCard,
}) => {
+ const { t } = useTranslation();
// When the parent drives discovery, ``manualUrl`` is unused — the URL
// comes from ``discoveryRequest.url`` directly. When the parent hasn't
// supplied a plan, the admin types into this field manually.
@@ -117,14 +120,16 @@ const AgentCardDiscovery: React.FC = ({
const handleDiscover = useCallback(async () => {
if (!accessToken) {
- setError("No access token available");
+ setError(i18n.t("agents.cardDiscovery.errors.noAccessToken"));
onApplyRef.current(null);
return;
}
const trimmed = effectiveUrl.trim();
if (!trimmed) {
setError(
- isParentDriven ? "Fill in the agent's connection details above first" : "Enter the agent's base URL first",
+ isParentDriven
+ ? i18n.t("agents.cardDiscovery.errors.fillConnectionDetails")
+ : i18n.t("agents.cardDiscovery.errors.enterBaseUrl"),
);
setCard(null);
onApplyRef.current(null);
@@ -152,7 +157,7 @@ const AgentCardDiscovery: React.FC = ({
resetSelections(response.agent_card);
} catch (e: any) {
if (requestId !== discoverRequestIdRef.current) return;
- setError(e?.message ? String(e.message) : "Failed to discover agent card");
+ setError(e?.message ? String(e.message) : i18n.t("agents.cardDiscovery.errors.discoverFailed"));
setCard(null);
lastSyncedSelectionRef.current = null;
onApplyRef.current(null);
@@ -247,13 +252,13 @@ const AgentCardDiscovery: React.FC = ({
if (card) return ;
return ;
};
- const discoverLabel = card ? "Re-discover" : "Discover";
+ const discoverLabel = card ? t("agents.cardDiscovery.rediscover") : t("agents.cardDiscovery.discover");
return (
- Discover from agent URL
+ {t("agents.cardDiscovery.title")}
= ({
}
/>
- LiteLLM will fetch /.well-known/agent-card.json from this URL and let you pick which skills and
- capabilities to expose through the proxy.
+ {t("agents.cardDiscovery.titleHint", { path: "/.well-known/agent-card.json" })}
{isParentDriven ? (
<>
-
- Using the connection details you entered above. We'll fetch:
-
+
{t("agents.cardDiscovery.parentDrivenIntro")}
{discoveryRequest!.display_url || effectiveUrl || (
- Fill in the fields above first
+ {t("agents.cardDiscovery.fillFieldsFirst")}
)}
@@ -290,8 +292,14 @@ const AgentCardDiscovery: React.FC
= ({
) : (
<>
- Paste the upstream agent's base URL. We'll try /.well-known/agent-card.json,{" "}
- /.well-known/agent.json, and /agent.json in order.
+ /.well-known/agent-card.json,
+ agentfile: /.well-known/agent.json,
+ rootfile: /agent.json,
+ }}
+ />
@@ -315,10 +323,15 @@ const AgentCardDiscovery: React.FC
= ({
{error && (
- Discovery failed
+ {t("agents.cardDiscovery.failedTitle")}
{error}
- setError(null)}>
+ setError(null)}
+ >
@@ -335,7 +348,7 @@ const AgentCardDiscovery: React.FC = ({
- Upstream card loaded
+ {t("agents.cardDiscovery.cardLoaded")}
{card.version && v{card.version} }
{card.provider?.organization && {card.provider.organization} }
@@ -343,18 +356,24 @@ const AgentCardDiscovery: React.FC
= ({
@@ -366,17 +385,19 @@ const AgentCardDiscovery: React.FC = ({
render={
- Skills
+ {t("agents.cardDiscovery.skills")}
}
/>
- {selectedSkillCount} / {skillCount} selected
+ {t("agents.cardDiscovery.skillsSelected", { selected: selectedSkillCount, total: skillCount })}
{skillCount === 0 ? (
- Upstream card has no skills
+
+ {t("agents.cardDiscovery.noSkills")}
+
) : (
{(card.skills ?? []).map((skill, idx) => {
@@ -394,9 +415,9 @@ const AgentCardDiscovery: React.FC
= ({
{skill.name || id}
{skill.id && {skill.id} }
- {(skill.tags ?? []).map((t: string) => (
-
- {t}
+ {(skill.tags ?? []).map((tag: string) => (
+
+ {tag}
))}
@@ -418,7 +439,9 @@ const AgentCardDiscovery: React.FC = ({
render={
- Capabilities
+
+ {t("agents.cardDiscovery.capabilities")}
+
}
/>
@@ -431,10 +454,7 @@ const AgentCardDiscovery: React.FC = ({
}
/>
-
- Only capabilities LiteLLM can faithfully proxy today are listed. Others (push notifications,
- extensions) are coming soon.
-
+ {t("agents.cardDiscovery.capabilitiesHint")}
@@ -449,7 +469,7 @@ const AgentCardDiscovery: React.FC = ({
>
{key}
- {!upstreamHas && not advertised upstream }
+ {!upstreamHas && {t("agents.cardDiscovery.notAdvertised")} }
= ({ agent }) => {
+ const { t } = useTranslation();
const params = agent.litellm_params;
if (params?.cost_per_query == null && params?.input_cost_per_token == null && params?.output_cost_per_token == null) {
@@ -14,15 +16,15 @@ const AgentCostView: React.FC = ({ agent }) => {
const rows = (
[
- ["Cost Per Query", params.cost_per_query],
- ["Input Cost Per Token", params.input_cost_per_token],
- ["Output Cost Per Token", params.output_cost_per_token],
+ [t("agents.costView.costPerQuery"), params.cost_per_query],
+ [t("agents.costView.inputCostPerToken"), params.input_cost_per_token],
+ [t("agents.costView.outputCostPerToken"), params.output_cost_per_token],
] as const
).filter(([, value]) => value != null);
return (
-
Cost Configuration
+
{t("agents.costView.title")}
{rows.map(([label, value]) => (
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_form_fields.tsx b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_form_fields.tsx
index bf32af30fb6..41e25d14054 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_form_fields.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_form_fields.tsx
@@ -1,5 +1,6 @@
import React from "react";
import { useFieldArray, useFormContext } from "react-hook-form";
+import { useTranslation } from "react-i18next";
import { Plus, Trash2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@@ -47,6 +48,7 @@ interface AgentFormFieldsProps {
}
const SkillsFieldArray = () => {
+ const { t } = useTranslation();
const { control } = useFormContext
();
const { fields, append, remove } = useFieldArray({ control, name: "skills" });
@@ -57,14 +59,14 @@ const SkillsFieldArray = () => {
{({ value, onChange, ref, ...control }) => (
@@ -73,14 +75,16 @@ const SkillsFieldArray = () => {
{({ value, onChange, ref, ...control }) => (
@@ -89,15 +93,19 @@ const SkillsFieldArray = () => {
{({ value, onChange, ref, ...control }) => (
@@ -106,26 +114,28 @@ const SkillsFieldArray = () => {
{({ id, value, onChange }) => (
)}
-
+
{({ id, value, onChange }) => (
)}
@@ -138,19 +148,20 @@ const SkillsFieldArray = () => {
onClick={() => remove(index)}
>
- Remove Skill
+ {t("agents.formFields.skills.remove")}
))}
append({})}>
- Add Skill
+ {t("agents.formFields.skills.add")}
>
);
};
const StaticHeadersFieldArray = () => {
+ const { t } = useTranslation();
const { control } = useFormContext();
const { fields, append, remove } = useFieldArray({ control, name: "static_headers" });
@@ -158,25 +169,31 @@ const StaticHeadersFieldArray = () => {
<>
{fields.map((item, index) => (
-
+
{({ value, onChange, ref, ...control }) => (
)}
-
+
{({ value, onChange, ref, ...control }) => (
@@ -186,7 +203,7 @@ const StaticHeadersFieldArray = () => {
type="button"
variant="ghost"
size="icon"
- aria-label="Remove static header"
+ aria-label={t("agents.formFields.staticHeaders.remove")}
className="text-destructive hover:text-destructive/80"
onClick={() => remove(index)}
>
@@ -196,13 +213,14 @@ const StaticHeadersFieldArray = () => {
))}
append({})}>
- Add Static Header
+ {t("agents.formFields.staticHeaders.add")}
>
);
};
const AgentFormFields: React.FC = ({ panels, showAgentName = true, visiblePanels }) => {
+ const { t } = useTranslation();
const shouldShow = (key: string) => !visiblePanels || visiblePanels.includes(key);
return (
@@ -211,14 +229,14 @@ const AgentFormFields: React.FC = ({ panels, showAgentName
{({ value, onChange, ref, ...control }) => (
@@ -231,7 +249,7 @@ const AgentFormFields: React.FC = ({ panels, showAgentName
{shouldShow(AGENT_FORM_CONFIG.basic.key) && (
{AGENT_FORM_CONFIG.basic.fields.map((field) => (
@@ -239,8 +257,12 @@ const AgentFormFields: React.FC = ({ panels, showAgentName
key={field.name}
name={field.name}
label={field.tooltip ? labelWithHint(field.label, field.tooltip) : field.label}
- description={field.helpText}
- rules={field.required ? { required: `Please enter ${field.label.toLowerCase()}` } : undefined}
+ description={field.helpTextKey ? t(field.helpTextKey) : undefined}
+ rules={
+ field.required
+ ? { required: t("agents.formFields.errors.enterField", { field: field.label.toLowerCase() }) }
+ : undefined
+ }
>
{({ value, onChange, ref, ...control }) => {
const text = typeof value === "string" ? value : "";
@@ -284,7 +306,7 @@ const AgentFormFields: React.FC = ({ panels, showAgentName
{shouldShow(AGENT_FORM_CONFIG.skills.key) && (
@@ -294,7 +316,7 @@ const AgentFormFields: React.FC = ({ panels, showAgentName
{shouldShow(AGENT_FORM_CONFIG.capabilities.key) && (
{AGENT_FORM_CONFIG.capabilities.fields.map((field) => (
@@ -310,7 +332,7 @@ const AgentFormFields: React.FC = ({ panels, showAgentName
{shouldShow(AGENT_FORM_CONFIG.optional.key) && (
{AGENT_FORM_CONFIG.optional.fields.map((field) => (
@@ -334,7 +356,11 @@ const AgentFormFields: React.FC = ({ panels, showAgentName
)}
{shouldShow(AGENT_FORM_CONFIG.cost.key) && (
-
+
)}
@@ -342,7 +368,7 @@ const AgentFormFields: React.FC = ({ panels, showAgentName
{shouldShow(AGENT_FORM_CONFIG.litellm.key) && (
{AGENT_FORM_CONFIG.litellm.fields.map((field) => (
@@ -366,12 +392,16 @@ const AgentFormFields: React.FC = ({ panels, showAgentName
)}
{shouldShow(AUTH_HEADERS_PANEL_KEY) && (
-
+
{labelWithHint(
- "Static Headers",
- "Headers always sent to the backend agent, regardless of the client request. Admin-configured, static wins on conflict.",
+ t("agents.formFields.authHeaders.staticHeaders"),
+ t("agents.formFields.authHeaders.staticHeadersHint"),
)}
@@ -382,8 +412,8 @@ const AgentFormFields: React.FC
= ({ panels, showAgentName
{({ id, value, onChange }) => (
@@ -391,7 +421,7 @@ const AgentFormFields: React.FC = ({ panels, showAgentName
id={id}
value={Array.isArray(value) ? (value as string[]) : []}
onValueChange={onChange}
- placeholder="e.g. x-api-key, Authorization"
+ placeholder={t("agents.formFields.authHeaders.forwardHeadersPlaceholder")}
/>
)}
@@ -399,7 +429,7 @@ const AgentFormFields: React.FC = ({ panels, showAgentName
)}
{shouldShow(KILL_SWITCH_PANEL_KEY) && (
-
+
)}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_info.tsx b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_info.tsx
index 68c0a6a702d..554116716ea 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_info.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_info.tsx
@@ -14,6 +14,7 @@ import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner";
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
import { toast } from "@/lib/toast";
import { ArrowLeft } from "lucide-react";
+import { useTranslation } from "react-i18next";
import { getAgentInfo, patchAgentCall, getAgentCreateMetadata, AgentCreateInfo } from "@/components/networking";
import { Agent } from "@/components/agents/types";
import { KeyResponse } from "@/components/key_team_helpers/key_list";
@@ -79,6 +80,7 @@ const DetailItem: React.FC<{ label: React.ReactNode; children: React.ReactNode }
);
const AgentInfoView: React.FC = ({ agentId, onClose, accessToken, isAdmin }) => {
+ const { t } = useTranslation();
const [agent, setAgent] = useState(null);
const [selectedKey, setSelectedKey] = useState(null);
const { data: keysData, isLoading: keysLoading, refetch: refetchAgentKeys } = useKeys(1, 100, { agentID: agentId });
@@ -127,7 +129,7 @@ const AgentInfoView: React.FC = ({ agentId, onClose, accessT
if (agentType === "a2a") {
form.reset(parseAgentForForm(data));
} else {
- const typeInfo = agentTypeMetadata.find((t) => t.agent_type === agentType);
+ const typeInfo = agentTypeMetadata.find((info) => info.agent_type === agentType);
if (typeInfo) {
form.reset({
...parseDynamicAgentForForm(data, typeInfo),
@@ -140,7 +142,7 @@ const AgentInfoView: React.FC = ({ agentId, onClose, accessT
}
} catch (error) {
console.error("Error fetching agent info:", error);
- toast.error("Failed to load agent information");
+ toast.error(t("agents.info.toasts.loadFailed"));
} finally {
setIsLoading(false);
}
@@ -151,7 +153,7 @@ const AgentInfoView: React.FC = ({ agentId, onClose, accessT
if (agent && agentTypeMetadata.length > 0) {
const agentType = detectAgentType(agent);
if (agentType !== "a2a") {
- const typeInfo = agentTypeMetadata.find((t) => t.agent_type === agentType);
+ const typeInfo = agentTypeMetadata.find((info) => info.agent_type === agentType);
if (typeInfo) {
form.reset({
...parseDynamicAgentForForm(agent, typeInfo),
@@ -163,7 +165,7 @@ const AgentInfoView: React.FC = ({ agentId, onClose, accessT
}
}, [agentTypeMetadata, agent]);
- const selectedAgentTypeInfo = agentTypeMetadata.find((t) => t.agent_type === detectedAgentType);
+ const selectedAgentTypeInfo = agentTypeMetadata.find((info) => info.agent_type === detectedAgentType);
const watchedFormValues = useWatch({ control: form.control });
const mcpSelection = useWatch({ control: form.control, name: "allowed_mcp_servers_and_groups" });
const mcpToolPermissions = useWatch({ control: form.control, name: "mcp_tool_permissions" });
@@ -249,12 +251,12 @@ const AgentInfoView: React.FC = ({ agentId, onClose, accessT
object_permission: buildMcpObjectPermission(values),
access_group_ids: values.access_group_ids ?? [],
});
- toast.success("Agent updated successfully");
+ toast.success(t("agents.info.toasts.updated"));
setIsEditing(false);
fetchAgentInfo();
} catch (error) {
console.error("Error updating agent:", error);
- toast.error("Failed to update agent");
+ toast.error(t("agents.info.toasts.updateFailed"));
} finally {
setIsSaving(false);
}
@@ -273,9 +275,9 @@ const AgentInfoView: React.FC = ({ agentId, onClose, accessT
if (!agent) {
return (
-
Agent not found
+
{t("agents.info.notFound")}
- Back to Agents List
+ {t("agents.info.backToList")}
);
@@ -297,7 +299,7 @@ const AgentInfoView: React.FC = ({ agentId, onClose, accessT
onChange={onChange}
inputRef={ref}
min={0}
- placeholder="Unlimited"
+ placeholder={t("agents.info.unlimited")}
/>
)}
@@ -314,7 +316,7 @@ const AgentInfoView: React.FC = ({ agentId, onClose, accessT
refetchAgentKeys();
}}
teams={null}
- backButtonText="Back to Agent"
+ backButtonText={t("agents.info.backToAgent")}
/>
);
}
@@ -324,20 +326,20 @@ const AgentInfoView: React.FC = ({ agentId, onClose, accessT
- Back to Agents
+ {t("agents.info.backToAgents")}
-
{agent.agent_name || "Unnamed Agent"}
+
{agent.agent_name || t("agents.info.unnamed")}
{agent.agent_id}
- Overview
+ {t("agents.info.tabs.overview")}
{isAdmin && (
- Settings
+ {t("agents.info.tabs.settings")}
)}
@@ -352,38 +354,62 @@ const AgentInfoView: React.FC = ({ agentId, onClose, accessT
isAdmin={isAdmin}
/>
- {agent.agent_id}
- {agent.agent_name}
- {agent.agent_card_params?.name || "-"}
- {agent.agent_card_params?.description || "-"}
- {agent.agent_card_params?.url || "-"}
- {agent.agent_card_params?.version || "-"}
- {agent.agent_card_params?.protocolVersion || "-"}
-
- {agent.agent_card_params?.capabilities?.streaming ? "Yes" : "No"}
+ {agent.agent_id}
+ {agent.agent_name}
+
+ {agent.agent_card_params?.name || "-"}
+
+
+ {agent.agent_card_params?.description || "-"}
+
+ {agent.agent_card_params?.url || "-"}
+
+ {agent.agent_card_params?.version || "-"}
+
+
+ {agent.agent_card_params?.protocolVersion || "-"}
+
+
+ {agent.agent_card_params?.capabilities?.streaming ? t("agents.info.yes") : t("agents.info.no")}
{agent.agent_card_params?.capabilities?.pushNotifications && (
- Yes
+ {t("agents.info.yes")}
)}
{agent.agent_card_params?.capabilities?.stateTransitionHistory && (
- Yes
+ {t("agents.info.yes")}
+ )}
+
+ {t("agents.info.skillsConfigured", { count: agent.agent_card_params?.skills?.length || 0 })}
+
+ {agent.litellm_params?.model && (
+ {agent.litellm_params.model}
)}
- {agent.agent_card_params?.skills?.length || 0} configured
- {agent.litellm_params?.model && {agent.litellm_params.model} }
{agent.litellm_params?.make_public !== undefined && (
- {agent.litellm_params.make_public ? "Yes" : "No"}
+
+ {agent.litellm_params.make_public ? t("agents.info.yes") : t("agents.info.no")}
+
)}
{agent.agent_card_params?.iconUrl && (
- {agent.agent_card_params.iconUrl}
+ {agent.agent_card_params.iconUrl}
)}
{agent.agent_card_params?.documentationUrl && (
- {agent.agent_card_params.documentationUrl}
+
+ {agent.agent_card_params.documentationUrl}
+
)}
- {agent.tpm_limit ?? "Unlimited"}
- {agent.rpm_limit ?? "Unlimited"}
- {agent.session_tpm_limit ?? "Unlimited"}
- {agent.session_rpm_limit ?? "Unlimited"}
-
+
+ {agent.tpm_limit ?? t("agents.info.unlimited")}
+
+
+ {agent.rpm_limit ?? t("agents.info.unlimited")}
+
+
+ {agent.session_tpm_limit ?? t("agents.info.unlimited")}
+
+
+ {agent.session_rpm_limit ?? t("agents.info.unlimited")}
+
+
{agent.access_group_ids?.length ? (
{agent.access_group_ids.map((accessGroupId) => (
@@ -391,11 +417,11 @@ const AgentInfoView: React.FC
= ({ agentId, onClose, accessT
))}
) : (
- "None"
+ t("agents.info.none")
)}
- {formatDate(agent.created_at)}
- {formatDate(agent.updated_at)}
+ {formatDate(agent.created_at)}
+ {formatDate(agent.updated_at)}
@@ -407,10 +433,10 @@ const AgentInfoView: React.FC = ({ agentId, onClose, accessT
(agent.object_permission.mcp_tool_permissions &&
Object.keys(agent.object_permission.mcp_tool_permissions).length > 0)) && (
-
MCP Tool Permissions
+
{t("agents.info.mcpPermissions.title")}
{agent.object_permission.mcp_servers && agent.object_permission.mcp_servers.length > 0 && (
-
+
{agent.object_permission.mcp_servers.map((serverId) => (
{mcpServerLabel(serverId)}
@@ -420,16 +446,18 @@ const AgentInfoView: React.FC
= ({ agentId, onClose, accessT
)}
{agent.object_permission.mcp_access_groups &&
agent.object_permission.mcp_access_groups.length > 0 && (
-
+
{agent.object_permission.mcp_access_groups.join(", ")}
)}
{agent.object_permission.mcp_toolsets && agent.object_permission.mcp_toolsets.length > 0 && (
- {agent.object_permission.mcp_toolsets.join(", ")}
+
+ {agent.object_permission.mcp_toolsets.join(", ")}
+
)}
{agent.object_permission.mcp_tool_permissions &&
Object.keys(agent.object_permission.mcp_tool_permissions).length > 0 && (
-
+
{Object.entries(agent.object_permission.mcp_tool_permissions).map(([serverId, tools]) => (
@@ -448,23 +476,27 @@ const AgentInfoView: React.FC
= ({ agentId, onClose, accessT
{agent.agent_card_params?.skills && agent.agent_card_params.skills.length > 0 && (
-
Skills
+
{t("agents.info.skillsSection.title")}
{agent.agent_card_params.skills.map((skill, index) => (
-
+
- ID: {skill.id}
+ {t("agents.info.skillsSection.id")} {skill.id}
- Description: {skill.description}
+ {t("agents.info.skillsSection.description")} {skill.description}
- Tags: {Array.isArray(skill.tags) ? skill.tags.join(", ") : skill.tags}
+ {t("agents.info.skillsSection.tags")} {" "}
+ {Array.isArray(skill.tags) ? skill.tags.join(", ") : skill.tags}
{skill.examples && skill.examples.length > 0 && (
- Examples: {" "}
+ {t("agents.info.skillsSection.examples")} {" "}
{Array.isArray(skill.examples) ? skill.examples.join(", ") : skill.examples}
)}
@@ -489,7 +521,7 @@ const AgentInfoView: React.FC
= ({ agentId, onClose, accessT
-
Agent Settings
+
{t("agents.info.settings.title")}
{!isEditing && (
{
@@ -497,7 +529,7 @@ const AgentInfoView: React.FC = ({ agentId, onClose, accessT
setIsEditing(true);
}}
>
- Edit Settings
+ {t("agents.info.settings.edit")}
)}
@@ -508,7 +540,7 @@ const AgentInfoView: React.FC = ({ agentId, onClose, accessT
) : (
- Click "Edit Settings" to modify agent configuration.
+ {t("agents.info.settings.editHint")}
)}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_virtual_keys.tsx b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_virtual_keys.tsx
index f9485c2b1a7..c6846b9a071 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_virtual_keys.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_virtual_keys.tsx
@@ -1,4 +1,5 @@
import React from "react";
+import { useTranslation } from "react-i18next";
import { KeyRound } from "lucide-react";
import { KeyResponse } from "@/components/key_team_helpers/key_list";
import { Button } from "@/components/ui/button";
@@ -11,19 +12,22 @@ interface AgentVirtualKeysProps {
}
const AgentVirtualKeys: React.FC = ({ keys, isLoading, onKeyClick }) => {
+ const { t } = useTranslation();
return (
-
Virtual Keys
+
{t("agents.virtualKeys.title")}
{isLoading ? (
-
Loading keys...
+
{t("agents.virtualKeys.loading")}
) : keys.length === 0 ? (
-
No virtual key assigned to this agent.
+
{t("agents.virtualKeys.empty")}
) : (
{keys.map((key) => (
-
{key.key_alias || "Unnamed key"}
+
+ {key.key_alias || t("agents.virtualKeys.unnamed")}
+
{key.key_name &&
{key.key_name} }
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/dynamic_agent_form_fields.tsx b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/dynamic_agent_form_fields.tsx
index 85ffbffeab1..4ec37821989 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/dynamic_agent_form_fields.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/dynamic_agent_form_fields.tsx
@@ -1,10 +1,13 @@
import React from "react";
+import type { TFunction } from "i18next";
+import { useTranslation } from "react-i18next";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import { FieldGroup } from "@/components/ui/field";
import { AgentCreateInfo, AgentCredentialFieldMetadata } from "@/components/networking";
import { PasswordInput } from "@/components/shared/PasswordInput";
+import i18n from "@/i18n/i18n";
import { AGENT_FORM_CONFIG, applyKillSwitchToPayload } from "./agent_config";
import CostConfigFields, { COST_FIELD_NAMES } from "./cost_config_fields";
import KillSwitchFormFields from "./KillSwitchFormFields";
@@ -32,12 +35,13 @@ export const unmountedDynamicFieldNames = (mountedPanels: readonly string[]): re
// validation rather than throwing during render and taking the whole form down with it.
const buildValidationPatternRule = (
field: AgentCredentialFieldMetadata,
+ t: TFunction,
): { value: RegExp; message: string } | undefined => {
if (!field.validation_pattern) return undefined;
try {
return {
value: new RegExp(field.validation_pattern),
- message: field.validation_message || `${field.label} looks incomplete or malformed`,
+ message: field.validation_message || t("agents.dynamicFields.errors.malformed", { field: field.label }),
};
} catch {
return undefined;
@@ -45,14 +49,15 @@ const buildValidationPatternRule = (
};
const CredentialField = ({ field }: { field: AgentCredentialFieldMetadata }) => {
- const patternRule = buildValidationPatternRule(field);
+ const { t } = useTranslation();
+ const patternRule = buildValidationPatternRule(field, t);
return (
@@ -108,14 +113,17 @@ const DynamicAgentFormFields: React.FC = ({ agentTy
{({ value, onChange, ref, ...control }) => (
@@ -124,14 +132,17 @@ const DynamicAgentFormFields: React.FC = ({ agentTy
{({ value, onChange, ref, ...control }) => (
@@ -144,10 +155,18 @@ const DynamicAgentFormFields: React.FC = ({ agentTy
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/kill_switch_config.ts b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/kill_switch_config.ts
index d59808bc1b3..c14b2b68d2b 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/kill_switch_config.ts
+++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/kill_switch_config.ts
@@ -1,3 +1,5 @@
+import type { ParseKeys, TFunction } from "i18next";
+import i18n from "@/i18n/i18n";
import type { components } from "@/lib/http/schema";
export type KillSwitchConfig = components["schemas"]["AgentKillSwitchConfig"];
@@ -6,11 +8,11 @@ export type KillSwitchMethod = NonNullable;
export type KillSwitchAuthType = KillSwitchAuth["type"] | "none";
export const KILL_SWITCH_METHODS: readonly KillSwitchMethod[] = ["POST", "PUT", "PATCH", "DELETE", "GET"];
-export const KILL_SWITCH_AUTH_TYPES: readonly { value: KillSwitchAuthType; label: string }[] = [
- { value: "none", label: "None" },
- { value: "bearer", label: "Bearer token" },
- { value: "api_key", label: "API key header" },
- { value: "basic", label: "Basic auth" },
+export const KILL_SWITCH_AUTH_TYPES: readonly { value: KillSwitchAuthType; labelKey: ParseKeys }[] = [
+ { value: "none", labelKey: "agents.killSwitchConfig.authTypes.none" },
+ { value: "bearer", labelKey: "agents.killSwitchConfig.authTypes.bearer" },
+ { value: "api_key", labelKey: "agents.killSwitchConfig.authTypes.apiKey" },
+ { value: "basic", labelKey: "agents.killSwitchConfig.authTypes.basic" },
];
export interface KeyValueFormValue {
@@ -61,12 +63,12 @@ export const parseKillSwitchBody = (text: string | undefined): Record;
};
-export const validateKillSwitchBody = (text: string | undefined): true | string => {
+export const validateKillSwitchBody = (text: string | undefined, t: TFunction = i18n.t.bind(i18n)): true | string => {
try {
parseKillSwitchBody(text);
return true;
} catch (error) {
- return error instanceof Error ? error.message : "Body must be valid JSON";
+ return error instanceof Error ? error.message : t("agents.killSwitchConfig.errors.invalidJson");
}
};
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/memory/_components/MemoryDetailDrawer.tsx b/ui/litellm-dashboard/src/app/(dashboard)/memory/_components/MemoryDetailDrawer.tsx
index 5c2fac2d5a9..ae88b42c906 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/memory/_components/MemoryDetailDrawer.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/memory/_components/MemoryDetailDrawer.tsx
@@ -1,6 +1,7 @@
"use client";
import React from "react";
+import { useTranslation } from "react-i18next";
import { MemoryRow } from "@/components/networking";
import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet";
@@ -25,6 +26,7 @@ function formatTimestamp(ts?: string): string {
}
export function MemoryDetailDrawer({ row, onClose }: MemoryDetailDrawerProps) {
+ const { t } = useTranslation();
return (
- {row ? {row.key} : "Memory"}
+
+ {row ? {row.key} : t("memory.detailDrawer.title")}
+
{row && (
- Memory ID
+ {t("memory.detailDrawer.memoryId")}
{row.memory_id}
- User ID
+ {t("memory.detailDrawer.userId")}
{row.user_id ?? "-"}
- Team ID
+ {t("memory.detailDrawer.teamId")}
{row.team_id ?? "-"}
-
Value
+
{t("memory.detailDrawer.value")}
{row.value}
{row.metadata !== undefined && row.metadata !== null && (
-
Metadata
+
{t("memory.detailDrawer.metadata")}
{JSON.stringify(row.metadata, null, 2)}
)}
- Created {formatTimestamp(row.created_at)}
- {row.created_by ? ` by ${row.created_by}` : ""}
+ {t("memory.detailDrawer.created", { time: formatTimestamp(row.created_at) })}
+ {row.created_by ? t("memory.detailDrawer.by", { name: row.created_by }) : ""}
·
- Updated {formatTimestamp(row.updated_at)}
- {row.updated_by ? ` by ${row.updated_by}` : ""}
+ {t("memory.detailDrawer.updated", { time: formatTimestamp(row.updated_at) })}
+ {row.updated_by ? t("memory.detailDrawer.by", { name: row.updated_by }) : ""}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/memory/_components/MemoryEditModal.tsx b/ui/litellm-dashboard/src/app/(dashboard)/memory/_components/MemoryEditModal.tsx
index dac209057f1..39d2f3b559d 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/memory/_components/MemoryEditModal.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/memory/_components/MemoryEditModal.tsx
@@ -2,6 +2,8 @@
import { CircleHelp } from "lucide-react";
import React, { useEffect, useState } from "react";
+import { Trans, useTranslation } from "react-i18next";
+import i18n from "@/i18n/i18n";
import { z } from "zod";
import type { MemoryRow } from "@/components/networking";
@@ -15,8 +17,8 @@ import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "
import { Button } from "@/components/ui/button";
const memorySchema = z.object({
- key: z.string().min(1, "Key is required"),
- value: z.string().min(1, "Value is required"),
+ key: z.string().min(1, { error: () => i18n.t("memory.editModal.errors.keyRequired") }),
+ value: z.string().min(1, { error: () => i18n.t("memory.editModal.errors.valueRequired") }),
metadata: z.string(),
});
@@ -43,6 +45,7 @@ interface MemoryEditModalProps {
}
export const MemoryEditModal: React.FC = ({ open, mode, initialRow, onClose, onSave }) => {
+ const { t } = useTranslation();
const form = useZodForm(memorySchema, { defaultValues: EMPTY_MEMORY, mode: "onChange" });
const [submitting, setSubmitting] = useState(false);
@@ -80,7 +83,11 @@ export const MemoryEditModal: React.FC = ({ open, mode, in
>
- {mode === "create" ? "Create memory" : `Edit ${initialRow?.key ?? ""}`}
+
+ {mode === "create"
+ ? t("memory.editModal.createTitle")
+ : t("memory.editModal.editTitle", { name: initialRow?.key ?? "" })}
+
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/memory/_components/MemoryTable.tsx b/ui/litellm-dashboard/src/app/(dashboard)/memory/_components/MemoryTable.tsx
index 3e37faafe15..6feb9795601 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/memory/_components/MemoryTable.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/memory/_components/MemoryTable.tsx
@@ -3,6 +3,7 @@
import { OnChangeFn, PaginationState } from "@tanstack/react-table";
import { Database } from "lucide-react";
import React, { useMemo } from "react";
+import { useTranslation } from "react-i18next";
import { MemoryRow } from "@/components/networking";
import { DataTable, DataTableToolbar } from "@/components/shared/DataTable";
@@ -26,18 +27,17 @@ interface MemoryTableProps {
}
function MemoryEmptyState({ hasActiveSearch }: { hasActiveSearch: boolean }) {
+ const { t } = useTranslation();
return (
- {hasActiveSearch ? "No matching memories" : "No memories stored yet"}
+ {hasActiveSearch ? t("memory.table.empty.titleSearch") : t("memory.table.empty.title")}
- {hasActiveSearch
- ? "No memories match your search."
- : "Memories your agents store under /v1/memory will appear here."}
+ {hasActiveSearch ? t("memory.table.empty.descriptionSearch") : t("memory.table.empty.description")}
);
@@ -58,10 +58,11 @@ export function MemoryTable({
onEditClick,
onDeleteClick,
}: MemoryTableProps) {
+ const { t } = useTranslation();
const columns = useMemo(() => {
- const columnDeps = { onViewClick, onEditClick, onDeleteClick };
+ const columnDeps = { onViewClick, onEditClick, onDeleteClick, t };
return getMemoryTableColumns(columnDeps);
- }, [onViewClick, onEditClick, onDeleteClick]);
+ }, [onViewClick, onEditClick, onDeleteClick, t]);
return (
}
size="compact"
toolbar={(table) => (
@@ -81,7 +82,7 @@ export function MemoryTable({
table={table}
searchValue={searchValue}
onSearchChange={onSearchChange}
- searchPlaceholder="Search by key prefix or memory ID…"
+ searchPlaceholder={t("memory.table.searchPlaceholder")}
onRefresh={onRefresh}
isRefreshing={isRefreshing}
showViewOptions={false}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/memory/_components/MemoryTableColumns.tsx b/ui/litellm-dashboard/src/app/(dashboard)/memory/_components/MemoryTableColumns.tsx
index 62b8ec624fa..4b43582abb2 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/memory/_components/MemoryTableColumns.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/memory/_components/MemoryTableColumns.tsx
@@ -1,7 +1,9 @@
"use client";
import { ColumnDef } from "@tanstack/react-table";
+import type { TFunction } from "i18next";
import { Eye, MoreHorizontal, Pencil, Trash2 } from "lucide-react";
+import { useTranslation } from "react-i18next";
import { MemoryRow } from "@/components/networking";
import { DateCell, IdCell, IdentityCell } from "@/components/shared/table_cells";
@@ -24,10 +26,11 @@ interface MemoryRowActionsProps {
}
function MemoryRowActions({ row, onViewClick, onEditClick, onDeleteClick }: MemoryRowActionsProps) {
+ const { t } = useTranslation();
return (
@@ -36,16 +39,16 @@ function MemoryRowActions({ row, onViewClick, onEditClick, onDeleteClick }: Memo
onViewClick(row)}>
- View
+ {t("memory.table.actions.view")}
onEditClick(row)}>
- Edit
+ {t("memory.table.actions.edit")}
onDeleteClick(row)}>
- Delete
+ {t("memory.table.actions.delete")}
@@ -56,18 +59,20 @@ export interface MemoryTableColumnsDeps {
onViewClick: (row: MemoryRow) => void;
onEditClick: (row: MemoryRow) => void;
onDeleteClick: (row: MemoryRow) => void;
+ t: TFunction;
}
export const getMemoryTableColumns = ({
onViewClick,
onEditClick,
onDeleteClick,
+ t,
}: MemoryTableColumnsDeps): ColumnDef[] => [
{
id: "memory_id",
accessorKey: "memory_id",
- meta: { title: "ID" },
- header: "ID",
+ meta: { title: t("memory.table.columns.id") },
+ header: t("memory.table.columns.id"),
size: 180,
enableSorting: false,
cell: ({ row }) => (
@@ -81,8 +86,8 @@ export const getMemoryTableColumns = ({
{
id: "key",
accessorKey: "key",
- meta: { title: "Name" },
- header: "Name",
+ meta: { title: t("memory.table.columns.name") },
+ header: t("memory.table.columns.name"),
size: 200,
enableSorting: false,
cell: ({ row }) => (
@@ -94,8 +99,8 @@ export const getMemoryTableColumns = ({
{
id: "value",
accessorKey: "value",
- meta: { title: "Preview" },
- header: "Preview",
+ meta: { title: t("memory.table.columns.preview") },
+ header: t("memory.table.columns.preview"),
enableSorting: false,
cell: ({ row }) => (
@@ -106,8 +111,8 @@ export const getMemoryTableColumns = ({
{
id: "user_id",
accessorKey: "user_id",
- meta: { title: "User ID" },
- header: "User ID",
+ meta: { title: t("memory.table.columns.userId") },
+ header: t("memory.table.columns.userId"),
size: 160,
enableSorting: false,
cell: ({ row }) => {
@@ -118,8 +123,8 @@ export const getMemoryTableColumns = ({
{
id: "team_id",
accessorKey: "team_id",
- meta: { title: "Team ID" },
- header: "Team ID",
+ meta: { title: t("memory.table.columns.teamId") },
+ header: t("memory.table.columns.teamId"),
size: 160,
enableSorting: false,
cell: ({ row }) => {
@@ -130,8 +135,8 @@ export const getMemoryTableColumns = ({
{
id: "updated_at",
accessorKey: "updated_at",
- meta: { title: "Updated" },
- header: "Updated",
+ meta: { title: t("memory.table.columns.updated") },
+ header: t("memory.table.columns.updated"),
size: 170,
enableSorting: false,
cell: ({ row }) => ,
@@ -139,7 +144,7 @@ export const getMemoryTableColumns = ({
{
id: "actions",
meta: { className: "text-right", headerClassName: "text-right" },
- header: () => Actions ,
+ header: () => {t("memory.table.columns.actions")} ,
size: 64,
enableSorting: false,
enableHiding: false,
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/memory/_components/MemoryView.tsx b/ui/litellm-dashboard/src/app/(dashboard)/memory/_components/MemoryView.tsx
index 58d4e42aa96..cf5191da864 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/memory/_components/MemoryView.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/memory/_components/MemoryView.tsx
@@ -5,6 +5,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import type { PaginationState } from "@tanstack/react-table";
import { Plus } from "lucide-react";
import React, { useCallback, useMemo, useState } from "react";
+import { Trans, useTranslation } from "react-i18next";
import { MemoryRow, createMemory, deleteMemory, fetchMemoryList, updateMemory } from "@/components/networking";
import DeleteResourceModal from "@/components/common_components/DeleteResourceModal";
@@ -25,6 +26,7 @@ interface MemoryViewProps {
const DEFAULT_PAGE_SIZE = 50;
export const MemoryView: React.FC = ({ accessToken }) => {
+ const { t } = useTranslation();
const [searchInput, setSearchInput] = useState("");
const [debouncedSearch] = useDebouncedValue(searchInput, { wait: DEBOUNCE_WAIT_MS });
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: DEFAULT_PAGE_SIZE });
@@ -72,11 +74,11 @@ export const MemoryView: React.FC = ({ accessToken }) => {
return createMemory(accessToken, args);
},
onSuccess: (row) => {
- toast.success(`Created ${row.key}`);
+ toast.success(t("memory.view.toasts.created", { name: row.key }));
invalidateList();
},
onError: (err: Error) => {
- toast.error(`Save failed: ${err.message}`);
+ toast.error(t("memory.view.toasts.saveFailed", { error: err.message }));
},
});
@@ -87,11 +89,11 @@ export const MemoryView: React.FC = ({ accessToken }) => {
return updateMemory(accessToken, key, payload);
},
onSuccess: (row) => {
- toast.success(`Updated ${row.key}`);
+ toast.success(t("memory.view.toasts.updated", { name: row.key }));
invalidateList();
},
onError: (err: Error) => {
- toast.error(`Save failed: ${err.message}`);
+ toast.error(t("memory.view.toasts.saveFailed", { error: err.message }));
},
});
@@ -101,11 +103,11 @@ export const MemoryView: React.FC = ({ accessToken }) => {
return deleteMemory(accessToken, key).then(() => key);
},
onSuccess: (key) => {
- toast.success(`Deleted ${key}`);
+ toast.success(t("memory.view.toasts.deleted", { name: key }));
invalidateList();
},
onError: (err: Error) => {
- toast.error(`Delete failed: ${err.message}`);
+ toast.error(t("memory.view.toasts.deleteFailed", { error: err.message }));
},
});
@@ -147,7 +149,7 @@ export const MemoryView: React.FC = ({ accessToken }) => {
try {
metadataPayload = JSON.parse(metadataText);
} catch {
- toast.error("Metadata must be valid JSON (or leave empty).");
+ toast.error(t("memory.view.toasts.invalidMetadata"));
return false;
}
}
@@ -178,18 +180,23 @@ export const MemoryView: React.FC = ({ accessToken }) => {
-
Memory
+
{t("memory.view.title")}
- Inspect what your agents have stored under{" "}
-
- /v1/memory
-
- . Scoped to memories visible to your user / team (admins see all).
+
+ /v1/memory
+
+ ),
+ }}
+ />
setIsCreateOpen(true)}>
- New memory
+ {t("memory.view.newMemory")}
@@ -228,16 +235,16 @@ export const MemoryView: React.FC
= ({ accessToken }) => {
{/* Delete confirmation modal */}
;
+ return ;
}
return ;
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/PromptTable.tsx b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/PromptTable.tsx
index e810c3622d1..9beb52f7b40 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/PromptTable.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/PromptTable.tsx
@@ -3,6 +3,7 @@
import { SortingState } from "@tanstack/react-table";
import { Inbox } from "lucide-react";
import React, { useEffect, useMemo, useState } from "react";
+import { useTranslation } from "react-i18next";
import { DataTable } from "@/components/shared/DataTable";
import { modelHubCall, PromptSpec } from "@/components/networking";
@@ -22,13 +23,14 @@ interface PromptTableProps {
const DEFAULT_SORTING: SortingState = [{ id: "created_at", desc: true }];
function EmptyState() {
+ const { t } = useTranslation();
return (
-
No prompts yet
-
Add a prompt to start managing reusable templates.
+
{t("prompts.table.empty.title")}
+
{t("prompts.table.empty.description")}
);
}
@@ -41,6 +43,7 @@ const PromptTable: React.FC = ({
accessToken,
isAdmin,
}) => {
+ const { t } = useTranslation();
const [sorting, setSorting] = useState(DEFAULT_SORTING);
const [modelHubData, setModelHubData] = useState>(new Map());
@@ -66,8 +69,8 @@ const PromptTable: React.FC = ({
}, [accessToken]);
const columns = useMemo(
- () => getPromptTableColumns({ modelHubData, isAdmin, onPromptClick, onDeleteClick }),
- [modelHubData, isAdmin, onPromptClick, onDeleteClick],
+ () => getPromptTableColumns({ modelHubData, isAdmin, onPromptClick, onDeleteClick, t }),
+ [modelHubData, isAdmin, onPromptClick, onDeleteClick, t],
);
return (
@@ -82,7 +85,7 @@ const PromptTable: React.FC = ({
sorting={sorting}
onSortingChange={setSorting}
isLoading={isLoading}
- loadingMessage="Loading prompts…"
+ loadingMessage={t("prompts.table.loading")}
noDataMessage={ }
size="compact"
/>
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/PromptTableColumns.tsx b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/PromptTableColumns.tsx
index db0392bbd6e..a7e1cfcaccb 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/PromptTableColumns.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/PromptTableColumns.tsx
@@ -1,7 +1,9 @@
"use client";
import { ColumnDef } from "@tanstack/react-table";
+import type { TFunction } from "i18next";
import { Copy, MoreHorizontal, Trash2 } from "lucide-react";
+import { useTranslation } from "react-i18next";
import { DataTableSortHeader } from "@/components/shared/DataTable";
import { CellTooltip, DateCell, IdentityCell, StatusBadge, StatusTone } from "@/components/shared/table_cells";
@@ -69,10 +71,11 @@ interface PromptRowActionsProps {
}
function PromptRowActions({ prompt, isAdmin, onDeleteClick }: PromptRowActionsProps) {
+ const { t } = useTranslation();
return (
@@ -81,10 +84,10 @@ function PromptRowActions({ prompt, isAdmin, onDeleteClick }: PromptRowActionsPr
void copyToClipboard(prompt.prompt_id, "Prompt ID copied")}
+ onClick={() => void copyToClipboard(prompt.prompt_id, t("prompts.table.rowActions.idCopied"))}
>
- Copy prompt ID
+ {t("prompts.table.rowActions.copyId")}
{isAdmin && (
<>
@@ -95,13 +98,13 @@ function PromptRowActions({ prompt, isAdmin, onDeleteClick }: PromptRowActionsPr
onClick={() =>
onDeleteClick?.(
prompt.prompt_id,
- prompt.prompt_id || "Unknown Prompt",
+ prompt.prompt_id || t("prompts.table.rowActions.unknownPrompt"),
prompt.environment || "development",
)
}
>
- Delete
+ {t("prompts.table.rowActions.delete")}
>
)}
@@ -115,6 +118,7 @@ interface PromptTableColumnsDeps {
isAdmin: boolean;
onPromptClick?: (id: string, environment: string) => void;
onDeleteClick?: (id: string, name: string, environment: string) => void;
+ t: TFunction;
}
export const getPromptTableColumns = ({
@@ -122,12 +126,13 @@ export const getPromptTableColumns = ({
isAdmin,
onPromptClick,
onDeleteClick,
+ t,
}: PromptTableColumnsDeps): ColumnDef[] => [
{
id: "prompt_id",
accessorKey: "prompt_id",
- meta: { title: "Prompt ID" },
- header: ({ column }) => ,
+ meta: { title: t("prompts.table.columns.promptId") },
+ header: ({ column }) => ,
size: 220,
enableSorting: true,
cell: ({ row }) => (
@@ -145,8 +150,8 @@ export const getPromptTableColumns = ({
},
{
id: "model",
- meta: { title: "Model" },
- header: "Model",
+ meta: { title: t("prompts.table.columns.model") },
+ header: t("prompts.table.columns.model"),
size: 200,
enableSorting: false,
cell: ({ row }) => ,
@@ -155,8 +160,8 @@ export const getPromptTableColumns = ({
id: "created_at",
accessorKey: "created_at",
sortingFn: "datetime",
- meta: { title: "Created At" },
- header: ({ column }) => ,
+ meta: { title: t("prompts.table.columns.createdAt") },
+ header: ({ column }) => ,
size: 160,
enableSorting: true,
cell: ({ row }) => ,
@@ -165,8 +170,8 @@ export const getPromptTableColumns = ({
id: "updated_at",
accessorKey: "updated_at",
sortingFn: "datetime",
- meta: { title: "Updated At" },
- header: ({ column }) => ,
+ meta: { title: t("prompts.table.columns.updatedAt") },
+ header: ({ column }) => ,
size: 160,
enableSorting: true,
cell: ({ row }) => ,
@@ -174,8 +179,8 @@ export const getPromptTableColumns = ({
{
id: "environment",
accessorKey: "environment",
- meta: { title: "Environment", skeleton: "badge" },
- header: "Environment",
+ meta: { title: t("prompts.table.columns.environment"), skeleton: "badge" },
+ header: t("prompts.table.columns.environment"),
size: 130,
enableSorting: false,
cell: ({ row }) => {
@@ -186,8 +191,8 @@ export const getPromptTableColumns = ({
{
id: "created_by",
accessorKey: "created_by",
- meta: { title: "Created By" },
- header: "Created By",
+ meta: { title: t("prompts.table.columns.createdBy") },
+ header: t("prompts.table.columns.createdBy"),
size: 160,
enableSorting: false,
cell: ({ row }) => {
@@ -209,8 +214,8 @@ export const getPromptTableColumns = ({
{
id: "prompt_type",
accessorKey: "prompt_info.prompt_type",
- meta: { title: "Type" },
- header: "Type",
+ meta: { title: t("prompts.table.columns.type") },
+ header: t("prompts.table.columns.type"),
size: 140,
enableSorting: false,
cell: ({ row }) => {
@@ -225,7 +230,7 @@ export const getPromptTableColumns = ({
{
id: "actions",
meta: { className: "text-right", headerClassName: "text-right" },
- header: () => Actions ,
+ header: () => {t("prompts.table.columns.actions")} ,
size: 64,
enableSorting: false,
enableHiding: false,
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/add_prompt_form.tsx b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/add_prompt_form.tsx
index b12f5fdef4d..954d897de9c 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/add_prompt_form.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/add_prompt_form.tsx
@@ -1,8 +1,10 @@
import React, { useRef, useState } from "react";
+import { useTranslation } from "react-i18next";
import { Upload as UploadIcon, X } from "lucide-react";
import { z } from "zod";
import { convertPromptFileToJson, createPromptCall } from "@/components/networking";
import { toast } from "@/lib/toast";
+import i18n from "@/i18n/i18n";
import { Field, FieldDescription, FieldGroup, FieldSeparator, FieldTitle } from "@/components/ui/field";
import { FormField } from "@/components/shared/form/FormField";
import { Button } from "@/components/ui/button";
@@ -36,8 +38,8 @@ const PROMPT_INTEGRATION_OPTIONS = [{ label: "dotprompt", value: "dotprompt" }];
const addPromptSchema = z.object({
prompt_id: z
.string()
- .min(1, "Please enter a prompt ID")
- .regex(/^[a-zA-Z0-9_-]+$/, "Prompt ID can only contain letters, numbers, underscores, and hyphens"),
+ .min(1, { error: () => i18n.t("prompts.addForm.errors.idRequired") })
+ .regex(/^[a-zA-Z0-9_-]+$/, { error: () => i18n.t("prompts.addForm.errors.idInvalid") }),
prompt_integration: z.string(),
});
@@ -46,6 +48,7 @@ type AddPromptFormValues = z.infer;
const EMPTY_VALUES: AddPromptFormValues = { prompt_id: "", prompt_integration: "dotprompt" };
const AddPromptForm: React.FC = ({ visible, onClose, accessToken, onSuccess }) => {
+ const { t } = useTranslation();
const form = useZodForm(addPromptSchema, { defaultValues: EMPTY_VALUES });
const [loading, setLoading] = useState(false);
const [selectedFile, setSelectedFile] = useState(null);
@@ -70,7 +73,7 @@ const AddPromptForm: React.FC = ({ visible, onClose, accessT
const picked = event.target.files?.[0];
if (!picked) return;
if (!picked.name.endsWith(".prompt")) {
- toast.fromError("Please upload a .prompt file");
+ toast.fromError(t("prompts.addForm.toasts.fileRequired"));
clearSelectedFile();
return;
}
@@ -104,21 +107,21 @@ const AddPromptForm: React.FC = ({ visible, onClose, accessT
};
} catch (conversionError) {
console.error("Error converting prompt file:", conversionError);
- toast.fromError("Failed to convert prompt file to JSON");
+ toast.fromError(t("prompts.addForm.toasts.convertFailed"));
return null;
}
};
const handleSubmit = async (values: AddPromptFormValues) => {
if (!accessToken) {
- toast.fromError("Access token is required");
+ toast.fromError(t("prompts.addForm.toasts.tokenRequired"));
return;
}
const isDotprompt = promptIntegration === "dotprompt";
if (isDotprompt && !selectedFile) {
- toast.fromError("Please upload a .prompt file");
+ toast.fromError(t("prompts.addForm.toasts.fileRequired"));
return;
}
@@ -134,12 +137,12 @@ const AddPromptForm: React.FC = ({ visible, onClose, accessT
try {
await createPromptCall(accessToken, promptData);
- toast.success("Prompt created successfully!");
+ toast.success(t("prompts.addForm.toasts.created"));
handleCancel();
onSuccess();
} catch (createError) {
console.error("Error creating prompt:", createError);
- toast.fromError("Failed to create prompt");
+ toast.fromError(t("prompts.addForm.toasts.createFailed"));
} finally {
setLoading(false);
}
@@ -149,17 +152,17 @@ const AddPromptForm: React.FC = ({ visible, onClose, accessT
!open && handleCancel()}>
- Add New Prompt
+ {t("prompts.addForm.title")}
event.preventDefault()} noValidate>
-
+
{({ ref, ...field }) => (
-
+
)}
-
+
{({ id, value, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy }) => (
@@ -180,25 +183,25 @@ const AddPromptForm: React.FC = ({ visible, onClose, accessT
<>
- Prompt File
+ {t("prompts.addForm.fields.file")}
fileInputRef.current?.click()}>
- Select .prompt File
+ {t("prompts.addForm.fields.selectFile")}
{selectedFile && (
-
Selected: {selectedFile.name}
+
{t("prompts.addForm.fields.selected", { name: selectedFile.name })}
@@ -206,7 +209,7 @@ const AddPromptForm: React.FC = ({ visible, onClose, accessT
)}
- Upload a .prompt file that follows the Dotprompt specification
+ {t("prompts.addForm.fields.fileDescription")}
>
)}
@@ -214,11 +217,11 @@ const AddPromptForm: React.FC = ({ visible, onClose, accessT
- Cancel
+ {t("prompts.addForm.cancel")}
void form.handleSubmit(handleSubmit)()}>
{loading && }
- Create Prompt
+ {t("prompts.addForm.submit")}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/index.tsx b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/index.tsx
index 2d8d905c480..b6e795f7a47 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/index.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/index.tsx
@@ -1,4 +1,6 @@
import React, { useState, useEffect } from "react";
+import type { ParseKeys } from "i18next";
+import { useTranslation } from "react-i18next";
import { Plus, Upload } from "lucide-react";
import { getPromptsList, PromptSpec, ListPromptsResponse, deletePromptCall } from "@/components/networking";
@@ -20,16 +22,16 @@ import {
} from "@/components/ui/alert-dialog";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
-const ALL_ENVIRONMENTS_LABEL = "All Environments";
+const ALL_ENVIRONMENTS_LABEL_KEY: ParseKeys = "prompts.panel.environments.all";
-const ENVIRONMENT_OPTIONS = [
- { label: "Development", value: "development" },
- { label: "Staging", value: "staging" },
- { label: "Production", value: "production" },
+const ENVIRONMENT_OPTIONS: { labelKey: ParseKeys; value: string }[] = [
+ { labelKey: "prompts.panel.environments.development", value: "development" },
+ { labelKey: "prompts.panel.environments.staging", value: "staging" },
+ { labelKey: "prompts.panel.environments.production", value: "production" },
];
// SelectValue falls back to the raw value unless the root can map it to a label.
-const ENVIRONMENT_ITEMS = [{ label: ALL_ENVIRONMENTS_LABEL, value: null }, ...ENVIRONMENT_OPTIONS];
+const ENVIRONMENT_ITEMS = [{ labelKey: ALL_ENVIRONMENTS_LABEL_KEY, value: null }, ...ENVIRONMENT_OPTIONS];
interface PromptsProps {
accessToken: string | null;
@@ -37,6 +39,7 @@ interface PromptsProps {
}
const PromptsPanel: React.FC = ({ accessToken, userRole }) => {
+ const { t } = useTranslation();
const [promptsList, setPromptsList] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [selectedEnvironment, setSelectedEnvironment] = useState(undefined);
@@ -123,11 +126,13 @@ const PromptsPanel: React.FC = ({ accessToken, userRole }) => {
setIsDeleting(true);
try {
await deletePromptCall(accessToken, promptToDelete.id, promptToDelete.environment);
- toast.success(`Prompt "${promptToDelete.name}" deleted successfully from ${promptToDelete.environment}`);
+ toast.success(
+ t("prompts.panel.toasts.deleted", { name: promptToDelete.name, environment: promptToDelete.environment }),
+ );
fetchPrompts(); // Refresh the list
} catch (error) {
console.error("Error deleting prompt:", error);
- toast.fromError("Failed to delete prompt");
+ toast.fromError(t("prompts.panel.toasts.deleteFailed"));
} finally {
setIsDeleting(false);
setPromptToDelete(null);
@@ -165,28 +170,28 @@ const PromptsPanel: React.FC = ({ accessToken, userRole }) => {
<>
- Add New Prompt
+ {t("prompts.panel.addNew")}
- Upload .prompt File
+ {t("prompts.panel.uploadFile")}
>
)}
({ label: t(item.labelKey), value: item.value }))}
value={selectedEnvironment ?? null}
onValueChange={(value) => setSelectedEnvironment((value as string | null) ?? undefined)}
>
-
+
- {ALL_ENVIRONMENTS_LABEL}
+ {t(ALL_ENVIRONMENTS_LABEL_KEY)}
{ENVIRONMENT_OPTIONS.map((option) => (
- {option.label}
+ {t(option.labelKey)}
))}
@@ -220,16 +225,18 @@ const PromptsPanel: React.FC = ({ accessToken, userRole }) => {
>
- Delete Prompt
+ {t("prompts.panel.deleteDialog.title")}
- Are you sure you want to delete the {promptToDelete.environment} copy of prompt: {promptToDelete.name}?
- This action cannot be undone.
+ {t("prompts.panel.deleteDialog.description", {
+ environment: promptToDelete.environment,
+ name: promptToDelete.name,
+ })}
- Cancel
+ {t("prompts.panel.deleteDialog.cancel")}
- Delete
+ {t("prompts.panel.deleteDialog.confirm")}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/DeveloperMessageCard.tsx b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/DeveloperMessageCard.tsx
index 27c8907012c..ce417d00c62 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/DeveloperMessageCard.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/DeveloperMessageCard.tsx
@@ -1,4 +1,5 @@
import React from "react";
+import { useTranslation } from "react-i18next";
import { Card, CardContent } from "@/components/ui/card";
import VariableTextArea from "../variable_textarea";
@@ -8,16 +9,17 @@ interface DeveloperMessageCardProps {
}
const DeveloperMessageCard: React.FC = ({ value, onChange }) => {
+ const { t } = useTranslation();
return (
- Developer message
- Optional system instructions for the model
+ {t("prompts.developerMessage.title")}
+ {t("prompts.developerMessage.description")}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/DotpromptViewTab.tsx b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/DotpromptViewTab.tsx
index 1be456c9b09..b1e1f78bbd3 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/DotpromptViewTab.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/DotpromptViewTab.tsx
@@ -1,4 +1,5 @@
import React from "react";
+import { useTranslation } from "react-i18next";
import { PromptType } from "./types";
import { convertToDotPrompt } from "./utils";
@@ -7,13 +8,14 @@ interface DotpromptViewTabProps {
}
const DotpromptViewTab: React.FC = ({ prompt }) => {
+ const { t } = useTranslation();
const dotpromptContent = convertToDotPrompt(prompt);
return (
-
Generated .prompt file
-
This is the dotprompt format that will be saved to the database
+
{t("prompts.dotpromptView.title")}
+
{t("prompts.dotpromptView.description")}
{dotpromptContent}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/ModelConfigCard.tsx b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/ModelConfigCard.tsx
index e9b526c9703..83264e86d1a 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/ModelConfigCard.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/ModelConfigCard.tsx
@@ -3,6 +3,7 @@ import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { SettingsIcon } from "lucide-react";
+import { useTranslation } from "react-i18next";
import ModelSelector from "@/components/common_components/ModelSelector";
interface ModelConfigCardProps {
@@ -24,6 +25,7 @@ const ModelConfigCard: React.FC
= ({
onTemperatureChange,
onMaxTokensChange,
}) => {
+ const { t } = useTranslation();
const [showConfig, setShowConfig] = useState(false);
return (
@@ -34,19 +36,19 @@ const ModelConfigCard: React.FC = ({
setShowConfig(!showConfig)} className="gap-2">
- Parameters
+ {t("prompts.modelConfig.parameters")}
- Model Parameters
+ {t("prompts.modelConfig.title")}
- Temperature
+ {t("prompts.modelConfig.temperature")}
= ({
- Max Tokens
+ {t("prompts.modelConfig.maxTokens")}
= ({
environment,
proxySettings,
}) => {
+ const { t } = useTranslation();
const syntaxTheme = useSyntaxTheme(coy);
const [isModalVisible, setIsModalVisible] = useState(false);
const [selectedLanguage, setSelectedLanguage] = useState<"curl" | "python" | "javascript">("curl");
@@ -252,18 +254,18 @@ main();`;
<>
- Get Code
+ {t("prompts.codeSnippets.trigger")}
!open && handleCancel()}>
- Generated Code
+ {t("prompts.codeSnippets.title")}
- Language
+ {t("prompts.codeSnippets.language")}
{
navigator.clipboard.writeText(generatedCode);
- toast.success("Copied to clipboard!");
+ toast.success(t("prompts.codeSnippets.copied"));
}}
>
- Copy to Clipboard
+ {t("prompts.codeSnippets.copy")}
setSelectedTab(String(value))}>
-
- Basic
- With Messages
- With Version
+
+ {t("prompts.codeSnippets.tabs.basic")}
+ {t("prompts.codeSnippets.tabs.messages")}
+ {t("prompts.codeSnippets.tabs.version")}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/PromptEditorHeader.tsx b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/PromptEditorHeader.tsx
index 0f001979c28..c0e28631ade 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/PromptEditorHeader.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/PromptEditorHeader.tsx
@@ -1,5 +1,6 @@
import React from "react";
import { ArrowLeftIcon, SaveIcon, ClockIcon, LoaderCircleIcon } from "lucide-react";
+import { useTranslation } from "react-i18next";
import PromptCodeSnippets from "./PromptCodeSnippets";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
@@ -7,9 +8,9 @@ import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
const ENVIRONMENT_ITEMS = [
- { value: "development", label: "Development" },
- { value: "staging", label: "Staging" },
- { value: "production", label: "Production" },
+ { value: "development", labelKey: "prompts.editorHeader.environments.development" },
+ { value: "staging", labelKey: "prompts.editorHeader.environments.staging" },
+ { value: "production", labelKey: "prompts.editorHeader.environments.production" },
] as const;
interface PromptEditorHeaderProps {
@@ -48,15 +49,16 @@ const PromptEditorHeader: React.FC
= ({
environment,
onEnvironmentChange,
}) => {
+ const { t } = useTranslation();
return (
= ({
{editMode && onShowHistory && (
- History
+ {t("prompts.editorHeader.history")}
)}
{isSaving ? : }
- {editMode ? "Update" : "Save"}
+ {editMode ? t("prompts.editorHeader.update") : t("prompts.editorHeader.save")}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/PromptMessagesCard.tsx b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/PromptMessagesCard.tsx
index e69cae030c0..04722d372f4 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/PromptMessagesCard.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/PromptMessagesCard.tsx
@@ -1,4 +1,5 @@
import React, { useState } from "react";
+import { Trans, useTranslation } from "react-i18next";
import { PlusIcon, TrashIcon, GripVerticalIcon } from "lucide-react";
import VariableTextArea from "../variable_textarea";
import { Message } from "./types";
@@ -7,9 +8,9 @@ import { Card } from "@/components/ui/card";
import { Select as ShadcnSelect, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
const ROLE_ITEMS = [
- { value: "user", label: "User" },
- { value: "assistant", label: "Assistant" },
- { value: "system", label: "System" },
+ { value: "user", labelKey: "prompts.messagesCard.roles.user" },
+ { value: "assistant", labelKey: "prompts.messagesCard.roles.assistant" },
+ { value: "system", labelKey: "prompts.messagesCard.roles.system" },
] as const;
interface PromptMessagesCardProps {
@@ -27,6 +28,7 @@ const PromptMessagesCard: React.FC = ({
onRemoveMessage,
onMoveMessage,
}) => {
+ const { t } = useTranslation();
const [draggedIndex, setDraggedIndex] = useState(null);
const [dragOverIndex, setDragOverIndex] = useState(null);
@@ -56,9 +58,15 @@ const PromptMessagesCard: React.FC = ({
return (
-
Prompt messages
+
{t("prompts.messagesCard.title")}
- Use {"{{variable}}"} syntax for template variables
+ ,
+ variable: <>{"{{variable}}"}>,
+ }}
+ />
@@ -76,21 +84,21 @@ const PromptMessagesCard: React.FC
= ({
>
({ value: item.value, label: t(item.labelKey) }))}
value={message.role}
onValueChange={(value) => onUpdateMessage(index, "role", String(value))}
>
{ROLE_ITEMS.map((item) => (
- {item.label}
+ {t(item.labelKey)}
))}
@@ -100,7 +108,7 @@ const PromptMessagesCard: React.FC = ({
onRemoveMessage(index)}
>
@@ -116,7 +124,7 @@ const PromptMessagesCard: React.FC = ({
value={message.content}
onChange={(value) => onUpdateMessage(index, "content", value)}
rows={3}
- placeholder="Enter prompt content..."
+ placeholder={t("prompts.messagesCard.contentPlaceholder")}
/>
@@ -124,7 +132,7 @@ const PromptMessagesCard: React.FC = ({
- Add message
+ {t("prompts.messagesCard.add")}
);
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/PublishModal.tsx b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/PublishModal.tsx
index e37efd3464a..8aeb52efab1 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/PublishModal.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/PublishModal.tsx
@@ -1,5 +1,6 @@
import React from "react";
import { LoaderCircleIcon } from "lucide-react";
+import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -28,36 +29,35 @@ const PublishModal: React.FC = ({
onPublish,
onCancel,
}) => {
+ const { t } = useTranslation();
return (
!open && onCancel()}>
- Publish Prompt
- Published prompts are versioned and can be used in API calls.
+ {t("prompts.publishModal.title")}
+ {t("prompts.publishModal.description")}
- Name
+ {t("prompts.publishModal.nameLabel")}
onNameChange(e.target.value)}
- placeholder="Enter prompt name"
+ placeholder={t("prompts.publishModal.namePlaceholder")}
onKeyDown={(event) => event.key === "Enter" && onPublish()}
autoFocus
/>
-
- Published prompts can be used in API calls and are versioned for easy tracking.
-
+
{t("prompts.publishModal.hint")}
- Cancel
+ {t("prompts.publishModal.cancel")}
{isSaving && }
- Publish
+ {t("prompts.publishModal.publish")}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/ToolsCard.tsx b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/ToolsCard.tsx
index c8c8f3e68fa..17f22abfe13 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/ToolsCard.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/ToolsCard.tsx
@@ -1,5 +1,6 @@
import React from "react";
import { PlusIcon, TrashIcon } from "lucide-react";
+import { useTranslation } from "react-i18next";
import { Tool } from "./types";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
@@ -12,17 +13,18 @@ interface ToolsCardProps {
}
const ToolsCard: React.FC = ({ tools, onAddTool, onEditTool, onRemoveTool }) => {
+ const { t } = useTranslation();
return (
-
Tools
+
{t("prompts.toolsCard.title")}
- Add
+ {t("prompts.toolsCard.add")}
{tools.length === 0 ? (
- No tools added
+ {t("prompts.toolsCard.empty")}
) : (
{tools.map((tool, index) => (
@@ -33,12 +35,12 @@ const ToolsCard: React.FC = ({ tools, onAddTool, onEditTool, onR
onEditTool(index)}>
- Edit
+ {t("prompts.toolsCard.edit")}
onRemoveTool(index)}
>
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/VersionHistorySidePanel.tsx b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/VersionHistorySidePanel.tsx
index 64bc7755694..9449317d5bf 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/VersionHistorySidePanel.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/VersionHistorySidePanel.tsx
@@ -4,6 +4,7 @@ import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { XIcon } from "lucide-react";
+import { useTranslation } from "react-i18next";
interface VersionHistorySidePanelProps {
isOpen: boolean;
@@ -22,6 +23,7 @@ const VersionHistorySidePanel: React.FC = ({
activeVersionId,
onSelectVersion,
}) => {
+ const { t } = useTranslation();
const [versions, setVersions] = useState([]);
const [loading, setLoading] = useState(false);
@@ -90,23 +92,23 @@ const VersionHistorySidePanel: React.FC = ({
>
- Close
+ {t("prompts.versionHistory.close")}
- Version History
+ {t("prompts.versionHistory.title")}
{loading ? (
-
+
) : versions.length === 0 ? (
-
No version history available.
+
{t("prompts.versionHistory.empty")}
) : (
{versions.map((item, index) => {
@@ -138,15 +140,17 @@ const VersionHistorySidePanel: React.FC
= ({
{getVersionNumber(item)}
- {index === 0 && Latest }
+ {index === 0 && {t("prompts.versionHistory.latest")} }
- {isSelected &&
Active }
+ {isSelected &&
{t("prompts.versionHistory.active")} }
{formatDate(item.created_at)}
- {item.prompt_info?.prompt_type === "db" ? "Saved to Database" : "Config Prompt"}
+ {item.prompt_info?.prompt_type === "db"
+ ? t("prompts.versionHistory.savedToDatabase")
+ : t("prompts.versionHistory.configPrompt")}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/EmptyState.tsx b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/EmptyState.tsx
index 5b381427988..962a946f483 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/EmptyState.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/EmptyState.tsx
@@ -1,18 +1,20 @@
import React from "react";
import { Bot } from "lucide-react";
+import { useTranslation } from "react-i18next";
interface EmptyStateProps {
hasVariables: boolean;
}
const EmptyState: React.FC = ({ hasVariables }) => {
+ const { t } = useTranslation();
return (
{hasVariables
- ? "Fill in the variables above, then type a message to start testing"
- : "Type a message below to start testing your prompt"}
+ ? t("prompts.conversation.emptyState.withVariables")
+ : t("prompts.conversation.emptyState.withoutVariables")}
);
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/MessageInput.tsx b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/MessageInput.tsx
index d2c47a2dd4d..35943d6283e 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/MessageInput.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/MessageInput.tsx
@@ -1,5 +1,6 @@
import React from "react";
import { ArrowUp } from "lucide-react";
+import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
@@ -22,6 +23,7 @@ const MessageInput: React.FC = ({
onKeyDown,
onCancel,
}) => {
+ const { t } = useTranslation();
return (
@@ -29,7 +31,7 @@ const MessageInput: React.FC
= ({
value={inputMessage}
onChange={(e) => onInputChange(e.target.value)}
onKeyDown={onKeyDown}
- placeholder="Type your message... (Shift+Enter for new line)"
+ placeholder={t("prompts.conversation.input.placeholder")}
disabled={isLoading}
rows={1}
className="field-sizing-content max-h-24 min-h-8 flex-1 resize-none overflow-y-auto border-0 bg-transparent px-0 py-1 text-sm shadow-none focus-visible:ring-0"
@@ -41,7 +43,7 @@ const MessageInput: React.FC = ({
onClick={onSend}
disabled={isDisabled}
className="ml-2 shrink-0 rounded-full"
- aria-label="Send message"
+ aria-label={t("prompts.conversation.input.send")}
>
@@ -49,7 +51,7 @@ const MessageInput: React.FC = ({
{isLoading && (
- Cancel
+ {t("prompts.conversation.input.cancel")}
)}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/MessageList.tsx b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/MessageList.tsx
index 48d6697a052..c5e4498a722 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/MessageList.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/MessageList.tsx
@@ -1,5 +1,6 @@
import React from "react";
import { Loader2 } from "lucide-react";
+import { useTranslation } from "react-i18next";
import EmptyState from "./EmptyState";
import MessageBubble from "./MessageBubble";
import { Message } from "./types";
@@ -12,6 +13,7 @@ interface MessageListProps {
}
const MessageList: React.FC
= ({ messages, isLoading, hasVariables, messagesEndRef }) => {
+ const { t } = useTranslation();
return (
{messages.length === 0 &&
}
@@ -22,7 +24,10 @@ const MessageList: React.FC
= ({ messages, isLoading, hasVaria
{isLoading && (
-
+
)}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/VariableInput.tsx b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/VariableInput.tsx
index 96397ef4afa..64a8b790ade 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/VariableInput.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/VariableInput.tsx
@@ -1,4 +1,5 @@
import React from "react";
+import { useTranslation } from "react-i18next";
import { Input } from "@/components/ui/input";
interface VariableInputProps {
@@ -8,13 +9,14 @@ interface VariableInputProps {
}
const VariableInput: React.FC = ({ extractedVariables, variables, onVariableChange }) => {
+ const { t } = useTranslation();
if (extractedVariables.length === 0) {
return null;
}
return (
-
Fill in template variables to start testing
+
{t("prompts.conversation.variables.title")}
{extractedVariables.map((varName) => (
@@ -26,7 +28,7 @@ const VariableInput: React.FC = ({ extractedVariables, varia
onVariableChange(varName, e.target.value)}
- placeholder={`Enter value for ${varName}`}
+ placeholder={t("prompts.conversation.variables.placeholder", { name: varName })}
/>
))}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/VariableWarning.tsx b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/VariableWarning.tsx
index b4b710e37a0..4443ca07baa 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/VariableWarning.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/VariableWarning.tsx
@@ -1,4 +1,5 @@
import React from "react";
+import { useTranslation } from "react-i18next";
interface VariableWarningProps {
extractedVariables: string[];
@@ -6,6 +7,7 @@ interface VariableWarningProps {
}
const VariableWarning: React.FC
= ({ extractedVariables, variables }) => {
+ const { t } = useTranslation();
const missingVariables = extractedVariables.filter(
(varName) => !variables[varName] || variables[varName].trim() === "",
);
@@ -19,9 +21,11 @@ const VariableWarning: React.FC = ({ extractedVariables, v
⚠️
-
Please fill in all template variables above
+
{t("prompts.conversation.warning.title")}
- Missing: {missingVariables.map((varName) => `{{${varName}}}`).join(", ")}
+ {t("prompts.conversation.warning.missing", {
+ variables: missingVariables.map((varName) => `{{${varName}}}`).join(", "),
+ })}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/index.tsx b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/index.tsx
index e3cc15bf48b..4f53f4189cf 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/index.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/index.tsx
@@ -1,5 +1,6 @@
import React from "react";
import { Trash2 } from "lucide-react";
+import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import { ConversationPanelProps } from "./types";
import { useConversation } from "./useConversation";
@@ -9,6 +10,7 @@ import VariableWarning from "./VariableWarning";
import MessageInput from "./MessageInput";
const ConversationPanel: React.FC = ({ prompt, accessToken }) => {
+ const { t } = useTranslation();
const {
isLoading,
messages,
@@ -40,7 +42,7 @@ const ConversationPanel: React.FC = ({ prompt, accessTok
- Clear Chat
+ {t("prompts.conversation.panel.clearChat")}
)}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/useConversation.ts b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/useConversation.ts
index 199c2cb3970..6fd351a3162 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/useConversation.ts
+++ b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/conversation_panel/useConversation.ts
@@ -1,4 +1,5 @@
import { useState, useRef, useEffect } from "react";
+import { useTranslation } from "react-i18next";
import { toast } from "@/lib/toast";
import { TokenUsage } from "@/components/chat_ui/ResponseMetrics";
import { Message } from "./types";
@@ -7,6 +8,7 @@ import { getProxyBaseUrl, getGlobalLitellmHeaderName } from "@/components/networ
import { streamErrorMessage } from "@/utils/streamErrorMessage";
export const useConversation = (prompt: any, accessToken: string | null) => {
+ const { t } = useTranslation();
const [isLoading, setIsLoading] = useState(false);
const [messages, setMessages] = useState([]);
const [inputMessage, setInputMessage] = useState("");
@@ -38,12 +40,12 @@ export const useConversation = (prompt: any, accessToken: string | null) => {
const handleSendMessage = async () => {
if (!accessToken) {
- toast.fromError("Access token is required");
+ toast.fromError(t("prompts.conversation.toasts.tokenRequired"));
return;
}
if (extractedVariables.length > 0 && !allVariablesFilled) {
- toast.fromError("Please fill in all template variables");
+ toast.fromError(t("prompts.conversation.toasts.variablesRequired"));
return;
}
@@ -208,14 +210,14 @@ export const useConversation = (prompt: any, accessToken: string | null) => {
abortController.abort();
setAbortController(null);
setIsLoading(false);
- toast.info("Request cancelled");
+ toast.info(t("prompts.conversation.toasts.cancelled"));
}
};
const handleClearConversation = () => {
setMessages([]);
setVariablesFilled(false);
- toast.success("Chat history cleared.");
+ toast.success(t("prompts.conversation.toasts.cleared"));
};
const handleKeyDown = (event: React.KeyboardEvent) => {
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/index.tsx b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/index.tsx
index 2c2908a50f5..f7efb8175cc 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/index.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_editor_view/index.tsx
@@ -1,4 +1,5 @@
import React, { useState } from "react";
+import { useTranslation } from "react-i18next";
import ToolModal from "../tool_modal";
import { toast } from "@/lib/toast";
import { createPromptCall, updatePromptCall, getPromptInfo } from "@/components/networking";
@@ -15,13 +16,14 @@ import DotpromptViewTab from "./DotpromptViewTab";
import VersionHistorySidePanel from "./VersionHistorySidePanel";
const PromptEditorView: React.FC = ({ onClose, onSuccess, accessToken, initialPromptData }) => {
+ const { t } = useTranslation();
const getInitialPrompt = (): PromptType => {
if (initialPromptData) {
try {
return parseExistingPrompt(initialPromptData);
} catch (error) {
console.error("Error parsing existing prompt:", error);
- toast.fromError("Failed to parse prompt data");
+ toast.fromError(t("prompts.editor.toasts.parseFailed"));
}
}
return {
@@ -120,7 +122,7 @@ const PromptEditorView: React.FC = ({ onClose, onSuccess,
try {
const parsed = JSON.parse(json);
const tool: Tool = {
- name: parsed.function?.name || "Unnamed Tool",
+ name: parsed.function?.name || t("prompts.editor.unnamedTool"),
description: parsed.function?.description || "",
json: json,
};
@@ -142,7 +144,7 @@ const PromptEditorView: React.FC = ({ onClose, onSuccess,
setShowToolModal(false);
setEditingToolIndex(null);
} catch (error) {
- toast.fromError("Invalid JSON format");
+ toast.fromError(t("prompts.editor.toasts.invalidJson"));
}
};
@@ -171,7 +173,7 @@ const PromptEditorView: React.FC = ({ onClose, onSuccess,
setActiveVersionId(`${versionData.prompt_id}.v${versionNum}`);
} catch (error) {
console.error("Error loading version:", error);
- toast.fromError("Failed to load prompt version");
+ toast.fromError(t("prompts.editor.toasts.loadVersionFailed"));
}
};
@@ -185,12 +187,12 @@ const PromptEditorView: React.FC = ({ onClose, onSuccess,
const handleSave = async () => {
if (!accessToken) {
- toast.fromError("Access token is required");
+ toast.fromError(t("prompts.editor.toasts.tokenRequired"));
return;
}
if (!prompt.name || prompt.name.trim() === "") {
- toast.fromError("Please enter a valid prompt name");
+ toast.fromError(t("prompts.editor.toasts.nameRequired"));
return;
}
@@ -214,16 +216,16 @@ const PromptEditorView: React.FC = ({ onClose, onSuccess,
if (editMode && initialPromptData?.prompt_spec?.prompt_id) {
await updatePromptCall(accessToken, initialPromptData.prompt_spec.prompt_id, promptData);
- toast.success("Prompt updated successfully!");
+ toast.success(t("prompts.editor.toasts.updated"));
} else {
await createPromptCall(accessToken, promptData);
- toast.success("Prompt created successfully!");
+ toast.success(t("prompts.editor.toasts.created"));
}
onSuccess();
onClose();
} catch (error) {
console.error("Error saving prompt:", error);
- toast.fromError(editMode ? "Failed to update prompt" : "Failed to save prompt");
+ toast.fromError(editMode ? t("prompts.editor.toasts.updateFailed") : t("prompts.editor.toasts.saveFailed"));
} finally {
setIsSaving(false);
setShowNameModal(false);
@@ -320,7 +322,7 @@ const PromptEditorView: React.FC = ({ onClose, onSuccess,
}`}
onClick={() => setViewMode("pretty")}
>
- PRETTY
+ {t("prompts.editor.viewModes.pretty")}
{
@@ -117,7 +118,7 @@ const parseToolsFromFrontmatter = (lines: string[]): Tool[] => {
try {
const toolObj = JSON.parse(rawJson);
tools.push({
- name: toolObj?.function?.name || "Unnamed Tool",
+ name: toolObj?.function?.name || i18n.t("prompts.editor.unnamedTool"),
description: toolObj?.function?.description || "",
json: JSON.stringify(toolObj, null, 2),
});
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_info.tsx b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_info.tsx
index 062e1d84a0a..9a37f026c12 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_info.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/prompts/_components/prompt_info.tsx
@@ -1,4 +1,5 @@
import React, { useState, useEffect, useRef } from "react";
+import { Trans, useTranslation } from "react-i18next";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
@@ -37,6 +38,7 @@ const PromptInfoView: React.FC = ({
onDelete,
onEdit,
}) => {
+ const { t } = useTranslation();
const [promptData, setPromptData] = useState(null);
const [promptTemplate, setPromptTemplate] = useState(null);
const [rawApiResponse, setRawApiResponse] = useState(null);
@@ -71,7 +73,7 @@ const PromptInfoView: React.FC = ({
}
setSelectedVersion(response.prompt_spec.version || null);
} catch (error) {
- toast.fromError("Failed to load prompt information");
+ toast.fromError(t("prompts.info.toasts.loadFailed"));
console.error("Error fetching prompt info:", error);
} finally {
setLoading(false);
@@ -118,11 +120,11 @@ const PromptInfoView: React.FC = ({
}, [selectedEnv]);
if (loading && !promptData) {
- return Loading...
;
+ return {t("prompts.info.loading")}
;
}
if (!promptData) {
- return Prompt not found
;
+ return {t("prompts.info.notFound")}
;
}
const formatDate = (dateString?: string) => {
@@ -150,12 +152,12 @@ const PromptInfoView: React.FC = ({
setIsDeleting(true);
try {
await deletePromptCall(accessToken, basePromptId);
- toast.success(`Prompt "${basePromptId}" deleted successfully`);
+ toast.success(t("prompts.info.toasts.deleted", { name: basePromptId }));
onDelete?.();
onClose();
} catch (error) {
console.error("Error deleting prompt:", error);
- toast.fromError("Failed to delete prompt");
+ toast.fromError(t("prompts.info.toasts.deleteFailed"));
} finally {
setIsDeleting(false);
setShowDeleteConfirm(false);
@@ -178,7 +180,7 @@ const PromptInfoView: React.FC = ({
setPromptTemplate(response.raw_prompt_template);
setRawApiResponse(response);
} catch {
- toast.fromError(`Failed to load version v${versionNum}`);
+ toast.fromError(t("prompts.info.toasts.loadVersionFailed", { version: versionNum }));
}
};
@@ -193,11 +195,11 @@ const PromptInfoView: React.FC = ({
- Back to Prompts
+ {t("prompts.info.back")}
-
Prompt Details
+
{t("prompts.info.title")}
{basePromptId}
= ({
/>
onEdit?.(rawApiResponse)} className="flex items-center">
- Prompt Studio
+ {t("prompts.info.openStudio")}
{isAdmin && (
- Delete Prompt
+ {t("prompts.info.deletePrompt")}
)}
@@ -264,7 +266,9 @@ const PromptInfoView: React.FC
= ({
>
{env}
{versionHistory.length > 0 && selectedEnv === env && (
- (v{latestVersion})
+
+ {t("prompts.info.environmentVersion", { version: latestVersion })}
+
)}
))}
@@ -275,7 +279,7 @@ const PromptInfoView: React.FC = ({
{isViewingOldVersion && (
- Viewing v{selectedVersion} — not the latest version (v{latestVersion})
+ {t("prompts.info.oldVersionBanner", { selected: selectedVersion, latest: latestVersion })}
= ({
if (latest) handleVersionClick(latest);
}}
>
- Go to latest
+ {t("prompts.info.goToLatest")}
)}
@@ -293,15 +297,15 @@ const PromptInfoView: React.FC = ({
- Overview
+ {t("prompts.info.tabs.overview")}
{promptTemplate && (
- Prompt Template
+ {t("prompts.info.tabs.template")}
)}
- Raw JSON
+ {t("prompts.info.tabs.rawJson")}
@@ -310,51 +314,55 @@ const PromptInfoView: React.FC = ({
- Version
+ {t("prompts.info.overview.version")}
{currentVersion}
- v{currentVersion}
+ {t("prompts.info.versionLabel", { version: currentVersion })}
- Prompt Type
+ {t("prompts.info.overview.promptType")}
{promptData.prompt_info?.prompt_type || "-"}
- Created By
+ {t("prompts.info.overview.createdBy")}
{promptData.created_by || "-"}
- Created At
+ {t("prompts.info.overview.createdAt")}
{formatDate(promptData.created_at)}
-
Updated: {formatDate(promptData.updated_at)}
+
+ {t("prompts.info.overview.updated", { date: formatDate(promptData.updated_at) })}
+
{/* Version History Table */}
- Version History — {selectedEnv}
+
+ {t("prompts.info.history.title", { environment: selectedEnv })}
+
{loadingVersions ? (
- Loading versions...
+ {t("prompts.info.history.loading")}
) : versionHistory.length > 0 ? (
- Version
- Created By
- Date
- Actions
+ {t("prompts.info.history.columns.version")}
+ {t("prompts.info.history.columns.createdBy")}
+ {t("prompts.info.history.columns.date")}
+ {t("prompts.info.history.columns.actions")}
@@ -371,10 +379,12 @@ const PromptInfoView: React.FC = ({
onClick={() => handleVersionClick(v)}
>
- v{vNum}
+
+ {t("prompts.info.versionLabel", { version: vNum })}
+
{isLatest && (
- latest
+ {t("prompts.info.history.latest")}
)}
@@ -403,7 +413,7 @@ const PromptInfoView: React.FC = ({
}}
>
- Edit
+ {t("prompts.info.history.edit")}
@@ -412,7 +422,7 @@ const PromptInfoView: React.FC = ({
) : (
- No versions found in {selectedEnv}
+ {t("prompts.info.history.empty", { environment: selectedEnv })}
)}
@@ -422,7 +432,7 @@ const PromptInfoView: React.FC = ({
-
Prompt Template
+ {t("prompts.info.template.title")}
= ({
}`}
>
{copiedStates["prompt-content"] ? : }
- {copiedStates["prompt-content"] ? "Copied!" : "Copy Content"}
+ {copiedStates["prompt-content"] ? t("prompts.info.copied") : t("prompts.info.template.copy")}
-
Template ID
+
{t("prompts.info.template.id")}
{promptTemplate.litellm_prompt_id}
-
Content
+
{t("prompts.info.template.content")}
@@ -453,7 +463,7 @@ const PromptInfoView: React.FC
= ({
{promptTemplate.metadata && Object.keys(promptTemplate.metadata).length > 0 && (