From fd28940f498b95e3e6e82cbd4cda6238ccc36a5b 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 03:06:02 +0900 Subject: [PATCH] feat(ui): i18n for team batch 3 --- .../src/components/team/TeamMemberTab.tsx | 40 +++++---- .../components/team/TeamVirtualKeysTable.tsx | 84 +++++++++---------- .../src/components/team/available_teams.tsx | 48 ++++++----- .../components/team/member_permissions.tsx | 28 ++++--- ui/litellm-dashboard/src/locales/en.json | 71 ++++++++++++++++ ui/litellm-dashboard/src/locales/zh-CN.json | 71 ++++++++++++++++ 6 files changed, 248 insertions(+), 94 deletions(-) diff --git a/ui/litellm-dashboard/src/components/team/TeamMemberTab.tsx b/ui/litellm-dashboard/src/components/team/TeamMemberTab.tsx index e2f108dcbf5..03fe1fb40dd 100644 --- a/ui/litellm-dashboard/src/components/team/TeamMemberTab.tsx +++ b/ui/litellm-dashboard/src/components/team/TeamMemberTab.tsx @@ -8,6 +8,7 @@ import { InfoCircleOutlined } from "@ant-design/icons"; import { Space, Tooltip, Typography } from "antd"; import type { ColumnsType } from "antd/es/table"; import MemberTable from "@/components/common_components/MemberTable"; +import { useTranslation } from "react-i18next"; import { TeamData } from "./TeamInfo"; interface TeamMemberTabProps { @@ -27,6 +28,7 @@ export default function TeamMemberTab({ setIsEditMemberModalVisible, setIsAddMemberModalVisible, }: TeamMemberTabProps) { + const { t } = useTranslation(); const formatNumber = (value: number | null): string => { if (value === null || value === undefined) return "0"; @@ -70,7 +72,7 @@ export default function TeamMemberTab({ // Helper function to get rate limits for a user const getUserRateLimits = (userId: string | null): string => { - if (!userId) return "No Limits"; + if (!userId) return t("teamPage.teamMemberTab.noLimits"); const membership = teamData.team_memberships.find((tm) => tm.user_id === userId); const rpmLimit = membership?.litellm_budget_table?.rpm_limit; const tpmLimit = membership?.litellm_budget_table?.tpm_limit; @@ -79,7 +81,7 @@ export default function TeamMemberTab({ const tpmText = tpmLimit ? `${formatNumber(tpmLimit)} TPM` : null; const limits = [rpmText, tpmText].filter(Boolean); - return limits.length > 0 ? limits.join(" / ") : "No Limits"; + return limits.length > 0 ? limits.join(" / ") : t("teamPage.teamMemberTab.noLimits"); }; const { data: uiSettingsData } = useUISettings(); @@ -105,8 +107,8 @@ export default function TeamMemberTab({ { title: ( - Model Scope - + {t("teamPage.teamMemberTab.modelScopeTitle")} + @@ -115,7 +117,7 @@ export default function TeamMemberTab({ render: (_: unknown, record: Member) => { const models = getUserAllowedModels(record.user_id); if (!models) { - return (all team models); + return {t("teamPage.teamMemberTab.allTeamModels")}; } const displayed = models.slice(0, 2); const remaining = models.length - displayed.length; @@ -128,7 +130,9 @@ export default function TeamMemberTab({ ))} {remaining > 0 && ( - +{remaining} more + + {t("teamPage.teamMemberTab.moreModels", { count: remaining })} + )} @@ -138,8 +142,8 @@ export default function TeamMemberTab({ { title: ( - Current Cycle Spend (USD) - + {t("teamPage.teamMemberTab.currentCycleSpendTitle")} + @@ -152,8 +156,8 @@ export default function TeamMemberTab({ { title: ( - Total Spend (USD) - + {t("teamPage.teamMemberTab.totalSpendTitle")} + @@ -164,17 +168,19 @@ export default function TeamMemberTab({ ), }, { - title: "Team Member Budget (USD)", + title: t("teamPage.teamMemberTab.memberBudgetTitle"), key: "budget", render: (_: unknown, record: Member) => { const budget = getUserBudget(record.user_id); return ( - {budget ? `$${formatNumberWithCommas(Number(budget), 4)}` : "No Limit"} + + {budget ? `$${formatNumberWithCommas(Number(budget), 4)}` : t("teamPage.teamMemberTab.noLimit")} + ); }, }, { - title: "Budget Reset", + title: t("teamPage.teamMemberTab.budgetResetTitle"), key: "budget_reset", render: (_: unknown, record: Member) => { const reset = getUserBudgetReset(record.user_id); @@ -188,8 +194,8 @@ export default function TeamMemberTab({ { title: ( - Team Member Rate Limits - + {t("teamPage.teamMemberTab.rateLimitsTitle")} + @@ -218,8 +224,8 @@ export default function TeamMemberTab({ }} onDelete={handleMemberDelete} onAddMember={() => setIsAddMemberModalVisible(true)} - roleColumnTitle="Team Role" - roleTooltip="This role applies only to this team and is independent from the user's proxy-level role." + roleColumnTitle={t("teamPage.teamMemberTab.roleColumnTitle")} + roleTooltip={t("teamPage.teamMemberTab.roleTooltip")} extraColumns={extraColumns} showDeleteForMember={() => isProxyAdmin || (canEditTeam && !isUserTeamAdmin) || (isUserTeamAdmin && !disableTeamAdminDeleteTeamUser) diff --git a/ui/litellm-dashboard/src/components/team/TeamVirtualKeysTable.tsx b/ui/litellm-dashboard/src/components/team/TeamVirtualKeysTable.tsx index e51c124d618..297c9602e9a 100644 --- a/ui/litellm-dashboard/src/components/team/TeamVirtualKeysTable.tsx +++ b/ui/litellm-dashboard/src/components/team/TeamVirtualKeysTable.tsx @@ -28,6 +28,7 @@ import { InfoCircleOutlined } from "@ant-design/icons"; import { Popover, Skeleton, Tooltip, Typography } from "antd"; import DefaultProxyAdminTag from "../common_components/DefaultProxyAdminTag"; import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; import { getModelDisplayName } from "../key_team_helpers/fetch_available_models_team_key"; import { KeyResponse, Team } from "../key_team_helpers/key_list"; import FilterComponent, { FilterOption } from "../molecules/filter"; @@ -48,6 +49,7 @@ interface TeamVirtualKeysTableProps { * Displays all virtual keys belonging to the team with same format and styling. */ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVirtualKeysTableProps) { + const { t } = useTranslation(); const { accessToken } = useAuthorized(); const [selectedKey, setSelectedKey] = useState(null); const [sorting, setSorting] = useState([{ id: "created_at", desc: true }]); @@ -211,7 +213,7 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi { id: "token", accessorKey: "token", - header: "Key ID", + header: t("teamPage.teamVirtualKeysTable.colKeyId"), size: 100, enableSorting: true, cell: (info) => { @@ -235,7 +237,7 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi { id: "key_alias", accessorKey: "key_alias", - header: "Key Alias", + header: t("teamPage.teamVirtualKeysTable.colKeyAlias"), size: 150, enableSorting: true, cell: (info) => { @@ -253,7 +255,7 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi { id: "key_name", accessorKey: "key_name", - header: "Secret Key", + header: t("teamPage.teamVirtualKeysTable.colSecretKey"), size: 120, enableSorting: false, cell: (info) => {info.getValue() as string}, @@ -261,7 +263,7 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi { id: "organization_id", accessorKey: "organization_id", - header: "Organization ID", + header: t("teamPage.teamVirtualKeysTable.colOrganizationId"), size: 140, enableSorting: false, cell: (info) => (info.getValue() ? info.renderValue() : "-"), @@ -269,7 +271,7 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi { id: "user_email", accessorKey: "user", - header: "User Email", + header: t("teamPage.teamVirtualKeysTable.colUserEmail"), size: 160, enableSorting: false, cell: (info) => { @@ -288,12 +290,13 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi { id: "user_id", accessorKey: "user_id", - header: "User ID", + header: t("teamPage.teamVirtualKeysTable.colUserId"), size: 70, enableSorting: false, cell: (info) => { const userId = info.getValue() as string | null; - const displayValue = userId === "default_user_id" ? "Default Proxy Admin" : userId; + const displayValue = + userId === "default_user_id" ? t("teamPage.teamVirtualKeysTable.defaultProxyAdmin") : userId; const width = info.cell.column.getSize(); return ( @@ -307,7 +310,7 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi { id: "created_at", accessorKey: "created_at", - header: "Created At", + header: t("common.createdAt"), size: 120, enableSorting: true, cell: (info) => { @@ -318,7 +321,7 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi { id: "created_by", accessorKey: "created_by", - header: "Created By", + header: t("teamPage.teamVirtualKeysTable.colCreatedBy"), size: 70, enableSorting: false, cell: (info) => { @@ -334,9 +337,9 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi const popoverContent = ( {[ - { label: "User Alias", value: userAlias }, - { label: "User Email", value: userEmail }, - { label: "User ID", value: userId }, + { label: t("teamPage.teamVirtualKeysTable.popoverUserAlias"), value: userAlias }, + { label: t("teamPage.teamVirtualKeysTable.popoverUserEmail"), value: userEmail }, + { label: t("teamPage.teamVirtualKeysTable.popoverUserId"), value: userId }, ].map(({ label, value }) => ( {label} @@ -377,12 +380,12 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi { id: "updated_at", accessorKey: "updated_at", - header: "Updated At", + header: t("common.updatedAt"), size: 120, enableSorting: true, cell: (info) => { const value = info.getValue(); - return value ? new Date(value as string).toLocaleDateString() : "Never"; + return value ? new Date(value as string).toLocaleDateString() : t("common.never"); }, }, { @@ -390,11 +393,8 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi accessorKey: "last_active", header: () => ( - Last Active - + {t("teamPage.teamVirtualKeysTable.colLastActive")} + @@ -403,7 +403,7 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi enableSorting: false, cell: (info) => { const value = info.getValue(); - if (!value) return "Unknown"; + if (!value) return t("common.unknown"); const date = new Date(value as string); return ( @@ -415,18 +415,18 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi { id: "expires", accessorKey: "expires", - header: "Expires", + header: t("teamPage.teamVirtualKeysTable.colExpires"), size: 120, enableSorting: false, cell: (info) => { const value = info.getValue(); - return value ? new Date(value as string).toLocaleDateString() : "Never"; + return value ? new Date(value as string).toLocaleDateString() : t("common.never"); }, }, { id: "spend", accessorKey: "spend", - header: "Spend (USD)", + header: t("teamPage.teamVirtualKeysTable.colSpend"), size: 100, enableSorting: true, cell: (info) => formatNumberWithCommas(info.getValue() as number, 4), @@ -434,30 +434,30 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi { id: "max_budget", accessorKey: "max_budget", - header: "Budget (USD)", + header: t("teamPage.teamVirtualKeysTable.colBudget"), size: 110, enableSorting: true, cell: (info) => { const maxBudget = info.getValue() as number | null; - if (maxBudget === null) return "Unlimited"; + if (maxBudget === null) return t("teamPage.teamVirtualKeysTable.unlimited"); return `$${formatNumberWithCommas(maxBudget)}`; }, }, { id: "budget_reset_at", accessorKey: "budget_reset_at", - header: "Budget Reset", + header: t("teamPage.teamVirtualKeysTable.colBudgetReset"), size: 130, enableSorting: false, cell: (info) => { const value = info.getValue(); - return value ? new Date(value as string).toLocaleString() : "Never"; + return value ? new Date(value as string).toLocaleString() : t("common.never"); }, }, { id: "models", accessorKey: "models", - header: "Models", + header: t("teamPage.teamVirtualKeysTable.colModels"), size: 200, enableSorting: false, cell: (info) => { @@ -468,7 +468,7 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi {models.length === 0 ? ( - All Proxy Models + {t("teamPage.teamVirtualKeysTable.allProxyModels")} ) : ( <> @@ -492,7 +492,7 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi {models.slice(0, 3).map((model, index) => model === "all-proxy-models" ? ( - All Proxy Models + {t("teamPage.teamVirtualKeysTable.allProxyModels")} ) : ( @@ -506,9 +506,7 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi )} {models.length > 3 && !expandedAccordions[info.row.id] && ( - - +{models.length - 3} {models.length - 3 === 1 ? "more model" : "more models"} - + {t("teamPage.teamVirtualKeysTable.moreModel", { count: models.length - 3 })} )} {expandedAccordions[info.row.id] && ( @@ -516,7 +514,7 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi {models.slice(3).map((model, index) => model === "all-proxy-models" ? ( - All Proxy Models + {t("teamPage.teamVirtualKeysTable.allProxyModels")} ) : ( @@ -542,21 +540,21 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi }, { id: "rate_limits", - header: "Rate Limits", + header: t("teamPage.teamVirtualKeysTable.colRateLimits"), size: 140, enableSorting: false, cell: ({ row }) => { const key = row.original; return ( - TPM: {key.tpm_limit !== null ? key.tpm_limit : "Unlimited"} - RPM: {key.rpm_limit !== null ? key.rpm_limit : "Unlimited"} + TPM: {key.tpm_limit !== null ? key.tpm_limit : t("teamPage.teamVirtualKeysTable.unlimited")} + RPM: {key.rpm_limit !== null ? key.rpm_limit : t("teamPage.teamVirtualKeysTable.unlimited")} ); }, }, ], - [expandedAccordions], + [expandedAccordions, t], ); const handleSortingChange = useCallback( @@ -620,7 +618,7 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi ) : ( - Page {pageIndex + 1} of {table.getPageCount()} + {t("teamPage.teamVirtualKeysTable.pageOf", { current: pageIndex + 1, total: table.getPageCount() })} )} @@ -632,7 +630,7 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi disabled={isLoading || isFetching || !table.getCanPreviousPage()} className="px-3 py-1 text-sm border rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed" > - Previous + {t("common.previous")} )} @@ -644,7 +642,7 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi disabled={isLoading || isFetching || !table.getCanNextPage()} className="px-3 py-1 text-sm border rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed" > - Next + {t("common.next")} )} @@ -730,7 +728,7 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi - Loading keys... + {t("teamPage.teamVirtualKeysTable.loadingKeys")} @@ -763,7 +761,7 @@ export function TeamVirtualKeysTable({ teamId, teamAlias, organization }: TeamVi - No keys found + {t("teamPage.teamVirtualKeysTable.noKeysFound")} diff --git a/ui/litellm-dashboard/src/components/team/available_teams.tsx b/ui/litellm-dashboard/src/components/team/available_teams.tsx index f1c7ab07818..a3e7013cee5 100644 --- a/ui/litellm-dashboard/src/components/team/available_teams.tsx +++ b/ui/litellm-dashboard/src/components/team/available_teams.tsx @@ -13,6 +13,7 @@ import { } from "@tremor/react"; import { availableTeamListCall, teamMemberAddCall } from "../networking"; import NotificationsManager from "../molecules/notifications_manager"; +import { useTranslation, Trans } from "react-i18next"; interface AvailableTeam { team_id: string; @@ -28,6 +29,7 @@ interface AvailableTeamsProps { } const AvailableTeamsPanel: React.FC = ({ accessToken, userID }) => { + const { t } = useTranslation(); const [availableTeams, setAvailableTeams] = useState([]); useEffect(() => { @@ -55,12 +57,12 @@ const AvailableTeamsPanel: React.FC = ({ accessToken, userI role: "user", }); - NotificationsManager.success("Successfully joined team"); + NotificationsManager.success(t("teamPage.availableTeams.joinedSuccess")); // Update available teams list setAvailableTeams((teams) => teams.filter((team) => team.team_id !== teamId)); } catch (error) { console.error("Error joining team:", error); - NotificationsManager.fromBackend("Failed to join team"); + NotificationsManager.fromBackend(t("teamPage.availableTeams.joinFailed")); } }; @@ -69,11 +71,11 @@ const AvailableTeamsPanel: React.FC = ({ accessToken, userI - Team Name - Description - Members - Models - Actions + {t("teamPage.availableTeams.colTeamName")} + {t("common.description")} + {t("teamPage.availableTeams.colMembers")} + {t("teamPage.teamInfo.modelsLabel")} + {t("common.actions")} @@ -83,16 +85,16 @@ const AvailableTeamsPanel: React.FC = ({ accessToken, userI {team.team_alias} - {team.description || "No description available"} + {team.description || t("teamPage.availableTeams.noDescription")} - {team.members_with_roles.length} members + {t("teamPage.availableTeams.memberCount", { count: team.members_with_roles.length })} {!team.models || team.models.length === 0 ? ( - All Proxy Models + {t("teamPage.availableTeams.allProxyModels")} ) : ( team.models.map((model, index) => ( @@ -105,7 +107,7 @@ const AvailableTeamsPanel: React.FC = ({ accessToken, userI handleJoinTeam(team.team_id)}> - Join Team + {t("teamPage.availableTeams.joinTeam")} @@ -114,16 +116,20 @@ const AvailableTeamsPanel: React.FC = ({ accessToken, userI - No available teams to join. See how to set available teams{" "} - - here - - . + + ), + }} + /> diff --git a/ui/litellm-dashboard/src/components/team/member_permissions.tsx b/ui/litellm-dashboard/src/components/team/member_permissions.tsx index 5bbd82f4a5d..0153dc73ca9 100644 --- a/ui/litellm-dashboard/src/components/team/member_permissions.tsx +++ b/ui/litellm-dashboard/src/components/team/member_permissions.tsx @@ -3,6 +3,7 @@ import { ReloadOutlined, SaveOutlined } from "@ant-design/icons"; import { Card, Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow, Text, Title } from "@tremor/react"; import { Button, Checkbox, Empty } from "antd"; import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; import NotificationsManager from "../molecules/notifications_manager"; import { getPermissionInfo } from "./permission_definitions"; @@ -13,6 +14,7 @@ interface MemberPermissionsProps { } const MemberPermissions: React.FC = ({ teamId, accessToken, canEditTeam }) => { + const { t } = useTranslation(); const [permissions, setPermissions] = useState([]); const [selectedPermissions, setSelectedPermissions] = useState([]); const [loading, setLoading] = useState(true); @@ -30,7 +32,7 @@ const MemberPermissions: React.FC = ({ teamId, accessTok setSelectedPermissions(teamPermissions); setHasChanges(false); } catch (error) { - NotificationsManager.fromBackend("Failed to load permissions"); + NotificationsManager.fromBackend(t("teamPage.memberPermissions.loadFailed")); console.error("Error fetching permissions:", error); } finally { setLoading(false); @@ -54,10 +56,10 @@ const MemberPermissions: React.FC = ({ teamId, accessTok if (!accessToken) return; setSaving(true); await teamPermissionsUpdateCall(accessToken, teamId, selectedPermissions); - NotificationsManager.success("Permissions updated successfully"); + NotificationsManager.success(t("teamPage.memberPermissions.saveSuccess")); setHasChanges(false); } catch (error) { - NotificationsManager.fromBackend("Failed to update permissions"); + NotificationsManager.fromBackend(t("teamPage.memberPermissions.saveFailed")); console.error("Error updating permissions:", error); } finally { setSaving(false); @@ -69,7 +71,7 @@ const MemberPermissions: React.FC = ({ teamId, accessTok }; if (loading) { - return Loading permissions...; + return {t("teamPage.memberPermissions.loadingPermissions")}; } const hasPermissions = permissions.length > 0; @@ -77,31 +79,31 @@ const MemberPermissions: React.FC = ({ teamId, accessTok return ( - Member Permissions + {t("teamPage.memberPermissions.title")} {canEditTeam && hasChanges && ( } onClick={handleReset}> - Reset + {t("common.reset")} }> - Save Changes + {t("teamPage.memberPermissions.saveChanges")} )} - Control what team members can do when they are not team admins. + {t("teamPage.memberPermissions.subtitle")} {hasPermissions ? ( - Method - Endpoint - Description + {t("teamPage.memberPermissions.colMethod")} + {t("teamPage.memberPermissions.colEndpoint")} + {t("common.description")} - Allow Access + {t("teamPage.memberPermissions.colAllowAccess")} @@ -138,7 +140,7 @@ const MemberPermissions: React.FC = ({ teamId, accessTok ) : ( - + )} diff --git a/ui/litellm-dashboard/src/locales/en.json b/ui/litellm-dashboard/src/locales/en.json index 4aceafb8625..a0013e8324d 100644 --- a/ui/litellm-dashboard/src/locales/en.json +++ b/ui/litellm-dashboard/src/locales/en.json @@ -6418,6 +6418,77 @@ "userIdLabel": "User ID", "emailLabel": "Email", "roleLabel": "Role" + }, + "teamMemberTab": { + "noLimits": "No Limits", + "modelScopeTitle": "Model Scope", + "modelScopeTooltip": "Models this member can access. Empty means they inherit all team models.", + "allTeamModels": "(all team models)", + "moreModels": "+{{count}} more", + "currentCycleSpendTitle": "Current Cycle Spend (USD)", + "currentCycleSpendTooltip": "Spend for the current budget cycle. Resets to $0 when the member's budget window rolls over. This is the value checked against the member's budget.", + "totalSpendTitle": "Total Spend (USD)", + "totalSpendTooltip": "Cumulative spend by this member within this team, across all budget cycles. Tracking began 2026-04-21; spend from before that date is not included.", + "memberBudgetTitle": "Team Member Budget (USD)", + "noLimit": "No Limit", + "budgetResetTitle": "Budget Reset", + "rateLimitsTitle": "Team Member Rate Limits", + "rateLimitsTooltip": "Rate limits for this member's usage within this team.", + "roleColumnTitle": "Team Role", + "roleTooltip": "This role applies only to this team and is independent from the user's proxy-level role." + }, + "teamVirtualKeysTable": { + "colKeyId": "Key ID", + "colKeyAlias": "Key Alias", + "colSecretKey": "Secret Key", + "colOrganizationId": "Organization ID", + "colUserEmail": "User Email", + "colUserId": "User ID", + "colCreatedBy": "Created By", + "colLastActive": "Last Active", + "colLastActiveTooltip": "This is a new field and is not backfilled. Only new key usage will update this value.", + "colExpires": "Expires", + "colSpend": "Spend (USD)", + "colBudget": "Budget (USD)", + "colBudgetReset": "Budget Reset", + "colModels": "Models", + "colRateLimits": "Rate Limits", + "defaultProxyAdmin": "Default Proxy Admin", + "popoverUserAlias": "User Alias", + "popoverUserEmail": "User Email", + "popoverUserId": "User ID", + "unlimited": "Unlimited", + "allProxyModels": "All Proxy Models", + "moreModel_one": "+{{count}} more model", + "moreModel_other": "+{{count}} more models", + "pageOf": "Page {{current}} of {{total}}", + "loadingKeys": "Loading keys...", + "noKeysFound": "No keys found" + }, + "availableTeams": { + "colTeamName": "Team Name", + "colMembers": "Members", + "memberCount_one": "{{count}} member", + "memberCount_other": "{{count}} members", + "allProxyModels": "All Proxy Models", + "noDescription": "No description available", + "joinTeam": "Join Team", + "joinedSuccess": "Successfully joined team", + "joinFailed": "Failed to join team", + "noTeamsAvailable": "No available teams to join. See how to set available teams here." + }, + "memberPermissions": { + "loadingPermissions": "Loading permissions...", + "title": "Member Permissions", + "subtitle": "Control what team members can do when they are not team admins.", + "saveChanges": "Save Changes", + "colMethod": "Method", + "colEndpoint": "Endpoint", + "colAllowAccess": "Allow Access", + "noPermissions": "No permissions available", + "loadFailed": "Failed to load permissions", + "saveSuccess": "Permissions updated successfully", + "saveFailed": "Failed to update permissions" } } } diff --git a/ui/litellm-dashboard/src/locales/zh-CN.json b/ui/litellm-dashboard/src/locales/zh-CN.json index 0805be9fb2a..b5161e8fa22 100644 --- a/ui/litellm-dashboard/src/locales/zh-CN.json +++ b/ui/litellm-dashboard/src/locales/zh-CN.json @@ -6418,6 +6418,77 @@ "userIdLabel": "用户 ID", "emailLabel": "邮箱", "roleLabel": "角色" + }, + "teamMemberTab": { + "noLimits": "无限制", + "modelScopeTitle": "模型范围", + "modelScopeTooltip": "该成员可访问的模型。为空表示继承所有团队模型。", + "allTeamModels": "(所有团队模型)", + "moreModels": "+{{count}} 个更多", + "currentCycleSpendTitle": "当前周期花费(USD)", + "currentCycleSpendTooltip": "当前预算周期的花费。成员预算窗口滚动时将重置为 $0,此值与成员预算进行比较。", + "totalSpendTitle": "累计花费(USD)", + "totalSpendTooltip": "该成员在此团队内跨所有预算周期的累计花费。统计始于 2026-04-21,之前的花费不包含在内。", + "memberBudgetTitle": "团队成员预算(USD)", + "noLimit": "无限制", + "budgetResetTitle": "预算重置", + "rateLimitsTitle": "团队成员速率限制", + "rateLimitsTooltip": "该成员在此团队内的速率限制。", + "roleColumnTitle": "团队角色", + "roleTooltip": "此角色仅适用于当前团队,与用户的代理级角色相互独立。" + }, + "teamVirtualKeysTable": { + "colKeyId": "密钥 ID", + "colKeyAlias": "密钥别名", + "colSecretKey": "密钥", + "colOrganizationId": "组织 ID", + "colUserEmail": "用户邮箱", + "colUserId": "用户 ID", + "colCreatedBy": "创建者", + "colLastActive": "最后活跃", + "colLastActiveTooltip": "这是一个新字段,未进行历史数据回填。只有新的密钥使用记录才会更新此值。", + "colExpires": "到期时间", + "colSpend": "花费(USD)", + "colBudget": "预算(USD)", + "colBudgetReset": "预算重置", + "colModels": "模型", + "colRateLimits": "速率限制", + "defaultProxyAdmin": "默认代理管理员", + "popoverUserAlias": "用户别名", + "popoverUserEmail": "用户邮箱", + "popoverUserId": "用户 ID", + "unlimited": "无限制", + "allProxyModels": "所有代理模型", + "moreModel_one": "+{{count}} 个模型", + "moreModel_other": "+{{count}} 个模型", + "pageOf": "第 {{current}} 页,共 {{total}} 页", + "loadingKeys": "正在加载密钥...", + "noKeysFound": "未找到密钥" + }, + "availableTeams": { + "colTeamName": "团队名称", + "colMembers": "成员", + "memberCount_one": "{{count}} 位成员", + "memberCount_other": "{{count}} 位成员", + "allProxyModels": "所有代理模型", + "noDescription": "暂无描述", + "joinTeam": "加入团队", + "joinedSuccess": "成功加入团队", + "joinFailed": "加入团队失败", + "noTeamsAvailable": "暂无可加入的团队。查看如何设置可用团队,请点击此处。" + }, + "memberPermissions": { + "loadingPermissions": "正在加载权限...", + "title": "成员权限", + "subtitle": "控制非团队管理员的成员可以执行的操作。", + "saveChanges": "保存更改", + "colMethod": "方法", + "colEndpoint": "端点", + "colAllowAccess": "允许访问", + "noPermissions": "暂无可用权限", + "loadFailed": "加载权限失败", + "saveSuccess": "权限更新成功", + "saveFailed": "更新权限失败" } } }
Loading keys...
{t("teamPage.teamVirtualKeysTable.loadingKeys")}
No keys found
{t("teamPage.teamVirtualKeysTable.noKeysFound")}