From ebe6cb92f59a225a20da3dd85536618261978772 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 07:18:33 +0900 Subject: [PATCH] feat(ui): i18n for virtual keys table --- .../VirtualKeysPage/VirtualKeysTable.tsx | 125 +++++++++--------- ui/litellm-dashboard/src/locales/en.json | 44 ++++++ ui/litellm-dashboard/src/locales/zh-CN.json | 44 ++++++ 3 files changed, 153 insertions(+), 60 deletions(-) diff --git a/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx index 295638545c9..95db5f0cbae 100644 --- a/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx +++ b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx @@ -3,6 +3,7 @@ import { useKeys } from "@/app/(dashboard)/hooks/keys/useKeys"; import { useOrganizations } from "@/app/(dashboard)/hooks/organizations/useOrganizations"; import { formatNumberWithCommas } from "@/utils/dataUtils"; import { ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, SwitchVerticalIcon } from "@heroicons/react/outline"; +import { useTranslation } from "react-i18next"; import { ColumnDef, flexRender, @@ -53,6 +54,7 @@ interface VirtualKeysTableProps { */ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSort }: VirtualKeysTableProps) { + const { t } = useTranslation(); const { data: fetchedOrganizations } = useOrganizations(); const resolvedOrganizations = fetchedOrganizations ?? organizations ?? []; const [selectedKey, setSelectedKey] = useState(null); @@ -154,7 +156,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo { id: "token", accessorKey: "token", - header: "Key ID", + header: t("virtualKeys.virtualKeysTable.keyId"), size: 100, enableSorting: true, cell: (info) => { @@ -178,7 +180,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo { id: "key_alias", accessorKey: "key_alias", - header: "Key Alias", + header: t("virtualKeys.virtualKeysTable.keyAlias"), size: 150, enableSorting: true, cell: (info) => { @@ -193,7 +195,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo }, { id: "status", - header: "Status", + header: t("common.status"), size: 100, enableSorting: false, cell: ({ row }) => { @@ -201,18 +203,18 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo if (key.blocked !== true) { return ( - Active + {t("common.active")} ); } const isScimBlocked = (key.metadata as Record | null | undefined)?.scim_blocked === true; const reason = isScimBlocked - ? "Blocked by SCIM (external identity provider deactivated or deleted the owning user)." - : "Blocked. Requests using this key will be rejected with 401."; + ? t("virtualKeys.virtualKeysTable.blockedByScimReason") + : t("virtualKeys.virtualKeysTable.blockedReason"); return ( - Blocked + {t("virtualKeys.virtualKeysTable.blocked")} ); @@ -221,7 +223,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo { id: "key_name", accessorKey: "key_name", - header: "Secret Key", + header: t("virtualKeys.virtualKeysTable.secretKey"), size: 120, enableSorting: false, cell: (info) => {info.getValue() as string}, @@ -229,7 +231,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo { id: "team_alias", accessorKey: "team_id", - header: "Team", + header: t("virtualKeys.virtualKeysTable.team"), size: 120, enableSorting: false, cell: (info) => { @@ -248,7 +250,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo { id: "organization_alias", accessorKey: "org_id", - header: "Organization", + header: t("virtualKeys.virtualKeysTable.organization"), size: 140, enableSorting: false, cell: (info) => { @@ -269,8 +271,8 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo accessorKey: "user", header: () => ( - User - + {t("virtualKeys.virtualKeysTable.user")} + @@ -289,9 +291,9 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo const popoverContent = (
{[ - { label: "User Alias", value: userAlias }, - { label: "User Email", value: userEmail }, - { label: "User ID", value: userId }, + { label: t("virtualKeys.virtualKeysTable.userAlias"), value: userAlias }, + { label: t("virtualKeys.virtualKeysTable.userEmail"), value: userEmail }, + { label: t("virtualKeys.virtualKeysTable.userId"), value: userId }, ].map(({ label, value }) => (
{label} @@ -332,7 +334,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo { id: "created_at", accessorKey: "created_at", - header: "Created At", + header: t("common.createdAt"), size: 120, enableSorting: true, cell: (info) => { @@ -343,7 +345,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo { id: "created_by", accessorKey: "created_by", - header: "Created By", + header: t("virtualKeys.virtualKeysTable.createdBy"), size: 160, enableSorting: false, cell: (info) => { @@ -360,9 +362,9 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo const popoverContent = (
{[ - { label: "User Alias", value: userAlias }, - { label: "User Email", value: userEmail }, - { label: "User ID", value: userId }, + { label: t("virtualKeys.virtualKeysTable.userAlias"), value: userAlias }, + { label: t("virtualKeys.virtualKeysTable.userEmail"), value: userEmail }, + { label: t("virtualKeys.virtualKeysTable.userId"), value: userId }, ].map(({ label, value }) => (
{label} @@ -403,12 +405,12 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo { 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"); }, }, { @@ -416,11 +418,8 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo accessorKey: "last_active", header: () => ( - Last Active - + {t("virtualKeys.virtualKeysTable.lastActive")} + @@ -429,7 +428,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo 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 ( @@ -441,18 +440,18 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo { id: "expires", accessorKey: "expires", - header: "Expires", + header: t("virtualKeys.virtualKeysTable.expires"), 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("virtualKeys.virtualKeysTable.spendUsd"), size: 100, enableSorting: true, cell: (info) => formatNumberWithCommas(info.getValue() as number, 4), @@ -460,13 +459,13 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo { id: "max_budget", accessorKey: "max_budget", - header: "Budget (USD)", + header: t("virtualKeys.virtualKeysTable.budgetUsd"), size: 110, enableSorting: true, cell: (info) => { const maxBudget = info.getValue() as number | null; if (maxBudget === null) { - return "Unlimited"; + return t("virtualKeys.virtualKeysTable.unlimited"); } return `$${formatNumberWithCommas(maxBudget)}`; }, @@ -474,18 +473,18 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo { id: "budget_reset_at", accessorKey: "budget_reset_at", - header: "Budget Reset", + header: t("virtualKeys.virtualKeysTable.budgetReset"), 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("virtualKeys.virtualKeysTable.models"), size: 200, enableSorting: false, cell: (info) => { @@ -496,7 +495,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
{models.length === 0 ? ( - All Proxy Models + {t("virtualKeys.virtualKeysTable.allProxyModels")} ) : ( <> @@ -520,7 +519,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo {models.slice(0, 3).map((model, index) => model === "all-proxy-models" ? ( - All Proxy Models + {t("virtualKeys.virtualKeysTable.allProxyModels")} ) : ( @@ -534,9 +533,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo )} {models.length > 3 && !expandedAccordions[info.row.id] && ( - - +{models.length - 3} {models.length - 3 === 1 ? "more model" : "more models"} - + {t("virtualKeys.virtualKeysTable.moreModel", { count: models.length - 3 })} )} {expandedAccordions[info.row.id] && ( @@ -544,7 +541,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo {models.slice(3).map((model, index) => model === "all-proxy-models" ? ( - All Proxy Models + {t("virtualKeys.virtualKeysTable.allProxyModels")} ) : ( @@ -570,27 +567,35 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo }, { id: "rate_limits", - header: "Rate Limits", + header: t("virtualKeys.virtualKeysTable.rateLimits"), 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"}
+
+ {t("virtualKeys.virtualKeysTable.tpmLimit", { + value: key.tpm_limit !== null ? key.tpm_limit : t("virtualKeys.virtualKeysTable.unlimited"), + })} +
+
+ {t("virtualKeys.virtualKeysTable.rpmLimit", { + value: key.rpm_limit !== null ? key.rpm_limit : t("virtualKeys.virtualKeysTable.unlimited"), + })} +
); }, }, ], - [teams, resolvedOrganizations], + [teams, resolvedOrganizations, t], ); const filterOptions: FilterOption[] = [ { name: "Team ID", - label: "Team ID", + label: t("virtualKeys.virtualKeysTable.filterTeamId"), isSearchable: true, searchFn: async (searchText: string) => { if (!allTeams || allTeams.length === 0) return []; @@ -609,7 +614,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo }, { name: "Organization ID", - label: "Organization ID", + label: t("virtualKeys.virtualKeysTable.filterOrgId"), isSearchable: true, searchFn: async (searchText: string) => { if (!allOrganizations || allOrganizations.length === 0) return []; @@ -621,24 +626,24 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo return filteredOrgs .filter((org) => org.organization_id !== null && org.organization_id !== undefined) .map((org) => ({ - label: `${org.organization_id || "Unknown"} (${org.organization_id})`, + label: `${org.organization_id || t("common.unknown")} (${org.organization_id})`, value: org.organization_id as string, })); }, }, { name: "Key Alias", - label: "Key Alias", + label: t("virtualKeys.virtualKeysTable.filterKeyAlias"), customComponent: PaginatedKeyAliasSelect, }, { name: "User ID", - label: "User ID", + label: t("virtualKeys.virtualKeysTable.filterUserId"), isSearchable: false, }, { name: "Key Hash", - label: "Key Hash", + label: t("virtualKeys.virtualKeysTable.filterKeyHash"), isSearchable: false, }, ]; @@ -725,7 +730,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo ) : ( - Showing {rangeLabel} of {totalCount} results + {t("virtualKeys.virtualKeysTable.showingResults", { range: rangeLabel, total: totalCount })} )} @@ -734,9 +739,9 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo icon={} onClick={handleRefresh} disabled={isButtonLoading} - title="Fetch data" + title={t("virtualKeys.virtualKeysTable.fetchData")} > - {isButtonLoading ? "Fetching" : "Fetch"} + {isButtonLoading ? t("virtualKeys.virtualKeysTable.fetching") : t("virtualKeys.virtualKeysTable.fetch")}
@@ -745,7 +750,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo ) : ( - Page {pageIndex + 1} of {table.getPageCount()} + {t("virtualKeys.virtualKeysTable.pageOf", { page: pageIndex + 1, total: table.getPageCount() })} )} @@ -757,7 +762,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo disabled={isLoading || !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")} )} @@ -769,7 +774,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo disabled={isLoading || !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")} )}
@@ -856,7 +861,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
-

🚅 Loading keys...

+

{t("virtualKeys.virtualKeysTable.loadingKeys")}

@@ -883,7 +888,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo
-

No keys found

+

{t("virtualKeys.virtualKeysTable.noKeysFound")}

diff --git a/ui/litellm-dashboard/src/locales/en.json b/ui/litellm-dashboard/src/locales/en.json index 2dfd91ed8ee..615b22d1412 100644 --- a/ui/litellm-dashboard/src/locales/en.json +++ b/ui/litellm-dashboard/src/locales/en.json @@ -8316,5 +8316,49 @@ "notSet": "Not Set", "updateSettings": "Update Settings" } + }, + "virtualKeys": { + "virtualKeysTable": { + "keyId": "Key ID", + "keyAlias": "Key Alias", + "secretKey": "Secret Key", + "team": "Team", + "organization": "Organization", + "user": "User", + "createdBy": "Created By", + "lastActive": "Last Active", + "expires": "Expires", + "spendUsd": "Spend (USD)", + "budgetUsd": "Budget (USD)", + "budgetReset": "Budget Reset", + "models": "Models", + "rateLimits": "Rate Limits", + "blocked": "Blocked", + "blockedByScimReason": "Blocked by SCIM (external identity provider deactivated or deleted the owning user).", + "blockedReason": "Blocked. Requests using this key will be rejected with 401.", + "userPopoverHint": "Displays the first available value: User Alias, User Email, or User ID.", + "userAlias": "User Alias", + "userEmail": "User Email", + "userId": "User ID", + "allProxyModels": "All Proxy Models", + "moreModel_one": "+{{count}} more model", + "moreModel_other": "+{{count}} more models", + "tpmLimit": "TPM: {{value}}", + "rpmLimit": "RPM: {{value}}", + "unlimited": "Unlimited", + "lastActiveHint": "This is a new field and is not backfilled. Only new key usage will update this value.", + "loadingKeys": "🚅 Loading keys...", + "noKeysFound": "No keys found", + "showingResults": "Showing {{range}} of {{total}} results", + "fetchData": "Fetch data", + "fetching": "Fetching", + "fetch": "Fetch", + "pageOf": "Page {{page}} of {{total}}", + "filterTeamId": "Team ID", + "filterOrgId": "Organization ID", + "filterKeyAlias": "Key Alias", + "filterUserId": "User ID", + "filterKeyHash": "Key Hash" + } } } diff --git a/ui/litellm-dashboard/src/locales/zh-CN.json b/ui/litellm-dashboard/src/locales/zh-CN.json index 09e3fbad33a..554a373f02e 100644 --- a/ui/litellm-dashboard/src/locales/zh-CN.json +++ b/ui/litellm-dashboard/src/locales/zh-CN.json @@ -8316,5 +8316,49 @@ "notSet": "未设置", "updateSettings": "更新设置" } + }, + "virtualKeys": { + "virtualKeysTable": { + "keyId": "密钥 ID", + "keyAlias": "密钥别名", + "secretKey": "密钥", + "team": "团队", + "organization": "组织", + "user": "用户", + "createdBy": "创建者", + "lastActive": "最后活跃", + "expires": "过期时间", + "spendUsd": "花费(USD)", + "budgetUsd": "预算(USD)", + "budgetReset": "预算重置", + "models": "模型", + "rateLimits": "速率限制", + "blocked": "已封禁", + "blockedByScimReason": "被 SCIM 封禁(外部身份提供商已停用或删除了该密钥所属用户)。", + "blockedReason": "已封禁。使用此密钥的请求将以 401 被拒绝。", + "userPopoverHint": "按优先顺序显示:用户别名、用户邮箱或用户 ID。", + "userAlias": "用户别名", + "userEmail": "用户邮箱", + "userId": "用户 ID", + "allProxyModels": "所有代理模型", + "moreModel_one": "+{{count}} 个模型", + "moreModel_other": "+{{count}} 个模型", + "tpmLimit": "TPM: {{value}}", + "rpmLimit": "RPM: {{value}}", + "unlimited": "无限制", + "lastActiveHint": "这是一个新字段,未回填历史数据。仅新的密钥使用记录会更新此值。", + "loadingKeys": "🚅 正在加载密钥...", + "noKeysFound": "未找到密钥", + "showingResults": "显示第 {{range}} 条,共 {{total}} 条", + "fetchData": "拉取数据", + "fetching": "拉取中", + "fetch": "拉取", + "pageOf": "第 {{page}} 页,共 {{total}} 页", + "filterTeamId": "团队 ID", + "filterOrgId": "组织 ID", + "filterKeyAlias": "密钥别名", + "filterUserId": "用户 ID", + "filterKeyHash": "密钥哈希" + } } }