From b6e1cf642f9f145df3032834cbea1e1bfe0695a7 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 21:11:19 +0900 Subject: [PATCH] feat(ui): i18n for MCP server view and list --- .../components/mcp_tools/mcp_server_view.tsx | 96 ++++++++++++------- .../src/components/mcp_tools/mcp_servers.tsx | 93 ++++++++++-------- ui/litellm-dashboard/src/locales/en.json | 61 ++++++++++++ ui/litellm-dashboard/src/locales/zh-CN.json | 61 ++++++++++++ 4 files changed, 238 insertions(+), 73 deletions(-) diff --git a/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_view.tsx b/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_view.tsx index 3ee5676235a..c19543530b2 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_view.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/mcp_server_view.tsx @@ -1,4 +1,5 @@ import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; import { ArrowLeftIcon, EyeIcon, EyeOffIcon } from "@heroicons/react/outline"; import { Title, Card, Button, Text, Grid, TabGroup, TabList, TabPanel, TabPanels, Tab, Icon } from "@tremor/react"; @@ -56,6 +57,7 @@ export const MCPServerView: React.FC = ({ }) => { // Open the editing Settings tab on first render when returning from the edit OAuth // redirect, so the "token fetched" feedback shows where the user left off (Settings=2). + const { t } = useTranslation(); const returningFromEditOAuth = isReturningFromEditOAuth(isProxyAdmin, mcpServer.server_id); const [editing, setEditing] = useState(isEditing || returningFromEditOAuth); const [showFullUrl, setShowFullUrl] = useState(false); @@ -107,10 +109,12 @@ export const MCPServerView: React.FC = ({
- {mcpServer.server_name || mcpServer.alias || "Unnamed Server"} + + {mcpServer.server_name || mcpServer.alias || t("mcpTools.mcpServerView.unnamedServer")} + = ({ {[ - Overview, - MCP Tools, - ...(isProxyAdmin ? [Settings] : []), + {t("mcpTools.mcpServerView.tabOverview")}, + {t("mcpTools.mcpServerView.tabMcpTools")}, + ...(isProxyAdmin ? [{t("common.settings")}] : []), ]} @@ -160,7 +164,9 @@ export const MCPServerView: React.FC = ({ - Transport + + {t("mcpTools.mcpServerView.transport")} +
{getTransportBadge( handleTransport(mcpServer.transport ?? undefined, mcpServer.spec_path ?? undefined), @@ -169,12 +175,16 @@ export const MCPServerView: React.FC = ({ - Authentication + + {t("mcpTools.mcpServerView.authentication")} +
{getAuthBadge(handleAuth(mcpServer.auth_type ?? undefined))}
- Host URL + + {t("mcpTools.mcpServerView.hostUrl")} +
{renderUrlWithToggle(mcpServer.url, showFullUrl)} @@ -196,7 +206,9 @@ export const MCPServerView: React.FC = ({ - Cost Configuration + + {t("mcpTools.mcpServerView.costConfiguration")} +
@@ -223,10 +235,10 @@ export const MCPServerView: React.FC = ({
- MCP Server Settings + {t("mcpTools.mcpServerView.mcpServerSettings")} {editing ? null : ( )}
@@ -242,25 +254,25 @@ export const MCPServerView: React.FC = ({ ) : (
- Server Name + {t("mcpTools.mcpServerView.serverName")}
{mcpServer.server_name || —}
- Alias + {t("mcpTools.mcpServerView.alias")}
{mcpServer.alias || —}
- Description + {t("common.description")}
{mcpServer.description || —}
- URL + {t("mcpTools.mcpServerView.url")}
{renderUrlWithToggle(mcpServer.url, showFullUrl)} {hasToken && ( @@ -274,17 +286,21 @@ export const MCPServerView: React.FC = ({
- Transport + {t("mcpTools.mcpServerView.transport")}
{getTransportBadge(handleTransport(mcpServer.transport, mcpServer.spec_path))}
- Authentication + + {t("mcpTools.mcpServerView.authentication")} +
{getAuthBadge(handleAuth(mcpServer.auth_type))}
- Extra Headers + + {t("mcpTools.mcpServerView.extraHeaders")} +
{mcpServer.extra_headers && mcpServer.extra_headers.length > 0 ? ( mcpServer.extra_headers.join(", ") @@ -294,48 +310,54 @@ export const MCPServerView: React.FC = ({
- Allow All Keys + + {t("mcpTools.mcpServerView.allowAllKeys")} +
{mcpServer.allow_all_keys ? ( - Enabled + {t("common.enabled")} ) : ( - Disabled + {t("common.disabled")} )}
- Network Access + + {t("mcpTools.mcpServerView.networkAccess")} +
{mcpServer.available_on_public_internet ? ( - Public + {t("mcpTools.mcpServerView.networkPublic")} ) : ( - Internal only + {t("mcpTools.mcpServerView.networkInternalOnly")} )}
{handleAuth(mcpServer.auth_type) === "oauth2" && (
- Delegate Auth to Upstream + + {t("mcpTools.mcpServerView.delegateAuthToUpstream")} +
{mcpServer.delegate_auth_to_upstream ? ( - Enabled (PKCE passthrough) + {t("mcpTools.mcpServerView.enabledPkcePassthrough")} ) : ( - Disabled + {t("common.disabled")} )}
@@ -347,23 +369,27 @@ export const MCPServerView: React.FC = ({ (h) => typeof h === "string" && h.toLowerCase() === "authorization", ) && (
- OAuth Pass-through + + {t("mcpTools.mcpServerView.oauthPassthrough")} +
{mcpServer.oauth_passthrough ? ( - Enabled + {t("common.enabled")} ) : ( - Disabled + {t("common.disabled")} )}
)}
- Access Groups + + {t("mcpTools.mcpServerView.accessGroups")} +
{mcpServer.mcp_access_groups && mcpServer.mcp_access_groups.length > 0 ? (
@@ -382,7 +408,9 @@ export const MCPServerView: React.FC = ({
- Allowed Tools + + {t("mcpTools.mcpServerView.allowedTools")} +
{mcpServer.allowed_tools && mcpServer.allowed_tools.length > 0 ? (
@@ -397,13 +425,13 @@ export const MCPServerView: React.FC = ({
) : ( - All tools enabled + {t("mcpTools.mcpServerView.allToolsEnabled")} )}
- Cost + {t("mcpTools.mcpServerView.cost")}
diff --git a/ui/litellm-dashboard/src/components/mcp_tools/mcp_servers.tsx b/ui/litellm-dashboard/src/components/mcp_tools/mcp_servers.tsx index 0a445265e2d..0799c8ac8e7 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/mcp_servers.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/mcp_servers.tsx @@ -2,6 +2,8 @@ import { isAdminRole } from "@/utils/roles"; import { QuestionCircleOutlined, SearchOutlined } from "@ant-design/icons"; import { Button, Tab, TabGroup, TabList, TabPanel, TabPanels, Text, Title } from "@tremor/react"; import NewBadge from "../common_components/NewBadge"; +import { useTranslation } from "react-i18next"; +import type { TFunction } from "i18next"; import { Descriptions, Empty, Input, Modal, Select, Spin, Tooltip, Typography } from "antd"; import React, { useEffect, useState, useMemo, useCallback } from "react"; import { useQuery } from "@tanstack/react-query"; @@ -27,11 +29,11 @@ import { listMCPUserEnvVarStatus } from "../networking"; type SortKey = "created_desc" | "updated_desc" | "name_asc" | "health"; -const SORT_OPTIONS: { value: SortKey; label: string }[] = [ - { value: "created_desc", label: "Recently created" }, - { value: "updated_desc", label: "Recently updated" }, - { value: "name_asc", label: "Name (A→Z)" }, - { value: "health", label: "Health (unhealthy first)" }, +const getSortOptions = (t: TFunction): { value: SortKey; label: string }[] => [ + { value: "created_desc", label: t("mcpTools.mcpServers.sortRecentlyCreated") }, + { value: "updated_desc", label: t("mcpTools.mcpServers.sortRecentlyUpdated") }, + { value: "name_asc", label: t("mcpTools.mcpServers.sortNameAsc") }, + { value: "health", label: t("mcpTools.mcpServers.sortHealthFirst") }, ]; const HEALTH_RANK: Record = { @@ -92,6 +94,8 @@ const readToolsOAuthServerId = (): string | null => { const { Option } = Select; const MCPServers: React.FC = ({ accessToken, userRole, userID }) => { + const { t } = useTranslation(); + const sortOptions = useMemo(() => getSortOptions(t), [t]); const { data: mcpServers, isLoading: isLoadingServers, refetch } = useMCPServers(); // Fetch health status for all servers @@ -317,7 +321,7 @@ const MCPServers: React.FC = ({ accessToken, userRole, userID }) try { setIsDeletingServer(true); await deleteMCPServer(accessToken, serverIdToDelete); - NotificationsManager.success("Deleted MCP Server successfully"); + NotificationsManager.success(t("mcpTools.mcpServers.deleteSuccess")); // If the user is currently viewing the detail page of the server they // just deleted, return them to the All Servers list. Otherwise the // detail view would stay mounted, fall back to an empty stub server, @@ -381,44 +385,46 @@ const MCPServers: React.FC = ({ accessToken, userRole, userID }) if (!accessToken || !userRole || !userID) { console.log("Missing required authentication parameters", { accessToken, userRole, userID }); - return
Missing required authentication parameters.
; + return
{t("mcpTools.mcpServers.missingAuthParams")}
; } return (
- - This action is permanent and cannot be undone. All associated configurations will be removed. - + {t("mcpTools.mcpServers.deleteModalBody")} {serverToDelete && (
{serverToDelete.server_name && ( - Name}> + {t("common.name")}}> {serverToDelete.server_name} )} - ID}> + {t("mcpTools.mcpServers.idLabel")}} + > {serverToDelete.server_id} {serverToDelete.url && ( - URL}> + {t("mcpTools.mcpServerView.url")}} + > {serverToDelete.url} @@ -447,19 +453,19 @@ const MCPServers: React.FC = ({ accessToken, userRole, userID })
- MCP Servers + {t("mcpTools.mcpServers.pageTitle")} {filteredServers.length > 0 && ( {filteredServers.length} )}
- Configure and manage your MCP servers + {t("mcpTools.mcpServers.pageSubtitle")}
{isAdminRole(userRole) && ( )} {!isAdminRole(userRole) && ( @@ -471,7 +477,7 @@ const MCPServers: React.FC = ({ accessToken, userRole, userID }) }} variant="secondary" > - + Submit MCP Server + {t("mcpTools.mcpServers.submitServer")} )}
@@ -494,15 +500,15 @@ const MCPServers: React.FC = ({ accessToken, userRole, userID })
- All Servers - Toolsets - Connect - Semantic Filter - Network Settings + {t("mcpTools.mcpServers.tabAllServers")} + {t("mcpTools.mcpServers.tabToolsets")} + {t("mcpTools.mcpServers.tabConnect")} + {t("mcpTools.mcpServers.tabSemanticFilter")} + {t("mcpTools.mcpServers.tabNetworkSettings")} {isAdminRole(userRole) && ( - Submitted MCPs + {t("mcpTools.mcpServers.tabSubmittedMcps")} )} @@ -529,15 +535,19 @@ const MCPServers: React.FC = ({ accessToken, userRole, userID })
- Team + + {t("mcpTools.mcpServers.filterTeam")} + } - placeholder="Search by name, alias, URL, or ID" + placeholder={t("mcpTools.mcpServers.searchPlaceholder")} value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} style={{ maxWidth: 320 }} />
- Sort + + {t("mcpTools.mcpServers.sortLabel")} +
- {displayedServers.length} of {filteredServers.length} servers + {t("mcpTools.mcpServers.serverCount", { + displayed: displayedServers.length, + total: filteredServers.length, + })}
{isLoadingServers ? (
- +
) : displayedServers.length === 0 ? (
diff --git a/ui/litellm-dashboard/src/locales/en.json b/ui/litellm-dashboard/src/locales/en.json index 4b192d61b44..f5c38c105fe 100644 --- a/ui/litellm-dashboard/src/locales/en.json +++ b/ui/litellm-dashboard/src/locales/en.json @@ -3647,6 +3647,67 @@ "awsRegionLabel": "AWS Region", "awsRegionTooltip": "AWS region for SigV4 signing (e.g., us-east-1)", "awsRegionPlaceholder": "us-east-1 (leave blank to keep existing)" + }, + "mcpServerView": { + "backToAllServers": "Back to All Servers", + "unnamedServer": "Unnamed Server", + "tabOverview": "Overview", + "tabMcpTools": "MCP Tools", + "transport": "Transport", + "authentication": "Authentication", + "hostUrl": "Host URL", + "costConfiguration": "Cost Configuration", + "mcpServerSettings": "MCP Server Settings", + "editSettings": "Edit Settings", + "serverName": "Server Name", + "alias": "Alias", + "url": "URL", + "extraHeaders": "Extra Headers", + "allowAllKeys": "Allow All Keys", + "networkAccess": "Network Access", + "networkPublic": "Public", + "networkInternalOnly": "Internal only", + "delegateAuthToUpstream": "Delegate Auth to Upstream", + "enabledPkcePassthrough": "Enabled (PKCE passthrough)", + "oauthPassthrough": "OAuth Pass-through", + "accessGroups": "Access Groups", + "allowedTools": "Allowed Tools", + "allToolsEnabled": "All tools enabled", + "cost": "Cost" + }, + "mcpServers": { + "sortRecentlyCreated": "Recently created", + "sortRecentlyUpdated": "Recently updated", + "sortNameAsc": "Name (A→Z)", + "sortHealthFirst": "Health (unhealthy first)", + "missingAuthParams": "Missing required authentication parameters.", + "deleteModalTitle": "Delete MCP Server?", + "deleteModalBody": "This action is permanent and cannot be undone. All associated configurations will be removed.", + "idLabel": "ID", + "pageTitle": "MCP Servers", + "pageSubtitle": "Configure and manage your MCP servers", + "addNewServer": "+ Add New MCP Server", + "submitServer": "+ Submit MCP Server", + "tabAllServers": "All Servers", + "tabToolsets": "Toolsets", + "tabConnect": "Connect", + "tabSemanticFilter": "Semantic Filter", + "tabNetworkSettings": "Network Settings", + "tabSubmittedMcps": "Submitted MCPs", + "filterTeam": "Team", + "allAvailableServers": "All Available Servers", + "allServers": "All Servers", + "personal": "Personal", + "filterAccessGroup": "Access Group", + "accessGroupTooltip": "An MCP Access Group is a set of users or teams that have permission to access specific MCP servers. Use access groups to control and organize who can connect to which servers.", + "allAccessGroups": "All Access Groups", + "searchPlaceholder": "Search by name, alias, URL, or ID", + "sortLabel": "Sort", + "serverCount": "{{displayed}} of {{total}} servers", + "loadingServers": "Loading MCP servers...", + "emptyNoServers": "No MCP servers configured. Click '+ Add New MCP Server' to get started.", + "emptyNoMatch": "No servers match the current filters or search.", + "deleteSuccess": "Deleted MCP Server successfully" } } } diff --git a/ui/litellm-dashboard/src/locales/zh-CN.json b/ui/litellm-dashboard/src/locales/zh-CN.json index 233bbd1e6b5..87916789452 100644 --- a/ui/litellm-dashboard/src/locales/zh-CN.json +++ b/ui/litellm-dashboard/src/locales/zh-CN.json @@ -3647,6 +3647,67 @@ "awsRegionLabel": "AWS 区域", "awsRegionTooltip": "用于 SigV4 签名的 AWS 区域(例如 us-east-1)", "awsRegionPlaceholder": "us-east-1(留空则保留现有值)" + }, + "mcpServerView": { + "backToAllServers": "返回所有服务器", + "unnamedServer": "未命名服务器", + "tabOverview": "概览", + "tabMcpTools": "MCP 工具", + "transport": "传输协议", + "authentication": "身份验证", + "hostUrl": "主机 URL", + "costConfiguration": "费用配置", + "mcpServerSettings": "MCP 服务器设置", + "editSettings": "编辑设置", + "serverName": "服务器名称", + "alias": "别名", + "url": "URL", + "extraHeaders": "额外请求头", + "allowAllKeys": "允许所有密钥", + "networkAccess": "网络访问", + "networkPublic": "公开", + "networkInternalOnly": "仅内部", + "delegateAuthToUpstream": "委托认证至上游", + "enabledPkcePassthrough": "已启用(PKCE 透传)", + "oauthPassthrough": "OAuth 透传", + "accessGroups": "访问组", + "allowedTools": "允许的工具", + "allToolsEnabled": "所有工具已启用", + "cost": "费用" + }, + "mcpServers": { + "sortRecentlyCreated": "最新创建", + "sortRecentlyUpdated": "最近更新", + "sortNameAsc": "名称(A→Z)", + "sortHealthFirst": "健康状态(不健康优先)", + "missingAuthParams": "缺少必要的身份验证参数。", + "deleteModalTitle": "删除 MCP 服务器?", + "deleteModalBody": "此操作不可撤销,所有关联配置将被移除。", + "idLabel": "ID", + "pageTitle": "MCP 服务器", + "pageSubtitle": "配置和管理您的 MCP 服务器", + "addNewServer": "+ 添加新 MCP 服务器", + "submitServer": "+ 提交 MCP 服务器", + "tabAllServers": "所有服务器", + "tabToolsets": "工具集", + "tabConnect": "连接", + "tabSemanticFilter": "语义过滤", + "tabNetworkSettings": "网络设置", + "tabSubmittedMcps": "已提交的 MCP", + "filterTeam": "团队", + "allAvailableServers": "所有可用服务器", + "allServers": "所有服务器", + "personal": "个人", + "filterAccessGroup": "访问组", + "accessGroupTooltip": "MCP 访问组是一组具有访问特定 MCP 服务器权限的用户或团队。使用访问组来控制和组织谁可以连接到哪些服务器。", + "allAccessGroups": "所有访问组", + "searchPlaceholder": "按名称、别名、URL 或 ID 搜索", + "sortLabel": "排序", + "serverCount": "显示 {{displayed}} / {{total}} 个服务器", + "loadingServers": "正在加载 MCP 服务器...", + "emptyNoServers": "未配置 MCP 服务器。点击「+ 添加新 MCP 服务器」开始使用。", + "emptyNoMatch": "没有服务器匹配当前过滤条件或搜索。", + "deleteSuccess": "MCP 服务器已成功删除" } } }