diff --git a/src/app/(dashboard)/dashboard/tools/traffic-inspector/components/shared/TimingWaterfall.tsx b/src/app/(dashboard)/dashboard/tools/traffic-inspector/components/shared/TimingWaterfall.tsx index 03ce7cf2de..2f427dc71b 100644 --- a/src/app/(dashboard)/dashboard/tools/traffic-inspector/components/shared/TimingWaterfall.tsx +++ b/src/app/(dashboard)/dashboard/tools/traffic-inspector/components/shared/TimingWaterfall.tsx @@ -13,7 +13,7 @@ export function TimingWaterfall({ request }: TimingWaterfallProps) { const total = totalLatencyMs ?? (proxyLatencyMs ?? 0) + (upstreamLatencyMs ?? 0); if (!total) { - return

No timing data available.

; + return

{t("timingNoData")}

; } const segments: Array<{ label: string; ms: number; color: string }> = [ @@ -51,7 +51,7 @@ export function TimingWaterfall({ request }: TimingWaterfallProps) { })}
- Total latency + {t("timingTotalLatency")} {total}ms
diff --git a/src/app/(dashboard)/dashboard/tools/traffic-inspector/components/tabs/TimingTab.tsx b/src/app/(dashboard)/dashboard/tools/traffic-inspector/components/tabs/TimingTab.tsx index e62bd88ea8..f4238cb815 100644 --- a/src/app/(dashboard)/dashboard/tools/traffic-inspector/components/tabs/TimingTab.tsx +++ b/src/app/(dashboard)/dashboard/tools/traffic-inspector/components/tabs/TimingTab.tsx @@ -1,5 +1,6 @@ "use client"; +import { useTranslations } from "next-intl"; import type { InterceptedRequest } from "@/mitm/inspector/types"; import { TimingWaterfall } from "../shared/TimingWaterfall"; @@ -8,28 +9,29 @@ interface TimingTabProps { } export function TimingTab({ request }: TimingTabProps) { + const t = useTranslations("trafficInspector"); return (
- Timestamp + {t("timingTimestamp")} {request.timestamp}
- Method + {t("timingMethod")} {request.method}
- Status + {t("timingStatus")} {String(request.status)}
- Request size + {t("timingRequestSize")} {request.requestSize} B
- Response size + {t("timingResponseSize")} {request.responseSize} B
diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 521e8f1640..7123a1ccd5 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -2,6 +2,7 @@ "common": { "save": "Save", "cancel": "Cancel", + "understand": "I understand", "delete": "Delete", "loading": "Loading...", "error": "An error occurred", @@ -7480,6 +7481,13 @@ "statsSuccessful": "Successful", "statsTotalRequests": "Total requests", "timingProxyOverhead": "Proxy overhead", - "timingUpstreamResponse": "Upstream response" + "timingUpstreamResponse": "Upstream response", + "timingNoData": "No timing data available.", + "timingTotalLatency": "Total latency", + "timingTimestamp": "Timestamp", + "timingMethod": "Method", + "timingStatus": "Status", + "timingRequestSize": "Request size", + "timingResponseSize": "Response size" } } diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json index 7aaa0e115c..2219e32eb9 100644 --- a/src/i18n/messages/pt-BR.json +++ b/src/i18n/messages/pt-BR.json @@ -2,6 +2,7 @@ "common": { "save": "Salvar", "cancel": "Cancelar", + "understand": "Eu entendo", "delete": "Excluir", "loading": "Carregando...", "error": "Ocorreu um erro", @@ -7470,6 +7471,13 @@ "statsSuccessful": "Bem-sucedidas", "statsTotalRequests": "Total de requisições", "timingProxyOverhead": "Overhead do proxy", - "timingUpstreamResponse": "Resposta upstream" + "timingUpstreamResponse": "Resposta upstream", + "timingNoData": "Nenhum dado de tempo disponível.", + "timingTotalLatency": "Latência total", + "timingTimestamp": "Timestamp", + "timingMethod": "Método", + "timingStatus": "Status", + "timingRequestSize": "Tamanho da requisição", + "timingResponseSize": "Tamanho da resposta" } } diff --git a/tests/unit/ui/timing-i18n.test.tsx b/tests/unit/ui/timing-i18n.test.tsx new file mode 100644 index 0000000000..5d2ee14a77 --- /dev/null +++ b/tests/unit/ui/timing-i18n.test.tsx @@ -0,0 +1,73 @@ +/** + * i18n coverage for TimingTab + TimingWaterfall (R4 fix #3). + * Source-text inspection — no JSDOM render needed. + * + * Round-3 F-I18N translated ConversationTab/StatsTab/StatsCharts but missed + * TimingTab (5 labels) and TimingWaterfall (2 labels). Round-4 closed the gap. + */ +import { describe, it } from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import path from "node:path"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const TIMING_TAB = path.resolve( + __dirname, + "../../../src/app/(dashboard)/dashboard/tools/traffic-inspector/components/tabs/TimingTab.tsx", +); +const TIMING_WATERFALL = path.resolve( + __dirname, + "../../../src/app/(dashboard)/dashboard/tools/traffic-inspector/components/shared/TimingWaterfall.tsx", +); +const EN = path.resolve(__dirname, "../../../src/i18n/messages/en.json"); +const PT = path.resolve(__dirname, "../../../src/i18n/messages/pt-BR.json"); + +const tabSrc = readFileSync(TIMING_TAB, "utf-8"); +const waterfallSrc = readFileSync(TIMING_WATERFALL, "utf-8"); +const en = JSON.parse(readFileSync(EN, "utf-8")); +const pt = JSON.parse(readFileSync(PT, "utf-8")); + +describe("Timing i18n (R4 fix #3)", () => { + it("TimingTab uses useTranslations and has no hardcoded English labels", () => { + assert.ok(tabSrc.includes('useTranslations("trafficInspector")')); + for (const lit of ["Timestamp", "Method", "Status", "Request size", "Response size"]) { + assert.ok( + !new RegExp(`]*>${lit}`).test(tabSrc), + `TimingTab must not render hardcoded "${lit}" — use t() instead`, + ); + } + for (const key of ["timingTimestamp", "timingMethod", "timingStatus", "timingRequestSize", "timingResponseSize"]) { + assert.ok(tabSrc.includes(`t("${key}")`), `TimingTab must call t("${key}")`); + } + }); + + it("TimingWaterfall translates the empty state and total latency label", () => { + assert.ok(!waterfallSrc.includes("No timing data available.")); + assert.ok(!waterfallSrc.includes(">Total latency<")); + assert.ok(waterfallSrc.includes('t("timingNoData")')); + assert.ok(waterfallSrc.includes('t("timingTotalLatency")')); + }); + + it("All 7 new timing keys exist in both en.json and pt-BR.json", () => { + const keys = [ + "timingNoData", + "timingTotalLatency", + "timingTimestamp", + "timingMethod", + "timingStatus", + "timingRequestSize", + "timingResponseSize", + ]; + for (const k of keys) { + assert.ok(en.trafficInspector?.[k], `en.json must have trafficInspector.${k}`); + assert.ok(pt.trafficInspector?.[k], `pt-BR.json must have trafficInspector.${k}`); + } + }); + + it("common.understand is present in both locales (RiskNoticeModal namespace)", () => { + assert.equal(en.common?.understand, "I understand"); + assert.equal(pt.common?.understand, "Eu entendo"); + }); +});