diff --git a/ROADMAP.md b/ROADMAP.md index 65cfc3a..e27b4a9 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -516,6 +516,7 @@ services/api.ts (1324L) | U20 | ESLint static-components 清零 + CredDetailDialog 内联组件修复 + i18n 批量迁移损坏修复(CredDetailDialog.tsx `Field` 内联组件提取为模块级 `CredField` — 23 处 react-hooks/static-components 警告清零;修复 8 处被批量正则损坏的 `i18n.t()` 调用(`i18n.t('>名称:')` 等 → 语义化键);6 处硬编码中文清零;新增 `deploy.cred.sensitiveWarn` 键;ESLint warnings 66→43 (-35%),tsc -b=0,npm build✓ (17.29s),17/17 测试通过) | P3 | **done** | | U23 | Task 模块 i18n 治理(CronJobManager + CronJobFormDialog + ScriptManager + RemoteExecPage 4 文件 — `import i18n` 全局调用 → `useTranslation()` hook;42 处 phantom key (`i18n.t('Chinese text')` → 语义化 `task.cronJob.*`/`task.scriptList.*`/`task.remoteExec.*` 键);28 个孤儿 hash 键清理 (cronjobformdial.s*/remoteexecpaget.s*/systemtaskspanel.s*);50+ 处硬编码 JSX 中文 → t() 调用;zh/en 6825→6886 +61 键对齐,tsc -b=0,npm build✓ (17.46s),ESLint=0/45/0,go build=0,17/17 测试通过) | P3 | **done** | | U24 | Monitor 模块 i18n hook 迁移(26 个 .tsx 文件从 `import i18n` 全局 → `useTranslation()` hook + 89 个 phantom 中文文本键 → `monitor.lbl.*` 语义化键;299 处 `i18n.t()` 调用 → 74 处保留(模块级/.ts 工具文件) + 225 处迁移到 `t()` hook;1 个 .ts 工具文件 helpers.ts 中文文本键替换为语义化键;zh/en 6886→6975 +89 键对齐,tsc -b=0,npm build✓ (17.16s),ESLint=0/53/0,go build=0,Docker healthy) | P3 | **done** | +| U26 | Kubernetes 模块 phantom key 大规模清理(32 个 .tsx/.ts 文件,171 个中文 phantom key → 8 个残留(均为 ANSI 转义码/JSX 表达式误匹配),163 个成功替换为 k8s.* 语义化键;新增 19 个子命名空间(nsResource/nsOverview/deploymentWorkload/deploymentView/workloadView/controlBar/clusterOverview/nodes/podLogs/podExec/pvc/configMap/secret/service/ingress/nsMgmt/editContainers/capacityPage/podFile)+ 扩展 8 个已有子命名空间;zh/en 6990→7185 +195 键对齐,k8s 命名空间 65 子命名空间/940 键,tsc -b=0,npm build✓ (17.59s),ESLint=0/53/0,go build=0,17/17 测试通过) | P3 | **done** | --- diff --git a/frontend/src/components/kubernetes/ClusterOverviewPage.tsx b/frontend/src/components/kubernetes/ClusterOverviewPage.tsx index d01ab16..e42d4f0 100644 --- a/frontend/src/components/kubernetes/ClusterOverviewPage.tsx +++ b/frontend/src/components/kubernetes/ClusterOverviewPage.tsx @@ -54,7 +54,7 @@ export default function ClusterOverviewPage() { const current = list.find((c: JsonData) => (c.code || String(c.id)) === clusterCode); setClusterInfo(current || null); if (!current && list.length > 0 && clusterCode) { - toast({ title: i18n.t('集群未找到'), description: `找不到集群 ${clusterCode}`, variant: 'destructive' }); + toast({ title: i18n.t('k8s.clusterOverview.notFound'), description: `找不到集群 ${clusterCode}`, variant: 'destructive' }); } } catch { setClusters([]); } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -85,8 +85,8 @@ export default function ClusterOverviewPage() { return ( } metrics={!loading ? ( p.namespace === namespace && p.name.startsWith(deploymentName))); setHpas(allHpas.filter((h: JsonData) => h.target_name === deploymentName && h.namespace === namespace)); } catch (e) { - console.error(i18n.t('加载详情失败:'), e); + console.error(i18n.t('k8s.deploymentWorkload.loadFailed'), e); } finally { setLoading(false); } @@ -86,7 +86,7 @@ export function DeploymentDetail({ clusterCode, namespace, deploymentName, onBac setActionLoading('restart'); try { await kubernetesApi.restartDeployment(clusterCode, namespace, deploymentName); - toast({ title: i18n.t('重启指令已下发'), description: i18n.t('Deployment 正在滚动重启') }); + toast({ title: i18n.t('k8s.workloadView.restartSent'), description: i18n.t('k8s.deploymentWorkload.restarting') }); } catch (e) { toast({ title: i18n.t('kubernetes.deployment.restartFailed'), description: e?.message || String(e), variant: 'destructive' }); } finally { setActionLoading(''); } @@ -97,7 +97,7 @@ export function DeploymentDetail({ clusterCode, namespace, deploymentName, onBac setActionLoading('image'); try { await kubernetesApi.updateDeploymentImage(clusterCode, namespace, deploymentName, newImage); - toast({ title: i18n.t('镜像更新成功'), description: `正在滚动更新为 ${newImage}` }); + toast({ title: i18n.t('k8s.deploymentDetail.imageUpdated'), description: `正在滚动更新为 ${newImage}` }); setShowImageUpdate(false); loadData(); } catch (e) { @@ -115,7 +115,7 @@ export function DeploymentDetail({ clusterCode, namespace, deploymentName, onBac if (!ok) return; try { await kubernetesApi.deleteHPA(clusterCode, namespace, hpa.name); - toast({ title: i18n.t('HPA 已删除') }); + toast({ title: i18n.t('k8s.deploymentDetail.hpaDeleted') }); loadData(); } catch (e) { toast({ title: i18n.t('kubernetes.msg.deleteFailed2'), description: e?.message || String(e), variant: 'destructive' }); diff --git a/frontend/src/components/kubernetes/DeploymentEditContainers.tsx b/frontend/src/components/kubernetes/DeploymentEditContainers.tsx index fde0126..ffb1cea 100644 --- a/frontend/src/components/kubernetes/DeploymentEditContainers.tsx +++ b/frontend/src/components/kubernetes/DeploymentEditContainers.tsx @@ -102,7 +102,7 @@ export function DeploymentEditContainers({ clusterCode, namespace, deploymentNam ? imagePullSecrets.filter(s => s.trim()).map((s: string) => ({ name: s })) : undefined; await saveDeploymentManifest(clusterCode, namespace, deploymentName, base); - toast({ title: i18n.t('容器配置已保存'), description: `${deploymentName} 更新已提交` }); + toast({ title: i18n.t('k8s.editContainers.saved'), description: `${deploymentName} 更新已提交` }); onSaved(base); } catch (e) { toast({ title: i18n.t('kubernetes.msg.saveFailed'), description: e?.message || String(e), variant: 'destructive' }); @@ -152,7 +152,7 @@ export function DeploymentEditContainers({ clusterCode, namespace, deploymentNam {imagePullSecrets.length === 0 && 无}
- { if (e.key === 'Enter') { const val = (e.target as HTMLInputElement).value.trim(); @@ -170,7 +170,7 @@ export function DeploymentEditContainers({ clusterCode, namespace, deploymentNam {/* 初始化容器 */} } containers={initContainers} accentColor="purple" @@ -181,8 +181,8 @@ export function DeploymentEditContainers({ clusterCode, namespace, deploymentNam {/* 工作容器 */} } containers={containers} accentColor="blue" diff --git a/frontend/src/components/kubernetes/DeploymentEditService.tsx b/frontend/src/components/kubernetes/DeploymentEditService.tsx index 1a5a2b1..fb43972 100644 --- a/frontend/src/components/kubernetes/DeploymentEditService.tsx +++ b/frontend/src/components/kubernetes/DeploymentEditService.tsx @@ -172,7 +172,7 @@ export function DeploymentEditService({ clusterCode, namespace, deploymentName, try { const yaml = await dumpManifest(svcManifest); await kubernetesApi.applyYAML(clusterCode, yaml); - toast({ title: i18n.t('Service 已创建'), description: name }); + toast({ title: i18n.t('k8s.editService.serviceCreated'), description: name }); setShowCreate(null); load(); } catch (e) { @@ -209,7 +209,7 @@ export function DeploymentEditService({ clusterCode, namespace, deploymentName, try { const yaml = await dumpManifest(ingManifest); await kubernetesApi.applyYAML(clusterCode, yaml); - toast({ title: i18n.t('Service 已创建'), description: name }); + toast({ title: i18n.t('k8s.editService.serviceCreated'), description: name }); setShowCreate(null); load(); } catch (e) { @@ -221,7 +221,7 @@ export function DeploymentEditService({ clusterCode, namespace, deploymentName, // 删除资源 const deleteResource = async (kind: 'service' | 'ingress', name: string) => { const ok = await confirm({ - title: i18n.t('删除资源'), + title: i18n.t('common.delete'), description: `确认删除 ${kind === 'service' ? 'Service' : 'Ingress'} 「${name}」?此操作不可撤销。`, confirmText: i18n.t('kubernetes.storage.delete2'), variant: 'destructive', diff --git a/frontend/src/components/kubernetes/DeploymentEditStorage.tsx b/frontend/src/components/kubernetes/DeploymentEditStorage.tsx index d2ae0bc..2950c35 100644 --- a/frontend/src/components/kubernetes/DeploymentEditStorage.tsx +++ b/frontend/src/components/kubernetes/DeploymentEditStorage.tsx @@ -40,12 +40,12 @@ interface Props { } const VOLUME_TYPES: { key: VolumeForm['type']; label: string; icon: typeof HardDrive; color: string }[] = [ - { key: 'persistentVolumeClaim', label: i18n.t('存储卷声明'), icon: Database, color: 'dg-text-green' }, - { key: 'emptyDir', label: i18n.t('临时目录(EmptyDir)'), icon: Disc, color: 'dg-text-secondary' }, + { key: 'persistentVolumeClaim', label: i18n.t('k8s.nsResource.pvcDesc'), icon: Database, color: 'dg-text-green' }, + { key: 'emptyDir', label: i18n.t('k8s.editStorage.emptyDir'), icon: Disc, color: 'dg-text-secondary' }, { key: 'hostPath', label: 'HostPath', icon: HardDrive, color: 'dg-text-yellow' }, { key: 'nfs', label: 'NFS', icon: Server, color: 'dg-text-purple' }, - { key: 'configMap', label: i18n.t('配置字典'), icon: File, color: 'dg-text-blue' }, - { key: 'secret', label: i18n.t('密文'), icon: Lock, color: 'text-rose-400' }, + { key: 'configMap', label: i18n.t('k8s.nsResource.configMapDesc'), icon: File, color: 'dg-text-blue' }, + { key: 'secret', label: i18n.t('k8s.secret.data'), icon: Lock, color: 'text-rose-400' }, ]; const SELECTABLE_TYPES = new Set(VOLUME_TYPES.map((t) => t.key)); @@ -129,20 +129,20 @@ export function DeploymentEditStorage({ clusterCode, namespace, deploymentName, const save = async () => { for (const entry of entries) { if (!entry.volume.name.trim()) { - toast({ title: i18n.t('请填写数据卷名称'), variant: 'destructive' }); + toast({ title: i18n.t('k8s.editStorage.volumeNameRequired'), variant: 'destructive' }); return; } if (!formToVolume(entry.volume)) { - const hint = entry.volume.type === 'configMap' ? i18n.t('请选择 ConfigMap') - : entry.volume.type === 'secret' ? i18n.t('请选择 Secret') - : entry.volume.type === 'persistentVolumeClaim' ? i18n.t('请选择 PVC') - : i18n.t('的类型必填项'); - toast({ title: i18n.t('数据卷配置不完整'), description: `请为「${entry.volume.name}」${hint}`, variant: 'destructive' }); + const hint = entry.volume.type === 'configMap' ? i18n.t('k8s.editStorage.selectConfigMap') + : entry.volume.type === 'secret' ? i18n.t('k8s.editStorage.selectSecret') + : entry.volume.type === 'persistentVolumeClaim' ? i18n.t('k8s.editStorage.selectPVC') + : i18n.t('k8s.editStorage.typeRequiredHint'); + toast({ title: i18n.t('k8s.editStorage.incompleteConfig'), description: `请为「${entry.volume.name}」${hint}`, variant: 'destructive' }); return; } const validMounts = entry.mounts.filter((m) => m.mountPath.trim()); if (validMounts.length === 0) { - toast({ title: i18n.t('请填写挂载路径'), description: `数据卷「${entry.volume.name}」需至少挂载到一个容器`, variant: 'destructive' }); + toast({ title: i18n.t('k8s.editStorage.mountPathRequired'), description: `数据卷「${entry.volume.name}」需至少挂载到一个容器`, variant: 'destructive' }); return; } } @@ -151,7 +151,7 @@ export function DeploymentEditStorage({ clusterCode, namespace, deploymentName, const { volumes, mounts } = volumeEntriesToStorage(entries); const base = applyStorageToManifest(rawManifest, volumes, mounts, passthroughVolumes); await saveDeploymentManifest(clusterCode, namespace, deploymentName, base); - toast({ title: i18n.t('存储配置已保存'), description: `${deploymentName} 更新已提交` }); + toast({ title: i18n.t('k8s.editStorage.saved'), description: `${deploymentName} 更新已提交` }); onSaved(base); } catch (e) { toast({ title: i18n.t('kubernetes.msg.saveFailed'), description: (e as Error)?.message || String(e), variant: 'destructive' }); @@ -234,7 +234,7 @@ export function DeploymentEditStorage({ clusterCode, namespace, deploymentName, if (vol.type === 'persistentVolumeClaim' && !vol.pvcName) patch.pvcName = name; updateVolume(i, patch); }} - placeholder={i18n.t('如 app-logs / fluent-bit-config')} + placeholder={i18n.t('k8s.editStorage.namePlaceholder')} className="h-8 w-44 dg-input text-sm font-mono" /> {/* 原生 select,避免 Radix Select 在 overflow 容器内无法下拉 // Logan.Li */} @@ -312,7 +312,7 @@ export function DeploymentEditStorage({ clusterCode, namespace, deploymentName, updateMount(i, mi, { mountPath: e.target.value })} - placeholder={i18n.t('挂载路径 /bitcloud/app/.../logs')} + placeholder={i18n.t('k8s.editStorage.mountPathPlaceholder')} className="h-8 flex-1 min-w-[200px] dg-input text-sm font-mono" /> {entry.mounts.length > 1 && ( @@ -326,7 +326,7 @@ export function DeploymentEditStorage({ clusterCode, namespace, deploymentName, updateMount(i, mi, { subPath: e.target.value })} - placeholder={i18n.t('挂载路径 /bitcloud/app/.../logs')} + placeholder={i18n.t('k8s.editStorage.mountPathPlaceholder')} className="h-7 flex-1 max-w-md dg-input text-[var(--dg-text-secondary)] text-xs font-mono" />
diff --git a/frontend/src/components/kubernetes/DeploymentView.tsx b/frontend/src/components/kubernetes/DeploymentView.tsx index 64e145e..5107dc7 100644 --- a/frontend/src/components/kubernetes/DeploymentView.tsx +++ b/frontend/src/components/kubernetes/DeploymentView.tsx @@ -139,7 +139,7 @@ export function DeploymentView(props: DeploymentViewProps) { const handleRollback = async (rev: JsonData) => { const revision = rev.revision ?? rev.revision_number ?? rev.number; const ok = await confirm({ - title: i18n.t('回滚版本'), + title: i18n.t('k8s.deploymentView.rollbackVersion'), description: `确认回滚到修订版本 #${revision}?`, confirmText: i18n.t('pipeline.rollback'), variant: 'destructive', @@ -148,7 +148,7 @@ export function DeploymentView(props: DeploymentViewProps) { setRollbackLoading(revision); try { await kubernetesApi.rollbackDeployment(clusterCode, namespace, deployment.name, Number(revision)); - toast({ title: i18n.t('回滚已下发'), description: `正在回滚到 #${revision}` }); + toast({ title: i18n.t('k8s.deploymentView.rollbackSent'), description: `正在回滚到 #${revision}` }); onRefresh(); } catch (e) { toast({ title: i18n.t('pipeline.rollbackFailed'), description: e?.message || String(e), variant: 'destructive' }); @@ -160,7 +160,7 @@ export function DeploymentView(props: DeploymentViewProps) { setScaleLoading(true); try { await kubernetesApi.scaleDeployment(clusterCode, namespace, deployment.name, scaleValue); - toast({ title: i18n.t('伸缩已提交'), description: `副本数 → ${scaleValue}` }); + toast({ title: i18n.t('k8s.deploymentView.scaleSent'), description: `副本数 → ${scaleValue}` }); onRefresh(); } catch (e) { toast({ title: i18n.t('kubernetes.msg.scaleFailed'), description: e?.message || String(e), variant: 'destructive' }); @@ -168,10 +168,10 @@ export function DeploymentView(props: DeploymentViewProps) { }; const TABS = [ - { key: 'pods', label: i18n.t('容器组'), icon: Container, count: pods.length }, - { key: 'services', label: i18n.t('访问方式'), icon: Network, count: services.length }, + { key: 'pods', label: i18n.t('k8s.deploymentView.pods'), icon: Container, count: pods.length }, + { key: 'services', label: i18n.t('k8s.deploymentView.access'), icon: Network, count: services.length }, { key: 'events', label: i18n.t('kubernetes.event.title'), icon: Activity, count: events.length }, - { key: 'scale', label: i18n.t('容器伸缩'), icon: Plus }, + { key: 'scale', label: i18n.t('k8s.deploymentView.scale'), icon: Plus }, { key: 'history', label: i18n.t('kubernetes.deployment.revisions'), icon: History, count: revisions.length }, ]; @@ -186,8 +186,8 @@ export function DeploymentView(props: DeploymentViewProps) { {isHealthy ? 'Running' : 'Updating'} } /> - {ready}/{total}} /> - + {ready}/{total}} /> + {pods[0] && ( <> {i18n.t('common.back')} }> + {i18n.t('common.back')} }> {null} ); @@ -308,7 +308,7 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod if (name) navigate(`/kubernetes/${clusterCode}/namespace/${namespace}/workload/view/Deployment/${name}`); }; return ( - {i18n.t('common.back')} }> + {i18n.t('common.back')} }> ); @@ -379,7 +379,7 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod resource="deployments" name={deploymentName} inline - resourceHint={i18n.t('含关联 Service')} + resourceHint={i18n.t('k8s.deploymentWorkload.withService')} loadYamlFn={loadBundleYaml} saveYamlFn={saveBundleYaml} onSaved={loadData} @@ -404,7 +404,7 @@ export function DeploymentWorkload({ clusterCode, namespace, deploymentName, mod namespace={namespace} resource="deployments" name={deploymentName} - resourceHint={i18n.t('含关联 Service')} + resourceHint={i18n.t('k8s.deploymentWorkload.withService')} loadYamlFn={loadBundleYaml} saveYamlFn={saveBundleYaml} onClose={() => setModal(null)} diff --git a/frontend/src/components/kubernetes/EventsPage.tsx b/frontend/src/components/kubernetes/EventsPage.tsx index 30f8905..b0d0ae8 100644 --- a/frontend/src/components/kubernetes/EventsPage.tsx +++ b/frontend/src/components/kubernetes/EventsPage.tsx @@ -179,16 +179,16 @@ export default function EventsPage() { return ( } metrics={!loading ? ( ) : undefined} @@ -229,10 +229,10 @@ export default function EventsPage() { > {reasonData.length > 0 && (
- + {nsData.length > 0 && } {kindData.length > 0 && ( - + )}
)} diff --git a/frontend/src/components/kubernetes/KubernetesClusterDialog.tsx b/frontend/src/components/kubernetes/KubernetesClusterDialog.tsx index b7f4a02..c59d61b 100644 --- a/frontend/src/components/kubernetes/KubernetesClusterDialog.tsx +++ b/frontend/src/components/kubernetes/KubernetesClusterDialog.tsx @@ -89,7 +89,7 @@ export function KubernetesClusterDialog({ if (!value.trim()) { setNameError(''); } else if (!NAME_RE.test(value)) { - setNameError(i18n.t('只允许英文、数字和连字符(-),首尾不能是连字符')); + setNameError(i18n.t('k8s.clusterDialog.nameRule')); } else { setNameError(''); } @@ -111,7 +111,7 @@ export function KubernetesClusterDialog({ const handleSubmit = async () => { if (!formData.name.trim() || !NAME_RE.test(formData.name.trim())) { - toast.error(i18n.t('请输入有效的集群名称(英文/数字/连字符)')); return; + toast.error(i18n.t('k8s.clusterDialog.nameInvalid')); return; } try { setSubmitting(true); @@ -133,7 +133,7 @@ export function KubernetesClusterDialog({ onOpenChange(false); onSaved(); } catch (e) { - console.error(i18n.t('保存集群失败:'), e); + console.error(i18n.t('k8s.clusterDialog.saveFailed'), e); } finally { setSubmitting(false); } @@ -149,7 +149,7 @@ export function KubernetesClusterDialog({ {editingCluster ? i18n.t('kubernetes.cluster.editCluster') : i18n.t('kubernetes.cluster.addCluster')} - {editingCluster ? i18n.t('编辑集群') : i18n.t('添加集群')} + {editingCluster ? i18n.t('k8s.clusterDialog.editTitle') : i18n.t('k8s.clusterDialog.addTitle')}
@@ -159,7 +159,7 @@ export function KubernetesClusterDialog({ handleNameChange(e.target.value)} - placeholder={i18n.t('如: prod-cluster')} + placeholder={i18n.t('k8s.clusterDialog.namePlaceholder')} className={`bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)] ${nameError ? 'border-red-500' : ''}`} /> {nameError &&

{nameError}

} @@ -172,7 +172,7 @@ export function KubernetesClusterDialog({ setFormData({ ...formData, version: e.target.value })} - placeholder={i18n.t('如: prod-cluster')} + placeholder={i18n.t('k8s.clusterDialog.namePlaceholder')} className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)]" />
@@ -183,7 +183,7 @@ export function KubernetesClusterDialog({ setFormData({ ...formData, description: e.target.value })} - placeholder={i18n.t('集群用途描述')} + placeholder={i18n.t('k8s.clusterDialog.descLabel')} className="bg-[var(--dg-bg-tertiary)] border-[var(--dg-border)] text-[var(--dg-text-primary)] placeholder:text-[var(--dg-text-muted)]" /> @@ -224,7 +224,7 @@ export function KubernetesClusterDialog({