From 867abf24f936cb6a2f1df5ac55ae10d34fa64735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B9=E9=9B=AA?= <2b@yorha.xyz> Date: Wed, 10 Jun 2026 12:21:22 +0900 Subject: [PATCH] feat(ui): i18n for top-level components batch 1 --- .../src/components/AdminPanel.tsx | 86 ++++----- .../src/components/BulkEditUsers.tsx | 75 +++++--- .../src/components/CreateUserButton.tsx | 93 ++++++---- .../src/components/DebugWarningBanner.tsx | 12 +- .../src/components/DefaultUserSettings.tsx | 64 ++++--- .../src/components/GuardrailSettingsView.tsx | 29 +-- ui/litellm-dashboard/src/locales/en.json | 172 ++++++++++++++++++ ui/litellm-dashboard/src/locales/zh-CN.json | 172 ++++++++++++++++++ 8 files changed, 543 insertions(+), 160 deletions(-) diff --git a/ui/litellm-dashboard/src/components/AdminPanel.tsx b/ui/litellm-dashboard/src/components/AdminPanel.tsx index e4aec7706a0..28144b16a3d 100644 --- a/ui/litellm-dashboard/src/components/AdminPanel.tsx +++ b/ui/litellm-dashboard/src/components/AdminPanel.tsx @@ -16,6 +16,7 @@ import { } from "@tremor/react"; import { Alert, Button as Button2, Form, Input, Modal, Space, Tabs, Typography } from "antd"; import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; import NewBadge from "./common_components/NewBadge"; import { useBaseUrl } from "./constants"; import NotificationsManager from "./molecules/notifications_manager"; @@ -35,6 +36,7 @@ interface AdminPanelProps { } const AdminPanel: React.FC = ({ proxySettings }) => { + const { t } = useTranslation(); const { premiumUser, accessToken, userId: userID } = useAuthorized(); const [form] = Form.useForm(); const [isAddSSOModalVisible, setIsAddSSOModalVisible] = useState(false); @@ -48,7 +50,7 @@ const AdminPanel: React.FC = ({ proxySettings }) => { const [ssoConfigured, setSsoConfigured] = useState(false); const baseUrl = useBaseUrl(); - const all_ip_address_allowed = "All IP Addresses Allowed"; + const all_ip_address_allowed = t("adminPanel.allIPAddressesAllowed"); let nonSssoUrl = baseUrl; nonSssoUrl += "/fallback/login"; @@ -77,9 +79,7 @@ const AdminPanel: React.FC = ({ proxySettings }) => { const handleShowAllowedIPs = async () => { try { if (premiumUser !== true) { - NotificationsManager.fromBackend( - "This feature is only available for premium users. Please upgrade your account.", - ); + NotificationsManager.fromBackend(t("adminPanel.notifications.premiumOnlyFeature")); return; } if (accessToken) { @@ -90,7 +90,7 @@ const AdminPanel: React.FC = ({ proxySettings }) => { } } catch (error) { console.error("Error fetching allowed IPs:", error); - NotificationsManager.fromBackend(`Failed to fetch allowed IPs ${error}`); + NotificationsManager.fromBackend(t("adminPanel.fetchAllowedIpsFailed", { error })); setAllowedIPs([all_ip_address_allowed]); } finally { if (premiumUser === true) { @@ -106,11 +106,11 @@ const AdminPanel: React.FC = ({ proxySettings }) => { // Fetch the updated list of IPs const updatedIPs = await getAllowedIPs(accessToken); setAllowedIPs(updatedIPs); - NotificationsManager.success("IP address added successfully"); + NotificationsManager.success(t("adminPanel.notifications.ipAddedSuccess")); } } catch (error) { console.error("Error adding IP:", error); - NotificationsManager.fromBackend(`Failed to add IP address ${error}`); + NotificationsManager.fromBackend(t("adminPanel.addIpFailed", { error })); } finally { setIsAddIPModalVisible(false); } @@ -128,10 +128,10 @@ const AdminPanel: React.FC = ({ proxySettings }) => { // Fetch the updated list of IPs const updatedIPs = await getAllowedIPs(accessToken); setAllowedIPs(updatedIPs.length > 0 ? updatedIPs : [all_ip_address_allowed]); - NotificationsManager.success("IP address deleted successfully"); + NotificationsManager.success(t("adminPanel.notifications.ipDeletedSuccess")); } catch (error) { console.error("Error deleting IP:", error); - NotificationsManager.fromBackend(`Failed to delete IP address ${error}`); + NotificationsManager.fromBackend(t("adminPanel.deleteIpFailed", { error })); } finally { setIsDeleteIPModalVisible(false); setIPToDelete(null); @@ -186,19 +186,19 @@ const AdminPanel: React.FC = ({ proxySettings }) => { const tabItems = [ { key: "sso-settings", - label: "SSO Settings", + label: t("adminPanel.tabs.ssoSettings"), children: , }, { key: "security-settings", - label: "Security Settings", + label: t("adminPanel.tabs.securitySettings"), children: ( <> - ✨ Security Settings + ✨ {t("adminPanel.securityCard.title")} @@ -213,12 +213,12 @@ const AdminPanel: React.FC = ({ proxySettings }) => { >
@@ -227,10 +227,10 @@ const AdminPanel: React.FC = ({ proxySettings }) => { onClick={() => premiumUser === true ? setIsUIAccessControlModalVisible(true) - : NotificationsManager.fromBackend("Only premium users can configure UI access control") + : NotificationsManager.fromBackend(t("adminPanel.notifications.premiumOnlyUIAccessControl")) } > - UI Access Control + {t("adminPanel.securityCard.uiAccessControl")}
@@ -250,24 +250,26 @@ const AdminPanel: React.FC = ({ proxySettings }) => { ssoConfigured={ssoConfigured} /> setIsAllowedIPModalVisible(false)} footer={[ , , ]} > - IP Address - Action + {t("adminPanel.manageAllowedIPModal.ipAddressColumn")} + + {t("adminPanel.manageAllowedIPModal.actionColumn")} + @@ -277,7 +279,7 @@ const AdminPanel: React.FC = ({ proxySettings }) => { {ip !== all_ip_address_allowed && ( )} @@ -288,41 +290,41 @@ const AdminPanel: React.FC = ({ proxySettings }) => { setIsAddIPModalVisible(false)} footer={null} >
- - + + - Add IP Address + {t("adminPanel.addIPModal.submitButton")}
setIsDeleteIPModalVisible(false)} onOk={confirmDeleteIP} footer={[ , , ]} > - Are you sure you want to delete the IP address: {ipToDelete}? + {t("adminPanel.deleteIPModal.confirmText", { ip: ipToDelete })} {/* UI Access Control Modal */} = ({ proxySettings }) => { accessToken={accessToken} onSuccess={() => { handleUIAccessControlOk(); - NotificationsManager.success("UI Access Control settings updated successfully"); + NotificationsManager.success(t("adminPanel.uiAccessControlModal.updateSuccess")); }} /> - - If you need to login without sso, you can access{" "} + + {t("adminPanel.loginWithoutSSO.description")}{" "} {nonSssoUrl}{" "} @@ -349,7 +351,7 @@ const AdminPanel: React.FC = ({ proxySettings }) => { }, { key: "scim", - label: "SCIM", + label: t("adminPanel.tabs.scim"), children: , }, { @@ -357,7 +359,7 @@ const AdminPanel: React.FC = ({ proxySettings }) => { label: ( - UI Settings + {t("adminPanel.tabs.uiSettings")} ), @@ -365,20 +367,20 @@ const AdminPanel: React.FC = ({ proxySettings }) => { }, { key: "logging-settings", - label: "Logging Settings", + label: t("adminPanel.tabs.loggingSettings"), children: , }, { key: "hashicorp-vault", - label: "Hashicorp Vault", + label: t("adminPanel.tabs.hashicorpVault"), children: , }, ]; return (
- Admin Access - Go to 'Internal Users' page to add other admins. + {t("adminPanel.title")} + {t("adminPanel.goToInternalUsers")}
); diff --git a/ui/litellm-dashboard/src/components/BulkEditUsers.tsx b/ui/litellm-dashboard/src/components/BulkEditUsers.tsx index 7bcfd793604..7cb7b441980 100644 --- a/ui/litellm-dashboard/src/components/BulkEditUsers.tsx +++ b/ui/litellm-dashboard/src/components/BulkEditUsers.tsx @@ -4,6 +4,7 @@ import { userBulkUpdateUserCall, teamBulkMemberAddCall, Member } from "./network import { UserEditView } from "./user_edit_view"; import NotificationsManager from "./molecules/notifications_manager"; import MessageManager from "@/components/molecules/message_manager"; +import { useTranslation } from "react-i18next"; const { Text, Title } = Typography; @@ -32,6 +33,7 @@ const BulkEditUserModal: React.FC = ({ userModels, allowAllUsers = false, }) => { + const { t } = useTranslation(); const [loading, setLoading] = useState(false); const [selectedTeams, setSelectedTeams] = useState([]); const [teamBudget, setTeamBudget] = useState(null); @@ -71,7 +73,7 @@ const BulkEditUserModal: React.FC = ({ const handleSubmit = async (formValues: any) => { console.log("formValues", formValues); if (!accessToken) { - NotificationsManager.fromBackend("Access token not found"); + NotificationsManager.fromBackend(t("bulkEditUsers.notifications.accessTokenNotFound")); return; } @@ -107,7 +109,7 @@ const BulkEditUserModal: React.FC = ({ const hasTeamAdditions = addToTeams && selectedTeams.length > 0; if (!hasUserUpdates && !hasTeamAdditions) { - NotificationsManager.fromBackend("Please modify at least one field or select teams to add users to"); + NotificationsManager.fromBackend(t("bulkEditUsers.notifications.noFieldsModified")); return; } @@ -117,10 +119,10 @@ const BulkEditUserModal: React.FC = ({ if (hasUserUpdates) { if (updateAllUsers) { const result = await userBulkUpdateUserCall(accessToken, updatePayload, undefined, true); - successMessages.push(`Updated all users (${result.total_requested} total)`); + successMessages.push(t("bulkEditUsers.notifications.updatedAllUsers", { total: result.total_requested })); } else { await userBulkUpdateUserCall(accessToken, updatePayload, userIds); - successMessages.push(`Updated ${userIds.length} user(s)`); + successMessages.push(t("bulkEditUsers.notifications.updatedUsers", { count: userIds.length })); } } @@ -174,11 +176,13 @@ const BulkEditUserModal: React.FC = ({ if (successfulTeams.length > 0) { const totalAdditions = successfulTeams.reduce((sum, r) => sum + r.successfulAdditions, 0); - successMessages.push(`Added users to ${successfulTeams.length} team(s) (${totalAdditions} total additions)`); + successMessages.push( + t("bulkEditUsers.notifications.addedToTeams", { count: successfulTeams.length, total: totalAdditions }), + ); } if (failedTeams.length > 0) { - MessageManager.warning(`Failed to add users to ${failedTeams.length} team(s)`); + MessageManager.warning(t("bulkEditUsers.notifications.failedToAddToTeams", { count: failedTeams.length })); } } @@ -196,7 +200,7 @@ const BulkEditUserModal: React.FC = ({ onCancel(); } catch (error) { console.error("Bulk operation failed:", error); - NotificationsManager.fromBackend("Failed to perform bulk operations"); + NotificationsManager.fromBackend(t("bulkEditUsers.notifications.bulkOpFailed")); } finally { setLoading(false); } @@ -207,18 +211,22 @@ const BulkEditUserModal: React.FC = ({ open={open} onCancel={handleCancel} footer={null} - title={updateAllUsers ? "Bulk Edit All Users" : `Bulk Edit ${selectedUsers.length} User(s)`} + title={ + updateAllUsers + ? t("bulkEditUsers.titleAllUsers") + : t("bulkEditUsers.titleSelectedUsers", { count: selectedUsers.length }) + } width={800} > {allowAllUsers && (
setUpdateAllUsers(e.target.checked)}> - Update ALL users in the system + {t("bulkEditUsers.updateAllUsersLabel")} {updateAllUsers && (
- ⚠️ This will apply changes to ALL users in the system, not just the selected ones. + ⚠️ {t("bulkEditUsers.updateAllUsersWarning")}
)} @@ -227,7 +235,7 @@ const BulkEditUserModal: React.FC = ({ {!updateAllUsers && (
- Selected Users ({selectedUsers.length}): + {t("bulkEditUsers.selectedUsersTitle", { count: selectedUsers.length })}
= ({ rowKey="user_id" columns={[ { - title: "User ID", + title: t("bulkEditUsers.columns.userId"), dataIndex: "user_id", key: "user_id", width: "30%", @@ -248,18 +256,18 @@ const BulkEditUserModal: React.FC = ({ ), }, { - title: "Email", + title: t("bulkEditUsers.columns.email"), dataIndex: "user_email", key: "user_email", width: "25%", render: (text: string) => ( - {text || "No email"} + {text || t("bulkEditUsers.noEmail")} ), }, { - title: "Current Role", + title: t("bulkEditUsers.columns.currentRole"), dataIndex: "user_role", key: "user_role", width: "25%", @@ -268,12 +276,14 @@ const BulkEditUserModal: React.FC = ({ ), }, { - title: "Budget", + title: t("bulkEditUsers.columns.budget"), dataIndex: "max_budget", key: "max_budget", width: "20%", render: (budget: number | null) => ( - {budget !== null ? `$${budget}` : "Unlimited"} + + {budget !== null ? `$${budget}` : t("bulkEditUsers.unlimited")} + ), }, ]} @@ -285,25 +295,29 @@ const BulkEditUserModal: React.FC = ({
- Instructions: Fill in the fields below with the values you want to apply to all selected - users. You can bulk edit: role, budget, models, and metadata. You can also add users to teams. + {t("bulkEditUsers.instructionsLabel")} {t("bulkEditUsers.instructions")}
{/* Team Management Section */} - + setAddToTeams(e.target.checked)}> - Add selected users to teams + {t("bulkEditUsers.teamManagement.addToTeamsLabel")} {addToTeams && ( <>
- Select Teams: + {t("bulkEditUsers.teamManagement.selectTeamsLabel")} - Global Proxy Role{" "} - + {t("createUserButton.globalProxyRoleLabel")}{" "} + @@ -318,20 +319,24 @@ export const CreateUserButton: React.FC = ({ - {organizations.map((org) => ( - - + + - + - Personal Key Creation + {t("createUserButton.personalKeyCreation")} - Models{" "} - + {t("createUserButton.modelsLabel")}{" "} + } name="models" - help="Models user has access to, outside of team scope." + help={t("createUserButton.modelsHelp")} > - + - All Proxy Models + {t("createUserButton.allProxyModels")} - No Default Models + {t("createUserButton.noDefaultModels")} {userModels.map((model) => ( @@ -383,7 +396,7 @@ export const CreateUserButton: React.FC = ({
diff --git a/ui/litellm-dashboard/src/components/DebugWarningBanner.tsx b/ui/litellm-dashboard/src/components/DebugWarningBanner.tsx index 94474e78b14..663497bc804 100644 --- a/ui/litellm-dashboard/src/components/DebugWarningBanner.tsx +++ b/ui/litellm-dashboard/src/components/DebugWarningBanner.tsx @@ -3,12 +3,14 @@ import React from "react"; import { Alert } from "antd"; import { useHealthReadinessDetails } from "@/app/(dashboard)/hooks/healthReadiness/useHealthReadinessDetails"; +import { Trans, useTranslation } from "react-i18next"; interface DebugWarningBannerProps { accessToken: string | null; } export const DebugWarningBanner: React.FC = ({ accessToken }) => { + const { t } = useTranslation(); const { data: healthData } = useHealthReadinessDetails(accessToken); // Only show banner if detailed debug mode is explicitly enabled @@ -18,14 +20,8 @@ export const DebugWarningBanner: React.FC = ({ accessTo return ( - Detailed debug logging (LITELLM_LOG=DEBUG) is currently enabled. This mode logs extensive - diagnostic information and will significantly degrade performance. It should only be used for troubleshooting - and disabled in production environments. - - } + message={t("debugWarningBanner.message")} + description={ }} />} type="warning" showIcon banner diff --git a/ui/litellm-dashboard/src/components/DefaultUserSettings.tsx b/ui/litellm-dashboard/src/components/DefaultUserSettings.tsx index 1aa7744d317..45f3fe4b957 100644 --- a/ui/litellm-dashboard/src/components/DefaultUserSettings.tsx +++ b/ui/litellm-dashboard/src/components/DefaultUserSettings.tsx @@ -7,6 +7,7 @@ import BudgetDurationDropdown, { getBudgetDurationLabel } from "./common_compone import { getModelDisplayName } from "./key_team_helpers/fetch_available_models_team_key"; import { formatNumberWithCommas } from "@/utils/dataUtils"; import NotificationManager from "./molecules/notifications_manager"; +import { useTranslation } from "react-i18next"; interface DefaultUserSettingsProps { accessToken: string | null; @@ -27,6 +28,7 @@ const DefaultUserSettings: React.FC = ({ userID, userRole, }) => { + const { t } = useTranslation(); const [loading, setLoading] = useState(true); const [settings, setSettings] = useState(null); const [isEditing, setIsEditing] = useState(false); @@ -62,7 +64,7 @@ const DefaultUserSettings: React.FC = ({ } } catch (error) { console.error("Error fetching SSO settings:", error); - NotificationManager.fromBackend("Failed to fetch SSO settings"); + NotificationManager.fromBackend(t("defaultUserSettings.notifications.fetchFailed")); } finally { setLoading(false); } @@ -90,7 +92,7 @@ const DefaultUserSettings: React.FC = ({ setIsEditing(false); } catch (error) { console.error("Error updating SSO settings:", error); - NotificationManager.fromBackend("Failed to update settings: " + error); + NotificationManager.fromBackend(t("defaultUserSettings.notifications.updateFailed", { error: String(error) })); } finally { setSaving(false); } @@ -158,29 +160,29 @@ const DefaultUserSettings: React.FC = ({ {normalizedTeams.map((team, index) => (
- Team {index + 1} + {t("defaultUserSettings.teamEntry.teamLabel", { number: index + 1 })}
- Team ID + {t("defaultUserSettings.teamEntry.teamIdLabel")} updateTeam(index, "team_id", e.target.value)} - placeholder="Enter team ID" + placeholder={t("defaultUserSettings.teamEntry.teamIdPlaceholder")} />
- Max Budget in Team + {t("defaultUserSettings.teamEntry.maxBudgetLabel")} updateTeam(index, "max_budget_in_team", value)} - placeholder="Optional" + placeholder={t("defaultUserSettings.teamEntry.maxBudgetPlaceholder")} min={0} step={0.01} precision={2} @@ -188,14 +190,14 @@ const DefaultUserSettings: React.FC = ({
- User Role + {t("defaultUserSettings.teamEntry.userRoleLabel")}
@@ -203,7 +205,7 @@ const DefaultUserSettings: React.FC = ({ ))}
); @@ -314,10 +316,12 @@ const DefaultUserSettings: React.FC = ({ }; const renderValue = (key: string, value: any): JSX.Element => { - if (value === null || value === undefined) return Not set; + if (value === null || value === undefined) + return {t("defaultUserSettings.displayValues.notSet")}; if (key === "teams" && Array.isArray(value)) { - if (value.length === 0) return No teams assigned; + if (value.length === 0) + return {t("defaultUserSettings.displayValues.noTeamsAssigned")}; const normalizedTeams = normalizeTeams(value); @@ -327,19 +331,21 @@ const DefaultUserSettings: React.FC = ({
- Team ID: -

{team.team_id || "Not specified"}

+ {t("defaultUserSettings.teamEntry.teamIdLabel")}: +

{team.team_id || t("defaultUserSettings.displayValues.notSpecified")}

- Max Budget: + + {t("defaultUserSettings.teamEntry.maxBudgetLabel")}: +

{team.max_budget_in_team !== undefined ? `$${formatNumberWithCommas(team.max_budget_in_team, 4)}` - : "No limit"} + : t("defaultUserSettings.displayValues.noLimit")}

- Role: + {t("defaultUserSettings.teamEntry.userRoleLabel")}:

{team.user_role}

@@ -364,11 +370,11 @@ const DefaultUserSettings: React.FC = ({ } if (typeof value === "boolean") { - return {value ? "Enabled" : "Disabled"}; + return {value ? t("common.enabled") : t("common.disabled")}; } if (key === "models" && Array.isArray(value)) { - if (value.length === 0) return None; + if (value.length === 0) return {t("common.none")}; return (
@@ -383,7 +389,7 @@ const DefaultUserSettings: React.FC = ({ if (typeof value === "object") { if (Array.isArray(value)) { - if (value.length === 0) return None; + if (value.length === 0) return {t("common.none")}; return (
@@ -413,7 +419,7 @@ const DefaultUserSettings: React.FC = ({ if (!settings) { return ( - No settings available or you do not have permission to view them. + {t("defaultUserSettings.noSettings")} ); } @@ -423,7 +429,7 @@ const DefaultUserSettings: React.FC = ({ const { values, field_schema } = settings; if (!field_schema || !field_schema.properties) { - return No schema information available; + return {t("defaultUserSettings.noSchemaInfo")}; } return Object.entries(field_schema.properties).map(([key, property]: [string, any]) => { @@ -434,7 +440,7 @@ const DefaultUserSettings: React.FC = ({
{displayName} - {property.description || "No description available"} + {property.description || t("defaultUserSettings.noDescriptionAvailable")} {isEditing ? ( @@ -450,7 +456,7 @@ const DefaultUserSettings: React.FC = ({ return (
- Default User Settings + {t("defaultUserSettings.title")} {!loading && settings && (isEditing ? ( @@ -462,15 +468,15 @@ const DefaultUserSettings: React.FC = ({ }} disabled={saving} > - Cancel + {t("common.cancel")}
) : ( ))}
diff --git a/ui/litellm-dashboard/src/components/GuardrailSettingsView.tsx b/ui/litellm-dashboard/src/components/GuardrailSettingsView.tsx index 95957e845d9..b6a9a13b0bc 100644 --- a/ui/litellm-dashboard/src/components/GuardrailSettingsView.tsx +++ b/ui/litellm-dashboard/src/components/GuardrailSettingsView.tsx @@ -1,6 +1,7 @@ import React from "react"; import { Tag } from "antd"; import { GlobalOutlined } from "@ant-design/icons"; +import { useTranslation } from "react-i18next"; interface GuardrailSettingsViewProps { globalGuardrailNames: Set; @@ -19,6 +20,7 @@ export function GuardrailSettingsView({ variant = "card", className = "", }: GuardrailSettingsViewProps) { + const { t } = useTranslation(); const optedOutSet = new Set(optedOutGlobalGuardrails); const globalsRunning = Array.from(globalGuardrailNames).filter((n) => !optedOutSet.has(n)); const nonGlobalOptIns = teamGuardrails.filter((n) => !globalGuardrailNames.has(n)); @@ -26,16 +28,19 @@ export function GuardrailSettingsView({ const isEmpty = !killSwitchOn && globalsRunning.length === 0 && nonGlobalOptIns.length === 0; const content = isEmpty ? ( - No guardrails configured + {t("guardrailSettingsView.noGuardrailsConfigured")} ) : (
- - Global + + {t("guardrailSettingsView.globalLabel")} {killSwitchOn ? ( - Bypassed for this team + {t("guardrailSettingsView.bypassedForTeam")} ) : globalsRunning.length > 0 ? (
{globalsRunning.map((name) => ( @@ -45,11 +50,13 @@ export function GuardrailSettingsView({ ))}
) : ( - None configured + {t("guardrailSettingsView.noneConfigured")} )}
- Team-specific + + {t("guardrailSettingsView.teamSpecificLabel")} + {nonGlobalOptIns.length > 0 ? (
{nonGlobalOptIns.map((name) => ( @@ -59,7 +66,7 @@ export function GuardrailSettingsView({ ))}
) : ( - None configured + {t("guardrailSettingsView.noneConfigured")} )}
@@ -70,10 +77,8 @@ export function GuardrailSettingsView({
- Guardrails Settings - - Global and team-specific guardrails applied to this team - + {t("guardrailSettingsView.cardTitle")} + {t("guardrailSettingsView.cardSubtitle")}
{content} @@ -83,7 +88,7 @@ export function GuardrailSettingsView({ return (
- Guardrails Settings + {t("guardrailSettingsView.inlineTitle")} {content}
); diff --git a/ui/litellm-dashboard/src/locales/en.json b/ui/litellm-dashboard/src/locales/en.json index bdf85c947b3..44817610aee 100644 --- a/ui/litellm-dashboard/src/locales/en.json +++ b/ui/litellm-dashboard/src/locales/en.json @@ -393,6 +393,178 @@ "addMember": "Add Member" } }, + "adminPanel": { + "title": "Admin Access", + "goToInternalUsers": "Go to 'Internal Users' page to add other admins.", + "allIPAddressesAllowed": "All IP Addresses Allowed", + "tabs": { + "ssoSettings": "SSO Settings", + "securitySettings": "Security Settings", + "scim": "SCIM", + "uiSettings": "UI Settings", + "loggingSettings": "Logging Settings", + "hashicorpVault": "Hashicorp Vault" + }, + "securityCard": { + "title": "Security Settings", + "deprecatedAlertMessage": "SSO Configuration Deprecated", + "deprecatedAlertDescription": "Editing SSO Settings on this page is deprecated and will be removed in a future version. Please use the SSO Settings tab for SSO configuration.", + "editSSOSettings": "Edit SSO Settings", + "addSSO": "Add SSO", + "allowedIPs": "Allowed IPs", + "uiAccessControl": "UI Access Control" + }, + "loginWithoutSSO": { + "title": "Login without SSO", + "description": "If you need to login without sso, you can access" + }, + "manageAllowedIPModal": { + "title": "Manage Allowed IP Addresses", + "addIPAddress": "Add IP Address", + "ipAddressColumn": "IP Address", + "actionColumn": "Action" + }, + "addIPModal": { + "title": "Add Allowed IP Address", + "inputPlaceholder": "Enter IP address", + "inputRequired": "Please enter an IP address", + "submitButton": "Add IP Address" + }, + "deleteIPModal": { + "title": "Confirm Delete", + "confirmText": "Are you sure you want to delete the IP address: {{ip}}?" + }, + "uiAccessControlModal": { + "title": "UI Access Control Settings", + "updateSuccess": "UI Access Control settings updated successfully" + }, + "notifications": { + "premiumOnlyFeature": "This feature is only available for premium users. Please upgrade your account.", + "premiumOnlyUIAccessControl": "Only premium users can configure UI access control", + "ipAddedSuccess": "IP address added successfully", + "ipDeletedSuccess": "IP address deleted successfully" + }, + "fetchAllowedIpsFailed": "Failed to fetch allowed IPs {{error}}", + "addIpFailed": "Failed to add IP address {{error}}", + "deleteIpFailed": "Failed to delete IP address {{error}}" + }, + "bulkEditUsers": { + "titleAllUsers": "Bulk Edit All Users", + "titleSelectedUsers": "Bulk Edit {{count}} User(s)", + "updateAllUsersLabel": "Update ALL users in the system", + "updateAllUsersWarning": "This will apply changes to ALL users in the system, not just the selected ones.", + "selectedUsersTitle": "Selected Users ({{count}}):", + "columns": { + "userId": "User ID", + "email": "Email", + "currentRole": "Current Role", + "budget": "Budget" + }, + "noEmail": "No email", + "unlimited": "Unlimited", + "instructions": "Fill in the fields below with the values you want to apply to all selected users. You can bulk edit: role, budget, models, and metadata. You can also add users to teams.", + "instructionsLabel": "Instructions:", + "teamManagement": { + "sectionTitle": "Team Management", + "addToTeamsLabel": "Add selected users to teams", + "selectTeamsLabel": "Select Teams:", + "selectTeamsPlaceholder": "Select teams to add users to", + "teamBudgetLabel": "Team Budget (Optional):", + "teamBudgetPlaceholder": "Max budget per user in team", + "teamBudgetHint": "Leave empty for unlimited budget within team limits", + "defaultRoleNote": "Users will be added with \"user\" role by default. All users will be added to each selected team." + }, + "updatingUsers": "Updating {{count}} user(s)...", + "updatingAllUsers": "Updating all users...", + "notifications": { + "noFieldsModified": "Please modify at least one field or select teams to add users to", + "accessTokenNotFound": "Access token not found", + "bulkOpFailed": "Failed to perform bulk operations", + "updatedAllUsers": "Updated all users ({{total}} total)", + "updatedUsers": "Updated {{count}} user(s)", + "addedToTeams": "Added users to {{count}} team(s) ({{total}} total additions)", + "failedToAddToTeams": "Failed to add users to {{count}} team(s)" + } + }, + "createUserButton": { + "inviteUserButton": "+ Invite User", + "createUserOwnsKeys": "Create a User who can own keys", + "emailInvitations": "Email invitations", + "emailInvitationsDesc": "New users receive an email invite only when an email integration (SMTP, Resend, or SendGrid) is configured.", + "learnHowToSetupEmail": "Learn how to set up email notifications", + "userEmailLabel": "User Email", + "userRoleLabel": "User Role", + "globalProxyRoleLabel": "Global Proxy Role", + "globalProxyRoleTooltip": "This role is independent of any team/org specific roles. Configure Team / Organization Admins in the Settings", + "teamLabel": "Team", + "teamHelp": "If selected, user will be added as a 'user' role to the team.", + "organizationLabel": "Organization", + "organizationHelp": "The user will be added to the selected organization(s).", + "selectOrganizationPlaceholder": "Select Organization", + "metadataLabel": "Metadata", + "metadataPlaceholder": "Enter metadata as JSON", + "sendInviteEmailLabel": "Send invitation email", + "personalKeyCreation": "Personal Key Creation", + "modelsLabel": "Models", + "modelsTooltip": "Models user has access to, outside of team scope.", + "modelsHelp": "Models user has access to, outside of team scope.", + "modelsPlaceholder": "Select models", + "allProxyModels": "All Proxy Models", + "noDefaultModels": "No Default Models", + "inviteUserModalTitle": "Invite User", + "inviteUserSubmitButton": "Invite User", + "createUserSubmitButton": "Create User", + "notifications": { + "makingApiCall": "Making API Call", + "userCreated": "API user Created", + "errorCreatingUser": "Error creating the user" + } + }, + "debugWarningBanner": { + "message": "Performance Warning: Detailed Debug Mode Active", + "description": "Detailed debug logging (LITELLM_LOG=DEBUG) is currently enabled. This mode logs extensive diagnostic information and will significantly degrade performance. It should only be used for troubleshooting and disabled in production environments." + }, + "defaultUserSettings": { + "title": "Default User Settings", + "editSettings": "Edit Settings", + "saveChanges": "Save Changes", + "noSettings": "No settings available or you do not have permission to view them.", + "noSchemaInfo": "No schema information available", + "noDescriptionAvailable": "No description available", + "teamEntry": { + "teamLabel": "Team {{number}}", + "removeButton": "Remove", + "teamIdLabel": "Team ID", + "teamIdPlaceholder": "Enter team ID", + "maxBudgetLabel": "Max Budget in Team", + "maxBudgetPlaceholder": "Optional", + "userRoleLabel": "User Role", + "addTeamButton": "Add Team" + }, + "displayValues": { + "notSet": "Not set", + "noTeamsAssigned": "No teams assigned", + "notSpecified": "Not specified", + "noLimit": "No limit", + "userRoleOption": "User", + "adminRoleOption": "Admin" + }, + "notifications": { + "fetchFailed": "Failed to fetch SSO settings", + "updateFailed": "Failed to update settings: {{error}}" + } + }, + "guardrailSettingsView": { + "noGuardrailsConfigured": "No guardrails configured", + "globalLabel": "Global", + "globalGuardrailAriaLabel": "Global guardrail", + "bypassedForTeam": "Bypassed for this team", + "noneConfigured": "None configured", + "teamSpecificLabel": "Team-specific", + "cardTitle": "Guardrails Settings", + "cardSubtitle": "Global and team-specific guardrails applied to this team", + "inlineTitle": "Guardrails Settings" + }, "common": { "save": "Save", "cancel": "Cancel", diff --git a/ui/litellm-dashboard/src/locales/zh-CN.json b/ui/litellm-dashboard/src/locales/zh-CN.json index 52b1c847c6e..03adf7e0839 100644 --- a/ui/litellm-dashboard/src/locales/zh-CN.json +++ b/ui/litellm-dashboard/src/locales/zh-CN.json @@ -393,6 +393,178 @@ "addMember": "添加成员" } }, + "adminPanel": { + "title": "管理员访问", + "goToInternalUsers": "前往「内部用户」页面添加其他管理员。", + "allIPAddressesAllowed": "允许所有 IP 地址", + "tabs": { + "ssoSettings": "SSO 设置", + "securitySettings": "安全设置", + "scim": "SCIM", + "uiSettings": "界面设置", + "loggingSettings": "日志设置", + "hashicorpVault": "Hashicorp Vault" + }, + "securityCard": { + "title": "安全设置", + "deprecatedAlertMessage": "SSO 配置已弃用", + "deprecatedAlertDescription": "在此页面编辑 SSO 设置已弃用,将在未来版本中移除。请使用 SSO 设置标签页进行 SSO 配置。", + "editSSOSettings": "编辑 SSO 设置", + "addSSO": "添加 SSO", + "allowedIPs": "允许的 IP", + "uiAccessControl": "界面访问控制" + }, + "loginWithoutSSO": { + "title": "不使用 SSO 登录", + "description": "如果您需要在不使用 SSO 的情况下登录,可以访问" + }, + "manageAllowedIPModal": { + "title": "管理允许的 IP 地址", + "addIPAddress": "添加 IP 地址", + "ipAddressColumn": "IP 地址", + "actionColumn": "操作" + }, + "addIPModal": { + "title": "添加允许的 IP 地址", + "inputPlaceholder": "输入 IP 地址", + "inputRequired": "请输入 IP 地址", + "submitButton": "添加 IP 地址" + }, + "deleteIPModal": { + "title": "确认删除", + "confirmText": "确定要删除 IP 地址:{{ip}}?" + }, + "uiAccessControlModal": { + "title": "界面访问控制设置", + "updateSuccess": "界面访问控制设置已成功更新" + }, + "notifications": { + "premiumOnlyFeature": "此功能仅对高级用户开放,请升级您的账户。", + "premiumOnlyUIAccessControl": "只有高级用户才能配置界面访问控制", + "ipAddedSuccess": "IP 地址添加成功", + "ipDeletedSuccess": "IP 地址删除成功" + }, + "fetchAllowedIpsFailed": "获取允许的 IP 列表失败 {{error}}", + "addIpFailed": "添加 IP 地址失败 {{error}}", + "deleteIpFailed": "删除 IP 地址失败 {{error}}" + }, + "bulkEditUsers": { + "titleAllUsers": "批量编辑所有用户", + "titleSelectedUsers": "批量编辑 {{count}} 个用户", + "updateAllUsersLabel": "更新系统中的所有用户", + "updateAllUsersWarning": "这将对系统中的所有用户应用更改,而不仅仅是所选用户。", + "selectedUsersTitle": "已选用户({{count}} 人):", + "columns": { + "userId": "用户 ID", + "email": "邮箱", + "currentRole": "当前角色", + "budget": "预算" + }, + "noEmail": "无邮箱", + "unlimited": "无限制", + "instructions": "在下方填写要应用于所有选定用户的值。可批量编辑:角色、预算、模型和元数据,也可将用户添加到团队。", + "instructionsLabel": "使用说明:", + "teamManagement": { + "sectionTitle": "团队管理", + "addToTeamsLabel": "将选定用户添加到团队", + "selectTeamsLabel": "选择团队:", + "selectTeamsPlaceholder": "选择要添加用户的团队", + "teamBudgetLabel": "团队预算(可选):", + "teamBudgetPlaceholder": "团队内每位用户的最大预算", + "teamBudgetHint": "留空表示在团队限制内不设预算上限", + "defaultRoleNote": "用户将以 user 角色默认添加,所有用户将被添加到每个选定的团队。" + }, + "updatingUsers": "正在更新 {{count}} 个用户...", + "updatingAllUsers": "正在更新所有用户...", + "notifications": { + "noFieldsModified": "请至少修改一个字段或选择要添加用户的团队", + "accessTokenNotFound": "未找到访问令牌", + "bulkOpFailed": "批量操作失败", + "updatedAllUsers": "已更新所有用户(共 {{total}} 个)", + "updatedUsers": "已更新 {{count}} 个用户", + "addedToTeams": "已将用户添加到 {{count}} 个团队(共 {{total}} 次添加)", + "failedToAddToTeams": "向 {{count}} 个团队添加用户失败" + } + }, + "createUserButton": { + "inviteUserButton": "+ 邀请用户", + "createUserOwnsKeys": "创建可拥有密钥的用户", + "emailInvitations": "邮件邀请", + "emailInvitationsDesc": "仅当配置了邮件集成(SMTP、Resend 或 SendGrid)时,新用户才会收到邮件邀请。", + "learnHowToSetupEmail": "了解如何设置邮件通知", + "userEmailLabel": "用户邮箱", + "userRoleLabel": "用户角色", + "globalProxyRoleLabel": "全局代理角色", + "globalProxyRoleTooltip": "此角色独立于任何团队/组织特定角色。在设置中配置团队/组织管理员。", + "teamLabel": "团队", + "teamHelp": "若选择,用户将以 user 角色添加到该团队。", + "organizationLabel": "组织", + "organizationHelp": "用户将被添加到所选组织。", + "selectOrganizationPlaceholder": "选择组织", + "metadataLabel": "元数据", + "metadataPlaceholder": "以 JSON 格式输入元数据", + "sendInviteEmailLabel": "发送邀请邮件", + "personalKeyCreation": "个人密钥创建", + "modelsLabel": "模型", + "modelsTooltip": "用户在团队范围外可访问的模型。", + "modelsHelp": "用户在团队范围外可访问的模型。", + "modelsPlaceholder": "选择模型", + "allProxyModels": "所有代理模型", + "noDefaultModels": "无默认模型", + "inviteUserModalTitle": "邀请用户", + "inviteUserSubmitButton": "邀请用户", + "createUserSubmitButton": "创建用户", + "notifications": { + "makingApiCall": "正在调用 API", + "userCreated": "API 用户已创建", + "errorCreatingUser": "创建用户时出错" + } + }, + "debugWarningBanner": { + "message": "性能警告:详细调试模式已启用", + "description": "详细调试日志(LITELLM_LOG=DEBUG)当前已启用。此模式会记录大量诊断信息并显著降低性能,应仅用于排查问题,生产环境请禁用。" + }, + "defaultUserSettings": { + "title": "默认用户设置", + "editSettings": "编辑设置", + "saveChanges": "保存更改", + "noSettings": "暂无可用设置,或您没有查看权限。", + "noSchemaInfo": "暂无 schema 信息", + "noDescriptionAvailable": "暂无描述", + "teamEntry": { + "teamLabel": "团队 {{number}}", + "removeButton": "移除", + "teamIdLabel": "团队 ID", + "teamIdPlaceholder": "输入团队 ID", + "maxBudgetLabel": "团队内最大预算", + "maxBudgetPlaceholder": "可选", + "userRoleLabel": "用户角色", + "addTeamButton": "添加团队" + }, + "displayValues": { + "notSet": "未设置", + "noTeamsAssigned": "未分配团队", + "notSpecified": "未指定", + "noLimit": "无上限", + "userRoleOption": "用户", + "adminRoleOption": "管理员" + }, + "notifications": { + "fetchFailed": "获取 SSO 设置失败", + "updateFailed": "更新设置失败:{{error}}" + } + }, + "guardrailSettingsView": { + "noGuardrailsConfigured": "未配置护栏", + "globalLabel": "全局", + "globalGuardrailAriaLabel": "全局护栏", + "bypassedForTeam": "已为此团队绕过", + "noneConfigured": "未配置", + "teamSpecificLabel": "团队专属", + "cardTitle": "护栏设置", + "cardSubtitle": "应用于此团队的全局和团队专属护栏", + "inlineTitle": "护栏设置" + }, "common": { "save": "保存", "cancel": "取消",