feat(ui): i18n for regenerate key modal

This commit is contained in:
吹雪 2026-06-11 06:55:17 +09:00
parent f293c01f85
commit f7ab412a24
3 changed files with 85 additions and 29 deletions

View file

@ -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<string | null>(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 (
<Modal
title="Regenerate Virtual Key"
title={t("organisms.regenerateKeyModal.title")}
open={visible}
onCancel={handleClose}
width={520}
@ -121,19 +123,19 @@ export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdat
regeneratedKey
? [
<Space key="footer-actions">
<Button onClick={handleClose}>Close</Button>
<Button onClick={handleClose}>{t("common.close")}</Button>
<CopyToClipboard text={regeneratedKey} onCopy={handleCopyKey}>
<Button type="primary" icon={copied ? <CheckOutlined /> : <CopyOutlined />}>
{copied ? "Copied" : "Copy Key"}
{copied ? t("common.copied") : t("organisms.regenerateKeyModal.copyKey")}
</Button>
</CopyToClipboard>
</Space>,
]
: [
<Space key="footer-actions">
<Button onClick={handleClose}>Cancel</Button>
<Button onClick={handleClose}>{t("common.cancel")}</Button>
<Button type="primary" icon={<SyncOutlined />} onClick={handleRegenerateKey} loading={isRegenerating}>
Regenerate
{t("organisms.regenerateKeyModal.regenerate")}
</Button>
</Space>,
]
@ -141,18 +143,18 @@ export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdat
>
{regeneratedKey ? (
<Flex vertical gap="middle">
<Alert type="warning" showIcon message="Save it now, you will not see it again" />
<Alert type="warning" showIcon message={t("organisms.regenerateKeyModal.saveWarning")} />
<Flex vertical gap={2}>
<Text type="secondary" style={{ fontSize: 12 }}>
Key Alias
{t("organisms.regenerateKeyModal.keyAlias")}
</Text>
<Text>{selectedToken?.key_alias || "No alias set"}</Text>
<Text>{selectedToken?.key_alias || t("organisms.regenerateKeyModal.noAliasSet")}</Text>
</Flex>
<Flex vertical gap={6}>
<Text type="secondary" style={{ fontSize: 12 }}>
Virtual Key
{t("organisms.regenerateKeyModal.virtualKey")}
</Text>
<div
style={{
@ -172,23 +174,23 @@ export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdat
</Flex>
) : (
<Form form={form} layout="vertical" style={{ marginTop: 4 }}>
<Form.Item name="key_alias" label="Key Alias">
<Form.Item name="key_alias" label={t("organisms.regenerateKeyModal.keyAlias")}>
<Input disabled />
</Form.Item>
<Row gutter={12}>
<Col span={8}>
<Form.Item name="max_budget" label="Max Budget (USD)">
<Form.Item name="max_budget" label={t("organisms.regenerateKeyModal.maxBudget")}>
<InputNumber step={0.01} precision={2} style={{ width: "100%" }} />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item name="tpm_limit" label="TPM Limit">
<Form.Item name="tpm_limit" label={t("organisms.regenerateKeyModal.tpmLimit")}>
<InputNumber style={{ width: "100%" }} />
</Form.Item>
</Col>
<Col span={8}>
<Form.Item name="rpm_limit" label="RPM Limit">
<Form.Item name="rpm_limit" label={t("organisms.regenerateKeyModal.rpmLimit")}>
<InputNumber style={{ width: "100%" }} />
</Form.Item>
</Col>
@ -198,38 +200,40 @@ export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdat
<Col span={12}>
<Form.Item
name="duration"
label="Expire Key"
label={t("organisms.regenerateKeyModal.expireKey")}
rules={durationRules}
extra={
<Flex vertical gap={2}>
<Text type={keyIsExpired ? "danger" : "secondary"} style={{ fontSize: 12 }}>
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")}
</Text>
{newExpiryTime && (
<Text type="success" style={{ fontSize: 12 }}>
New expiry: {newExpiryTime}
{t("organisms.regenerateKeyModal.newExpiry", { value: newExpiryTime })}
</Text>
)}
</Flex>
}
>
<Input placeholder="e.g. 30s, 30h, 30d" />
<Input placeholder={t("organisms.regenerateKeyModal.durationPlaceholder")} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
name="grace_period"
label="Grace Period"
tooltip="Keep the old key valid for this duration after rotation. Both keys work during this period for seamless cutover. Empty = immediate revoke."
label={t("organisms.regenerateKeyModal.gracePeriod")}
tooltip={t("organisms.regenerateKeyModal.gracePeriodTooltip")}
extra={
<Text type="secondary" style={{ fontSize: 12 }}>
Recommended: 24h to 72h for production keys
{t("organisms.regenerateKeyModal.gracePeriodRecommended")}
</Text>
}
rules={[DURATION_RULE]}
>
<Input placeholder="e.g. 24h, 2d" />
<Input placeholder={t("organisms.regenerateKeyModal.gracePeriodPlaceholder")} />
</Form.Item>
</Col>
</Row>

View file

@ -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."

View file

@ -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 秒。"