From 7cd89aaae97d9b8842442a44b247e449a7e12034 Mon Sep 17 00:00:00 2001 From: oliver Date: Wed, 5 Aug 2026 21:28:17 +0800 Subject: [PATCH] Localize LLDP and topology classify English leftovers. Translate job status/trigger/scope, item result, edge labels, and classify role/source option text in zh/en. Co-authored-by: Cursor --- web/src/i18n/en.ts | 37 ++++++++++++ web/src/i18n/zh.ts | 41 ++++++++++++- web/src/pages/network/LldpLinksPage.tsx | 59 ++++++++++++++++--- .../pages/network/TopologyClassifyPage.tsx | 54 ++++++++++++----- 4 files changed, 167 insertions(+), 24 deletions(-) diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index bd20678..a315e33 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -389,6 +389,34 @@ const en = { bPort: "Port B", lastSeen: "Last seen", }, + jobStatus: { + running: "Running", + pending: "Pending", + paused: "Paused", + done: "Done", + failed: "Failed", + cancelled: "Cancelled", + }, + triggerMode: { + manual: "Manual", + schedule: "Scheduled", + topology: "Topology canvas", + }, + jobScope: { + all: "All NEs", + ne_ids: "Selected NEs", + selected: "Selected NEs", + }, + itemResult: { + ok: "OK", + warn: "Warn", + fail: "Fail", + }, + edgeSource: { + lldp: "LLDP", + manual: "Manual", + stale: "Stale", + }, }, configSync: { title: "Config sync", @@ -1601,6 +1629,15 @@ const en = { linkUme: "UME", linkBoth: "Managed+UME", linkOrphaned: "Detached", + roleCore: "Core", + roleAggregation: "Aggregation", + roleAccess: "Access", + roleUnknown: "Unknown", + sourceManual: "Manual", + sourceUmeSync: "UME sync", + sourceLldp: "LLDP placeholder", + sourceTopology: "Topology placeholder", + sourceWebcrt: "WebCRT", regexTitle: "Regex find", patternPh: "Regex, e.g. ^CORE- or -BJ-", fieldName: "Name", diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 02981ae..ac4e5f5 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -385,6 +385,34 @@ const zh = { bPort: "对端口", lastSeen: "最近看见", }, + jobStatus: { + running: "运行中", + pending: "排队中", + paused: "已暂停", + done: "已完成", + failed: "失败", + cancelled: "已取消", + }, + triggerMode: { + manual: "手动", + schedule: "周期", + topology: "拓扑画布", + }, + jobScope: { + all: "全部网元", + ne_ids: "指定网元", + selected: "指定网元", + }, + itemResult: { + ok: "成功", + warn: "警告", + fail: "失败", + }, + edgeSource: { + lldp: "LLDP", + manual: "手动", + stale: "陈旧", + }, }, configSync: { title: "配置同步", @@ -1586,10 +1614,19 @@ const zh = { filterLinkLinked: "已关联库存", filterLinkOrphaned: "已解绑", colLink: "关联", - linkManaged: "Managed", + linkManaged: "托管", linkUme: "UME", - linkBoth: "Managed+UME", + linkBoth: "托管+UME", linkOrphaned: "已解绑", + roleCore: "核心", + roleAggregation: "汇聚", + roleAccess: "接入", + roleUnknown: "未知", + sourceManual: "手动", + sourceUmeSync: "UME 同步", + sourceLldp: "LLDP 占位", + sourceTopology: "拓扑占位", + sourceWebcrt: "WebCRT", regexTitle: "正则查找", patternPh: "正则,如 ^CORE- 或 -BJ-", fieldName: "名称", diff --git a/web/src/pages/network/LldpLinksPage.tsx b/web/src/pages/network/LldpLinksPage.tsx index 37703d3..de88ec1 100644 --- a/web/src/pages/network/LldpLinksPage.tsx +++ b/web/src/pages/network/LldpLinksPage.tsx @@ -25,6 +25,51 @@ const POLL_MS = 2500; const TARGET_PAGE_SIZE = 20; const SEP = " · "; +function lldpJobStatusLabel(t: (k: string) => string, status: string): string { + const s = String(status || "").trim().toLowerCase(); + const key = `lldpLinks.jobStatus.${s}`; + const label = t(key); + return label === key ? status || "—" : label; +} + +function lldpTriggerLabel(t: (k: string) => string, mode: string): string { + const s = String(mode || "").trim().toLowerCase(); + const key = `lldpLinks.triggerMode.${s}`; + const label = t(key); + return label === key ? mode || "—" : label; +} + +function lldpScopeLabel(t: (k: string) => string, scope: string): string { + const s = String(scope || "").trim().toLowerCase(); + const key = `lldpLinks.jobScope.${s}`; + const label = t(key); + return label === key ? scope || "—" : label; +} + +function lldpItemResultLabel( + t: (k: string) => string, + it: { ok?: boolean; parser_stub?: boolean | string | null; unmatched_count?: number; unmatched?: unknown[] }, +): string { + if (!it.ok) return t("lldpLinks.itemResult.fail"); + const unmatchedCount = it.unmatched_count ?? (it.unmatched?.length || 0); + if (it.parser_stub || unmatchedCount > 0) return t("lldpLinks.itemResult.warn"); + return t("lldpLinks.itemResult.ok"); +} + +function lldpEdgeStatusLabel(t: (k: string) => string, status: string): string { + const s = status === "stale" ? "missing" : String(status || "").trim().toLowerCase(); + if (s === "active") return t("lldpLinks.edgeStatusActive"); + if (s === "missing") return t("lldpLinks.edgeStatusMissing"); + return status || "—"; +} + +function lldpEdgeSourceLabel(t: (k: string) => string, source: string): string { + const s = String(source || "").trim().toLowerCase(); + const key = `lldpLinks.edgeSource.${s}`; + const label = t(key); + return label === key ? source || "—" : label; +} + export function LldpLinksPage() { const { t } = useI18n(); const { showOk, showError } = useToast(); @@ -545,7 +590,7 @@ export function LldpLinksPage() { const st = e.status === "stale" ? "missing" : e.status; return ( - {st} + {lldpEdgeStatusLabel(t, st)}
{aLabel}
{e.a_ip && e.a_name ?
{e.a_ip}
: null} @@ -556,7 +601,7 @@ export function LldpLinksPage() { {e.b_ip && e.b_name ?
{e.b_ip}
: null} {e.b_port || "—"} - {e.source} + {lldpEdgeSourceLabel(t, e.source)} {missCount || (st === "missing" ? 1 : 0)} {replaced ? ( @@ -635,8 +680,8 @@ export function LldpLinksPage() { {job.id.slice(0, 8)} - {job.trigger_mode} - {job.scope} + {lldpTriggerLabel(t, job.trigger_mode)} + {lldpScopeLabel(t, job.scope)} - {job.status} + {lldpJobStatusLabel(t, job.status)} @@ -744,7 +789,7 @@ export function LldpLinksPage() { {(() => { const job = jobDetailQuery.data; if (job) { - return `${job.id.slice(0, 8)} · ${job.status} · ${job.done}/${job.total}`; + return `${job.id.slice(0, 8)} · ${lldpJobStatusLabel(t, job.status)} · ${job.done}/${job.total}`; } if (jobDetailQuery.isLoading) { return `${expandedJobId.slice(0, 8)} · …`; @@ -793,7 +838,7 @@ export function LldpLinksPage() { {it.ne_name || it.ne_id || "—"} {it.ne_ip || "—"} - {!it.ok ? "fail" : it.parser_stub || unmatchedCount > 0 ? "warn" : "ok"} + {lldpItemResultLabel(t, it)} {it.neighbors} diff --git a/web/src/pages/network/TopologyClassifyPage.tsx b/web/src/pages/network/TopologyClassifyPage.tsx index 3e18677..d8ea9a9 100644 --- a/web/src/pages/network/TopologyClassifyPage.tsx +++ b/web/src/pages/network/TopologyClassifyPage.tsx @@ -37,6 +37,27 @@ function isFabricNodeDeletable(n: FabricNodeSearchHit): boolean { return src === "lldp" || src === "topology" || src === "webcrt"; } +const ROLE_OPTIONS = ["core", "aggregation", "access", "unknown"] as const; + +function roleLabel(t: (k: string) => string, role: string): string { + const s = String(role || "").trim().toLowerCase(); + if (s === "core") return t("topoClassify.roleCore"); + if (s === "aggregation") return t("topoClassify.roleAggregation"); + if (s === "access") return t("topoClassify.roleAccess"); + if (s === "unknown") return t("topoClassify.roleUnknown"); + return role || "-"; +} + +function managedSourceLabel(t: (k: string) => string, source: string): string { + const s = String(source || "").trim().toLowerCase(); + if (s === "manual") return t("topoClassify.sourceManual"); + if (s === "ume_sync") return t("topoClassify.sourceUmeSync"); + if (s === "lldp") return t("topoClassify.sourceLldp"); + if (s === "topology") return t("topoClassify.sourceTopology"); + if (s === "webcrt") return t("topoClassify.sourceWebcrt"); + return source; +} + export function TopologyClassifyPage() { const { t } = useI18n(); const { showOk, showError } = useToast(); @@ -285,10 +306,11 @@ export function TopologyClassifyPage() { }} > - - - - + {ROLE_OPTIONS.map((r) => ( + + ))} ) : null} {assignWhat !== "role" ? ( @@ -476,7 +499,7 @@ export function TopologyClassifyPage() { {n.ip} {linkLabel(n.link_status)} - {n.managed_source ? ` · ${n.managed_source}` : ""} + {n.managed_source ? ` · ${managedSourceLabel(t, n.managed_source)}` : ""} {editing ? ( @@ -486,13 +509,14 @@ export function TopologyClassifyPage() { onChange={(e) => setEditRole(e.target.value)} > - - - - + {ROLE_OPTIONS.map((r) => ( + + ))} ) : ( - n.role || "-" + roleLabel(t, n.role || "") )} @@ -681,7 +705,7 @@ export function TopologyClassifyPage() {
  • {m.name} {" · "} - {m.role} + {roleLabel(t, m.role)} {" · "} {t("topoClassify.nodeCount").replace("{{count}}", String(m.node_count))}