refactor(i18n): U15 ApiAccessKeySection i18n 迁移 — 56 个 hash 乱码键清理为语义化 user.apiKey.* 键 + 组件全量接入 useTranslation

- 清理 56 个 apiaccesskey.s{hash} 乱码键为语义化 user.apiKey.* 键 (title/issue/rotate/revoke/descLabel 等)
- ApiAccessKeySection.tsx 全量接入 useTranslation() + t() 调用, UI 中文 57→0
- 复用 common.loading/common.cancel/common.save 通用键
- zh-CN/en-US 完全对齐 (7556 = 7556), 零 hash 键残留
- tsc -b=0, npm build✓ (18.23s), ESLint errors=0/any=0, go build=0, 17/17 测试通过
This commit is contained in:
attacker committed 2026-07-10 18:29:03 +08:00
1 parent a2edf6aa9f
commit 178c98dd37
3 files changed
+177 -171

No files matched your search

@@ -1,5 +1,6 @@
// ApiAccessKeySection — 单条 CI/CD 访问凭证(凭证卡片布局)// Logan.Li
// ApiAccessKeySection — 单条 CI/CD 访问凭证(凭证卡片布局)
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@@ -52,7 +53,7 @@ function toDateInputValue(value?: string) {
return Number.isNaN(d.getTime()) ? defaultExpiresDate() : d.toISOString().slice(0, 10);
}
/** 根据创建/到期时间计算有效期进度(0-100)// Logan.Li */
/** Compute expiry progress (0-100) based on created/expires timestamps */
function expiryProgress(createdAt?: string, expiresAt?: string) {
if (!createdAt || !expiresAt) return { pct: 0, daysLeft: null as number | null };
const start = new Date(createdAt).getTime();
@@ -71,6 +72,7 @@ function shortKeyId(id?: string) {
}
export function ApiAccessKeySection() {
const { t } = useTranslation();
const { toast } = useToast();
const confirm = useConfirm();
const apiBase = getPublicApiBase();
@@ -137,9 +139,9 @@ export function ApiAccessKeySection() {
});
setKeyMeta(res);
setFormOpen(false);
toast({ title: '凭证信息已更新' });
toast({ title: t('user.apiKey.updated') });
} catch (e: unknown) {
toast({ title: '更新失败', description: getErrorMessage(e), variant: 'destructive' });
toast({ title: t('user.apiKey.updateFailed'), description: getErrorMessage(e), variant: 'destructive' });
} finally {
setActionLoading(false);
}
@@ -148,9 +150,9 @@ export function ApiAccessKeySection() {
const hasKey = !!keyMeta?.has_token;
const ok = await confirm({
title: hasKey ? '轮换凭证' : '签发凭证',
description: hasKey ? '旧凭证将立即作废,是否继续?' : '凭证明文仅显示一次,请妥善保存。',
confirmText: hasKey ? '轮换' : '签发',
title: hasKey ? t('user.apiKey.rotateTitle') : t('user.apiKey.issue'),
description: hasKey ? t('user.apiKey.rotateDesc') : t('user.apiKey.issueDesc'),
confirmText: hasKey ? t('user.apiKey.rotateBtn') : t('user.apiKey.issue'),
variant: hasKey ? 'destructive' : 'default',
});
if (!ok) return;
@@ -178,9 +180,9 @@ export function ApiAccessKeySection() {
expired: false,
days_left: Math.ceil((new Date(res.expires_at).getTime() - Date.now()) / 86400000),
});
toast({ title: '凭证已签发', description: '已保存至本浏览器,请立即复制' });
toast({ title: t('user.apiKey.issued'), description: t('user.apiKey.issuedDesc') });
} catch (e: unknown) {
toast({ title: '签发失败', description: getErrorMessage(e), variant: 'destructive' });
toast({ title: t('user.apiKey.issueFailed'), description: getErrorMessage(e), variant: 'destructive' });
} finally {
setActionLoading(false);
}
@@ -188,9 +190,9 @@ export function ApiAccessKeySection() {
const handleRevoke = async () => {
const ok = await confirm({
title: '吊销凭证',
description: '吊销后 Pipeline / curl 调用将全部失败,是否继续?',
confirmText: '吊销',
title: t('user.apiKey.revokeTitle'),
description: t('user.apiKey.revokeDesc'),
confirmText: t('user.apiKey.revokeBtn'),
variant: 'destructive',
});
if (!ok) return;
@@ -200,15 +202,15 @@ export function ApiAccessKeySection() {
setPlainToken('');
setSessionApiAccessKey('');
setKeyMeta({ has_token: false });
toast({ title: '凭证已吊销' });
toast({ title: t('user.apiKey.revoked') });
} catch (e: unknown) {
toast({ title: '吊销失败', description: getErrorMessage(e), variant: 'destructive' });
toast({ title: t('user.apiKey.revokeFailed'), description: getErrorMessage(e), variant: 'destructive' });
} finally {
setActionLoading(false);
}
};
const authHeader = copyableToken ? `X-AIOps-Access-Key: ${copyableToken}` : 'X-AIOps-Access-Key: <你的访问凭证>';
const authHeader = copyableToken ? `X-AIOps-Access-Key: ${copyableToken}` : `X-AIOps-Access-Key: <${t('user.apiKey.placeholderToken')}>`;
const sampleCluster = 'test-k8s';
const sampleNs = 'default';
const sampleDep = 'demo-jar';
@@ -232,27 +234,27 @@ export function ApiAccessKeySection() {
<div>
<CardTitle className="text-base flex items-center gap-2 text-[var(--dg-text-primary)]">
<KeyRound className="h-4 w-4 text-[var(--dg-blue)]" />
CI/CD 访问凭证
<Badge className="text-[10px] bg-[var(--dg-blue)]/15 text-[var(--dg-blue)] border-[var(--dg-blue)]/30">长期有效</Badge>
{t('user.apiKey.title')}
<Badge className="text-[10px] bg-[var(--dg-blue)]/15 text-[var(--dg-blue)] border-[var(--dg-blue)]/30">{t('user.apiKey.longTerm')}</Badge>
</CardTitle>
<CardDescription className="text-xs mt-1">
每人唯一凭证,Deployment 发布脚本自动引用,不随登录会话过期。
{t('user.apiKey.desc')}
</CardDescription>
</div>
{!loading && !keyMeta?.has_token && (
<Button size="sm" onClick={openCreate} disabled={actionLoading}>
<Plus className="h-4 w-4 mr-1" />签发凭证
<Plus className="h-4 w-4 mr-1" />{t('user.apiKey.issue')}
</Button>
)}
</CardHeader>
<CardContent className="space-y-4">
{loading ? (
<div className="flex items-center gap-2 py-8 text-sm text-[var(--dg-text-muted)] justify-center">
<Loader2 className="h-4 w-4 animate-spin" />加载中…
<Loader2 className="h-4 w-4 animate-spin" />{t('common.loading')}
</div>
) : keyMeta?.has_token ? (
<div className="rounded-xl border border-[var(--dg-border)] bg-[var(--dg-bg-tertiary)]/40 overflow-hidden">
{/* 凭证主体:左侧色条 + 内容区 // Logan.Li */}
{/* credential body: left color bar + content area */}
<div className="flex">
<div className={cn(
'w-1 shrink-0',
@@ -261,7 +263,7 @@ export function ApiAccessKeySection() {
<div className="flex-1 p-4 space-y-4">
<div className="flex flex-wrap items-start justify-between gap-3">
<div className="space-y-1 min-w-0 flex-1">
<p className="text-[10px] uppercase tracking-wider text-[var(--dg-text-muted)]">凭证字符串</p>
<p className="text-[10px] uppercase tracking-wider text-[var(--dg-text-muted)]">{t('user.apiKey.tokenLabel')}</p>
<div className="flex items-center gap-2">
<code className="flex-1 min-w-0 text-xs font-mono text-[var(--dg-text-primary)] bg-[var(--dg-bg-secondary)] border border-[var(--dg-border)] rounded-md px-3 py-2 truncate block">
{showToken && copyableToken ? copyableToken : mask}
@@ -272,7 +274,7 @@ export function ApiAccessKeySection() {
size="icon"
className="h-9 w-9 shrink-0 border-[var(--dg-border)]"
onClick={() => setShowToken(!showToken)}
title={showToken ? '隐藏' : '显示'}
title={showToken ? t('user.apiKey.hide') : t('user.apiKey.show')}
>
{showToken ? <EyeOff className="h-3.5 w-3.5" /> : <Eye className="h-3.5 w-3.5" />}
</Button>
@@ -281,41 +283,41 @@ export function ApiAccessKeySection() {
</div>
<div className="flex flex-wrap gap-1.5 shrink-0">
<Button variant="outline" size="sm" className="h-8 text-xs border-[var(--dg-border)]" onClick={openEdit} disabled={actionLoading}>
<Settings2 className="h-3 w-3 mr-1" />调整
<Settings2 className="h-3 w-3 mr-1" />{t('user.apiKey.adjust')}
</Button>
<Button variant="outline" size="sm" className="h-8 text-xs border-[var(--dg-border)]" onClick={openCreate} disabled={actionLoading}>
<RotateCw className="h-3 w-3 mr-1" />轮换
<RotateCw className="h-3 w-3 mr-1" />{t('user.apiKey.rotate')}
</Button>
<Button variant="outline" size="sm" className="h-8 text-xs border-red-500/30 dg-text-red" onClick={handleRevoke} disabled={actionLoading}>
<Ban className="h-3 w-3 mr-1" />吊销
<Ban className="h-3 w-3 mr-1" />{t('user.apiKey.revoke')}
</Button>
</div>
</div>
<div className="grid grid-cols-2 sm:grid-cols-3 gap-x-6 gap-y-3 text-xs">
<div>
<p className="text-[var(--dg-text-muted)] mb-0.5">用途说明</p>
<p className="text-[var(--dg-text-secondary)]">{keyMeta.description || '未填写'}</p>
<p className="text-[var(--dg-text-muted)] mb-0.5">{t('user.apiKey.descLabel')}</p>
<p className="text-[var(--dg-text-secondary)]">{keyMeta.description || t('user.apiKey.descEmpty')}</p>
</div>
<div>
<p className="text-[var(--dg-text-muted)] mb-0.5">凭证标识</p>
<p className="text-[var(--dg-text-muted)] mb-0.5">{t('user.apiKey.keyIdLabel')}</p>
<p className="font-mono text-[var(--dg-text-secondary)]" title={keyMeta.key_id}>{shortKeyId(keyMeta.key_id)}</p>
</div>
<div>
<p className="text-[var(--dg-text-muted)] mb-0.5">签发时间</p>
<p className="text-[var(--dg-text-muted)] mb-0.5">{t('user.apiKey.createdLabel')}</p>
<p className="text-[var(--dg-text-secondary)]">{formatDateTime(keyMeta.created_at)}</p>
</div>
</div>
{/* 有效期进度条 // Logan.Li */}
{/* expiry progress bar */}
<div className="space-y-1.5">
<div className="flex items-center justify-between text-[11px]">
<span className="flex items-center gap-1 text-[var(--dg-text-muted)]">
<Clock className="h-3 w-3" />
{isExpired ? '已过期' : daysLeft != null ? `剩余 ${daysLeft} 天` : '有效期'}
{isExpired ? t('user.apiKey.expired') : daysLeft != null ? t('user.apiKey.daysLeft', { days: daysLeft }) : t('user.apiKey.validity')}
</span>
<span className="text-[var(--dg-text-muted)]">
有效至 {formatDateTime(keyMeta.expires_at)}
{t('user.apiKey.validUntil', { date: formatDateTime(keyMeta.expires_at) })}
</span>
</div>
<div className="h-1.5 rounded-full bg-[var(--dg-bg-secondary)] overflow-hidden">
@@ -329,13 +331,13 @@ export function ApiAccessKeySection() {
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-[11px] text-[var(--dg-text-muted)] pt-1 border-t border-[var(--dg-border)]/60">
<span className="inline-flex items-center gap-1">
<Activity className="h-3 w-3" />
最近调用 {formatDateTime(keyMeta.last_used_at)}
{t('user.apiKey.lastUsed', { date: formatDateTime(keyMeta.last_used_at) })}
</span>
{keyMeta.prefix && (
<span>前缀 <code className="text-[var(--dg-text-secondary)]">{keyMeta.prefix}</code></span>
<span>{t('user.apiKey.prefix')} <code className="text-[var(--dg-text-secondary)]">{keyMeta.prefix}</code></span>
)}
{isExpired && (
<Badge variant="outline" className="text-[10px] border-red-500/40 dg-text-red">需续期或轮换</Badge>
<Badge variant="outline" className="text-[10px] border-red-500/40 dg-text-red">{t('user.apiKey.needRenew')}</Badge>
)}
</div>
</div>
@@ -346,35 +348,35 @@ export function ApiAccessKeySection() {
<div className="mx-auto mb-3 h-12 w-12 rounded-full bg-[var(--dg-blue)]/10 flex items-center justify-center">
<KeyRound className="h-6 w-6 text-[var(--dg-blue)]" />
</div>
<p className="text-sm text-[var(--dg-text-secondary)]">尚未签发 CI/CD 访问凭证</p>
<p className="text-xs text-[var(--dg-text-muted)] mt-1 mb-4">签发后可用于 Pipeline 触发镜像更新、滚动重启等操作</p>
<p className="text-sm text-[var(--dg-text-secondary)]">{t('user.apiKey.emptyTitle')}</p>
<p className="text-xs text-[var(--dg-text-muted)] mt-1 mb-4">{t('user.apiKey.emptyDesc')}</p>
<Button size="sm" onClick={openCreate} disabled={actionLoading}>
<Plus className="h-4 w-4 mr-1" />立即签发
<Plus className="h-4 w-4 mr-1" />{t('user.apiKey.issueNow')}
</Button>
</div>
)}
{keyMeta?.has_token && !copyableToken && (
<p className="text-[11px] dg-text-yellow/90">本机未缓存凭证明文,请点击「轮换」重新签发一次;签发后将自动填入 Deployment 发布 curl。</p>
<p className="text-[11px] dg-text-yellow/90">{t('user.apiKey.cacheWarning')}</p>
)}
<div className="rounded-lg bg-[var(--dg-bg-tertiary)]/60 p-4 space-y-3">
<p className="text-xs font-medium text-[var(--dg-text-secondary)] flex items-center gap-1.5">
<Rocket className="h-3.5 w-3.5 dg-text-purple" />调用示例
<Rocket className="h-3.5 w-3.5 dg-text-purple" />{t('user.apiKey.examples')}
</p>
<CodeSnippetBlock
label="更新镜像"
label={t('user.apiKey.updateImage')}
code={updateCurl}
copyLabel="curl 已复制"
copyLabel={t('user.apiKey.curlCopied')}
copyDisabled={!copyableToken}
copyDisabledTitle="请先签发访问凭证"
copyDisabledTitle={t('user.apiKey.copyDisabledTitle')}
/>
<CodeSnippetBlock
label="滚动重启"
label={t('user.apiKey.restart')}
code={restartCurl}
copyLabel="curl 已复制"
copyLabel={t('user.apiKey.curlCopied')}
copyDisabled={!copyableToken}
copyDisabledTitle="请先签发访问凭证"
copyDisabledTitle={t('user.apiKey.copyDisabledTitle')}
/>
</div>
</CardContent>
@@ -384,24 +386,24 @@ export function ApiAccessKeySection() {
<DialogContent className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] sm:max-w-md">
<DialogHeader>
<DialogTitle className="text-[var(--dg-text-primary)]">
{formMode === 'edit' ? '调整凭证' : keyMeta?.has_token ? '轮换凭证' : '签发凭证'}
{formMode === 'edit' ? t('user.apiKey.adjustTitle') : keyMeta?.has_token ? t('user.apiKey.rotateTitle') : t('user.apiKey.issue')}
</DialogTitle>
<DialogDescription className="text-xs text-[var(--dg-text-muted)]">
{formMode === 'edit' ? '修改用途说明与有效期限,凭证字符串不变。' : '填写用途并设定有效期,签发后请立即保存明文。'}
{formMode === 'edit' ? t('user.apiKey.editDesc') : t('user.apiKey.createDesc')}
</DialogDescription>
</DialogHeader>
<div className="space-y-3 py-2">
<div>
<Label className="text-xs text-[var(--dg-text-muted)]">用途说明</Label>
<Label className="text-xs text-[var(--dg-text-muted)]">{t('user.apiKey.descLabel')}</Label>
<Input
value={formDescription}
onChange={e => setFormDescription(e.target.value)}
placeholder="如 Jenkins 流水线、GitLab CI"
placeholder={t('user.apiKey.placeholderDesc')}
className="mt-1 h-9 bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)]"
/>
</div>
<div>
<Label className="text-xs text-[var(--dg-text-muted)]">有效至</Label>
<Label className="text-xs text-[var(--dg-text-muted)]">{t('user.apiKey.validUntilLabel')}</Label>
<Input
type="date"
value={formExpiresAt}
@@ -411,10 +413,10 @@ export function ApiAccessKeySection() {
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setFormOpen(false)} className="border-[var(--dg-border)]">取消</Button>
<Button variant="outline" onClick={() => setFormOpen(false)} className="border-[var(--dg-border)]">{t('common.cancel')}</Button>
<Button onClick={handleFormSubmit} disabled={actionLoading || !formExpiresAt}>
{actionLoading ? <Loader2 className="h-4 w-4 animate-spin mr-1" /> : null}
{formMode === 'edit' ? '保存' : keyMeta?.has_token ? '轮换' : '签发'}
{formMode === 'edit' ? t('common.save') : keyMeta?.has_token ? t('user.apiKey.rotateBtn') : t('user.apiKey.issue')}
</Button>
</DialogFooter>
</DialogContent>
@@ -423,19 +425,19 @@ export function ApiAccessKeySection() {
<Dialog open={tokenDialogOpen} onOpenChange={setTokenDialogOpen}>
<DialogContent className="bg-[var(--dg-bg-secondary)] border-[var(--dg-border)] sm:max-w-lg">
<DialogHeader>
<DialogTitle className="text-[var(--dg-text-primary)]">请保存凭证明文</DialogTitle>
<DialogTitle className="text-[var(--dg-text-primary)]">{t('user.apiKey.saveTokenTitle')}</DialogTitle>
<DialogDescription className="text-xs text-[var(--dg-text-muted)]">
仅此一次机会查看完整字符串,关闭后无法再次获取。
{t('user.apiKey.saveTokenDesc')}
</DialogDescription>
</DialogHeader>
<CodeSnippetBlock
label="访问凭证"
label={t('user.apiKey.accessTokenLabel')}
code={revealedToken}
copyLabel="凭证已复制"
copyLabel={t('user.apiKey.credentialCopied')}
className="mt-2"
/>
<DialogFooter>
<Button onClick={() => setTokenDialogOpen(false)}>我已保存</Button>
<Button onClick={() => setTokenDialogOpen(false)}>{t('user.apiKey.saved')}</Button>
</DialogFooter>
</DialogContent>
</Dialog>
+58 -56
View File
@@ -8018,62 +8018,64 @@
"breadcrumbNav": "Breadcrumb Navigation"
},
"user": {
"apiaccesskey.s1a2b3c4d": "CI/CD Access Credential",
"apiaccesskey.s2b3c4d5e": "Long-term",
"apiaccesskey.s3c4d5e6f": "Unique per user, auto-referenced by deployment scripts, does not expire with login session.",
"apiaccesskey.s4d5e6f7g": "Issue Credential",
"apiaccesskey.s5e6f7g8h": "Credential String",
"apiaccesskey.s6f7g8h9i": "Hide",
"apiaccesskey.s7g8h9i0j": "Show",
"apiaccesskey.s8h9i0j1k": "Edit",
"apiaccesskey.s9i0j1k2l": "Rotate",
"apiaccesskey.s0j1k2l3m": "Revoke",
"apiaccesskey.s1k2l3m4n": "Description",
"apiaccesskey.s2l3m4n5o": "Not specified",
"apiaccesskey.s3m4n5o6p": "Key ID",
"apiaccesskey.s4n5o6p7q": "Issued At",
"apiaccesskey.s5o6p7q8r": "Expired",
"apiaccesskey.s6p7q8r9s": "{{days}} days remaining",
"apiaccesskey.s7q8r9s0t": "Valid until",
"apiaccesskey.s8r9s0t1u": "Valid until {{date}}",
"apiaccesskey.s9s0t1u2v": "Last used {{date}}",
"apiaccesskey.s0t1u2v3w": "Prefix",
"apiaccesskey.s1u2v3w4x": "Needs renewal or rotation",
"apiaccesskey.s2v3w4x5y": "No CI/CD access credential issued yet",
"apiaccesskey.s3w4x5y6z": "After issuing, use for Pipeline image updates, rolling restarts, etc.",
"apiaccesskey.s4x5y6z7a": "Issue Now",
"apiaccesskey.s5y6z7a8b": "Credential plaintext not cached locally; click 'Rotate' to re-issue.",
"apiaccesskey.s6z7a8b9c": "Usage Examples",
"apiaccesskey.s7a8b9c0d": "Update Image",
"apiaccesskey.s8b9c0d1e": "curl copied",
"apiaccesskey.s9c0d1e2f": "Please issue a credential first",
"apiaccesskey.s0d1e2f3g": "Rolling Restart",
"apiaccesskey.s1e2f3g4h": "Credential info updated",
"apiaccesskey.s2f3g4h5i": "Update failed",
"apiaccesskey.s3g4h5i6j": "Credential issued",
"apiaccesskey.s4h5i6j7k": "Saved to this browser, please copy now",
"apiaccesskey.s5i6j7k8l": "Issue failed",
"apiaccesskey.s6j7k8l9m": "Revoke Credential",
"apiaccesskey.s7k8l9m0n": "After revoking, all Pipeline/curl calls will fail. Continue?",
"apiaccesskey.s8l9m0n1o": "Revoke",
"apiaccesskey.s9m0n1o2p": "Credential revoked",
"apiaccesskey.s0n1o2p3q": "Revoke failed",
"apiaccesskey.s1o2p3q4r": "Rotate Credential",
"apiaccesskey.s2p3q4r5s": "Old credential will be invalidated immediately. Continue?",
"apiaccesskey.s3q4r5s6t": "Rotate",
"apiaccesskey.s4r5s6t7u": "Plaintext shown only once, please save carefully.",
"apiaccesskey.s5s6t7u8v": "Edit Credential",
"apiaccesskey.s6t7u8v9w": "Update description and expiry; credential string unchanged.",
"apiaccesskey.s7u8v9w0x": "Set description and expiry; save plaintext immediately after issuing.",
"apiaccesskey.s8v9w0x1y": "e.g. Jenkins pipeline, GitLab CI",
"apiaccesskey.s9w0x1y2z": "Valid until",
"apiaccesskey.s0x1y2z3a": "Save",
"apiaccesskey.s1y2z3a4b": "Save Credential Plaintext",
"apiaccesskey.s2z3a4b5c": "This is the only chance to view the full string; cannot be retrieved after closing.",
"apiaccesskey.s3a4b5c6d": "Access Credential",
"apiaccesskey.s4b5c6d7e": "Credential copied",
"apiaccesskey.s5c6d7e8f": "I've saved it",
"apiaccesskey.s6d7e8f9g": "Your access credential"
"apiKey": {
"title": "CI/CD Access Credential",
"longTerm": "Long-term",
"desc": "Unique per user, auto-referenced by deployment scripts, does not expire with login session.",
"issue": "Issue Credential",
"tokenLabel": "Credential String",
"hide": "Hide",
"show": "Show",
"adjust": "Edit",
"rotate": "Rotate",
"revoke": "Revoke",
"descLabel": "Description",
"descEmpty": "Not specified",
"keyIdLabel": "Key ID",
"createdLabel": "Issued At",
"expired": "Expired",
"daysLeft": "{{days}} days remaining",
"validity": "Valid until",
"validUntil": "Valid until {{date}}",
"lastUsed": "Last used {{date}}",
"prefix": "Prefix",
"needRenew": "Needs renewal or rotation",
"emptyTitle": "No CI/CD access credential issued yet",
"emptyDesc": "After issuing, use for Pipeline image updates, rolling restarts, etc.",
"issueNow": "Issue Now",
"cacheWarning": "Credential plaintext not cached locally; click 'Rotate' to re-issue.",
"examples": "Usage Examples",
"updateImage": "Update Image",
"curlCopied": "curl copied",
"copyDisabledTitle": "Please issue a credential first",
"restart": "Rolling Restart",
"updated": "Credential info updated",
"updateFailed": "Update failed",
"issued": "Credential issued",
"issuedDesc": "Saved to this browser, please copy now",
"issueFailed": "Issue failed",
"revokeTitle": "Revoke Credential",
"revokeDesc": "After revoking, all Pipeline/curl calls will fail. Continue?",
"revokeBtn": "Revoke",
"revoked": "Credential revoked",
"revokeFailed": "Revoke failed",
"rotateTitle": "Rotate Credential",
"rotateDesc": "Old credential will be invalidated immediately. Continue?",
"rotateBtn": "Rotate",
"issueDesc": "Plaintext shown only once, please save carefully.",
"adjustTitle": "Edit Credential",
"editDesc": "Update description and expiry; credential string unchanged.",
"createDesc": "Set description and expiry; save plaintext immediately after issuing.",
"placeholderDesc": "e.g. Jenkins pipeline, GitLab CI",
"validUntilLabel": "Valid until",
"saveBtn": "Save",
"saveTokenTitle": "Save Credential Plaintext",
"saveTokenDesc": "This is the only chance to view the full string; cannot be retrieved after closing.",
"accessTokenLabel": "Access Credential",
"credentialCopied": "Credential copied",
"saved": "I've saved it",
"placeholderToken": "Your access credential"
}
},
"logCenter": {
"saveQuery": "Save as Query",
+58 -56
View File
@@ -8018,62 +8018,64 @@
"breadcrumbNav": "面包屑导航"
},
"user": {
"apiaccesskey.s1a2b3c4d": "CI/CD 访问凭证",
"apiaccesskey.s2b3c4d5e": "长期有效",
"apiaccesskey.s3c4d5e6f": "每人唯一凭证,Deployment 发布脚本自动引用,不随登录会话过期。",
"apiaccesskey.s4d5e6f7g": "签发凭证",
"apiaccesskey.s5e6f7g8h": "凭证字符串",
"apiaccesskey.s6f7g8h9i": "隐藏",
"apiaccesskey.s7g8h9i0j": "显示",
"apiaccesskey.s8h9i0j1k": "调整",
"apiaccesskey.s9i0j1k2l": "轮换",
"apiaccesskey.s0j1k2l3m": "吊销",
"apiaccesskey.s1k2l3m4n": "用途说明",
"apiaccesskey.s2l3m4n5o": "未填写",
"apiaccesskey.s3m4n5o6p": "凭证标识",
"apiaccesskey.s4n5o6p7q": "签发时间",
"apiaccesskey.s5o6p7q8r": "已过期",
"apiaccesskey.s6p7q8r9s": "剩余 {{days}} 天",
"apiaccesskey.s7q8r9s0t": "有效期",
"apiaccesskey.s8r9s0t1u": "有效至 {{date}}",
"apiaccesskey.s9s0t1u2v": "最近调用 {{date}}",
"apiaccesskey.s0t1u2v3w": "前缀",
"apiaccesskey.s1u2v3w4x": "需续期或轮换",
"apiaccesskey.s2v3w4x5y": "尚未签发 CI/CD 访问凭证",
"apiaccesskey.s3w4x5y6z": "签发后可用于 Pipeline 触发镜像更新、滚动重启等操作",
"apiaccesskey.s4x5y6z7a": "立即签发",
"apiaccesskey.s5y6z7a8b": "本机未缓存凭证明文,请点击「轮换」重新签发一次;签发后将自动填入 Deployment 发布 curl。",
"apiaccesskey.s6z7a8b9c": "调用示例",
"apiaccesskey.s7a8b9c0d": "更新镜像",
"apiaccesskey.s8b9c0d1e": "curl 已复制",
"apiaccesskey.s9c0d1e2f": "请先签发访问凭证",
"apiaccesskey.s0d1e2f3g": "滚动重启",
"apiaccesskey.s1e2f3g4h": "凭证信息已更新",
"apiaccesskey.s2f3g4h5i": "更新失败",
"apiaccesskey.s3g4h5i6j": "凭证已签发",
"apiaccesskey.s4h5i6j7k": "已保存至本浏览器,请立即复制",
"apiaccesskey.s5i6j7k8l": "签发失败",
"apiaccesskey.s6j7k8l9m": "吊销凭证",
"apiaccesskey.s7k8l9m0n": "吊销后 Pipeline / curl 调用将全部失败,是否继续?",
"apiaccesskey.s8l9m0n1o": "吊销",
"apiaccesskey.s9m0n1o2p": "凭证已吊销",
"apiaccesskey.s0n1o2p3q": "吊销失败",
"apiaccesskey.s1o2p3q4r": "轮换凭证",
"apiaccesskey.s2p3q4r5s": "旧凭证将立即作废,是否继续?",
"apiaccesskey.s3q4r5s6t": "轮换",
"apiaccesskey.s4r5s6t7u": "凭证明文仅显示一次,请妥善保存。",
"apiaccesskey.s5s6t7u8v": "调整凭证",
"apiaccesskey.s6t7u8v9w": "修改用途说明与有效期限,凭证字符串不变。",
"apiaccesskey.s7u8v9w0x": "填写用途并设定有效期,签发后请立即保存明文。",
"apiaccesskey.s8v9w0x1y": "如 Jenkins 流水线、GitLab CI",
"apiaccesskey.s9w0x1y2z": "有效至",
"apiaccesskey.s0x1y2z3a": "保存",
"apiaccesskey.s1y2z3a4b": "请保存凭证明文",
"apiaccesskey.s2z3a4b5c": "仅此一次机会查看完整字符串,关闭后无法再次获取。",
"apiaccesskey.s3a4b5c6d": "访问凭证",
"apiaccesskey.s4b5c6d7e": "凭证已复制",
"apiaccesskey.s5c6d7e8f": "我已保存",
"apiaccesskey.s6d7e8f9g": "你的访问凭证"
"apiKey": {
"title": "CI/CD 访问凭证",
"longTerm": "长期有效",
"desc": "每人唯一凭证,Deployment 发布脚本自动引用,不随登录会话过期。",
"issue": "签发凭证",
"tokenLabel": "凭证字符串",
"hide": "隐藏",
"show": "显示",
"adjust": "调整",
"rotate": "轮换",
"revoke": "吊销",
"descLabel": "用途说明",
"descEmpty": "未填写",
"keyIdLabel": "凭证标识",
"createdLabel": "签发时间",
"expired": "已过期",
"daysLeft": "剩余 {{days}} 天",
"validity": "有效期",
"validUntil": "有效至 {{date}}",
"lastUsed": "最近调用 {{date}}",
"prefix": "前缀",
"needRenew": "需续期或轮换",
"emptyTitle": "尚未签发 CI/CD 访问凭证",
"emptyDesc": "签发后可用于 Pipeline 触发镜像更新、滚动重启等操作",
"issueNow": "立即签发",
"cacheWarning": "本机未缓存凭证明文,请点击「轮换」重新签发一次;签发后将自动填入 Deployment 发布 curl。",
"examples": "调用示例",
"updateImage": "更新镜像",
"curlCopied": "curl 已复制",
"copyDisabledTitle": "请先签发访问凭证",
"restart": "滚动重启",
"updated": "凭证信息已更新",
"updateFailed": "更新失败",
"issued": "凭证已签发",
"issuedDesc": "已保存至本浏览器,请立即复制",
"issueFailed": "签发失败",
"revokeTitle": "吊销凭证",
"revokeDesc": "吊销后 Pipeline / curl 调用将全部失败,是否继续?",
"revokeBtn": "吊销",
"revoked": "凭证已吊销",
"revokeFailed": "吊销失败",
"rotateTitle": "轮换凭证",
"rotateDesc": "旧凭证将立即作废,是否继续?",
"rotateBtn": "轮换",
"issueDesc": "凭证明文仅显示一次,请妥善保存。",
"adjustTitle": "调整凭证",
"editDesc": "修改用途说明与有效期限,凭证字符串不变。",
"createDesc": "填写用途并设定有效期,签发后请立即保存明文。",
"placeholderDesc": "如 Jenkins 流水线、GitLab CI",
"validUntilLabel": "有效至",
"saveBtn": "保存",
"saveTokenTitle": "请保存凭证明文",
"saveTokenDesc": "仅此一次机会查看完整字符串,关闭后无法再次获取。",
"accessTokenLabel": "访问凭证",
"credentialCopied": "凭证已复制",
"saved": "我已保存",
"placeholderToken": "你的访问凭证"
}
},
"logCenter": {
"saveQuery": "另存为查询",