mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
Round-3 F-I18N covered ConversationTab, StatsTab, StatsCharts, and labels in
TimingWaterfall, but missed:
- TimingTab.tsx — 5 user-visible labels (Timestamp, Method, Status, Request
size, Response size) were hardcoded English.
- TimingWaterfall.tsx — empty state ("No timing data available.") and Total
latency label were also hardcoded.
- common.understand — RiskNoticeModal calls
`useTranslations("common")("understand")` but the key did not exist;
only the `|| "I understand"` fallback rescued it, leaving pt-BR users
seeing English.
Adds the missing 7 trafficInspector.timing* keys and common.understand to both
en.json and pt-BR.json, wires `useTranslations` in both components, and adds a
source-grep test asserting no English literals remain in JSX and that all 8
keys exist in both locales.
This commit is contained in:
@@ -13,7 +13,7 @@ export function TimingWaterfall({ request }: TimingWaterfallProps) {
|
||||
const total = totalLatencyMs ?? (proxyLatencyMs ?? 0) + (upstreamLatencyMs ?? 0);
|
||||
|
||||
if (!total) {
|
||||
return <p className="text-sm text-text-muted">No timing data available.</p>;
|
||||
return <p className="text-sm text-text-muted">{t("timingNoData")}</p>;
|
||||
}
|
||||
|
||||
const segments: Array<{ label: string; ms: number; color: string }> = [
|
||||
@@ -51,7 +51,7 @@ export function TimingWaterfall({ request }: TimingWaterfallProps) {
|
||||
})}
|
||||
</div>
|
||||
<div className="flex justify-between text-xs font-medium text-text-main border-t border-border pt-2">
|
||||
<span>Total latency</span>
|
||||
<span>{t("timingTotalLatency")}</span>
|
||||
<span>{total}ms</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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 (
|
||||
<div className="p-4 h-full overflow-auto space-y-4">
|
||||
<TimingWaterfall request={request} />
|
||||
<div className="border-t border-border pt-3 space-y-1 text-xs text-text-muted">
|
||||
<div className="flex justify-between">
|
||||
<span>Timestamp</span>
|
||||
<span>{t("timingTimestamp")}</span>
|
||||
<span className="font-mono">{request.timestamp}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Method</span>
|
||||
<span>{t("timingMethod")}</span>
|
||||
<span className="font-mono">{request.method}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Status</span>
|
||||
<span>{t("timingStatus")}</span>
|
||||
<span className="font-mono">{String(request.status)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Request size</span>
|
||||
<span>{t("timingRequestSize")}</span>
|
||||
<span className="font-mono">{request.requestSize} B</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span>Response size</span>
|
||||
<span>{t("timingResponseSize")}</span>
|
||||
<span className="font-mono">{request.responseSize} B</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
73
tests/unit/ui/timing-i18n.test.tsx
Normal file
73
tests/unit/ui/timing-i18n.test.tsx
Normal file
@@ -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(`<span[^>]*>${lit}</span>`).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");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user