From 552da4110da41ac35021ddfe12cbe27d32e8c469 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 22:25:59 +0900 Subject: [PATCH] feat(ui): i18n for admin settings batch 1 --- .../EditHashicorpVaultModal.tsx | 41 +++-- .../HashicorpVault/HashicorpVault.tsx | 53 ++++-- .../HashicorpVaultEmptyPlaceholder.tsx | 8 +- .../LoggingSettings/LoggingSettings.tsx | 29 ++- .../MCPSemanticFilterSettings.tsx | 60 +++--- .../MCPSemanticFilterTestPanel.tsx | 54 ++++-- .../Modals/AddSSOSettingsModal.tsx | 16 +- .../Modals/BaseSSOSettingsForm.tsx | 171 +++++++++++++----- .../Modals/DeleteSSOSettingsModal.tsx | 23 ++- .../Modals/EditSSOSettingsModal.tsx | 18 +- ui/litellm-dashboard/src/locales/en.json | 159 ++++++++++++++++ ui/litellm-dashboard/src/locales/zh-CN.json | 159 ++++++++++++++++ 12 files changed, 637 insertions(+), 154 deletions(-) diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/HashicorpVault/EditHashicorpVaultModal.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/HashicorpVault/EditHashicorpVaultModal.tsx index 5fa344f3ec5..9c3d072a213 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/HashicorpVault/EditHashicorpVaultModal.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/HashicorpVault/EditHashicorpVaultModal.tsx @@ -5,33 +5,37 @@ import { useUpdateHashicorpVaultConfig } from "@/app/(dashboard)/hooks/configOve import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import NotificationManager from "@/components/molecules/notifications_manager"; import { Button, Divider, Form, Input, Modal, Space, Typography } from "antd"; -import React, { useEffect } from "react"; +import React, { useEffect, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import type { TFunction } from "i18next"; import { SENSITIVE_FIELDS, FIELD_LABELS } from "./constants"; +type TranslateFn = TFunction; + interface FieldGroup { title: string; subtitle?: string; fields: string[]; } -const FIELD_GROUPS: FieldGroup[] = [ +const getFieldGroups = (t: TFunction): FieldGroup[] => [ { - title: "Connection", + title: t("settingsPages.editHashicorpVaultModal.groupConnection"), fields: ["vault_addr", "vault_namespace", "vault_mount_name", "vault_path_prefix"], }, { - title: "Token Authentication", - subtitle: "Use a Vault token to authenticate. Only one auth method is required.", + title: t("settingsPages.editHashicorpVaultModal.groupTokenAuth"), + subtitle: t("settingsPages.editHashicorpVaultModal.groupTokenAuthSubtitle"), fields: ["vault_token"], }, { - title: "AppRole Authentication", - subtitle: "Use AppRole credentials to authenticate. Only one auth method is required.", + title: t("settingsPages.editHashicorpVaultModal.groupAppRoleAuth"), + subtitle: t("settingsPages.editHashicorpVaultModal.groupAppRoleAuthSubtitle"), fields: ["approle_role_id", "approle_secret_id", "approle_mount_path"], }, { - title: "TLS", - subtitle: "Optional client certificate for mTLS.", + title: t("settingsPages.editHashicorpVaultModal.groupTls"), + subtitle: t("settingsPages.editHashicorpVaultModal.groupTlsSubtitle"), fields: ["client_cert", "client_key", "vault_cert_role"], }, ]; @@ -43,11 +47,14 @@ interface EditHashicorpVaultModalProps { } const EditHashicorpVaultModal: React.FC = ({ isVisible, onCancel, onSuccess }) => { + const { t } = useTranslation(); const [form] = Form.useForm(); const { accessToken } = useAuthorized(); const { data } = useHashicorpVaultConfig(); const { mutate, isPending } = useUpdateHashicorpVaultConfig(accessToken); + const fieldGroups = useMemo(() => getFieldGroups(t), [t]); + const schema = data?.field_schema; const properties = schema?.properties ?? {}; const rawValues = data?.values ?? {}; @@ -81,7 +88,7 @@ const EditHashicorpVaultModal: React.FC = ({ isVis mutate(config, { onSuccess: () => { - NotificationManager.success("Hashicorp Vault configuration updated successfully"); + NotificationManager.success(t("settingsPages.editHashicorpVaultModal.saveSuccess")); onSuccess(); }, onError: (err) => { @@ -101,13 +108,15 @@ const EditHashicorpVaultModal: React.FC = ({ isVis const rules = fieldName === "vault_addr" - ? [{ pattern: /^https?:\/\/.+/, message: "Must start with http:// or https://" }] + ? [{ pattern: /^https?:\/\/.+/, message: t("settingsPages.editHashicorpVaultModal.urlPatternMessage") }] : undefined; const isSensitive = SENSITIVE_FIELDS.has(fieldName); const existingValue = rawValues[fieldName]; const hasExistingValue = isSensitive && existingValue != null && existingValue !== ""; - const placeholder = hasExistingValue ? `Leave blank to keep existing (${existingValue})` : fieldSchema?.description; + const placeholder = hasExistingValue + ? t("settingsPages.editHashicorpVaultModal.leaveBlankToKeep", { existing: existingValue }) + : fieldSchema?.description; return ( @@ -118,23 +127,23 @@ const EditHashicorpVaultModal: React.FC = ({ isVis return ( } onCancel={handleCancel} >
- {FIELD_GROUPS.map((group, index) => ( + {fieldGroups.map((group, index) => (
{index > 0 && } diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/HashicorpVault/HashicorpVault.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/HashicorpVault/HashicorpVault.tsx index 569ea49198b..7f5b8318acf 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/HashicorpVault/HashicorpVault.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/HashicorpVault/HashicorpVault.tsx @@ -10,6 +10,7 @@ import NotificationManager from "@/components/molecules/notifications_manager"; import { testHashicorpVaultConnection } from "@/app/(dashboard)/hooks/configOverrides/hashicorpVaultApi"; import { Alert, Button, Card, Descriptions, Flex, Skeleton, Space, Typography } from "antd"; import { Edit, KeyRound, PlugZap, Trash2 } from "lucide-react"; +import { useTranslation } from "react-i18next"; import { SENSITIVE_FIELDS, FIELD_LABELS } from "./constants"; import EditHashicorpVaultModal from "./EditHashicorpVaultModal"; import HashicorpVaultEmptyPlaceholder from "./HashicorpVaultEmptyPlaceholder"; @@ -28,6 +29,7 @@ const descriptionsConfig = { }; export default function HashicorpVault() { + const { t } = useTranslation(); const { accessToken } = useAuthorized(); const { data, isLoading, isError, error } = useHashicorpVaultConfig(); const { mutate: deleteConfig, isPending: isDeleting } = useDeleteHashicorpVaultConfig(accessToken); @@ -46,7 +48,7 @@ export default function HashicorpVault() { setIsTesting(true); try { const result = await testHashicorpVaultConnection(accessToken); - NotificationManager.success(result.message || "Connection to Vault successful!"); + NotificationManager.success(result.message || t("settingsPages.hashicorpVault.connectionSuccess")); } catch (err) { NotificationManager.fromBackend(err); } finally { @@ -57,7 +59,7 @@ export default function HashicorpVault() { const handleDelete = () => { deleteConfig(undefined, { onSuccess: () => { - NotificationManager.success("Hashicorp Vault configuration deleted"); + NotificationManager.success(t("settingsPages.hashicorpVault.deleteSuccess")); setIsDeleteModalOpen(false); }, onError: (err) => { @@ -72,7 +74,11 @@ export default function HashicorpVault() { { [clearingField]: "" }, { onSuccess: () => { - NotificationManager.success(`${FIELD_LABELS[clearingField] ?? clearingField} cleared`); + NotificationManager.success( + t("settingsPages.hashicorpVault.clearSuccess", { + fieldLabel: FIELD_LABELS[clearingField] ?? clearingField, + }), + ); setClearingField(null); }, onError: (err) => { @@ -85,7 +91,7 @@ export default function HashicorpVault() { const renderValue = (key: string) => { const value = rawValues[key]; if (!value) { - return Not configured; + return {t("settingsPages.hashicorpVault.notConfigured")}; } if (SENSITIVE_FIELDS.has(key)) { return ( @@ -112,7 +118,7 @@ export default function HashicorpVault() { return ( - + {detectAuthMethod(rawValues)} {fieldsToShow.map(([key]) => ( @@ -134,7 +140,7 @@ export default function HashicorpVault() { @@ -149,7 +155,7 @@ export default function HashicorpVault() { Hashicorp Vault - Manage secret manager configuration + {t("settingsPages.hashicorpVault.manageSecretManager")}
@@ -157,13 +163,13 @@ export default function HashicorpVault() { {isConfigured && ( <> )} @@ -174,7 +180,7 @@ export default function HashicorpVault() { vault kv put secret/SECRET_NAME key=secret_value @@ -183,7 +189,7 @@ export default function HashicorpVault() { href="https://docs.litellm.ai/docs/secret_managers/hashicorp_vault" target="_blank" > - View documentation + {t("settingsPages.hashicorpVault.viewDocumentation")} } @@ -207,10 +213,12 @@ export default function HashicorpVault() { setIsDeleteModalOpen(false)} onOk={handleDelete} confirmLoading={isDeleting} @@ -218,11 +226,16 @@ export default function HashicorpVault() { setClearingField(null)} onOk={handleClearField} diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/HashicorpVault/HashicorpVaultEmptyPlaceholder.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/HashicorpVault/HashicorpVaultEmptyPlaceholder.tsx index 7839fbe4a4e..59bdd00da00 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/HashicorpVault/HashicorpVaultEmptyPlaceholder.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/HashicorpVault/HashicorpVaultEmptyPlaceholder.tsx @@ -1,4 +1,5 @@ import { Empty, Typography, Button } from "antd"; +import { useTranslation } from "react-i18next"; const { Title, Paragraph } = Typography; @@ -7,21 +8,22 @@ interface HashicorpVaultEmptyPlaceholderProps { } export default function HashicorpVaultEmptyPlaceholder({ onAdd }: HashicorpVaultEmptyPlaceholderProps) { + const { t } = useTranslation(); return (
- No Vault Configuration Found + {t("settingsPages.hashicorpVaultEmptyPlaceholder.noConfigTitle")} - Configure Hashicorp Vault to securely manage provider API keys and secrets for your LiteLLM deployment. + {t("settingsPages.hashicorpVaultEmptyPlaceholder.noConfigDesc")}
} > diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/LoggingSettings/LoggingSettings.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/LoggingSettings/LoggingSettings.tsx index 1c49035d36b..d5c32b34397 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/LoggingSettings/LoggingSettings.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/LoggingSettings/LoggingSettings.tsx @@ -15,8 +15,10 @@ import { parseErrorMessage } from "@/components/shared/errorUtils"; import { ClockCircleOutlined } from "@ant-design/icons"; import { Button, Card, Form, Input, Skeleton, Space, Switch, Typography } from "antd"; import React, { useMemo } from "react"; +import { useTranslation } from "react-i18next"; const LoggingSettings: React.FC = () => { + const { t } = useTranslation(); const [form] = Form.useForm(); const { mutate, isPending } = useStoreRequestInSpendLogs(); const { mutate: deleteField, isPending: isDeletingField } = useDeleteProxyConfigField(); @@ -53,9 +55,11 @@ const LoggingSettings: React.FC = () => { const submitUpdate = () => mutate(updateParams, { - onSuccess: () => NotificationsManager.success("Spend logs settings updated successfully"), + onSuccess: () => NotificationsManager.success(t("settingsPages.loggingSettings.saveSuccess")), onError: (error) => - NotificationsManager.fromBackend("Failed to save spend logs settings: " + parseErrorMessage(error)), + NotificationsManager.fromBackend( + t("settingsPages.loggingSettings.saveFailed", { error: parseErrorMessage(error) }), + ), }); if (hasRetentionPeriod) { @@ -76,10 +80,10 @@ const LoggingSettings: React.FC = () => { }; return ( - + - Proxy-wide settings that control how request and response data are written to spend logs. + {t("settingsPages.loggingSettings.description")} { initialValues={initialValues} > f.field_name === "store_prompts_in_spend_logs")?.field_description || - "When enabled, prompts will be stored in spend logs for tracking and analysis purposes." + t("settingsPages.loggingSettings.storePromptsTooltipDefault") } valuePropName="checked" > @@ -109,23 +113,28 @@ const LoggingSettings: React.FC = () => { f.field_name === "maximum_spend_logs_retention_period")?.field_description || - "Set the maximum retention period for spend logs (e.g., '7d' for 7 days, '30d' for 30 days). Leave empty for no limit." + t("settingsPages.loggingSettings.retentionPeriodTooltipDefault") } > {isLoadingConfig ? ( ) : ( - } /> + } + /> )} diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterSettings.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterSettings.tsx index c62e752565c..7b7fe4bfcfb 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterSettings.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterSettings.tsx @@ -21,6 +21,7 @@ import { } from "antd"; import { QuestionCircleOutlined, CheckCircleOutlined, SaveOutlined } from "@ant-design/icons"; import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; import { fetchAvailableModels, ModelGroup } from "@/components/playground/llm_calls/fetch_models"; import MCPSemanticFilterTestPanel from "./MCPSemanticFilterTestPanel"; import { getCurlCommand, runSemanticFilterTest, TestResult } from "./semanticFilterTestUtils"; @@ -30,6 +31,7 @@ interface MCPSemanticFilterSettingsProps { } export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFilterSettingsProps) { + const { t } = useTranslation(); const { data, isLoading, isError, error } = useMCPSemanticFilterSettings(); const { mutate: updateSettings, @@ -89,9 +91,7 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi setIsDirty(false); setSaveSuccess(true); setTimeout(() => setSaveSuccess(false), 3000); - NotificationManager.success( - "Settings updated successfully. Changes will be applied across all pods within 10 seconds.", - ); + NotificationManager.success(t("settingsPages.mcpSemanticFilterSettings.saveSuccess")); }, onError: (error) => { NotificationManager.fromBackend(error); @@ -117,7 +117,9 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi }; if (!accessToken) { - return
Please log in to configure semantic filter settings.
; + return ( +
{t("settingsPages.mcpSemanticFilterSettings.loginRequired")}
+ ); } return ( @@ -127,7 +129,7 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi ) : isError ? ( @@ -135,8 +137,8 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi <> @@ -144,7 +146,7 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi {saveSuccess && ( } showIcon closable @@ -155,7 +157,7 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi {updateError && ( @@ -177,8 +179,10 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi name="enabled" label={ - Enable Semantic Filtering - + + {t("settingsPages.mcpSemanticFilterSettings.enableLabel")} + + @@ -193,13 +197,15 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi
- + - Embedding Model - + + {t("settingsPages.mcpSemanticFilterSettings.embeddingModelLabel")} + + @@ -210,11 +216,19 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi label: model.model_group, value: model.model_group, }))} - placeholder={loadingModels ? "Loading models..." : "Select embedding model"} + placeholder={ + loadingModels + ? t("settingsPages.mcpSemanticFilterSettings.loadingModels") + : t("settingsPages.mcpSemanticFilterSettings.selectEmbeddingModel") + } showSearch disabled={isUpdating || loadingModels} loading={loadingModels} - notFoundContent={loadingModels ? "Loading..." : "No embedding models available"} + notFoundContent={ + loadingModels + ? t("common.loading") + : t("settingsPages.mcpSemanticFilterSettings.noEmbeddingModels") + } /> @@ -222,8 +236,10 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi name="top_k" label={ - Top K Results - + + {t("settingsPages.mcpSemanticFilterSettings.topKLabel")} + + @@ -236,8 +252,10 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi name="similarity_threshold" label={ - Similarity Threshold - + + {t("settingsPages.mcpSemanticFilterSettings.similarityThresholdLabel")} + + @@ -267,7 +285,7 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi loading={isUpdating} disabled={!isDirty} > - Save Settings + {t("settingsPages.mcpSemanticFilterSettings.saveSettingsButton")} diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.tsx index d8dc675361a..037931960b6 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.tsx @@ -1,5 +1,6 @@ import { CodeOutlined, PlayCircleOutlined } from "@ant-design/icons"; import { Alert, Button, Card, Input, Space, Tabs, Typography } from "antd"; +import { useTranslation } from "react-i18next"; import ModelSelector from "@/components/common_components/ModelSelector"; import { TestResult } from "./semanticFilterTestUtils"; @@ -28,22 +29,23 @@ export default function MCPSemanticFilterTestPanel({ testResult, curlCommand, }: MCPSemanticFilterTestPanelProps) { + const { t } = useTranslation(); return ( - +
- Test Query + {t("settingsPages.mcpSemanticFilterTestPanel.testQueryLabel")} setTestQuery(e.target.value)} rows={4} @@ -58,7 +60,7 @@ export default function MCPSemanticFilterTestPanel({ onChange={setTestModel} disabled={isTesting} showLabel={true} - labelText="Select Model" + labelText={t("settingsPages.mcpSemanticFilterTestPanel.selectModelLabel")} />
@@ -70,31 +72,37 @@ export default function MCPSemanticFilterTestPanel({ disabled={!testQuery || !testModel || !filterEnabled} block > - Test Filter + {t("settingsPages.mcpSemanticFilterTestPanel.testFilterButton")} {!filterEnabled && ( )} {testResult && (
- Results + + {t("settingsPages.mcpSemanticFilterTestPanel.resultsTitle")} +
- Selected Tools: + {t("settingsPages.mcpSemanticFilterTestPanel.selectedToolsLabel")}
    {testResult.tools.map((tool, index) => ( @@ -111,30 +119,36 @@ export default function MCPSemanticFilterTestPanel({ }, { key: "api", - label: "API Usage", + label: t("settingsPages.mcpSemanticFilterTestPanel.apiTabLabel"), children: (
    - API Usage + + {t("settingsPages.mcpSemanticFilterTestPanel.apiUsageTitle")} + - Use this curl command to test the semantic filter with your current configuration. + {t("settingsPages.mcpSemanticFilterTestPanel.apiUsageDesc")} - Response headers to check: + {t("settingsPages.mcpSemanticFilterTestPanel.responseHeadersLabel")}
    • - x-litellm-semantic-filter: shows total tools → selected tools + + {t("settingsPages.mcpSemanticFilterTestPanel.semanticFilterHeader")} + - Example: 10→3 + {t("settingsPages.mcpSemanticFilterTestPanel.semanticFilterHeaderExample")}
    • - x-litellm-semantic-filter-tools: CSV of selected tool names + + {t("settingsPages.mcpSemanticFilterTestPanel.semanticFilterToolsHeader")} + - Example: wikipedia-fetch,github-search,slack-post + {t("settingsPages.mcpSemanticFilterTestPanel.semanticFilterToolsHeaderExample")}
    diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/Modals/AddSSOSettingsModal.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/Modals/AddSSOSettingsModal.tsx index 7af6240b19e..786ab3b1d36 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/Modals/AddSSOSettingsModal.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/Modals/AddSSOSettingsModal.tsx @@ -4,6 +4,7 @@ import NotificationsManager from "@/components/molecules/notifications_manager"; import { parseErrorMessage } from "@/components/shared/errorUtils"; import { Button, Form, Modal, Space } from "antd"; import React from "react"; +import { useTranslation } from "react-i18next"; import BaseSSOSettingsForm from "./BaseSSOSettingsForm"; import { useEditSSOSettings } from "@/app/(dashboard)/hooks/sso/useEditSSOSettings"; import { processSSOSettingsPayload } from "../utils"; @@ -15,6 +16,7 @@ interface AddSSOSettingsModalProps { } const AddSSOSettingsModal: React.FC = ({ isVisible, onCancel, onSuccess }) => { + const { t } = useTranslation(); const [form] = Form.useForm(); const { mutateAsync, isPending } = useEditSSOSettings(); @@ -24,11 +26,13 @@ const AddSSOSettingsModal: React.FC = ({ isVisible, on await mutateAsync(payload, { onSuccess: () => { - NotificationsManager.success("SSO settings added successfully"); + NotificationsManager.success(t("settingsPages.addSSOSettingsModal.addSuccess")); onSuccess(); }, onError: (error) => { - NotificationsManager.fromBackend("Failed to save SSO settings: " + parseErrorMessage(error)); + NotificationsManager.fromBackend( + t("settingsPages.addSSOSettingsModal.saveFailed", { error: parseErrorMessage(error) }), + ); }, }); }; @@ -40,16 +44,18 @@ const AddSSOSettingsModal: React.FC = ({ isVisible, on return ( } diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/Modals/BaseSSOSettingsForm.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/Modals/BaseSSOSettingsForm.tsx index d16b04466e0..908f3037265 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/Modals/BaseSSOSettingsForm.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/Modals/BaseSSOSettingsForm.tsx @@ -3,8 +3,11 @@ import { TextInput } from "@tremor/react"; import { Checkbox, Form, Input, Select } from "antd"; import React from "react"; +import { useTranslation } from "react-i18next"; import { ssoProviderLogoMap, ssoProviderDisplayNames } from "../constants"; +type TranslateFn = (key: string, options?: Record) => string; + export interface BaseSSOSettingsFormProps { form: any; // Replace with proper Form type if available onFormSubmit: (formValues: Record) => Promise; @@ -14,22 +17,28 @@ export interface BaseSSOSettingsFormProps { export interface SSOProviderConfig { envVarMap: Record; fields: Array<{ - label: string; + labelKey: string; name: string; placeholder?: string; }>; } // Define configurations for each SSO provider -export const ssoProviderConfigs: Record = { +export const getSSOProviderConfigs = (t: TranslateFn): Record => ({ google: { envVarMap: { google_client_id: "GOOGLE_CLIENT_ID", google_client_secret: "GOOGLE_CLIENT_SECRET", }, fields: [ - { label: "Google Client ID", name: "google_client_id" }, - { label: "Google Client Secret", name: "google_client_secret" }, + { + labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.googleClientId"), + name: "google_client_id", + }, + { + labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.googleClientSecret"), + name: "google_client_secret", + }, ], }, microsoft: { @@ -39,9 +48,18 @@ export const ssoProviderConfigs: Record = { microsoft_tenant: "MICROSOFT_TENANT", }, fields: [ - { label: "Microsoft Client ID", name: "microsoft_client_id" }, - { label: "Microsoft Client Secret", name: "microsoft_client_secret" }, - { label: "Microsoft Tenant", name: "microsoft_tenant" }, + { + labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.microsoftClientId"), + name: "microsoft_client_id", + }, + { + labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.microsoftClientSecret"), + name: "microsoft_client_secret", + }, + { + labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.microsoftTenant"), + name: "microsoft_tenant", + }, ], }, okta: { @@ -53,16 +71,26 @@ export const ssoProviderConfigs: Record = { generic_userinfo_endpoint: "GENERIC_USERINFO_ENDPOINT", }, fields: [ - { label: "Generic Client ID", name: "generic_client_id" }, - { label: "Generic Client Secret", name: "generic_client_secret" }, { - label: "Authorization Endpoint", + labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.genericClientId"), + name: "generic_client_id", + }, + { + labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.genericClientSecret"), + name: "generic_client_secret", + }, + { + labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.authorizationEndpoint"), name: "generic_authorization_endpoint", placeholder: "https://your-domain/authorize", }, - { label: "Token Endpoint", name: "generic_token_endpoint", placeholder: "https://your-domain/token" }, { - label: "Userinfo Endpoint", + labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.tokenEndpoint"), + name: "generic_token_endpoint", + placeholder: "https://your-domain/token", + }, + { + labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.userinfoEndpoint"), name: "generic_userinfo_endpoint", placeholder: "https://your-domain/userinfo", }, @@ -77,26 +105,47 @@ export const ssoProviderConfigs: Record = { generic_userinfo_endpoint: "GENERIC_USERINFO_ENDPOINT", }, fields: [ - { label: "Generic Client ID", name: "generic_client_id" }, - { label: "Generic Client Secret", name: "generic_client_secret" }, - { label: "Authorization Endpoint", name: "generic_authorization_endpoint" }, - { label: "Token Endpoint", name: "generic_token_endpoint" }, - { label: "Userinfo Endpoint", name: "generic_userinfo_endpoint" }, + { + labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.genericClientId"), + name: "generic_client_id", + }, + { + labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.genericClientSecret"), + name: "generic_client_secret", + }, + { + labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.authorizationEndpoint"), + name: "generic_authorization_endpoint", + }, + { + labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.tokenEndpoint"), + name: "generic_token_endpoint", + }, + { + labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.userinfoEndpoint"), + name: "generic_userinfo_endpoint", + }, ], }, -}; +}); // Helper function to render provider fields -export const renderProviderFields = (provider: string) => { - const config = ssoProviderConfigs[provider]; +export const renderProviderFields = (provider: string, t: TranslateFn = (k: string) => k as any) => { + const configs = getSSOProviderConfigs(t); + const config = configs[provider]; if (!config) return null; return config.fields.map((field) => ( {field.name.includes("client") ? : } @@ -104,13 +153,14 @@ export const renderProviderFields = (provider: string) => { }; const BaseSSOSettingsForm: React.FC = ({ form, onFormSubmit }) => { + const { t } = useTranslation(); return (
    - Internal Viewer - Internal User - Admin Viewer - Proxy Admin + + {t("settingsPages.baseSSOSettingsForm.roleInternalViewer")} + + + {t("settingsPages.baseSSOSettingsForm.roleInternalUser")} + + + {t("settingsPages.baseSSOSettingsForm.roleAdminViewer")} + + + {t("settingsPages.baseSSOSettingsForm.roleProxyAdmin")} + - + - + - + - + @@ -259,7 +334,11 @@ const BaseSSOSettingsForm: React.FC = ({ form, onFormS {({ getFieldValue }) => { const provider = getFieldValue("sso_provider"); return provider === "okta" || provider === "generic" ? ( - + ) : null; @@ -279,9 +358,9 @@ const BaseSSOSettingsForm: React.FC = ({ form, onFormS const supportsTeamMappings = provider === "okta" || provider === "generic"; return useTeamMappings && supportsTeamMappings ? ( diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/Modals/DeleteSSOSettingsModal.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/Modals/DeleteSSOSettingsModal.tsx index 2656c861aa8..2661f5d4de6 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/Modals/DeleteSSOSettingsModal.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/Modals/DeleteSSOSettingsModal.tsx @@ -1,6 +1,7 @@ import { useEditSSOSettings } from "@/app/(dashboard)/hooks/sso/useEditSSOSettings"; import { useSSOSettings } from "@/app/(dashboard)/hooks/sso/useSSOSettings"; import React from "react"; +import { useTranslation } from "react-i18next"; import DeleteResourceModal from "../../../../common_components/DeleteResourceModal"; import NotificationsManager from "../../../../molecules/notifications_manager"; import { parseErrorMessage } from "../../../../shared/errorUtils"; @@ -13,6 +14,7 @@ interface DeleteSSOSettingsModalProps { } const DeleteSSOSettingsModal: React.FC = ({ isVisible, onCancel, onSuccess }) => { + const { t } = useTranslation(); const { data: ssoSettings } = useSSOSettings(); const { mutateAsync: editSSOSettings, isPending: isEditingSSOSettings } = useEditSSOSettings(); @@ -38,12 +40,14 @@ const DeleteSSOSettingsModal: React.FC = ({ isVisib await editSSOSettings(clearSettings, { onSuccess: () => { - NotificationsManager.success("SSO settings cleared successfully"); + NotificationsManager.success(t("settingsPages.deleteSSOSettingsModal.clearSuccess")); onCancel(); onSuccess(); }, onError: (error) => { - NotificationsManager.fromBackend("Failed to clear SSO settings: " + parseErrorMessage(error)); + NotificationsManager.fromBackend( + t("settingsPages.deleteSSOSettingsModal.clearFailed", { error: parseErrorMessage(error) }), + ); }, }); }; @@ -51,12 +55,17 @@ const DeleteSSOSettingsModal: React.FC = ({ isVisib return ( = ({ isVisible, onCancel, onSuccess }) => { + const { t } = useTranslation(); const [form] = Form.useForm(); // Use react-query hooks for SSO settings @@ -104,16 +106,20 @@ const EditSSOSettingsModal: React.FC = ({ isVisible, await mutateAsync(payload, { onSuccess: () => { - NotificationsManager.success("SSO settings updated successfully"); + NotificationsManager.success(t("settingsPages.editSSOSettingsModal.updateSuccess")); onSuccess(); }, onError: (error) => { - NotificationsManager.fromBackend("Failed to save SSO settings: " + parseErrorMessage(error)); + NotificationsManager.fromBackend( + t("settingsPages.editSSOSettingsModal.saveFailed", { error: parseErrorMessage(error) }), + ); }, }); } catch (error) { // Handle processing errors gracefully - NotificationsManager.fromBackend("Failed to process SSO settings: " + parseErrorMessage(error)); + NotificationsManager.fromBackend( + t("settingsPages.editSSOSettingsModal.processFailed", { error: parseErrorMessage(error) }), + ); } }; @@ -124,16 +130,16 @@ const EditSSOSettingsModal: React.FC = ({ isVisible, return ( } diff --git a/ui/litellm-dashboard/src/locales/en.json b/ui/litellm-dashboard/src/locales/en.json index 23ebf803dc2..32647c5ee1f 100644 --- a/ui/litellm-dashboard/src/locales/en.json +++ b/ui/litellm-dashboard/src/locales/en.json @@ -4195,5 +4195,164 @@ "verdictFalsePositive": "False positive — incorrectly blocked", "llmResponse": "LLM response:" } + }, + "settingsPages": { + "editHashicorpVaultModal": { + "title": "Edit Hashicorp Vault Configuration", + "groupConnection": "Connection", + "groupTokenAuth": "Token Authentication", + "groupTokenAuthSubtitle": "Use a Vault token to authenticate. Only one auth method is required.", + "groupAppRoleAuth": "AppRole Authentication", + "groupAppRoleAuthSubtitle": "Use AppRole credentials to authenticate. Only one auth method is required.", + "groupTls": "TLS", + "groupTlsSubtitle": "Optional client certificate for mTLS.", + "urlPatternMessage": "Must start with http:// or https://", + "leaveBlankToKeep": "Leave blank to keep existing ({{existing}})", + "saveSuccess": "Hashicorp Vault configuration updated successfully" + }, + "hashicorpVault": { + "manageSecretManager": "Manage secret manager configuration", + "testConnection": "Test Connection", + "editConfiguration": "Edit Configuration", + "deleteConfiguration": "Delete Configuration", + "secretsKeyHint": "Secrets must be stored with the field name \"key\"", + "viewDocumentation": "View documentation", + "authMethod": "Auth Method", + "notConfigured": "Not configured", + "loadError": "Could not load Hashicorp Vault configuration", + "deleteTitle": "Delete Hashicorp Vault Configuration?", + "deleteMessage": "Models using Vault secrets will lose access to their API keys until a new configuration is saved.", + "deleteResourceTitle": "Vault Configuration", + "vaultAddressLabel": "Vault Address", + "clearTitle": "Clear {{fieldLabel}}?", + "clearMessage": "This will remove the stored value.", + "clearResourceTitle": "Field", + "clearFieldLabel": "Field", + "deleteSuccess": "Hashicorp Vault configuration deleted", + "clearSuccess": "{{fieldLabel}} cleared", + "connectionSuccess": "Connection to Vault successful!" + }, + "hashicorpVaultEmptyPlaceholder": { + "noConfigTitle": "No Vault Configuration Found", + "noConfigDesc": "Configure Hashicorp Vault to securely manage provider API keys and secrets for your LiteLLM deployment.", + "configureButton": "Configure Vault" + }, + "loggingSettings": { + "cardTitle": "Logging Settings", + "description": "Proxy-wide settings that control how request and response data are written to spend logs.", + "storePromptsLabel": "Store Prompts in Spend Logs", + "storePromptsTooltipDefault": "When enabled, prompts will be stored in spend logs for tracking and analysis purposes.", + "retentionPeriodLabel": "Maximum Spend Logs Retention Period (Optional)", + "retentionPeriodTooltipDefault": "Set the maximum retention period for spend logs (e.g., '7d' for 7 days, '30d' for 30 days). Leave empty for no limit.", + "retentionPeriodPlaceholder": "e.g., 7d, 30d", + "saveSettingsButton": "Save Settings", + "saveSuccess": "Spend logs settings updated successfully", + "saveFailed": "Failed to save spend logs settings: {{error}}" + }, + "mcpSemanticFilterSettings": { + "loginRequired": "Please log in to configure semantic filter settings.", + "loadError": "Could not load MCP Semantic Filter settings", + "infoTitle": "Semantic Tool Filtering", + "infoDesc": "Filter MCP tools semantically based on query relevance. This reduces context window size and improves tool selection accuracy. Click 'Save Settings' to apply changes across all pods (takes effect within 10 seconds).", + "savedSuccessMessage": "Settings saved successfully", + "updateErrorTitle": "Could not update settings", + "enableLabel": "Enable Semantic Filtering", + "enableTooltip": "When enabled, only the most relevant MCP tools will be included in requests based on semantic similarity", + "configCardTitle": "Configuration", + "embeddingModelLabel": "Embedding Model", + "embeddingModelTooltip": "The model used to generate embeddings for semantic matching", + "loadingModels": "Loading models...", + "selectEmbeddingModel": "Select embedding model", + "noEmbeddingModels": "No embedding models available", + "topKLabel": "Top K Results", + "topKTooltip": "Maximum number of tools to return after filtering", + "similarityThresholdLabel": "Similarity Threshold", + "similarityThresholdTooltip": "Minimum similarity score (0-1) for a tool to be included", + "saveSettingsButton": "Save Settings", + "saveSuccess": "Settings updated successfully. Changes will be applied across all pods within 10 seconds." + }, + "mcpSemanticFilterTestPanel": { + "testConfigCardTitle": "Test Configuration", + "testTabLabel": "Test", + "apiTabLabel": "API Usage", + "testQueryLabel": "Test Query", + "testQueryPlaceholder": "Enter a test query to see which tools would be selected...", + "selectModelLabel": "Select Model", + "testFilterButton": "Test Filter", + "filterDisabledTitle": "Semantic filtering is disabled", + "filterDisabledDesc": "Enable semantic filtering and save settings to test the filter.", + "resultsTitle": "Results", + "toolsSelectedMessage": "{{selectedTools}} tools selected", + "filteredFromMessage": "Filtered from {{totalTools}} available tools", + "selectedToolsLabel": "Selected Tools:", + "apiUsageTitle": "API Usage", + "apiUsageDesc": "Use this curl command to test the semantic filter with your current configuration.", + "responseHeadersLabel": "Response headers to check:", + "semanticFilterHeader": "x-litellm-semantic-filter: shows total tools → selected tools", + "semanticFilterHeaderExample": "Example: 10→3", + "semanticFilterToolsHeader": "x-litellm-semantic-filter-tools: CSV of selected tool names", + "semanticFilterToolsHeaderExample": "Example: wikipedia-fetch,github-search,slack-post" + }, + "addSSOSettingsModal": { + "title": "Add SSO", + "addingButton": "Adding...", + "addSSOButton": "Add SSO", + "addSuccess": "SSO settings added successfully", + "saveFailed": "Failed to save SSO settings: {{error}}" + }, + "baseSSOSettingsForm": { + "ssoProviderLabel": "SSO Provider", + "ssoProviderRequired": "Please select an SSO provider", + "proxyAdminEmailLabel": "Proxy Admin Email", + "proxyAdminEmailRequired": "Please enter the email of the proxy admin", + "proxyBaseUrlLabel": "Proxy Base URL", + "proxyBaseUrlRequired": "Please enter the proxy base url", + "proxyBaseUrlInvalidScheme": "URL must start with http:// or https://", + "proxyBaseUrlTrailingSlash": "URL must not end with a trailing slash", + "useRoleMappingsLabel": "Use Role Mappings", + "groupClaimLabel": "Group Claim", + "groupClaimRequired": "Please enter the group claim", + "defaultRoleLabel": "Default Role", + "roleInternalViewer": "Internal Viewer", + "roleInternalUser": "Internal User", + "roleAdminViewer": "Admin Viewer", + "roleProxyAdmin": "Proxy Admin", + "proxyAdminTeamsLabel": "Proxy Admin Teams", + "adminViewerTeamsLabel": "Admin Viewer Teams", + "internalUserTeamsLabel": "Internal User Teams", + "internalViewerTeamsLabel": "Internal Viewer Teams", + "useTeamMappingsLabel": "Use Team Mappings", + "teamIdsJwtFieldLabel": "Team IDs JWT Field", + "teamIdsJwtFieldRequired": "Please enter the team IDs JWT field", + "fieldRequired": "Please enter the {{label}}", + "fieldLabels": { + "googleClientId": "Google Client ID", + "googleClientSecret": "Google Client Secret", + "microsoftClientId": "Microsoft Client ID", + "microsoftClientSecret": "Microsoft Client Secret", + "microsoftTenant": "Microsoft Tenant", + "genericClientId": "Generic Client ID", + "genericClientSecret": "Generic Client Secret", + "authorizationEndpoint": "Authorization Endpoint", + "tokenEndpoint": "Token Endpoint", + "userinfoEndpoint": "Userinfo Endpoint" + } + }, + "deleteSSOSettingsModal": { + "title": "Confirm Clear SSO Settings", + "alertMessage": "This action cannot be undone.", + "message": "Are you sure you want to clear all SSO settings? Users will no longer be able to login using SSO after this change.", + "resourceTitle": "SSO Settings", + "providerLabel": "Provider", + "providerDefault": "Generic", + "clearSuccess": "SSO settings cleared successfully", + "clearFailed": "Failed to clear SSO settings: {{error}}" + }, + "editSSOSettingsModal": { + "title": "Edit SSO Settings", + "updateSuccess": "SSO settings updated successfully", + "saveFailed": "Failed to save SSO settings: {{error}}", + "processFailed": "Failed to process SSO settings: {{error}}" + } } } diff --git a/ui/litellm-dashboard/src/locales/zh-CN.json b/ui/litellm-dashboard/src/locales/zh-CN.json index 897529d26ac..92ffcd67c0a 100644 --- a/ui/litellm-dashboard/src/locales/zh-CN.json +++ b/ui/litellm-dashboard/src/locales/zh-CN.json @@ -4195,5 +4195,164 @@ "verdictFalsePositive": "误报 — 被错误拦截", "llmResponse": "模型响应:" } + }, + "settingsPages": { + "editHashicorpVaultModal": { + "title": "编辑 Hashicorp Vault 配置", + "groupConnection": "连接", + "groupTokenAuth": "Token 认证", + "groupTokenAuthSubtitle": "使用 Vault Token 进行认证。只需配置一种认证方式。", + "groupAppRoleAuth": "AppRole 认证", + "groupAppRoleAuthSubtitle": "使用 AppRole 凭据进行认证。只需配置一种认证方式。", + "groupTls": "TLS", + "groupTlsSubtitle": "用于 mTLS 的可选客户端证书。", + "urlPatternMessage": "必须以 http:// 或 https:// 开头", + "leaveBlankToKeep": "留空以保留现有值({{existing}})", + "saveSuccess": "Hashicorp Vault 配置更新成功" + }, + "hashicorpVault": { + "manageSecretManager": "管理密钥管理器配置", + "testConnection": "测试连接", + "editConfiguration": "编辑配置", + "deleteConfiguration": "删除配置", + "secretsKeyHint": "密钥必须以字段名 \"key\" 存储", + "viewDocumentation": "查看文档", + "authMethod": "认证方式", + "notConfigured": "未配置", + "loadError": "无法加载 Hashicorp Vault 配置", + "deleteTitle": "删除 Hashicorp Vault 配置?", + "deleteMessage": "使用 Vault 密钥的模型将无法访问其 API 密钥,直到保存新配置为止。", + "deleteResourceTitle": "Vault 配置", + "vaultAddressLabel": "Vault 地址", + "clearTitle": "清除 {{fieldLabel}}?", + "clearMessage": "此操作将删除已存储的值。", + "clearResourceTitle": "字段", + "clearFieldLabel": "字段", + "deleteSuccess": "Hashicorp Vault 配置已删除", + "clearSuccess": "{{fieldLabel}} 已清除", + "connectionSuccess": "Vault 连接成功!" + }, + "hashicorpVaultEmptyPlaceholder": { + "noConfigTitle": "未找到 Vault 配置", + "noConfigDesc": "配置 Hashicorp Vault 以安全管理您的 LiteLLM 部署的提供商 API 密钥和密钥。", + "configureButton": "配置 Vault" + }, + "loggingSettings": { + "cardTitle": "日志设置", + "description": "控制请求和响应数据写入花费日志方式的代理全局设置。", + "storePromptsLabel": "在花费日志中存储提示词", + "storePromptsTooltipDefault": "启用后,提示词将存储在花费日志中以用于跟踪和分析。", + "retentionPeriodLabel": "花费日志最大保留期(可选)", + "retentionPeriodTooltipDefault": "设置花费日志的最大保留期(例如 '7d' 表示 7 天,'30d' 表示 30 天)。留空表示无限制。", + "retentionPeriodPlaceholder": "例如:7d、30d", + "saveSettingsButton": "保存设置", + "saveSuccess": "花费日志设置更新成功", + "saveFailed": "保存花费日志设置失败:{{error}}" + }, + "mcpSemanticFilterSettings": { + "loginRequired": "请登录以配置语义过滤器设置。", + "loadError": "无法加载 MCP 语义过滤器设置", + "infoTitle": "语义工具过滤", + "infoDesc": "基于查询相关性对 MCP 工具进行语义过滤。这可以减少上下文窗口大小并提高工具选择准确性。点击「保存设置」以在所有 Pod 上应用更改(10 秒内生效)。", + "savedSuccessMessage": "设置保存成功", + "updateErrorTitle": "无法更新设置", + "enableLabel": "启用语义过滤", + "enableTooltip": "启用后,请求中将只包含基于语义相似度最相关的 MCP 工具", + "configCardTitle": "配置", + "embeddingModelLabel": "嵌入模型", + "embeddingModelTooltip": "用于生成语义匹配嵌入向量的模型", + "loadingModels": "加载模型中...", + "selectEmbeddingModel": "选择嵌入模型", + "noEmbeddingModels": "没有可用的嵌入模型", + "topKLabel": "Top K 结果数", + "topKTooltip": "过滤后返回的最大工具数量", + "similarityThresholdLabel": "相似度阈值", + "similarityThresholdTooltip": "工具被包含所需的最低相似度分数(0-1)", + "saveSettingsButton": "保存设置", + "saveSuccess": "设置更新成功。更改将在 10 秒内应用到所有 Pod。" + }, + "mcpSemanticFilterTestPanel": { + "testConfigCardTitle": "测试配置", + "testTabLabel": "测试", + "apiTabLabel": "API 用法", + "testQueryLabel": "测试查询", + "testQueryPlaceholder": "输入测试查询以查看哪些工具将被选中...", + "selectModelLabel": "选择模型", + "testFilterButton": "测试过滤器", + "filterDisabledTitle": "语义过滤已禁用", + "filterDisabledDesc": "请启用语义过滤并保存设置后再测试过滤器。", + "resultsTitle": "结果", + "toolsSelectedMessage": "已选中 {{selectedTools}} 个工具", + "filteredFromMessage": "从 {{totalTools}} 个可用工具中过滤", + "selectedToolsLabel": "已选工具:", + "apiUsageTitle": "API 用法", + "apiUsageDesc": "使用此 curl 命令以当前配置测试语义过滤器。", + "responseHeadersLabel": "需检查的响应头:", + "semanticFilterHeader": "x-litellm-semantic-filter:显示总工具数 → 已选工具数", + "semanticFilterHeaderExample": "示例:10→3", + "semanticFilterToolsHeader": "x-litellm-semantic-filter-tools:已选工具名称的 CSV", + "semanticFilterToolsHeaderExample": "示例:wikipedia-fetch,github-search,slack-post" + }, + "addSSOSettingsModal": { + "title": "添加 SSO", + "addingButton": "添加中...", + "addSSOButton": "添加 SSO", + "addSuccess": "SSO 设置添加成功", + "saveFailed": "保存 SSO 设置失败:{{error}}" + }, + "baseSSOSettingsForm": { + "ssoProviderLabel": "SSO 提供商", + "ssoProviderRequired": "请选择一个 SSO 提供商", + "proxyAdminEmailLabel": "代理管理员邮箱", + "proxyAdminEmailRequired": "请输入代理管理员的邮箱", + "proxyBaseUrlLabel": "代理基础 URL", + "proxyBaseUrlRequired": "请输入代理基础 URL", + "proxyBaseUrlInvalidScheme": "URL 必须以 http:// 或 https:// 开头", + "proxyBaseUrlTrailingSlash": "URL 末尾不能有斜杠", + "useRoleMappingsLabel": "使用角色映射", + "groupClaimLabel": "组声明", + "groupClaimRequired": "请输入组声明", + "defaultRoleLabel": "默认角色", + "roleInternalViewer": "内部查看者", + "roleInternalUser": "内部用户", + "roleAdminViewer": "管理员查看者", + "roleProxyAdmin": "代理管理员", + "proxyAdminTeamsLabel": "代理管理员团队", + "adminViewerTeamsLabel": "管理员查看者团队", + "internalUserTeamsLabel": "内部用户团队", + "internalViewerTeamsLabel": "内部查看者团队", + "useTeamMappingsLabel": "使用团队映射", + "teamIdsJwtFieldLabel": "团队 ID JWT 字段", + "teamIdsJwtFieldRequired": "请输入团队 ID JWT 字段", + "fieldRequired": "请输入{{label}}", + "fieldLabels": { + "googleClientId": "Google 客户端 ID", + "googleClientSecret": "Google 客户端密钥", + "microsoftClientId": "Microsoft 客户端 ID", + "microsoftClientSecret": "Microsoft 客户端密钥", + "microsoftTenant": "Microsoft 租户", + "genericClientId": "通用客户端 ID", + "genericClientSecret": "通用客户端密钥", + "authorizationEndpoint": "授权端点", + "tokenEndpoint": "Token 端点", + "userinfoEndpoint": "用户信息端点" + } + }, + "deleteSSOSettingsModal": { + "title": "确认清除 SSO 设置", + "alertMessage": "此操作无法撤销。", + "message": "确定要清除所有 SSO 设置吗?此更改后用户将无法再使用 SSO 登录。", + "resourceTitle": "SSO 设置", + "providerLabel": "提供商", + "providerDefault": "通用", + "clearSuccess": "SSO 设置已成功清除", + "clearFailed": "清除 SSO 设置失败:{{error}}" + }, + "editSSOSettingsModal": { + "title": "编辑 SSO 设置", + "updateSuccess": "SSO 设置更新成功", + "saveFailed": "保存 SSO 设置失败:{{error}}", + "processFailed": "处理 SSO 设置失败:{{error}}" + } } }