From b60e82e36441a14328b67dce6f5de3f68afa6c84 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Sat, 31 Oct 2026 11:24:38 -0700 Subject: [PATCH] refactor(ui): move MCP text onto i18n Route the page-authored English on /mcp-servers, /mcp/oauth/callback and the shared MCP permission, server selector and BYOK components through t() and Trans, with 1,094 messages added to en.json under mcpServers, mcpServerManagement, mcpTools and mcpOAuthCallback. The rendered English is unchanged. --- .../_components/AwsSigV4Fields.tsx | 273 +-- .../_components/CreateMCPServer.tsx | 134 +- .../_components/DcrBridgeToggle.tsx | 6 +- .../_components/EnvVarsSection.tsx | 73 +- .../_components/IdJagFormFields.tsx | 88 +- .../_components/ImportMCPServers.tsx | 25 +- .../_components/MCPGatewaySessionsTab.tsx | 146 +- .../_components/MCPLogoSelector.tsx | 19 +- .../_components/MCPNetworkSettings.tsx | 115 +- .../_components/MCPPermissionManagement.tsx | 118 +- .../mcp-servers/_components/MCPServerCard.tsx | 103 +- .../MCPServerUserCredentialsPanel.tsx | 77 +- .../_components/MCPStandardsSettings.tsx | 57 +- .../_components/MCPSubmissionsTab.tsx | 153 +- .../_components/MCPToolsetTableColumns.tsx | 55 +- .../_components/MCPToolsetsTab.tsx | 160 +- .../_components/OAuthFormFields.tsx | 204 +- .../_components/OpenAPIFormSection.tsx | 8 +- .../_components/OpenAPIQuickPicker.tsx | 10 +- .../_components/OpenApiByokFields.tsx | 23 +- .../PassthroughAuthorizeSection.tsx | 84 +- .../_components/StdioAvailability.tsx | 58 +- .../_components/StdioConfiguration.tsx | 10 +- .../TokenEndpointAuthMethodField.tsx | 83 +- .../_components/TokenExchangeFormFields.tsx | 74 +- .../_components/ToolArgumentsForm.tsx | 68 +- .../mcp-servers/_components/ToolTestPanel.tsx | 76 +- .../_components/TruePassthroughWarning.tsx | 11 +- .../_components/UpstreamTokenHeaderField.tsx | 46 +- .../_components/UserEnvVarsModal.tsx | 67 +- .../_components/duplicateServerCheck.ts | 3 +- .../_components/editServerPayload.ts | 18 +- .../_components/editToolPreview.ts | 6 +- .../_components/importConnectorConfig.ts | 21 +- .../mcp-servers/_components/mcpFieldRules.ts | 7 +- .../mcp-servers/_components/mcp_connect.tsx | 245 ++- .../_components/mcp_connection_status.tsx | 42 +- .../mcp-servers/_components/mcp_discovery.tsx | 28 +- .../_components/mcp_server_cost_config.tsx | 49 +- .../_components/mcp_server_cost_display.tsx | 24 +- .../_components/mcp_server_edit.tsx | 194 +- .../_components/mcp_server_view.tsx | 122 +- .../mcp-servers/_components/mcp_servers.tsx | 121 +- .../_components/mcp_tool_configuration.tsx | 90 +- .../mcp-servers/_components/mcp_tools.tsx | 66 +- .../_components/toolCallArguments.ts | 22 +- .../mcp-servers/_components/utils.tsx | 34 +- .../src/app/mcp/oauth/callback/page.tsx | 15 +- .../MCPSemanticFilterSettings.tsx | 82 +- .../MCPSemanticFilterTestPanel.tsx | 67 +- .../semanticFilterTestUtils.ts | 46 +- .../MCPToolSearchSettings.tsx | 51 +- .../MCPServerSelector.tsx | 24 +- .../MCPToolPermissions.tsx | 91 +- .../mcp_tools/ByokCredentialModal.tsx | 64 +- .../mcp_tools/McpCrudPermissionPanel.tsx | 27 +- .../src/hooks/useMcpOAuthFlow.tsx | 18 +- .../src/hooks/useTestMCPConnection.tsx | 4 +- .../src/hooks/useToolsOAuthFlow.tsx | 8 +- ui/litellm-dashboard/src/i18n/locales/en.json | 1768 +++++++++++++++++ 60 files changed, 4151 insertions(+), 1630 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/AwsSigV4Fields.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/AwsSigV4Fields.tsx index 5f64d003c11..b153d0a6456 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/AwsSigV4Fields.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/AwsSigV4Fields.tsx @@ -1,5 +1,6 @@ import { Info } from "lucide-react"; import React from "react"; +import { useTranslation } from "react-i18next"; import { SimpleTooltip } from "@/components/ui/tooltip"; import { MountedFormField } from "@/components/common_components/MountedFormField"; @@ -22,134 +23,150 @@ const FieldLabel: React.FC<{ label: string; tooltip: string }> = ({ label, toolt const ACCESS_KEY_PATH = ["credentials", "aws_access_key_id"] as const; const SECRET_KEY_PATH = ["credentials", "aws_secret_access_key"] as const; -const AwsSigV4Fields: React.FC = () => ( - <> -

- For MCP servers hosted on AWS Bedrock AgentCore.{" "} - { + const { t } = useTranslation(); + return ( + <> +

+ {t("mcpServers.awsSigV4Fields.intro")}{" "} + + {t("mcpServers.awsSigV4Fields.viewDocs")} + +

+ + } + name={["credentials", "aws_region_name"]} + required + rules={{ validate: { required: requiredRule(t("mcpServers.awsSigV4Fields.region.required")) } }} > - View docs → - -

- } - name={["credentials", "aws_region_name"]} - required - rules={{ validate: { required: requiredRule("AWS region is required for SigV4 auth") } }} - > - {(control) => } - - - } - name={["credentials", "aws_service_name"]} - > - {(control) => } - - - } - name={ACCESS_KEY_PATH} - rules={{ - deps: ["credentials.aws_secret_access_key"], - validate: { - pairedWithSecret: requiredWhenSiblingSet( - SECRET_KEY_PATH, - "Access Key ID is required when Secret Access Key is provided", - ), - }, - }} - > - {(control) => ( - - )} - - - } - name={SECRET_KEY_PATH} - rules={{ - deps: ["credentials.aws_access_key_id"], - validate: { - pairedWithAccessKey: requiredWhenSiblingSet( - ACCESS_KEY_PATH, - "Secret Access Key is required when Access Key ID is provided", - ), - }, - }} - > - {(control) => ( - - )} - - } - name={["credentials", "aws_session_token"]} - > - {(control) => ( - - )} - - - } - name={["credentials", "aws_role_name"]} - > - {(control) => ( - - )} - - - } - name={["credentials", "aws_session_name"]} - > - {(control) => ( - - )} - - -); + {(control) => } +
+ + } + name={["credentials", "aws_service_name"]} + > + {(control) => } + + + } + name={ACCESS_KEY_PATH} + rules={{ + deps: ["credentials.aws_secret_access_key"], + validate: { + pairedWithSecret: requiredWhenSiblingSet( + SECRET_KEY_PATH, + t("mcpServers.awsSigV4Fields.accessKeyId.requiredWithSecret"), + ), + }, + }} + > + {(control) => ( + + )} + + + } + name={SECRET_KEY_PATH} + rules={{ + deps: ["credentials.aws_access_key_id"], + validate: { + pairedWithAccessKey: requiredWhenSiblingSet( + ACCESS_KEY_PATH, + t("mcpServers.awsSigV4Fields.secretAccessKey.requiredWithAccessKey"), + ), + }, + }} + > + {(control) => ( + + )} + + + } + name={["credentials", "aws_session_token"]} + > + {(control) => ( + + )} + + + } + name={["credentials", "aws_role_name"]} + > + {(control) => ( + + )} + + + } + name={["credentials", "aws_session_name"]} + > + {(control) => ( + + )} + + + ); +}; export default AwsSigV4Fields; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/CreateMCPServer.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/CreateMCPServer.tsx index 07618abb9a4..14a6e3d941f 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/CreateMCPServer.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/CreateMCPServer.tsx @@ -1,5 +1,7 @@ +import type { TFunction } from "i18next"; import React, { useState } from "react"; import { FormProvider, useForm, useWatch } from "react-hook-form"; +import { useTranslation } from "react-i18next"; import { ChevronDown, Info } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; @@ -35,7 +37,7 @@ import { buildCreateServerPayload, reduceStaticHeaders, } from "./createServerPayload"; -import { DUPLICATE_IDENTIFIER_MESSAGE, findDuplicateMcpServer, mcpSubmitErrorReason } from "./duplicateServerCheck"; +import { DUPLICATE_IDENTIFIER_MESSAGE_KEY, findDuplicateMcpServer, mcpSubmitErrorReason } from "./duplicateServerCheck"; import { readCreateUiSnapshot, writeCreateUiSnapshot } from "./createOAuthUiState"; import AwsSigV4Fields from "./AwsSigV4Fields"; import OpenApiByokFields from "./OpenApiByokFields"; @@ -86,14 +88,14 @@ interface CreateMCPServerProps { stdioEnabled?: boolean; } -const payloadErrorMessage = (result: Exclude): string => { +const payloadErrorMessage = (result: Exclude, t: TFunction): string => { switch (result.kind) { case "invalid_tool_display_name": - return `Tool display name "${result.displayName}" is invalid. Only letters, digits, underscores, and hyphens are allowed (no spaces).`; + return t("mcpServers.createMcpServer.payloadErrors.invalidToolDisplayName", { displayName: result.displayName }); case "invalid_stdio_json": - return "Invalid JSON in stdio configuration"; + return t("mcpServers.createMcpServer.payloadErrors.invalidStdioJson"); case "invalid_token_validation_json": - return "Invalid JSON in Token Validation Rules"; + return t("mcpServers.createMcpServer.payloadErrors.invalidTokenValidationJson"); } }; @@ -117,6 +119,7 @@ const CreateMCPServer: React.FC = ({ onBackToDiscovery, stdioEnabled = true, }) => { + const { t } = useTranslation(); const form = useForm({ mode: "onChange", defaultValues: CREATE_DEFAULTS }); const registry = useMountRegistry(); const [isLoading, setIsLoading] = useState(false); @@ -255,9 +258,7 @@ const CreateMCPServer: React.FC = ({ // which would persist it as server-level credentials on the created server row. Mirrors the // edit form's onTokenReceived early return. setAuthorizedIdentity(getOAuthAuthorizationIdentity(allFieldsValue(form))); - toast.success( - "Token held for this browser session. Tools can now be previewed and configured; the token is not saved to LiteLLM.", - ); + toast.success(t("mcpServers.createMcpServer.toasts.browserTokenHeld")); return; } @@ -283,7 +284,7 @@ const CreateMCPServer: React.FC = ({ // its own credential write. setAuthorizedIdentity(getOAuthAuthorizationIdentity(allFieldsValue(form))); - toast.success("OAuth authorization successful! Please click 'Create MCP Server' to save the configuration."); + toast.success(t("mcpServers.createMcpServer.toasts.oauthAuthorized")); }, onBeforeRedirect: persistCreateUiState, flowSource: "create", @@ -425,8 +426,8 @@ const CreateMCPServer: React.FC = ({ typeof values.alias === "string" ? values.alias : undefined, ); if (duplicate) { - form.setError(duplicate.field, { type: "duplicate", message: DUPLICATE_IDENTIFIER_MESSAGE }); - toast.fromError(DUPLICATE_IDENTIFIER_MESSAGE); + form.setError(duplicate.field, { type: "duplicate", message: t(DUPLICATE_IDENTIFIER_MESSAGE_KEY) }); + toast.fromError(t(DUPLICATE_IDENTIFIER_MESSAGE_KEY)); return; } const built = buildCreateServerPayload(values, { @@ -440,7 +441,7 @@ const CreateMCPServer: React.FC = ({ dcrClient: dcrClientRef.current, }); if (built.kind !== "ok") { - toast.fromError(payloadErrorMessage(built)); + toast.fromError(payloadErrorMessage(built, t)); return; } const payload = built.payload; @@ -482,10 +483,10 @@ const CreateMCPServer: React.FC = ({ } if (isAdmin) { - toast.success("MCP Server created successfully"); + toast.success(t("mcpServers.createMcpServer.toasts.created")); } else { - toast.success("MCP Server submitted for admin review", { - description: "Once an admin approves it, the server will appear in your MCP Servers list.", + toast.success(t("mcpServers.createMcpServer.toasts.submittedForReview"), { + description: t("mcpServers.createMcpServer.toasts.submittedForReviewDescription"), }); } form.reset(CREATE_DEFAULTS); @@ -500,7 +501,11 @@ const CreateMCPServer: React.FC = ({ } } catch (error) { const reason = mcpSubmitErrorReason(error); - toast.fromError(isAdmin ? `Error creating MCP Server: ${reason}` : `Error submitting MCP Server: ${reason}`); + toast.fromError( + isAdmin + ? t("mcpServers.createMcpServer.toasts.createFailed", { reason }) + : t("mcpServers.createMcpServer.toasts.submitFailed", { reason }), + ); } finally { setIsLoading(false); } @@ -640,9 +645,11 @@ const CreateMCPServer: React.FC = ({ ← )} - MCP Logo + {t("mcpServers.createMcpServer.logoAlt")} - {isAdmin ? "Add New MCP Server" : "Submit MCP Server for Review"} + {isAdmin + ? t("mcpServers.createMcpServer.title.admin") + : t("mcpServers.createMcpServer.title.submitForReview")} @@ -653,27 +660,26 @@ const CreateMCPServer: React.FC = ({
{!isAdmin && (
- Your submission will be sent for admin review. Once approved, the server will appear in your MCP - Servers list. The request must be made with a team-scoped API key. + {t("mcpServers.createMcpServer.reviewNotice")}
)}
- MCP Server Name - + {t("mcpServers.createMcpServer.serverName.label")} + } name="server_name" - rules={{ validate: validatorRules({ validator: (_, value) => validateMCPServerName(value) }) }} + rules={{ validate: validatorRules({ validator: (_, value) => validateMCPServerName(value, t) }) }} > {(control) => ( )} @@ -682,19 +688,19 @@ const CreateMCPServer: React.FC = ({ - Alias - + {t("mcpServers.createMcpServer.alias.label")} + } name="alias" - rules={{ validate: validatorRules({ validator: (_, value) => validateMCPServerName(value) }) }} + rules={{ validate: validatorRules({ validator: (_, value) => validateMCPServerName(value, t) }) }} > {(control) => ( { control.onChange(event); @@ -705,13 +711,17 @@ const CreateMCPServer: React.FC = ({ Description} + label={ + + {t("mcpServers.createMcpServer.description.label")} + + } name="description" > {(control) => ( )} @@ -720,7 +730,11 @@ const CreateMCPServer: React.FC = ({ GitHub / Source URL} + label={ + + {t("mcpServers.createMcpServer.sourceUrl.label")} + + } name="source_url" > {(control) => ( @@ -733,10 +747,14 @@ const CreateMCPServer: React.FC = ({ Transport Type} + label={ + + {t("mcpServers.createMcpServer.transport.label")} + + } name="transport" required - rules={{ validate: { required: requiredRule("Please select a transport type") } }} + rules={{ validate: { required: requiredRule(t("mcpServers.createMcpServer.transport.required")) } }} > {(control) => ( (control)} items={AUTH_TYPE_ITEMS}> - + {AUTH_TYPE_ITEMS.map((item) => ( @@ -886,8 +912,8 @@ const CreateMCPServer: React.FC = ({ - Authentication Value - + {t("mcpServers.createMcpServer.auth.valueLabel")} + @@ -895,14 +921,14 @@ const CreateMCPServer: React.FC = ({ name={["credentials", "auth_value"]} rules={{ validate: { - notWhitespace: notOnlyWhitespace("Authentication value cannot be empty whitespace"), + notWhitespace: notOnlyWhitespace(t("mcpServers.createMcpServer.auth.valueWhitespace")), }, }} > {(control) => ( )} @@ -1000,11 +1026,13 @@ const CreateMCPServer: React.FC = ({
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/DcrBridgeToggle.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/DcrBridgeToggle.tsx index 893981fa9c6..48a8f4d7a64 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/DcrBridgeToggle.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/DcrBridgeToggle.tsx @@ -1,5 +1,6 @@ import { Info } from "lucide-react"; import React from "react"; +import { useTranslation } from "react-i18next"; import { SimpleTooltip } from "@/components/ui/tooltip"; import { Switch } from "@/components/ui/switch"; @@ -23,13 +24,14 @@ export default function DcrBridgeToggle({ authType?: string | null; initialChecked?: boolean; }) { + const { t } = useTranslation(); if (!isClientForwardedTokenMode(authType)) return null; return ( - Gateway-hosted sign-in (DCR bridge) - + {t("mcpServers.dcrBridgeToggle.label")} + diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/EnvVarsSection.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/EnvVarsSection.tsx index 378410e4790..a804d4697c4 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/EnvVarsSection.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/EnvVarsSection.tsx @@ -1,5 +1,7 @@ +import type { ParseKeys } from "i18next"; import { CircleMinus, Info, Plus } from "lucide-react"; import React from "react"; +import { Trans, useTranslation } from "react-i18next"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { SimpleTooltip } from "@/components/ui/tooltip"; import { Button } from "@/components/ui/button"; @@ -16,11 +18,13 @@ import { requiredRule } from "@/components/common_components/formRules"; import { matchesPattern, selectControl, selectTriggerControl, textControl } from "./mcpFieldRules"; import { listControl } from "./mcpFormStore"; -const SCOPE_OPTIONS = [ - { value: "global", label: "Instance" }, - { value: "user", label: "Per-user" }, +const SCOPE_OPTIONS: ReadonlyArray<{ value: string; labelKey: ParseKeys }> = [ + { value: "global", labelKey: "mcpServers.envVarsSection.scopes.instance" }, + { value: "user", labelKey: "mcpServers.envVarsSection.scopes.perUser" }, ]; +const VARIABLE_SYNTAX = "${VAR_NAME}"; + const VARIABLE_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/; /** @@ -34,31 +38,34 @@ const VARIABLE_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/; * The parent form reads the ``env_vars`` field from the form values. */ const EnvVarsSection: React.FC = () => { + const { t } = useTranslation(); const { control } = useFormContext(); const { fields, append, remove } = useFieldArray({ control: listControl(control), name: "env_vars" }); useMountedName("env_vars"); + const scopeOptions = SCOPE_OPTIONS.map((option) => ({ value: option.value, label: t(option.labelKey) })); return (
- Variables + {t("mcpServers.envVarsSection.title")} - Define variables you can interpolate in Static Headers or Authentication using{" "} - {"${VAR_NAME}"}.
- Instance: admin-defined value used for every user. -
- Per-user: each user supplies their own value (e.g. personal credentials) via the MCP Gateway - dashboard. - + , b: }} + /> } >
- Reference these in Static Headers or Authentication as {"${VAR_NAME}"}. For example:{" "} + }} + />{" "} {"${DB_PROTOCOL}://${CORP_USERNAME}:${CORP_PASSWORD}@${DB_HOSTNAME}"} @@ -67,9 +74,9 @@ const EnvVarsSection: React.FC = () => {
{fields.length > 0 && (
-
Variable Name
-
Value / Description
-
Scope
+
{t("mcpServers.envVarsSection.columns.name")}
+
{t("mcpServers.envVarsSection.columns.value")}
+
{t("mcpServers.envVarsSection.columns.scope")}
)} @@ -80,16 +87,17 @@ const EnvVarsSection: React.FC = () => { className="mb-0 flex-1" rules={{ validate: { - required: requiredRule("Variable name is required"), - pattern: matchesPattern( - VARIABLE_NAME_PATTERN, - "Use letters, digits, underscores; cannot start with a digit.", - ), + required: requiredRule(t("mcpServers.envVarsSection.name.required")), + pattern: matchesPattern(VARIABLE_NAME_PATTERN, t("mcpServers.envVarsSection.name.invalid")), }, }} > {(control) => ( - + )}
@@ -97,12 +105,12 @@ const EnvVarsSection: React.FC = () => {
{(control) => ( - (control)} items={scopeOptions}> - {SCOPE_OPTIONS.map((option) => ( + {scopeOptions.map((option) => ( {option.label} @@ -121,7 +129,7 @@ const EnvVarsSection: React.FC = () => { ))}
@@ -132,6 +140,7 @@ const EnvVarsSection: React.FC = () => { // vars the value comes from each user later, so the column instead captures an // optional description that the per-user fill-in modal shows as a hint. const ScopedValueOrDescription: React.FC<{ index: number }> = ({ index }) => { + const { t } = useTranslation(); const isPerUser = useWatch({ name: `env_vars.${index}.scope` }) === "user"; if (isPerUser) { return ( @@ -139,16 +148,16 @@ const ScopedValueOrDescription: React.FC<{ index: number }> = ({ index }) => { {(control) => ( - + - Hint + {t("mcpServers.envVarsSection.hint.label")} @@ -158,7 +167,13 @@ const ScopedValueOrDescription: React.FC<{ index: number }> = ({ index }) => { } return ( - {(control) => } + {(control) => ( + + )} ); }; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/IdJagFormFields.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/IdJagFormFields.tsx index 70d1bc40c18..98bb3254ba5 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/IdJagFormFields.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/IdJagFormFields.tsx @@ -1,5 +1,6 @@ import { Info } from "lucide-react"; import React from "react"; +import { useTranslation } from "react-i18next"; import { SimpleTooltip } from "@/components/ui/tooltip"; import { MountedFormField } from "@/components/common_components/MountedFormField"; @@ -27,9 +28,10 @@ const FieldLabel: React.FC<{ label: string; tooltip: string }> = ({ label, toolt ); const PRIVATE_KEY_PATH = ["credentials", "client_private_key"] as const; +const PEM_HEADER = "-----BEGIN PRIVATE KEY-----"; const IdJagFormFields: React.FC = ({ isEditing = false }) => { - const placeholderSuffix = isEditing ? " (leave blank to keep existing)" : ""; + const { t } = useTranslation(); const requiredWhenCreating = (message: string) => isEditing ? undefined : { validate: { required: requiredRule(message) } }; @@ -38,13 +40,13 @@ const IdJagFormFields: React.FC = ({ isEditing = false }) } name="token_exchange_endpoint" required={!isEditing} - rules={requiredWhenCreating("The org token endpoint is required for ID-JAG")} + rules={requiredWhenCreating(t("mcpServers.idJagFormFields.orgTokenEndpoint.required"))} > {(control) => ( = ({ isEditing = false }) } name={["credentials", "id_jag_resource_token_endpoint"]} required={!isEditing} - rules={requiredWhenCreating("The resource token endpoint is required for ID-JAG")} + rules={requiredWhenCreating(t("mcpServers.idJagFormFields.resourceTokenEndpoint.required"))} > {(control) => ( = ({ isEditing = false }) )} } + label={ + + } name={["credentials", "client_id"]} required={!isEditing} - rules={requiredWhenCreating("Client ID is required for ID-JAG")} + rules={requiredWhenCreating(t("mcpServers.idJagFormFields.clientId.required"))} > {(control) => ( )} @@ -90,8 +101,8 @@ const IdJagFormFields: React.FC = ({ isEditing = false }) } name={["credentials", "client_secret"]} @@ -103,7 +114,7 @@ const IdJagFormFields: React.FC = ({ isEditing = false }) validate: { secretOrPrivateKey: requiredUnlessSiblingSet( PRIVATE_KEY_PATH, - "Provide either a client secret or a client private key", + t("mcpServers.idJagFormFields.clientSecret.secretOrPrivateKey"), ), }, } @@ -112,7 +123,11 @@ const IdJagFormFields: React.FC = ({ isEditing = false }) {(control) => ( )} @@ -120,8 +135,8 @@ const IdJagFormFields: React.FC = ({ isEditing = false }) } name={PRIVATE_KEY_PATH} @@ -130,7 +145,11 @@ const IdJagFormFields: React.FC = ({ isEditing = false })