From fa7ee22cba734dfe7848fc39a19e7132f076abbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B9=E9=9B=AA?= <2b@yorha.xyz> Date: Thu, 11 Jun 2026 06:21:18 +0900 Subject: [PATCH] feat(ui): i18n for users table and info view --- .../src/components/view_users.tsx | 2 + .../src/components/view_users/columns.tsx | 66 ++++--- .../src/components/view_users/table.tsx | 42 +++-- .../components/view_users/user_info_view.tsx | 164 +++++++++--------- ui/litellm-dashboard/src/locales/en.json | 89 +++++++++- ui/litellm-dashboard/src/locales/zh-CN.json | 89 +++++++++- 6 files changed, 329 insertions(+), 123 deletions(-) diff --git a/ui/litellm-dashboard/src/components/view_users.tsx b/ui/litellm-dashboard/src/components/view_users.tsx index 24fea64a9e6..e498aac3f55 100644 --- a/ui/litellm-dashboard/src/components/view_users.tsx +++ b/ui/litellm-dashboard/src/components/view_users.tsx @@ -299,6 +299,8 @@ const ViewUserDashboard: React.FC = ({ handleDelete, handleResetPassword, () => {}, // placeholder function, will be overridden in UserDataTable + undefined, + t, ); return ( diff --git a/ui/litellm-dashboard/src/components/view_users/columns.tsx b/ui/litellm-dashboard/src/components/view_users/columns.tsx index 30107630c3c..0d4283bc8a2 100644 --- a/ui/litellm-dashboard/src/components/view_users/columns.tsx +++ b/ui/litellm-dashboard/src/components/view_users/columns.tsx @@ -5,6 +5,7 @@ import { UserInfo } from "./types"; import { PencilAltIcon, TrashIcon, InformationCircleIcon, RefreshIcon } from "@heroicons/react/outline"; import { CopyOutlined } from "@ant-design/icons"; import { formatNumberWithCommas, copyToClipboard } from "@/utils/dataUtils"; +import type { TFunction } from "i18next"; interface SelectionOptions { selectedUsers: UserInfo[]; @@ -21,12 +22,15 @@ export const columns = ( handleDelete: (user: UserInfo) => void, handleResetPassword: (userId: string) => void, handleUserClick: (userId: string, openInEditMode?: boolean) => void, - selectionOptions?: SelectionOptions, + selectionOptions: SelectionOptions | undefined, + t: TFunction, ): ColumnDef[] => { + const tr = (key: string, opts?: Record) => t(`viewUsers.columns.${key}`, opts ?? {}); + // Backend sortable columns: user_id, user_email, created_at, spend, user_alias, user_role const baseColumns: ColumnDef[] = [ { - header: "User ID", + header: tr("userId"), accessorKey: "user_id", enableSorting: true, cell: ({ row }) => ( @@ -35,11 +39,11 @@ export const columns = ( {row.original.user_id ? `${row.original.user_id.slice(0, 7)}...` : "-"} {row.original.user_id && ( - + { e.stopPropagation(); - copyToClipboard(row.original.user_id, "User ID copied to clipboard"); + copyToClipboard(row.original.user_id, tr("userIdCopied")); }} className="cursor-pointer text-gray-500 hover:text-blue-500 text-xs" /> @@ -49,48 +53,53 @@ export const columns = ( ), }, { - header: "Email", + header: tr("email"), accessorKey: "user_email", enableSorting: true, cell: ({ row }) => {row.original.user_email || "-"}, }, { id: "status", - header: "Status", + header: tr("status"), enableSorting: false, cell: ({ row }) => { const isScimInactive = (row.original.metadata as Record | null | undefined)?.scim_active === false; if (isScimInactive) { return ( - + - Inactive + {tr("inactive")} ); } return ( - Active + {tr("active")} ); }, }, { - header: "Global Proxy Role", + header: tr("globalProxyRole"), accessorKey: "user_role", enableSorting: true, cell: ({ row }) => {possibleUIRoles?.[row.original.user_role]?.ui_label || "-"}, }, { - header: "User Alias", + header: tr("userAlias"), accessorKey: "user_alias", enableSorting: false, cell: ({ row }) => {row.original.user_alias || "-"}, }, { - header: "Spend (USD)", + header: tr("spendUsd"), accessorKey: "spend", enableSorting: true, cell: ({ row }) => ( @@ -98,18 +107,23 @@ export const columns = ( ), }, { - header: "Budget (USD)", + header: tr("budgetUsd"), accessorKey: "max_budget", enableSorting: false, cell: ({ row }) => ( - {row.original.max_budget !== null ? row.original.max_budget : "Unlimited"} + {row.original.max_budget !== null ? row.original.max_budget : tr("unlimited")} ), }, { header: () => (
- SSO ID - + {tr("ssoId")} +
@@ -121,25 +135,27 @@ export const columns = ( ), }, { - header: "Virtual Keys", + header: tr("virtualKeys"), accessorKey: "key_count", enableSorting: false, cell: ({ row }) => ( {row.original.key_count > 0 ? ( - {row.original.key_count} {row.original.key_count === 1 ? "Key" : "Keys"} + {t + ? t("viewUsers.columns.key", { count: row.original.key_count }) + : `${row.original.key_count} ${row.original.key_count === 1 ? "Key" : "Keys"}`} ) : ( - No Keys + {tr("noKeys")} )} ), }, { - header: "Created At", + header: t ? t("common.createdAt") : "Created At", accessorKey: "created_at", enableSorting: true, cell: ({ row }) => ( @@ -149,7 +165,7 @@ export const columns = ( ), }, { - header: "Updated At", + header: t ? t("common.updatedAt") : "Updated At", accessorKey: "updated_at", enableSorting: false, cell: ({ row }) => ( @@ -160,11 +176,11 @@ export const columns = ( }, { id: "actions", - header: "Actions", + header: t ? t("common.actions") : "Actions", enableSorting: false, cell: ({ row }) => (
- + - + - + ([ { id: currentSort?.sortBy || "created_at", @@ -143,6 +145,7 @@ export function UserDataTable({ isIndeterminate, } : undefined, + t, ); } return originalColumns; @@ -157,6 +160,7 @@ export function UserDataTable({ selectedUsers, isAllSelected, isIndeterminate, + t, ]); const table = useReactTable({ @@ -220,7 +224,7 @@ export function UserDataTable({
{/* Email Search */} updateFilters({ email: value })} icon={Search} @@ -246,14 +250,14 @@ export function UserDataTable({
{/* User ID Search */} updateFilters({ user_id: value })} icon={User} /> updateFilters({ sso_user_id: value })} icon={CircleUserRound} @@ -264,7 +268,7 @@ export function UserDataTable({ updateFilters({ team: value })} - placeholder="Select Team" + placeholder={t("viewUsers.table.selectTeam")} > {teams?.map((team) => ( @@ -298,15 +302,17 @@ export function UserDataTable({ ) : ( - Showing{" "} - {userListResponse && userListResponse.users && userListResponse.users.length > 0 - ? (userListResponse.page - 1) * userListResponse.page_size + 1 - : 0}{" "} - -{" "} - {userListResponse && userListResponse.users - ? Math.min(userListResponse.page * userListResponse.page_size, userListResponse.total) - : 0}{" "} - of {userListResponse ? userListResponse.total : 0} results + {t("viewUsers.table.showing", { + from: + userListResponse && userListResponse.users && userListResponse.users.length > 0 + ? (userListResponse.page - 1) * userListResponse.page_size + 1 + : 0, + to: + userListResponse && userListResponse.users + ? Math.min(userListResponse.page * userListResponse.page_size, userListResponse.total) + : 0, + total: userListResponse ? userListResponse.total : 0, + })} )} @@ -326,7 +332,7 @@ export function UserDataTable({ currentPage === 1 ? "bg-gray-100 text-gray-400 cursor-not-allowed" : "hover:bg-gray-50" }`} > - Previous + {t("common.previous")} )} @@ -393,7 +399,7 @@ export function UserDataTable({
-

🚅 Loading users...

+

🚅 {t("viewUsers.table.loadingUsers")}

@@ -427,7 +433,7 @@ export function UserDataTable({
-

No users found

+

{t("viewUsers.table.noUsersFound")}

diff --git a/ui/litellm-dashboard/src/components/view_users/user_info_view.tsx b/ui/litellm-dashboard/src/components/view_users/user_info_view.tsx index 20df9c10399..a1cb272da0b 100644 --- a/ui/litellm-dashboard/src/components/view_users/user_info_view.tsx +++ b/ui/litellm-dashboard/src/components/view_users/user_info_view.tsx @@ -41,6 +41,7 @@ import { CopyIcon, CheckIcon } from "lucide-react"; import NotificationsManager from "../molecules/notifications_manager"; import { getBudgetDurationLabel } from "../common_components/budget_duration_dropdown"; import DeleteResourceModal from "../common_components/DeleteResourceModal"; +import { useTranslation } from "react-i18next"; interface UserInfoViewProps { userId: string; @@ -69,6 +70,7 @@ export default function UserInfoView({ initialTab = 0, startInEditMode = false, }: UserInfoViewProps) { + const { t } = useTranslation(); const [userData, setUserData] = useState(null); const [teamDetails, setTeamDetails] = useState([]); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); @@ -132,7 +134,7 @@ export default function UserInfoView({ setUserModels(availableModels); } catch (error) { console.error("Error fetching user data:", error); - NotificationsManager.fromBackend("Failed to fetch user data"); + NotificationsManager.fromBackend(t("viewUsers.notifications.failedFetchUserData")); } finally { setIsLoading(false); } @@ -177,7 +179,7 @@ export default function UserInfoView({ user_id: userId, }; await teamMemberAddCall(accessToken, selectedTeamId, member); - NotificationsManager.success("User added to team successfully"); + NotificationsManager.success(t("viewUsers.notifications.userAddedToTeam")); setIsAddTeamModalOpen(false); // Re-fetch user data to refresh teams const data = await userGetInfoV2(accessToken, userId); @@ -197,7 +199,7 @@ export default function UserInfoView({ } } catch (error: any) { console.error("Error adding user to team:", error); - NotificationsManager.fromBackend(error?.message || "Failed to add user to team"); + NotificationsManager.fromBackend(error?.message || t("viewUsers.notifications.failedAddToTeam")); } finally { setIsAddingTeam(false); } @@ -217,7 +219,7 @@ export default function UserInfoView({ user_id: userId, }; await teamMemberDeleteCall(accessToken, teamToRemove.team_id, member); - NotificationsManager.success("User removed from team successfully"); + NotificationsManager.success(t("viewUsers.notifications.userRemovedFromTeam")); setIsRemoveTeamModalOpen(false); setTeamToRemove(null); // Re-fetch user data to refresh teams @@ -238,7 +240,7 @@ export default function UserInfoView({ } } catch (error: any) { console.error("Error removing user from team:", error); - NotificationsManager.fromBackend(error?.message || "Failed to remove user from team"); + NotificationsManager.fromBackend(error?.message || t("viewUsers.notifications.failedRemoveFromTeam")); } finally { setIsRemovingTeam(false); } @@ -253,16 +255,16 @@ export default function UserInfoView({ const handleResetPassword = async () => { if (!accessToken) { - NotificationsManager.fromBackend("Access token not found"); + NotificationsManager.fromBackend(t("viewUsers.notifications.accessTokenNotFound")); return; } try { - NotificationsManager.success("Generating password reset link..."); + NotificationsManager.success(t("viewUsers.notifications.generatingPasswordResetLink")); const data = await invitationCreateCall(accessToken, userId); setInvitationLinkData(data); setIsInvitationLinkModalVisible(true); } catch (error) { - NotificationsManager.fromBackend("Failed to generate password reset link"); + NotificationsManager.fromBackend(t("viewUsers.notifications.failedGeneratePasswordReset")); } }; @@ -271,14 +273,14 @@ export default function UserInfoView({ if (!accessToken) return; setIsDeletingUser(true); await userDeleteCall(accessToken, [userId]); - NotificationsManager.success("User deleted successfully"); + NotificationsManager.success(t("viewUsers.notifications.userDeletedSuccess")); if (onDelete) { onDelete(); } onClose(); } catch (error) { console.error("Error deleting user:", error); - NotificationsManager.fromBackend("Failed to delete user"); + NotificationsManager.fromBackend(t("viewUsers.notifications.failedDeleteUser")); } finally { setIsDeleteModalOpen(false); setIsDeletingUser(false); @@ -306,11 +308,11 @@ export default function UserInfoView({ metadata: formValues.metadata ?? userData.metadata, }); - NotificationsManager.success("User updated successfully"); + NotificationsManager.success(t("viewUsers.notifications.userUpdatedSingle")); setIsEditing(false); } catch (error) { console.error("Error updating user:", error); - NotificationsManager.fromBackend("Failed to update user"); + NotificationsManager.fromBackend(t("viewUsers.notifications.failedUpdateUser")); } }; @@ -318,9 +320,9 @@ export default function UserInfoView({ return (
- Loading user data... + {t("viewUsers.userInfoView.loadingUserData")}
); } @@ -329,9 +331,9 @@ export default function UserInfoView({ return (
- User not found + {t("viewUsers.userInfoView.userNotFound")}
); } @@ -365,9 +367,9 @@ export default function UserInfoView({
- {userData.user_email || "User"} + {userData.user_email || t("viewUsers.userInfoView.userFallbackTitle")}
{userData.user_id}
)} @@ -402,18 +404,18 @@ export default function UserInfoView({ - Overview - Details + {t("viewUsers.userInfoView.overviewTab")} + {t("viewUsers.userInfoView.detailsTab")} @@ -433,22 +435,24 @@ export default function UserInfoView({ - Spend + {t("viewUsers.userInfoView.spend")}
${formatNumberWithCommas(userData.spend || 0, 4)} - of{" "} - {userData.max_budget !== null ? `$${formatNumberWithCommas(userData.max_budget, 4)}` : "Unlimited"} + {t("viewUsers.userInfoView.of")}{" "} + {userData.max_budget !== null + ? `$${formatNumberWithCommas(userData.max_budget, 4)}` + : t("viewUsers.userInfoView.unlimitedBudget")}
- Teams + {t("viewUsers.userInfoView.teams")} {isProxyAdmin && ( )}
@@ -458,8 +462,10 @@ export default function UserInfoView({ - Team Name - {isProxyAdmin && Actions} + {t("viewUsers.userInfoView.teamName")} + {isProxyAdmin && ( + {t("common.actions")} + )} @@ -483,28 +489,28 @@ export default function UserInfoView({
) : ( - No teams + {t("viewUsers.userInfoView.noTeams")} )} {!isTeamsExpanded && teamDetails.length > 20 && ( )} {isTeamsExpanded && teamDetails.length > 20 && ( )}
- Personal Models + {t("viewUsers.userInfoView.personalModels")}
{userData.models?.length && userData.models?.length > 0 ? ( userData.models?.map((model, index) => {model}) ) : ( - All proxy models + {t("viewUsers.userInfoView.allProxyModels")} )}
@@ -515,9 +521,9 @@ export default function UserInfoView({
- User Settings + {t("viewUsers.userInfoView.userSettings")} {!isEditing && userRole && rolesWithWriteAccess.includes(userRole) && ( - + )}
@@ -536,7 +542,7 @@ export default function UserInfoView({ ) : (
- User ID + {t("viewUsers.userInfoView.userIdLabel")}
{userData.user_id}
- Email - {userData.user_email || "Not Set"} + {t("viewUsers.userInfoView.emailLabel")} + {userData.user_email || t("viewUsers.userInfoView.notSet")}
- User Alias - {userData.user_alias || "Not Set"} + {t("viewUsers.userInfoView.userAliasLabel")} + {userData.user_alias || t("viewUsers.userInfoView.notSet")}
- Global Proxy Role - {userData.user_role || "Not Set"} + {t("viewUsers.userInfoView.globalProxyRoleLabel")} + {userData.user_role || t("viewUsers.userInfoView.notSet")}
- Created - {userData.created_at ? new Date(userData.created_at).toLocaleString() : "Unknown"} + {t("viewUsers.userInfoView.createdLabel")} + + {userData.created_at ? new Date(userData.created_at).toLocaleString() : t("common.unknown")} +
- Last Updated - {userData.updated_at ? new Date(userData.updated_at).toLocaleString() : "Unknown"} + {t("viewUsers.userInfoView.lastUpdatedLabel")} + + {userData.updated_at ? new Date(userData.updated_at).toLocaleString() : t("common.unknown")} +
- Personal Models + {t("viewUsers.userInfoView.personalModels")}
{userData.models?.length && userData.models?.length > 0 ? ( userData.models?.map((model, index) => ( @@ -588,27 +598,27 @@ export default function UserInfoView({ )) ) : ( - All proxy models + {t("viewUsers.userInfoView.allProxyModels")} )}
- Max Budget + {t("viewUsers.userInfoView.maxBudgetLabel")} {userData.max_budget !== null && userData.max_budget !== undefined ? `$${formatNumberWithCommas(userData.max_budget, 4)}` - : "Unlimited"} + : t("viewUsers.userInfoView.unlimitedBudget")}
- Budget Reset + {t("viewUsers.userInfoView.budgetResetLabel")} {getBudgetDurationLabel(userData.budget_duration ?? null)}
- Metadata + {t("viewUsers.userInfoView.metadataLabel")}
                       {JSON.stringify(userData.metadata || {}, null, 2)}
                     
@@ -630,14 +640,14 @@ export default function UserInfoView({ {/* Delete Team Member Modal */} setIsAddTeamModalOpen(false)} footer={null} @@ -654,12 +664,12 @@ export default function UserInfoView({ maskClosable={!isAddingTeam} >
- + { const team = availableTeamsForAdd.find((t) => t.team_id === option?.value); if (!team) return false; @@ -675,20 +685,18 @@ export default function UserInfoView({ - + - + user - - Can view team info, but not manage it + {t("viewUsers.userInfoView.roleUserDesc")} - + admin - - - Can create team keys, add members, and manage settings - + {t("viewUsers.userInfoView.roleAdminDesc")} @@ -696,7 +704,7 @@ export default function UserInfoView({
- {isAddingTeam ? "Adding..." : "Add to Team"} + {isAddingTeam ? t("viewUsers.userInfoView.adding") : t("viewUsers.userInfoView.addToTeam")}
diff --git a/ui/litellm-dashboard/src/locales/en.json b/ui/litellm-dashboard/src/locales/en.json index 165d3783e00..d9f259a9236 100644 --- a/ui/litellm-dashboard/src/locales/en.json +++ b/ui/litellm-dashboard/src/locales/en.json @@ -2665,7 +2665,14 @@ "userDeletedSuccess": "User deleted successfully", "failedDeleteUser": "Failed to delete user", "selectUsersToEdit": "Please select users to edit", - "userUpdatedSuccess": "User {{userId}} updated successfully" + "userUpdatedSuccess": "User {{userId}} updated successfully", + "failedFetchUserData": "Failed to fetch user data", + "userAddedToTeam": "User added to team successfully", + "failedAddToTeam": "Failed to add user to team", + "userRemovedFromTeam": "User removed from team successfully", + "failedRemoveFromTeam": "Failed to remove user from team", + "userUpdatedSingle": "User updated successfully", + "failedUpdateUser": "Failed to update user" }, "deleteModal": { "title": "Delete User?", @@ -2675,6 +2682,86 @@ "labelUserId": "User ID", "labelGlobalProxyRole": "Global Proxy Role", "labelTotalSpend": "Total Spend (USD)" + }, + "columns": { + "userId": "User ID", + "copyUserId": "Copy User ID", + "userIdCopied": "User ID copied to clipboard", + "email": "Email", + "status": "Status", + "scimInactiveTooltip": "Deactivated via SCIM (external identity provider). The user's virtual keys are blocked.", + "inactive": "Inactive", + "active": "Active", + "globalProxyRole": "Global Proxy Role", + "userAlias": "User Alias", + "spendUsd": "Spend (USD)", + "budgetUsd": "Budget (USD)", + "unlimited": "Unlimited", + "ssoId": "SSO ID", + "ssoIdTooltip": "SSO ID is the ID of the user in the SSO provider. If the user is not using SSO, this will be null.", + "virtualKeys": "Virtual Keys", + "key_one": "{{count}} Key", + "key_other": "{{count}} Keys", + "noKeys": "No Keys", + "editUserDetails": "Edit user details", + "deleteUser": "Delete user", + "resetPassword": "Reset Password" + }, + "table": { + "searchByEmail": "Search by email...", + "filterByUserId": "Filter by User ID", + "filterBySsoId": "Filter by SSO ID", + "selectRole": "Select Role", + "selectTeam": "Select Team", + "showing": "Showing {{from}} - {{to}} of {{total}} results", + "loadingUsers": "Loading users...", + "noUsersFound": "No users found" + }, + "userInfoView": { + "backToUsers": "Back to Users", + "loadingUserData": "Loading user data...", + "userNotFound": "User not found", + "resetPassword": "Reset Password", + "deleteUser": "Delete User", + "overviewTab": "Overview", + "detailsTab": "Details", + "spend": "Spend", + "of": "of", + "unlimitedBudget": "Unlimited", + "teams": "Teams", + "addTeam": "Add Team", + "teamName": "Team Name", + "noTeams": "No teams", + "moreTeams": "+{{count}} more", + "showLess": "Show Less", + "personalModels": "Personal Models", + "allProxyModels": "All proxy models", + "userSettings": "User Settings", + "editSettings": "Edit Settings", + "userIdLabel": "User ID", + "emailLabel": "Email", + "userAliasLabel": "User Alias", + "globalProxyRoleLabel": "Global Proxy Role", + "createdLabel": "Created", + "lastUpdatedLabel": "Last Updated", + "maxBudgetLabel": "Max Budget", + "budgetResetLabel": "Budget Reset", + "metadataLabel": "Metadata", + "notSet": "Not Set", + "removeFromTeamTitle": "Remove from Team", + "removeFromTeamAlert": "Removing this user from the team will also delete any keys the user created for this team.", + "removeFromTeamMessage": "Are you sure you want to remove this user from the team? This action cannot be undone.", + "teamMembershipTitle": "Team Membership", + "teamLabel": "Team", + "addUserToTeamTitle": "Add User to Team", + "teamFormLabel": "Team", + "selectTeamPlaceholder": "Select a team", + "memberRoleLabel": "Member Role", + "roleUserDesc": "- Can view team info, but not manage it", + "roleAdminDesc": "- Can create team keys, add members, and manage settings", + "adding": "Adding...", + "addToTeam": "Add to Team", + "userFallbackTitle": "User" } }, "viewLogs": { diff --git a/ui/litellm-dashboard/src/locales/zh-CN.json b/ui/litellm-dashboard/src/locales/zh-CN.json index 29da5ab6acf..ffc8804f4c4 100644 --- a/ui/litellm-dashboard/src/locales/zh-CN.json +++ b/ui/litellm-dashboard/src/locales/zh-CN.json @@ -2665,7 +2665,14 @@ "userDeletedSuccess": "用户已成功删除", "failedDeleteUser": "删除用户失败", "selectUsersToEdit": "请选择要编辑的用户", - "userUpdatedSuccess": "用户 {{userId}} 更新成功" + "userUpdatedSuccess": "用户 {{userId}} 更新成功", + "failedFetchUserData": "获取用户数据失败", + "userAddedToTeam": "用户已成功加入团队", + "failedAddToTeam": "将用户加入团队失败", + "userRemovedFromTeam": "用户已成功从团队中移除", + "failedRemoveFromTeam": "从团队中移除用户失败", + "userUpdatedSingle": "用户更新成功", + "failedUpdateUser": "更新用户失败" }, "deleteModal": { "title": "删除用户?", @@ -2675,6 +2682,86 @@ "labelUserId": "用户 ID", "labelGlobalProxyRole": "全局代理角色", "labelTotalSpend": "总花费(USD)" + }, + "columns": { + "userId": "用户 ID", + "copyUserId": "复制用户 ID", + "userIdCopied": "用户 ID 已复制到剪贴板", + "email": "邮箱", + "status": "状态", + "scimInactiveTooltip": "已通过 SCIM(外部身份提供商)停用。该用户的虚拟密钥已被封锁。", + "inactive": "未激活", + "active": "已激活", + "globalProxyRole": "全局代理角色", + "userAlias": "用户别名", + "spendUsd": "花费(USD)", + "budgetUsd": "预算(USD)", + "unlimited": "无限制", + "ssoId": "SSO ID", + "ssoIdTooltip": "SSO ID 是用户在 SSO 提供商中的 ID。如果用户未使用 SSO,此字段将为空。", + "virtualKeys": "虚拟密钥", + "key_one": "{{count}} 个密钥", + "key_other": "{{count}} 个密钥", + "noKeys": "无密钥", + "editUserDetails": "编辑用户详情", + "deleteUser": "删除用户", + "resetPassword": "重置密码" + }, + "table": { + "searchByEmail": "按邮箱搜索...", + "filterByUserId": "按用户 ID 筛选", + "filterBySsoId": "按 SSO ID 筛选", + "selectRole": "选择角色", + "selectTeam": "选择团队", + "showing": "显示第 {{from}} - {{to}} 条,共 {{total}} 条", + "loadingUsers": "加载用户中...", + "noUsersFound": "未找到用户" + }, + "userInfoView": { + "backToUsers": "返回用户列表", + "loadingUserData": "加载用户数据中...", + "userNotFound": "未找到用户", + "resetPassword": "重置密码", + "deleteUser": "删除用户", + "overviewTab": "概览", + "detailsTab": "详情", + "spend": "花费", + "of": "共", + "unlimitedBudget": "无限制", + "teams": "团队", + "addTeam": "添加团队", + "teamName": "团队名称", + "noTeams": "暂无团队", + "moreTeams": "+{{count}} 个更多", + "showLess": "收起", + "personalModels": "个人模型", + "allProxyModels": "所有代理模型", + "userSettings": "用户设置", + "editSettings": "编辑设置", + "userIdLabel": "用户 ID", + "emailLabel": "邮箱", + "userAliasLabel": "用户别名", + "globalProxyRoleLabel": "全局代理角色", + "createdLabel": "创建时间", + "lastUpdatedLabel": "最后更新", + "maxBudgetLabel": "最大预算", + "budgetResetLabel": "预算重置", + "metadataLabel": "元数据", + "notSet": "未设置", + "removeFromTeamTitle": "从团队中移除", + "removeFromTeamAlert": "将此用户从团队中移除后,该用户为此团队创建的所有密钥也将被删除。", + "removeFromTeamMessage": "确定要将此用户从团队中移除吗?此操作无法撤销。", + "teamMembershipTitle": "团队成员", + "teamLabel": "团队", + "addUserToTeamTitle": "将用户加入团队", + "teamFormLabel": "团队", + "selectTeamPlaceholder": "选择团队", + "memberRoleLabel": "成员角色", + "roleUserDesc": "- 可查看团队信息,但无法管理", + "roleAdminDesc": "- 可创建团队密钥、添加成员并管理设置", + "adding": "添加中...", + "addToTeam": "加入团队", + "userFallbackTitle": "用户" } }, "viewLogs": {