From f7ab412a249afafc5f744fd9445e35689799c021 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:55:17 +0900 Subject: [PATCH] feat(ui): i18n for regenerate key modal --- .../organisms/RegenerateKeyModal.tsx | 62 ++++++++++--------- ui/litellm-dashboard/src/locales/en.json | 26 ++++++++ ui/litellm-dashboard/src/locales/zh-CN.json | 26 ++++++++ 3 files changed, 85 insertions(+), 29 deletions(-) diff --git a/ui/litellm-dashboard/src/components/organisms/RegenerateKeyModal.tsx b/ui/litellm-dashboard/src/components/organisms/RegenerateKeyModal.tsx index 8138614982a..67569c10e0f 100644 --- a/ui/litellm-dashboard/src/components/organisms/RegenerateKeyModal.tsx +++ b/ui/litellm-dashboard/src/components/organisms/RegenerateKeyModal.tsx @@ -3,6 +3,7 @@ import { CheckOutlined, CopyOutlined, SyncOutlined } from "@ant-design/icons"; import { Alert, Button, Col, Flex, Form, Input, InputNumber, Modal, Row, Space, Typography } from "antd"; import { useEffect, useState } from "react"; import { CopyToClipboard } from "react-copy-to-clipboard"; +import { useTranslation } from "react-i18next"; import { KeyResponse } from "../key_team_helpers/key_list"; import NotificationManager from "../molecules/notifications_manager"; import { regenerateKeyCall } from "../networking"; @@ -10,11 +11,6 @@ import { calculateExpiryPreviewFromDuration, formatExpiresUtc, isKeyExpired } fr const { Text } = Typography; -const DURATION_RULE = { - pattern: /^(\d+(s|m|h|d|w|mo))?$/, - message: "Must be a duration like 30s, 30m, 24h, 2d, 1w, or 1mo", -}; - interface RegenerateKeyModalProps { selectedToken: KeyResponse | null; visible: boolean; @@ -23,6 +19,7 @@ interface RegenerateKeyModalProps { } export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdate }: RegenerateKeyModalProps) { + const { t } = useTranslation(); const { accessToken } = useAuthorized(); const [form] = Form.useForm(); const [regeneratedKey, setRegeneratedKey] = useState(null); @@ -32,10 +29,15 @@ export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdat const keyIsExpired = isKeyExpired(selectedToken?.expires); const durationValue = Form.useWatch("duration", form); + const DURATION_RULE = { + pattern: /^(\d+(s|m|h|d|w|mo))?$/, + message: t("organisms.regenerateKeyModal.durationRuleMessage"), + }; + // Expired keys must get a new duration, otherwise regeneration produces a key // that inherits the old (past) expiry and is immediately unusable. const durationRules = keyIsExpired - ? [{ required: true, message: "Expiration is required for expired keys" }, DURATION_RULE] + ? [{ required: true, message: t("organisms.regenerateKeyModal.expirationRequired") }, DURATION_RULE] : [DURATION_RULE]; useEffect(() => { @@ -62,7 +64,7 @@ export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdat const response = await regenerateKeyCall(accessToken, selectedToken.token || selectedToken.token_id, formValues); setRegeneratedKey(response.key); - NotificationManager.success("Virtual Key regenerated successfully"); + NotificationManager.success(t("organisms.regenerateKeyModal.regenerateSuccess")); // Build the update payload. Spread the API response first so any new // fields it returns (new token, timestamps, etc.) are captured, then @@ -112,7 +114,7 @@ export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdat return ( - + , ] : [ - + , ] @@ -141,18 +143,18 @@ export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdat > {regeneratedKey ? ( - + - Key Alias + {t("organisms.regenerateKeyModal.keyAlias")} - {selectedToken?.key_alias || "No alias set"} + {selectedToken?.key_alias || t("organisms.regenerateKeyModal.noAliasSet")} - Virtual Key + {t("organisms.regenerateKeyModal.virtualKey")}
) : (
- + - + - + - + @@ -198,38 +200,40 @@ export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdat - Current expiry: {selectedToken?.expires ? formatExpiresUtc(selectedToken.expires) : "Never"} - {keyIsExpired && " (expired)"} + {t("organisms.regenerateKeyModal.currentExpiry", { + value: selectedToken?.expires ? formatExpiresUtc(selectedToken.expires) : t("common.never"), + })} + {keyIsExpired && t("organisms.regenerateKeyModal.expiredSuffix")} {newExpiryTime && ( - New expiry: {newExpiryTime} + {t("organisms.regenerateKeyModal.newExpiry", { value: newExpiryTime })} )} } > - + - Recommended: 24h to 72h for production keys + {t("organisms.regenerateKeyModal.gracePeriodRecommended")} } rules={[DURATION_RULE]} > - + diff --git a/ui/litellm-dashboard/src/locales/en.json b/ui/litellm-dashboard/src/locales/en.json index 452079ac72d..b545159c4d5 100644 --- a/ui/litellm-dashboard/src/locales/en.json +++ b/ui/litellm-dashboard/src/locales/en.json @@ -8139,6 +8139,32 @@ "noModelsFound": "No models found" } }, + "organisms": { + "regenerateKeyModal": { + "title": "Regenerate Virtual Key", + "saveWarning": "Save it now, you will not see it again", + "keyAlias": "Key Alias", + "noAliasSet": "No alias set", + "virtualKey": "Virtual Key", + "copyKey": "Copy Key", + "regenerate": "Regenerate", + "maxBudget": "Max Budget (USD)", + "tpmLimit": "TPM Limit", + "rpmLimit": "RPM Limit", + "expireKey": "Expire Key", + "currentExpiry": "Current expiry: {{value}}", + "expiredSuffix": " (expired)", + "newExpiry": "New expiry: {{value}}", + "gracePeriod": "Grace Period", + "gracePeriodTooltip": "Keep the old key valid for this duration after rotation. Both keys work during this period for seamless cutover. Empty = immediate revoke.", + "gracePeriodRecommended": "Recommended: 24h to 72h for production keys", + "durationPlaceholder": "e.g. 30s, 30h, 30d", + "gracePeriodPlaceholder": "e.g. 24h, 2d", + "durationRuleMessage": "Must be a duration like 30s, 30m, 24h, 2d, 1w, or 1mo", + "expirationRequired": "Expiration is required for expired keys", + "regenerateSuccess": "Virtual Key regenerated successfully" + } + }, "alerting": { "alertingSettings": { "saveSuccess": "Wait 10s for proxy to update." diff --git a/ui/litellm-dashboard/src/locales/zh-CN.json b/ui/litellm-dashboard/src/locales/zh-CN.json index 8b091096586..c05cd7e130c 100644 --- a/ui/litellm-dashboard/src/locales/zh-CN.json +++ b/ui/litellm-dashboard/src/locales/zh-CN.json @@ -8139,6 +8139,32 @@ "noModelsFound": "未找到模型" } }, + "organisms": { + "regenerateKeyModal": { + "title": "重新生成虚拟密钥", + "saveWarning": "请立即保存,此后将无法再次查看", + "keyAlias": "密钥别名", + "noAliasSet": "未设置别名", + "virtualKey": "虚拟密钥", + "copyKey": "复制密钥", + "regenerate": "重新生成", + "maxBudget": "最大预算(美元)", + "tpmLimit": "TPM 限制", + "rpmLimit": "RPM 限制", + "expireKey": "密钥过期", + "currentExpiry": "当前过期时间:{{value}}", + "expiredSuffix": "(已过期)", + "newExpiry": "新过期时间:{{value}}", + "gracePeriod": "宽限期", + "gracePeriodTooltip": "轮换后旧密钥在此时长内仍然有效。宽限期内新旧密钥均可使用,便于平滑切换。留空则立即吊销旧密钥。", + "gracePeriodRecommended": "生产环境密钥建议设置 24h 至 72h", + "durationPlaceholder": "例如 30s、30h、30d", + "gracePeriodPlaceholder": "例如 24h、2d", + "durationRuleMessage": "请输入有效时长,如 30s、30m、24h、2d、1w 或 1mo", + "expirationRequired": "已过期的密钥必须设置新的过期时间", + "regenerateSuccess": "虚拟密钥重新生成成功" + } + }, "alerting": { "alertingSettings": { "saveSuccess": "等待代理更新,约需 10 秒。"