feat(i18n): 剩余模块全量国际化 — 51% 覆盖率 (231 文件接入 t())

批量转换 163 个文件,3204 处中文替换为 t() 调用:
- Monitor: 日志中心/告警中心/数据源/大屏/站点拨测/SNMP/中间件 全部转换
- Kubernetes: ResourceVisualEditor/NamespaceResourcePage/拓扑/事件 全部转换
- Deploy: 流水线/发布/凭证/RBAC 全部转换
- Server/Cloud/Agent/Task/Layout/UI: 全部转换
- 翻译词典扩展至 6474 行 (22 namespaces, 5000+ keys)
- tsc 编译零错误
This commit is contained in:
attacker committed 2026-07-03 01:51:57 +08:00
1 parent 78c27037e3
commit ecec73989b
166 files changed
+6925 -2800

No files matched your search

@@ -15,15 +15,17 @@ import { FileBrowserDialog } from './FileBrowserDialog';
import { PageShell } from '@/components/layout/PageShell';
import { StatStrip } from '@/components/ui/stat-strip';
import type { JsonData } from '@/types/common';
import { useTranslation } from 'react-i18next';
import i18n from '@/i18n';
// ═══════════ 阶段定义 (与 PipelineManager 共用) ═══════════
const STAGE_DEFS = [
{ key: 'checkout', label: '代码检出', icon: GitBranch, color: '#6366F1', order: 1 },
{ key: 'build', label: '构建', icon: Box, color: '#F59E0B', order: 2 },
{ key: 'test', label: '测试', icon: CheckCircle2,color: '#10B981', order: 3 },
{ key: 'image', label: '镜像打包', icon: Container, color: '#06B6D4', order: 4 },
{ key: 'deploy', label: '部署', icon: Rocket, color: '#EF4444', order: 5 },
{ key: 'verify', label: '验证', icon: ShieldCheck, color: '#3B82F6', order: 6 },
{ key: 'checkout', label: i18n.t('pipeline.checkout'), icon: GitBranch, color: '#6366F1', order: 1 },
{ key: 'build', label: i18n.t('pipeline.build'), icon: Box, color: '#F59E0B', order: 2 },
{ key: 'test', label: i18n.t('common.test'), icon: CheckCircle2,color: '#10B981', order: 3 },
{ key: 'image', label: i18n.t('deploy.stage.image'), icon: Container, color: '#06B6D4', order: 4 },
{ key: 'deploy', label: i18n.t('pipeline.deploy'), icon: Rocket, color: '#EF4444', order: 5 },
{ key: 'verify', label: i18n.t('common.validate'), icon: ShieldCheck, color: '#3B82F6', order: 6 },
].sort((a, b) => a.order - b.order);
interface PipelineRun {
@@ -74,10 +76,10 @@ const STATUS_ICONS: JsonData = {
function formatDuration(seconds: number): string {
if (!seconds) return '-';
if (seconds < 60) return `${seconds}秒`;
if (seconds < 60) return i18n.t('deploy.pipelinerundetail.sbe383c', { seconds: seconds });
const m = Math.floor(seconds / 60);
const s = seconds % 60;
return `${m}分${s}秒`;
return i18n.t('deploy.pipelinerundetail.s084409', { m: m, s: s });
}
function formatTime(ts: string): string {
@@ -130,6 +132,7 @@ function getStageLogSummary(logs: LogEntry[], stageKey: string): string {
}
export default function PipelineRunDetail() {
const { t } = useTranslation();
const { code: pipelineCode } = useParams<{ code: string }>();
const [pipeline, setPipeline] = useState<JsonData>(null);
const [runs, setRuns] = useState<PipelineRun[]>([]);
@@ -165,7 +168,7 @@ export default function PipelineRunDetail() {
if (!selectedRun && list.length > 0) {
setSelectedRun(list[0]);
}
} catch { toast.error('获取执行记录失败'); }
} catch { toast.error(t('deploy.pipelinerundetail.sa4e14e')); }
finally { setLoading(false); }
}, [pipelineCode, pageSize]);
@@ -236,18 +239,18 @@ export default function PipelineRunDetail() {
const triggerRun = async () => {
try {
await pipelineApi.triggerByCode(pipelineCode);
toast.success('已触发');
toast.success(t('automation.triggered'));
fetchRuns();
} catch { toast.error('触发失败'); }
} catch { toast.error(t('automation.triggerFailed')); }
};
// 停止
const stopRun = async (runId: number) => {
try {
await pipelineApi.stopRunByCode(pipelineCode, runId);
toast.success('已停止');
toast.success(t('common.stopped'));
fetchRuns();
} catch { toast.error('停止失败'); }
} catch { toast.error(t('pipeline.stopFailed')); }
};
// 批量删除
@@ -256,10 +259,10 @@ export default function PipelineRunDetail() {
setDeleting(true);
try {
await pipelineApi.batchDeleteRuns(pipelineCode, Array.from(selectedIds));
toast.success(`已删除 ${selectedIds.size} 条记录`);
toast.success(t('deploy.pipelinerundetail.s4b340c', { selectedIds.size: selectedIds.size }));
setSelectedIds(new Set());
fetchRuns();
} catch { toast.error('批量删除失败'); }
} catch { toast.error(t('cloud.batchDeleteFailed')); }
finally { setDeleting(false); }
};
@@ -298,16 +301,16 @@ export default function PipelineRunDetail() {
return (
<PageShell
variant="workspace"
title={pipeline?.name || '流水线详情'}
subtitle={selectedRun ? `发布 #${selectedRun.build_number}` : (pipeline?.code ? `code: ${pipeline.code}` : '执行记录与日志')}
title={pipeline?.name || t('deploy.pipeline.detail')}
subtitle={selectedRun ? t('deploy.pipelinerundetail.se9940a', { selectedRun.build_number: selectedRun.build_number }) : (pipeline?.code ? `code: ${pipeline.code}` : t('deploy.pipelinerundetail.sefc52b'))}
icon={<History className="h-5 w-5" />}
contentClassName="space-y-4 h-full flex flex-col"
metrics={(
<StatStrip
items={[
{ label: '执行记录', value: runs.length, color: 'blue' },
{ label: '成功', value: successCount, color: 'emerald' },
{ label: '执行中', value: runningCount, color: 'amber' },
{ label: t('deploy.pipelinerundetail.s9b409b'), value: runs.length, color: 'blue' },
{ label: t('common.success'), value: successCount, color: 'emerald' },
{ label: t('common.executing'), value: runningCount, color: 'amber' },
]}
/>
)}
@@ -331,9 +334,9 @@ export default function PipelineRunDetail() {
onChange={e => setPageSize(Number(e.target.value))}
className="h-8 px-2 text-xs rounded border border-[var(--dg-border)] dg-input"
>
<option value={10}>10条/页</option>
<option value={20}>20条/页</option>
<option value={30}>30条/页</option>
<option value={10}>{t('deploy.pipelinerundetail.s8776a1')}</option>
<option value={20}>{t('deploy.pipelinerundetail.s96dbf3')}</option>
<option value={30}>{t('deploy.pipelinerundetail.s074a81')}</option>
</select>
<Button variant="outline" size="sm" className="dg-btn-secondary" onClick={fetchRuns}>
<RefreshCw className="w-4 h-4 mr-1" />刷新
@@ -380,7 +383,7 @@ export default function PipelineRunDetail() {
<div key={def.key} className="flex items-center">
<button
onClick={() => setSelectedStage(selectedStage === def.key ? null : def.key)}
title={`${def.label}${status ? ` (${status})` : ''} — 点击查看日志`}
title={`${def.label}${status ? ` (${status})t('deploy.pipelinerundetail.sdaf2bb')}
className={`flex items-center gap-1.5 px-3 py-2 text-[12px] font-semibold border shadow-sm transition-all cursor-pointer
${isActive ? 'ring-1 ring-offset-0 scale-[1.02]' : 'hover:brightness-110'}`}
style={{
@@ -403,7 +406,7 @@ export default function PipelineRunDetail() {
})}
{selectedRun && (
<span className="ml-auto text-[10px] text-[var(--dg-text-secondary)] opacity-40 shrink-0 pl-3 whitespace-nowrap">
{selectedStage ? STAGE_DEFS.find(d => d.key === selectedStage)?.label : '全阶段'}
{selectedStage ? STAGE_DEFS.find(d => d.key === selectedStage)?.label : t('pipeline.allStages')}
</span>
)}
</div>
@@ -420,7 +423,7 @@ export default function PipelineRunDetail() {
) : runs.length === 0 ? (
<div className="text-center py-12 border border-dashed border-[var(--dg-border)] rounded-lg m-4">
<History className="w-10 h-10 mx-auto mb-3 text-[var(--dg-text-muted)]" />
<p className="text-[var(--dg-text-secondary)]">暂无执行记录,点击"立即执行"触发构建</p>
<p className="text-[var(--dg-text-secondary)]">{t('deploy.run.noRecordsHint')}</p>
</div>
) : (
<table className="w-full text-sm">
@@ -430,13 +433,13 @@ export default function PipelineRunDetail() {
<input type="checkbox" checked={selectedIds.size === runs.length && runs.length > 0}
onChange={toggleAll} className="rounded" />
</th>
<th className="px-3 py-2.5 text-left font-medium text-[var(--dg-text-secondary)] w-16">构建</th>
<th className="px-3 py-2.5 text-left font-medium text-[var(--dg-text-secondary)] w-20">状态</th>
<th className="px-3 py-2.5 text-left font-medium text-[var(--dg-text-secondary)]">触发方式</th>
<th className="px-3 py-2.5 text-left font-medium text-[var(--dg-text-secondary)]">用户</th>
<th className="px-3 py-2.5 text-left font-medium text-[var(--dg-text-secondary)]">耗时</th>
<th className="px-3 py-2.5 text-left font-medium text-[var(--dg-text-secondary)] hidden md:table-cell">时间</th>
<th className="px-3 py-2.5 text-right font-medium text-[var(--dg-text-secondary)] w-32">操作</th>
<th className="px-3 py-2.5 text-left font-medium text-[var(--dg-text-secondary)] w-16">{t('pipeline.build')}</th>
<th className="px-3 py-2.5 text-left font-medium text-[var(--dg-text-secondary)] w-20">{t('common.status')}</th>
<th className="px-3 py-2.5 text-left font-medium text-[var(--dg-text-secondary)]">{t('deploy.table.trigger')}</th>
<th className="px-3 py-2.5 text-left font-medium text-[var(--dg-text-secondary)]">{t('system.roleUsers')}</th>
<th className="px-3 py-2.5 text-left font-medium text-[var(--dg-text-secondary)]">{t('common.elapsed')}</th>
<th className="px-3 py-2.5 text-left font-medium text-[var(--dg-text-secondary)] hidden md:table-cell">{t('monitor.logTime')}</th>
<th className="px-3 py-2.5 text-right font-medium text-[var(--dg-text-secondary)] w-32">{t('common.actions')}</th>
</tr>
</thead>
<tbody>
@@ -457,11 +460,11 @@ export default function PipelineRunDetail() {
<td className="px-3 py-2.5">
<Badge className={`text-xs ${STATUS_BADGES[run.status] || ''} flex items-center gap-1 w-fit`}>
<StatusIcon className={`w-3 h-3 ${run.status === 'running' ? 'animate-spin' : ''}`} />
{run.status === 'success' ? '成功'
: run.status === 'failed' ? '失败'
: run.status === 'running' ? '执行中'
: run.status === 'cancelled' ? '已取消'
: '待执行'}
{run.status === 'success' ? t('common.success')
: run.status === 'failed' ? t('common.failed')
: run.status === 'running' ? t('common.executing')
: run.status === 'cancelled' ? t('deploy.toast.cancelled')
: t('deploy.status.pending')}
</Badge>
</td>
<td className="px-3 py-2.5">
@@ -572,7 +575,7 @@ export default function PipelineRunDetail() {
<div className="h-full overflow-y-auto bg-[#0d1117] font-mono text-xs leading-5 p-4" style={{ minHeight: 280 }}>
{filteredLogs.length === 0 && selectedRun.status !== 'running' ? (
<div className="text-center py-12 dg-text-muted">
{selectedStage ? '该阶段暂无日志输出' : '暂无日志输出'}
{selectedStage ? t('deploy.pipeline.noLogStage') : t('deploy.pipeline.logOutput')}
</div>
) : (
<>
@@ -630,7 +633,7 @@ export default function PipelineRunDetail() {
{selectedRun.status === 'running' && (
<div className="flex items-center gap-2 mt-2 py-1.5 px-3 bg-blue-500/10 border-l-4 border-blue-500 rounded-r">
<Loader2 className="w-3 h-3 animate-spin dg-text-blue shrink-0" />
<span className="text-[11px] text-blue-300 font-medium">流水线执行中,实时输出...</span>
<span className="text-[11px] text-blue-300 font-medium">{t('deploy.pipeline.realtimeOutput')}</span>
<span className="flex gap-1 ml-1">
<span className="w-1 h-1 rounded-full bg-blue-400 animate-bounce" style={{ animationDelay: '0ms' }}></span>
<span className="w-1 h-1 rounded-full bg-blue-400 animate-bounce" style={{ animationDelay: '150ms' }}></span>