Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds comprehensive i18n across the web app: replaces hard-coded UI strings with useTranslation hooks, threads translation functions through components and helpers, introduces helper utilities and new locale namespaces/files (docker, service‑monitors), and updates tests to assert localized labels. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
# Conflicts: # apps/web/src/components/network/latency-chart.tsx # apps/web/src/components/server/server-card.tsx # apps/web/src/routes/_authed/settings/network-probes.tsx
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
apps/web/src/components/dashboard/widgets/server-map.tsx (1)
141-143:⚠️ Potential issue | 🟡 MinorLocalize marker tooltip count label to complete i18n coverage.
server/server(s)is still hardcoded English in the SVG tooltip. This leaves one user-visible string outside translations.Suggested patch
- <title> - {group.name}: {group.count} server{group.count > 1 ? 's' : ''} + <title> + {group.name}: {t('widgets.serverMap.serverCount', { count: group.count })} {'\n'} {group.serverNames.join(', ')} </title>Also add
widgets.serverMap.serverCountwith plural forms in bothapps/web/src/locales/en/dashboard.jsonandapps/web/src/locales/zh/dashboard.json.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/components/dashboard/widgets/server-map.tsx` around lines 141 - 143, The tooltip still uses a hardcoded English plural "server/servers" in server-map.tsx; change the title construction to use the i18n pluralized string (e.g., call the translation function with a key like widgets.serverMap.serverCount and pass group.count as the count) instead of the inline "server{...}" logic (reference group.name and group.count in the title). Then add the new locale key widgets.serverMap.serverCount with proper plural forms to both apps/web/src/locales/en/dashboard.json and apps/web/src/locales/zh/dashboard.json so the tooltip label is fully localized.apps/web/src/components/server/traffic-tab.tsx (1)
226-228:⚠️ Potential issue | 🟡 MinorLocalize the remaining empty-state copy.
Line 227 is still hardcoded English, so this screen is not fully localized yet.
🌐 Proposed fix
- <div className="flex h-[200px] items-center justify-center text-muted-foreground text-sm"> - No daily traffic data available. - </div> + <div className="flex h-[200px] items-center justify-center text-muted-foreground text-sm"> + {t('traffic_daily_no_data', { defaultValue: 'No daily traffic data available.' })} + </div>Also add
traffic_daily_no_datatoapps/web/src/locales/en/servers.jsonandapps/web/src/locales/zh/servers.json.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/components/server/traffic-tab.tsx` around lines 226 - 228, Replace the hardcoded "No daily traffic data available." string in the ServerTrafficTab component (traffic-tab.tsx) with a localized lookup (e.g., use the project's translations hook such as useTranslations or t) and render the translation key "traffic_daily_no_data" instead; then add the "traffic_daily_no_data" entry to both the English and Chinese servers locale JSON files with appropriate translations so the component displays the localized empty-state copy.apps/web/src/routes/_authed/settings/service-monitors.tsx (1)
482-488:⚠️ Potential issue | 🟡 MinorVariable shadowing:
tin map callback shadows translation function.The loop variable
tshadows thettranslation function fromuseTranslation. While this doesn't cause a bug in the current code (sincet.labelandt.valueare intentional), it's confusing and error-prone if future changes need the translation function inside this callback.♻️ Recommended fix: rename loop variable
<SelectContent> - {MONITOR_TYPES.map((t) => ( - <SelectItem key={t.value} value={t.value}> - {t.label} + {MONITOR_TYPES.map((monitorType) => ( + <SelectItem key={monitorType.value} value={monitorType.value}> + {monitorType.label} </SelectItem> ))} </SelectContent>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/routes/_authed/settings/service-monitors.tsx` around lines 482 - 488, The map callback in the SelectContent rendering shadows the translation function named t from useTranslation; change the loop variable name used in MONITOR_TYPES.map (e.g., from t to typeItem or monitorType) and update references inside the callback (key, value, and label) to the new name so you no longer shadow the t translation function used elsewhere; ensure SelectItem key={...}, value={...} and content use the renamed identifier.apps/web/src/components/dashboard/widget-config-dialog.tsx (1)
221-221:⚠️ Potential issue | 🟠 MajorUse
<ScrollArea>instead of native overflow classes in this component.Both scrollable containers still use
overflow-y-auto/overflow-auto, which violates the component guideline for this path.♻️ Guideline-aligned refactor sketch
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +import { ScrollArea } from '@/components/ui/scroll-area' @@ - <div className="max-h-40 space-y-1 overflow-y-auto rounded-lg border p-2"> + <ScrollArea className="max-h-40 rounded-lg border p-2"> + <div className="space-y-1"> {servers.map((s) => ( ... ))} {servers.length === 0 && <p className="py-2 text-center text-muted-foreground text-xs">{emptyMessage}</p>} - </div> + </div> + </ScrollArea> @@ - <div - className="prose prose-sm dark:prose-invert max-h-32 max-w-none overflow-auto text-sm" + <ScrollArea className="max-h-32"> + <div + className="prose prose-sm dark:prose-invert max-w-none text-sm" // biome-ignore lint/security/noDangerouslySetInnerHtml: renderMarkdown escapes all raw HTML and validates URLs dangerouslySetInnerHTML={{ __html: html }} - /> + /> + </ScrollArea>As per coding guidelines,
apps/web/src/components/**/*.{ts,tsx}: All scrollable containers must use shadcn<ScrollArea>instead of nativeoverflow-y-autooroverflow-auto.Also applies to: 544-544
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/components/dashboard/widget-config-dialog.tsx` at line 221, The div using native scrolling ("<div className=\"max-h-40 space-y-1 overflow-y-auto rounded-lg border p-2\">") should be replaced with the shadcn ScrollArea pattern inside the WidgetConfigDialog component: import ScrollArea (and ScrollAreaViewport if your UI exposes it), wrap the scrollable content with <ScrollArea> and place sizing/rounded/border/padding classes on the ScrollAreaViewport (remove overflow-y-auto/overflow-auto from classNames), and keep spacing classes (space-y-1) on the inner content container; apply the same change for the other scrollable container in this file. Ensure imports are added and no native overflow classes remain.
🧹 Nitpick comments (8)
apps/web/src/routes/_authed/servers/$serverId/docker/components/container-detail-dialog.tsx (1)
17-23: Avoid string sentinel ('None') for ports state.Using
'None'as a control-flow sentinel is brittle. Prefer returning an array (ornull) and branch on length.Suggested patch
-function formatPortMapping(container: DockerContainer): string { - const mappings = container.ports +function getPortMappings(container: DockerContainer): string[] { + return container.ports .filter((p) => p.public_port != null) .map((p) => `${p.ip ?? '0.0.0.0'}:${p.public_port} -> ${p.private_port}/${p.port_type}`) - - return mappings.length > 0 ? mappings.join(', ') : 'None' } @@ - const portsDisplay = formatPortMapping(container) + const portMappings = getPortMappings(container) @@ - <p className="mt-0.5 font-mono text-sm">{portsDisplay === 'None' ? t('detail.noPorts') : portsDisplay}</p> + <p className="mt-0.5 font-mono text-sm"> + {portMappings.length === 0 ? t('detail.noPorts') : portMappings.join(', ')} + </p>Also applies to: 69-69
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/routes/_authed/servers/`$serverId/docker/components/container-detail-dialog.tsx around lines 17 - 23, The formatPortMapping function currently returns the string sentinel 'None' for no-port cases; change its signature to return an array of strings or null (e.g., string[] | null), return mappings (empty case -> null) instead of 'None', and update all call sites (notably the component render that uses formatPortMapping and the other helper at the same file around the other occurrence) to branch on a null/length check and join the array into a display string only when non-null; ensure callers handle null consistently rather than relying on the 'None' literal.apps/web/src/lib/network-i18n.ts (1)
7-16: NormalizeprobeTypebefore switching.Case-sensitive matching can miss translation for values like
HTTP/Tcpand leak raw strings.♻️ Proposed fix
export function getNetworkProbeTypeLabel(t: Translate, probeType: string): string { - switch (probeType) { + const normalized = probeType.trim().toLowerCase() + switch (normalized) { case 'icmp': return t('probe_type_icmp', { defaultValue: 'ICMP (Ping)' }) case 'tcp': return t('probe_type_tcp', { defaultValue: 'TCP' }) case 'http': return t('probe_type_http', { defaultValue: 'HTTP' }) default: return probeType } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/lib/network-i18n.ts` around lines 7 - 16, Normalize the incoming probeType before the switch in getNetworkProbeTypeLabel: create a normalized string (e.g., normalizedProbeType = String(probeType).toLowerCase()) and switch on that instead of the raw probeType so values like "HTTP" or "Tcp" match the cases; keep returning the original probeType when falling through to the default case to avoid changing output semantics.apps/web/src/components/dashboard/widgets/service-status.tsx (1)
88-114: UseScrollAreafor the scrollable monitor list.This block still relies on native
overflow-autoon Line 91. Please swap the scrollable wrapper to the sharedScrollAreacomponent so this widget follows the repo’s scrolling contract.As per coding guidelines, "All scrollable containers must use shadcn
<ScrollArea>instead of nativeoverflow-y-autooroverflow-auto".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/components/dashboard/widgets/service-status.tsx` around lines 88 - 114, Replace the native scroll container (the div with classes "flex flex-1 flex-wrap content-start gap-2 overflow-auto" that maps over filtered to render monitor items) with the shared shadcn ScrollArea component: import ScrollArea from the shared UI module, remove the "overflow-auto" class, wrap the same children (the filtered.map(...) block and the filtered.length === 0 fallback) inside <ScrollArea> (and its viewport element if your ScrollArea API exposes one), keeping the existing layout classes (flex flex-1 flex-wrap content-start gap-2) so getStatusLabel/getStatusColor/formatRelativeTime usages and the monitor mapping remain unchanged.apps/web/src/routes/_authed/settings/service-monitors.tsx (1)
70-88: Consider renaming helper functions to follow conventions.
useMonitorTypesanduseTypeLabelsare named like React hooks but don't use any React hooks internally. This violates the naming convention thatuse*functions should only be hooks. Consider renaming togetMonitorTypesandgetTypeLabels, or converting them touseMemo-based hooks if memoization is desired.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/routes/_authed/settings/service-monitors.tsx` around lines 70 - 88, Rename the helpers that are improperly named like hooks: change useMonitorTypes to getMonitorTypes and useTypeLabels to getTypeLabels (or alternatively wrap their returned values in useMemo inside new hook variants) so they no longer use the "use*" prefix without being React hooks; update all references to these functions (e.g., calls to useMonitorTypes and useTypeLabels) to the new names to keep naming conventions consistent and avoid the false hook-like signature.apps/web/src/locales/en/network.json (1)
98-99: Consider consolidating duplicate "no data" keys.
latency_no_dataandlatency_chart_no_databoth have identical values ("No data available"). If they're used in different contexts that might need different wording in other languages, this is fine. Otherwise, consider using a single key.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/locales/en/network.json` around lines 98 - 99, The two locale keys latency_no_data and latency_chart_no_data are duplicates; either consolidate them into a single key (e.g., keep latency_no_data and remove latency_chart_no_data) and update all usages to reference the remaining key, or if they represent different contexts keep both but rename one to be context-specific and update translations accordingly; locate references to latency_no_data and latency_chart_no_data in the UI components and replace or rename them consistently (e.g., in the chart component use the chosen chart-specific key or the consolidated key) so the JSON and code stay in sync.apps/web/src/routes/_authed/service-monitors/$id.tsx (1)
57-65: Same naming convention note applies here.
useTypeLabelsis duplicated from the settings page and has the same naming issue - it's named like a hook but doesn't use React hooks. Consider extracting to a shared module if both files need the same logic.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/routes/_authed/service-monitors/`$id.tsx around lines 57 - 65, The function useTypeLabels is named like a React hook but doesn't use hooks and is duplicated; rename it (e.g., getTypeLabels or typeLabelMap) and move the implementation into a shared utility module, then update imports in both files (replace useTypeLabels references with the new name) to avoid duplication and follow hook naming conventions; ensure the exported symbol from the new module accepts the same t: (key: string) => string signature so existing call sites need only an import change.apps/web/src/routes/status.$slug.tsx (1)
131-139: Consider type-safe severity key mapping.The dynamic key construction
incidents_severity_${severity}assumesseverityalways matches defined translation keys (critical,major,minor). If an unknown severity value is received from the API, the translation lookup will return the raw key.🛡️ Optional: Add fallback handling
function SeverityBadge({ severity, t }: { severity: string; t: (key: string, options?: { count: number }) => string }) { const variants: Record<string, 'default' | 'destructive' | 'secondary'> = { critical: 'destructive', major: 'destructive', minor: 'secondary' } - const severityKey = `incidents_severity_${severity}` as const - return <Badge variant={variants[severity] ?? 'default'}>{t(severityKey)}</Badge> + const knownSeverities = ['critical', 'major', 'minor'] + const severityKey = knownSeverities.includes(severity) + ? `incidents_severity_${severity}` + : null + return <Badge variant={variants[severity] ?? 'default'}>{severityKey ? t(severityKey) : severity}</Badge> }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/routes/status`.$slug.tsx around lines 131 - 139, SeverityBadge constructs a translation key using the raw severity, which can yield untranslated keys for unknown values; update SeverityBadge to validate severity against a known set or mapping (e.g., a const map like { critical: 'incidents_severity_critical', major: 'incidents_severity_major', minor: 'incidents_severity_minor' }) and use that mapped key or a safe fallback key/string when severity is not recognized, and keep the existing Badge variant mapping (variants[severity] ?? 'default') while passing the validated/mapped key to t instead of the raw `severityKey`.apps/web/src/locales/en/dashboard.json (1)
87-110: Minor duplication in translation keys.
widgets.common.labels.titleOptional(line 97) anddialogs.widgetConfig.labels.titleOptional(line 147) both contain "Title (optional)". Similarly for the widget title placeholder keys. If these are always meant to be identical, consider consolidating or referencing a single key to avoid divergence during future translation updates.Also applies to: 142-157
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/locales/en/dashboard.json` around lines 87 - 110, Duplicate translation values exist for widgets.common.labels.titleOptional and dialogs.widgetConfig.labels.titleOptional (and the matching placeholders widgetTitle); consolidate by creating a single shared key (e.g., common.labels.titleOptional and common.placeholders.widgetTitle) and update all usages to reference that single key so translators edit one entry and values cannot drift; ensure you remove the duplicate keys or replace their values with references to the shared keys in the same JSON structure.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/src/components/dashboard/widget-config-dialog.tsx`:
- Around line 80-88: The time-range helper useRangeOptions is missing the 30-day
option, which makes components like traffic-bar (default hours: 720) have an
invalid/missing select value; update useRangeOptions to include a { label:
t('common.timeRange.30days'), value: '720' } entry (and similarly add the 30-day
option where the duplicate range list exists around lines 459-463) so the
RangeSelect can represent/select the 30-day (720 hours) choice and match
traffic-bar's default.
In `@apps/web/src/components/dashboard/widgets/alert-list.tsx`:
- Around line 58-60: Replace the native scrollable div that has class "flex
flex-1 flex-col gap-1.5 overflow-auto" with the shared shadcn ScrollArea
component: remove the overflow-auto class, import ScrollArea from the shared UI
module, wrap the scrollable content inside <ScrollArea> (using the ScrollArea
viewport pattern required by your UI library if applicable) and preserve the
existing layout classes (e.g., flex, flex-1, gap-1.5) on the ScrollArea or its
inner wrapper so visual layout remains the same; update the JSX in
alert-list.tsx where that div is defined (and add the corresponding import).
In `@apps/web/src/components/dashboard/widgets/disk-io.tsx`:
- Line 34: The widget currently uses hardcoded strings ("Read", "Write",
"Unknown" and other labels shown around the Disk I/O rendering) instead of
translation keys; update the Disk I/O component to replace those literals with
calls to the i18n hook (use the existing const { t } =
useTranslation('dashboard')) using descriptive keys like t('diskIo.read'),
t('diskIo.write'), t('diskIo.unknown') (and add analogous keys for the labels
referenced around the same area), and add corresponding entries in the dashboard
translation JSON so the UI uses the localized strings.
In `@apps/web/src/components/dashboard/widgets/service-status.tsx`:
- Around line 93-103: The tooltip/title currently concatenates a partially
translated string; replace the hardcoded English "Last check:" by using a single
translation key (e.g. "monitor.tooltip" or similar) with interpolation for name,
status and lastChecked time, then call t(...) to build the string used in the
div title and the tooltip body; update the title attribute and the tooltip
content where monitor, getStatusLabel(monitor, t) and
formatRelativeTime(monitor.last_checked_at) are used so both use the same i18n
key (pass { name: monitor.name, status: getStatusLabel(monitor, t), lastChecked:
formatRelativeTime(monitor.last_checked_at) }) instead of concatenation.
In `@apps/web/src/components/dashboard/widgets/traffic-bar.tsx`:
- Around line 83-88: The fallback translation used in the serverName useMemo
(the 'unknown' key at return servers.find(... )?.name ?? t('unknown')) is not
present in the widget's locale files; either change that call to use an existing
shared key (e.g., t('widgets.common.placeholders.unknown') or whatever shared
key exists) or add the 'unknown' entry to both dashboard locale JSONs under the
same namespace used by this widget so the subtitle is translated; update the
serverName useMemo call or the locale files accordingly.
In `@apps/web/src/components/dashboard/widgets/uptime-timeline-widget.tsx`:
- Line 78: Replace the native scrollable div (the JSX element with className
"flex-1 space-y-3 overflow-auto" in the UptimeTimelineWidget component) with the
shadcn ScrollArea wrapper: import the ScrollArea component and wrap the same
contents using ScrollArea (and its viewport if your project pattern requires
it), move the "flex-1 space-y-3" classes to the ScrollArea/viewport element and
remove the native "overflow-auto" class; ensure the surrounding layout and
children remain unchanged so visual behavior is preserved.
In `@apps/web/src/components/server/traffic-card.tsx`:
- Line 40: The component renders hardcoded legend labels ("In", "Out", "Total")
instead of using i18n; update the TrafficCard UI to call the translation
function for those strings (replace the literal "In"/"Out"/"Total" used near
CardTitle and the legend elements with t('traffic_in'), t('traffic_out'),
t('traffic_total') or the existing locale keys), ensuring both the legend items
and any other hardcoded labels in the component are wired through t(...) for
full localization.
In `@apps/web/src/routes/_authed/settings/mobile-devices.tsx`:
- Around line 35-41: formatRelativeTime currently constructs a Date from dateStr
and may return "Invalid Date" to users; update the function (formatRelativeTime)
to validate the parsed date by checking date.getTime() for NaN (or use
Number.isFinite) after creating const date = new Date(dateStr) and if invalid
return t('mobile.time_never'); keep the existing null check for dateStr and
proceed to compute diffMs only when the date is valid.
In `@apps/web/src/routes/_authed/settings/network-probes.tsx`:
- Around line 231-243: The column with accessorKey 'source' currently uses
header: t('target_status', ...) but displays "Built-in"/"Custom"; change the
header to use a matching i18n key (e.g. t('source' or t('origin') with
defaultValue 'Source'/'Origin') so the label reflects the rendered values,
update the translation files for that new key, and ensure the header replacement
is applied where the column is defined (the header property on the column with
accessorKey 'source') to keep locales consistent.
In `@apps/web/src/routes/_authed/traffic/index.tsx`:
- Line 188: The chart legend labels are hardcoded as "Inbound"/"Outbound"
despite bringing in useTranslation; update the legend labels in the chart
configuration (where "Inbound" and "Outbound" are used) to use the translation
function `t` instead (for example `t('inbound')` / `t('outbound')` or the
appropriate keys in the 'servers' namespace) so the chart uses localized
strings; ensure the same translation keys are added to the i18n resource files
and that the component still passes the translated values to the chart's
legend/series labels (look for the variables/props that currently contain
"Inbound"/"Outbound").
---
Outside diff comments:
In `@apps/web/src/components/dashboard/widget-config-dialog.tsx`:
- Line 221: The div using native scrolling ("<div className=\"max-h-40 space-y-1
overflow-y-auto rounded-lg border p-2\">") should be replaced with the shadcn
ScrollArea pattern inside the WidgetConfigDialog component: import ScrollArea
(and ScrollAreaViewport if your UI exposes it), wrap the scrollable content with
<ScrollArea> and place sizing/rounded/border/padding classes on the
ScrollAreaViewport (remove overflow-y-auto/overflow-auto from classNames), and
keep spacing classes (space-y-1) on the inner content container; apply the same
change for the other scrollable container in this file. Ensure imports are added
and no native overflow classes remain.
In `@apps/web/src/components/dashboard/widgets/server-map.tsx`:
- Around line 141-143: The tooltip still uses a hardcoded English plural
"server/servers" in server-map.tsx; change the title construction to use the
i18n pluralized string (e.g., call the translation function with a key like
widgets.serverMap.serverCount and pass group.count as the count) instead of the
inline "server{...}" logic (reference group.name and group.count in the title).
Then add the new locale key widgets.serverMap.serverCount with proper plural
forms to both apps/web/src/locales/en/dashboard.json and
apps/web/src/locales/zh/dashboard.json so the tooltip label is fully localized.
In `@apps/web/src/components/server/traffic-tab.tsx`:
- Around line 226-228: Replace the hardcoded "No daily traffic data available."
string in the ServerTrafficTab component (traffic-tab.tsx) with a localized
lookup (e.g., use the project's translations hook such as useTranslations or t)
and render the translation key "traffic_daily_no_data" instead; then add the
"traffic_daily_no_data" entry to both the English and Chinese servers locale
JSON files with appropriate translations so the component displays the localized
empty-state copy.
In `@apps/web/src/routes/_authed/settings/service-monitors.tsx`:
- Around line 482-488: The map callback in the SelectContent rendering shadows
the translation function named t from useTranslation; change the loop variable
name used in MONITOR_TYPES.map (e.g., from t to typeItem or monitorType) and
update references inside the callback (key, value, and label) to the new name so
you no longer shadow the t translation function used elsewhere; ensure
SelectItem key={...}, value={...} and content use the renamed identifier.
---
Nitpick comments:
In `@apps/web/src/components/dashboard/widgets/service-status.tsx`:
- Around line 88-114: Replace the native scroll container (the div with classes
"flex flex-1 flex-wrap content-start gap-2 overflow-auto" that maps over
filtered to render monitor items) with the shared shadcn ScrollArea component:
import ScrollArea from the shared UI module, remove the "overflow-auto" class,
wrap the same children (the filtered.map(...) block and the filtered.length ===
0 fallback) inside <ScrollArea> (and its viewport element if your ScrollArea API
exposes one), keeping the existing layout classes (flex flex-1 flex-wrap
content-start gap-2) so getStatusLabel/getStatusColor/formatRelativeTime usages
and the monitor mapping remain unchanged.
In `@apps/web/src/lib/network-i18n.ts`:
- Around line 7-16: Normalize the incoming probeType before the switch in
getNetworkProbeTypeLabel: create a normalized string (e.g., normalizedProbeType
= String(probeType).toLowerCase()) and switch on that instead of the raw
probeType so values like "HTTP" or "Tcp" match the cases; keep returning the
original probeType when falling through to the default case to avoid changing
output semantics.
In `@apps/web/src/locales/en/dashboard.json`:
- Around line 87-110: Duplicate translation values exist for
widgets.common.labels.titleOptional and
dialogs.widgetConfig.labels.titleOptional (and the matching placeholders
widgetTitle); consolidate by creating a single shared key (e.g.,
common.labels.titleOptional and common.placeholders.widgetTitle) and update all
usages to reference that single key so translators edit one entry and values
cannot drift; ensure you remove the duplicate keys or replace their values with
references to the shared keys in the same JSON structure.
In `@apps/web/src/locales/en/network.json`:
- Around line 98-99: The two locale keys latency_no_data and
latency_chart_no_data are duplicates; either consolidate them into a single key
(e.g., keep latency_no_data and remove latency_chart_no_data) and update all
usages to reference the remaining key, or if they represent different contexts
keep both but rename one to be context-specific and update translations
accordingly; locate references to latency_no_data and latency_chart_no_data in
the UI components and replace or rename them consistently (e.g., in the chart
component use the chosen chart-specific key or the consolidated key) so the JSON
and code stay in sync.
In
`@apps/web/src/routes/_authed/servers/`$serverId/docker/components/container-detail-dialog.tsx:
- Around line 17-23: The formatPortMapping function currently returns the string
sentinel 'None' for no-port cases; change its signature to return an array of
strings or null (e.g., string[] | null), return mappings (empty case -> null)
instead of 'None', and update all call sites (notably the component render that
uses formatPortMapping and the other helper at the same file around the other
occurrence) to branch on a null/length check and join the array into a display
string only when non-null; ensure callers handle null consistently rather than
relying on the 'None' literal.
In `@apps/web/src/routes/_authed/service-monitors/`$id.tsx:
- Around line 57-65: The function useTypeLabels is named like a React hook but
doesn't use hooks and is duplicated; rename it (e.g., getTypeLabels or
typeLabelMap) and move the implementation into a shared utility module, then
update imports in both files (replace useTypeLabels references with the new
name) to avoid duplication and follow hook naming conventions; ensure the
exported symbol from the new module accepts the same t: (key: string) => string
signature so existing call sites need only an import change.
In `@apps/web/src/routes/_authed/settings/service-monitors.tsx`:
- Around line 70-88: Rename the helpers that are improperly named like hooks:
change useMonitorTypes to getMonitorTypes and useTypeLabels to getTypeLabels (or
alternatively wrap their returned values in useMemo inside new hook variants) so
they no longer use the "use*" prefix without being React hooks; update all
references to these functions (e.g., calls to useMonitorTypes and useTypeLabels)
to the new names to keep naming conventions consistent and avoid the false
hook-like signature.
In `@apps/web/src/routes/status`.$slug.tsx:
- Around line 131-139: SeverityBadge constructs a translation key using the raw
severity, which can yield untranslated keys for unknown values; update
SeverityBadge to validate severity against a known set or mapping (e.g., a const
map like { critical: 'incidents_severity_critical', major:
'incidents_severity_major', minor: 'incidents_severity_minor' }) and use that
mapped key or a safe fallback key/string when severity is not recognized, and
keep the existing Badge variant mapping (variants[severity] ?? 'default') while
passing the validated/mapped key to t instead of the raw `severityKey`.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 49b4c098-6fba-4cf9-bbbf-8fc35c42691f
📒 Files selected for processing (67)
apps/web/src/components/dashboard/widget-config-dialog.tsxapps/web/src/components/dashboard/widgets/alert-list.tsxapps/web/src/components/dashboard/widgets/disk-io.tsxapps/web/src/components/dashboard/widgets/server-map.tsxapps/web/src/components/dashboard/widgets/service-status.tsxapps/web/src/components/dashboard/widgets/traffic-bar.tsxapps/web/src/components/dashboard/widgets/uptime-timeline-widget.tsxapps/web/src/components/layout/theme-toggle.tsxapps/web/src/components/network/anomaly-table.tsxapps/web/src/components/network/latency-chart.tsxapps/web/src/components/network/target-card.test.tsxapps/web/src/components/network/target-card.tsxapps/web/src/components/server/capabilities-dialog.tsxapps/web/src/components/server/server-card.tsxapps/web/src/components/server/traffic-card.tsxapps/web/src/components/server/traffic-tab.tsxapps/web/src/components/settings/geoip-card.tsxapps/web/src/components/ui/breadcrumb.tsxapps/web/src/components/ui/dialog.tsxapps/web/src/components/ui/sheet.tsxapps/web/src/components/ui/sidebar.tsxapps/web/src/components/uptime/uptime-timeline.tsxapps/web/src/lib/i18n.tsapps/web/src/lib/network-i18n.tsapps/web/src/locales/en/common.jsonapps/web/src/locales/en/dashboard.jsonapps/web/src/locales/en/docker.jsonapps/web/src/locales/en/network.jsonapps/web/src/locales/en/servers.jsonapps/web/src/locales/en/service-monitors.jsonapps/web/src/locales/en/settings.jsonapps/web/src/locales/en/status.jsonapps/web/src/locales/zh/common.jsonapps/web/src/locales/zh/dashboard.jsonapps/web/src/locales/zh/docker.jsonapps/web/src/locales/zh/network.jsonapps/web/src/locales/zh/servers.jsonapps/web/src/locales/zh/service-monitors.jsonapps/web/src/locales/zh/settings.jsonapps/web/src/locales/zh/status.jsonapps/web/src/routes/_authed/network/$server-id.test.tsxapps/web/src/routes/_authed/network/$serverId.tsxapps/web/src/routes/_authed/servers/$id.tsxapps/web/src/routes/_authed/servers/$serverId/docker/components/container-detail-dialog.tsxapps/web/src/routes/_authed/servers/$serverId/docker/components/container-list.tsxapps/web/src/routes/_authed/servers/$serverId/docker/components/container-logs.tsxapps/web/src/routes/_authed/servers/$serverId/docker/components/docker-events.tsxapps/web/src/routes/_authed/servers/$serverId/docker/components/docker-networks-dialog.tsxapps/web/src/routes/_authed/servers/$serverId/docker/components/docker-volumes-dialog.tsxapps/web/src/routes/_authed/servers/$serverId/docker/index.tsxapps/web/src/routes/_authed/servers/index.tsxapps/web/src/routes/_authed/service-monitors/$id.tsxapps/web/src/routes/_authed/settings/alerts.tsxapps/web/src/routes/_authed/settings/api-keys.tsxapps/web/src/routes/_authed/settings/appearance.tsxapps/web/src/routes/_authed/settings/capabilities.tsxapps/web/src/routes/_authed/settings/mobile-devices.tsxapps/web/src/routes/_authed/settings/network-probes.test.tsxapps/web/src/routes/_authed/settings/network-probes.tsxapps/web/src/routes/_authed/settings/notifications.tsxapps/web/src/routes/_authed/settings/security.tsxapps/web/src/routes/_authed/settings/service-monitors.tsxapps/web/src/routes/_authed/settings/status-pages.tsxapps/web/src/routes/_authed/settings/users.tsxapps/web/src/routes/_authed/traffic/index.tsxapps/web/src/routes/status.$slug.tsxapps/web/src/routes/status.tsx
| function useRangeOptions(t: (key: string) => string): { label: string; value: string }[] { | ||
| return [ | ||
| { label: t('common.timeRange.1hour'), value: '1' }, | ||
| { label: t('common.timeRange.6hours'), value: '6' }, | ||
| { label: t('common.timeRange.12hours'), value: '12' }, | ||
| { label: t('common.timeRange.24hours'), value: '24' }, | ||
| { label: t('common.timeRange.3days'), value: '72' }, | ||
| { label: t('common.timeRange.7days'), value: '168' } | ||
| ] |
There was a problem hiding this comment.
RangeSelect dropped the 30-day option while traffic-bar still defaults to 720.
This creates an invalid/default-missing select state for traffic-bar (hours: 720) and prevents selecting 30 days in that form.
💡 Proposed fix
function useRangeOptions(t: (key: string) => string): { label: string; value: string }[] {
return [
{ label: t('common.timeRange.1hour'), value: '1' },
{ label: t('common.timeRange.6hours'), value: '6' },
{ label: t('common.timeRange.12hours'), value: '12' },
{ label: t('common.timeRange.24hours'), value: '24' },
{ label: t('common.timeRange.3days'), value: '72' },
- { label: t('common.timeRange.7days'), value: '168' }
+ { label: t('common.timeRange.7days'), value: '168' },
+ { label: t('common.timeRange.30days'), value: '720' }
]
}Also applies to: 459-463
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/components/dashboard/widget-config-dialog.tsx` around lines 80 -
88, The time-range helper useRangeOptions is missing the 30-day option, which
makes components like traffic-bar (default hours: 720) have an invalid/missing
select value; update useRangeOptions to include a { label:
t('common.timeRange.30days'), value: '720' } entry (and similarly add the 30-day
option where the duplicate range list exists around lines 459-463) so the
RangeSelect can represent/select the 30-day (720 hours) choice and match
traffic-bar's default.
| <div className="flex h-full flex-col rounded-lg border bg-card p-4"> | ||
| <h3 className="mb-3 font-semibold text-sm">Alerts</h3> | ||
| <h3 className="mb-3 font-semibold text-sm">{t('widgets.alertList.title')}</h3> | ||
| <div className="flex flex-1 flex-col gap-1.5 overflow-auto"> |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Replace native scroll overflow with <ScrollArea>.
Line 60 still uses overflow-auto in a scrollable container. This should use the shared shadcn ScrollArea wrapper.
Proposed fix
+import { ScrollArea } from '@/components/ui/scroll-area'
...
- <div className="flex flex-1 flex-col gap-1.5 overflow-auto">
- {filtered.map((event) => {
+ <ScrollArea className="flex-1">
+ <div className="flex flex-col gap-1.5">
+ {filtered.map((event) => {
...
- {filtered.length === 0 && (
- <div className="flex flex-1 items-center justify-center text-muted-foreground text-xs">
- {t('widgets.alertList.empty.noEvents')}
- </div>
- )}
- </div>
+ {filtered.length === 0 && (
+ <div className="flex min-h-[120px] items-center justify-center text-muted-foreground text-xs">
+ {t('widgets.alertList.empty.noEvents')}
+ </div>
+ )}
+ </div>
+ </ScrollArea>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div className="flex h-full flex-col rounded-lg border bg-card p-4"> | |
| <h3 className="mb-3 font-semibold text-sm">Alerts</h3> | |
| <h3 className="mb-3 font-semibold text-sm">{t('widgets.alertList.title')}</h3> | |
| <div className="flex flex-1 flex-col gap-1.5 overflow-auto"> | |
| import { ScrollArea } from '@/components/ui/scroll-area' | |
| <div className="flex h-full flex-col rounded-lg border bg-card p-4"> | |
| <h3 className="mb-3 font-semibold text-sm">{t('widgets.alertList.title')}</h3> | |
| <ScrollArea className="flex-1"> | |
| <div className="flex flex-col gap-1.5"> | |
| {filtered.map((event) => { | |
| ... | |
| })} | |
| {filtered.length === 0 && ( | |
| <div className="flex min-h-[120px] items-center justify-center text-muted-foreground text-xs"> | |
| {t('widgets.alertList.empty.noEvents')} | |
| </div> | |
| )} | |
| </div> | |
| </ScrollArea> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/components/dashboard/widgets/alert-list.tsx` around lines 58 -
60, Replace the native scrollable div that has class "flex flex-1 flex-col
gap-1.5 overflow-auto" with the shared shadcn ScrollArea component: remove the
overflow-auto class, import ScrollArea from the shared UI module, wrap the
scrollable content inside <ScrollArea> (using the ScrollArea viewport pattern
required by your UI library if applicable) and preserve the existing layout
classes (e.g., flex, flex-1, gap-1.5) on the ScrollArea or its inner wrapper so
visual layout remains the same; update the JSX in alert-list.tsx where that div
is defined (and add the corresponding import).
| } satisfies ChartConfig | ||
|
|
||
| export function DiskIoWidget({ config, servers }: DiskIoWidgetProps) { | ||
| const { t } = useTranslation('dashboard') |
There was a problem hiding this comment.
I18n coverage is still partial in this widget.
Good progress on title/empty-state, but this component still shows hardcoded user-facing strings (Read, Write, Unknown). Please move those to translations too so locale output is fully consistent.
Also applies to: 64-67, 76-76
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/components/dashboard/widgets/disk-io.tsx` at line 34, The widget
currently uses hardcoded strings ("Read", "Write", "Unknown" and other labels
shown around the Disk I/O rendering) instead of translation keys; update the
Disk I/O component to replace those literals with calls to the i18n hook (use
the existing const { t } = useTranslation('dashboard')) using descriptive keys
like t('diskIo.read'), t('diskIo.write'), t('diskIo.unknown') (and add analogous
keys for the labels referenced around the same area), and add corresponding
entries in the dashboard translation JSON so the UI uses the localized strings.
| <div | ||
| className="group relative inline-flex items-center justify-center" | ||
| key={monitor.id} | ||
| title={`${monitor.name} - ${getStatusLabel(monitor)} - Last check: ${formatRelativeTime(monitor.last_checked_at)}`} | ||
| title={`${monitor.name} - ${getStatusLabel(monitor, t)} - Last check: ${formatRelativeTime(monitor.last_checked_at)}`} | ||
| > | ||
| <span className={`inline-block size-3.5 rounded-full ${getStatusColor(monitor)} cursor-default`} /> | ||
| <div className="pointer-events-none absolute bottom-full left-1/2 z-10 mb-2 hidden -translate-x-1/2 rounded-md bg-popover px-2.5 py-1.5 shadow-md ring-1 ring-border group-hover:block"> | ||
| <div className="whitespace-nowrap text-xs"> | ||
| <p className="font-medium">{monitor.name}</p> | ||
| <p className="text-muted-foreground"> | ||
| {getStatusLabel(monitor)} · {formatRelativeTime(monitor.last_checked_at)} | ||
| {getStatusLabel(monitor, t)} · {formatRelativeTime(monitor.last_checked_at)} |
There was a problem hiding this comment.
Localize the remaining tooltip copy.
Line 96 still hardcodes Last check: in English, so this widget is not fully translated yet. Please move the full title format into a translation key with interpolation instead of concatenating a partially translated string.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/components/dashboard/widgets/service-status.tsx` around lines 93
- 103, The tooltip/title currently concatenates a partially translated string;
replace the hardcoded English "Last check:" by using a single translation key
(e.g. "monitor.tooltip" or similar) with interpolation for name, status and
lastChecked time, then call t(...) to build the string used in the div title and
the tooltip body; update the title attribute and the tooltip content where
monitor, getStatusLabel(monitor, t) and
formatRelativeTime(monitor.last_checked_at) are used so both use the same i18n
key (pass { name: monitor.name, status: getStatusLabel(monitor, t), lastChecked:
formatRelativeTime(monitor.last_checked_at) }) instead of concatenation.
| const serverName = useMemo(() => { | ||
| if (!hasServerId) { | ||
| return 'All Servers' | ||
| return t('widgets.common.placeholders.allServers') | ||
| } | ||
| return servers.find((s) => s.id === server_id)?.name ?? 'Unknown' | ||
| }, [hasServerId, server_id, servers]) | ||
| return servers.find((s) => s.id === server_id)?.name ?? t('unknown') | ||
| }, [hasServerId, server_id, servers, t]) |
There was a problem hiding this comment.
Add a locale entry for the unknown-server fallback.
Line 87 introduces a new translated fallback, but the locale additions shown for this widget only cover the widgets.* keys plus widgets.common.placeholders.allServers. Please wire this fallback to an existing shared key or add it to both dashboard locale files so the subtitle does not stay untranslated.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/components/dashboard/widgets/traffic-bar.tsx` around lines 83 -
88, The fallback translation used in the serverName useMemo (the 'unknown' key
at return servers.find(... )?.name ?? t('unknown')) is not present in the
widget's locale files; either change that call to use an existing shared key
(e.g., t('widgets.common.placeholders.unknown') or whatever shared key exists)
or add the 'unknown' entry to both dashboard locale JSONs under the same
namespace used by this widget so the subtitle is translated; update the
serverName useMemo call or the locale files accordingly.
| <div className="flex h-full flex-col rounded-lg border bg-card p-4"> | ||
| <h3 className="mb-3 font-semibold text-sm">Uptime Timeline</h3> | ||
| <h3 className="mb-3 font-semibold text-sm">{t('widgets.uptimeTimeline.title')}</h3> | ||
| <div className="flex-1 space-y-3 overflow-auto"> |
There was a problem hiding this comment.
Replace native overflow-auto with <ScrollArea> in widget list.
The scrollable list container is using native overflow styles instead of the required shadcn scroll wrapper.
Suggested patch
+import { ScrollArea } from '@/components/ui/scroll-area'
@@
- <div className="flex-1 space-y-3 overflow-auto">
- {serverIds.map((id, i) => {
+ <ScrollArea className="flex-1">
+ <div className="space-y-3">
+ {serverIds.map((id, i) => {
const uptimeData = queries[i]?.data ?? []
const pct = computeAggregateUptime(uptimeData)
const name = serverNameMap.get(id) ?? id
return (
<div key={id}>
@@
</div>
)
- })}
- </div>
+ })}
+ </div>
+ </ScrollArea>As per coding guidelines apps/web/src/components/**/*.{ts,tsx}: All scrollable containers must use shadcn <ScrollArea> instead of native overflow-y-auto or overflow-auto.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/components/dashboard/widgets/uptime-timeline-widget.tsx` at line
78, Replace the native scrollable div (the JSX element with className "flex-1
space-y-3 overflow-auto" in the UptimeTimelineWidget component) with the shadcn
ScrollArea wrapper: import the ScrollArea component and wrap the same contents
using ScrollArea (and its viewport if your project pattern requires it), move
the "flex-1 space-y-3" classes to the ScrollArea/viewport element and remove the
native "overflow-auto" class; ensure the surrounding layout and children remain
unchanged so visual behavior is preserved.
| <Tabs className="gap-0" defaultValue={defaultTab}> | ||
| <CardHeader> | ||
| <CardTitle>{t('traffic_title', { defaultValue: 'Traffic Statistics' })}</CardTitle> | ||
| <CardTitle>{t('traffic_title')}</CardTitle> |
There was a problem hiding this comment.
This card still has untranslated UI labels.
Nice cleanup removing defaultValue, but this component still renders hardcoded In/Out/Total (including legend labels). Since locale keys already exist, please wire those through t(...) as well for complete coverage.
Also applies to: 44-45
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/components/server/traffic-card.tsx` at line 40, The component
renders hardcoded legend labels ("In", "Out", "Total") instead of using i18n;
update the TrafficCard UI to call the translation function for those strings
(replace the literal "In"/"Out"/"Total" used near CardTitle and the legend
elements with t('traffic_in'), t('traffic_out'), t('traffic_total') or the
existing locale keys), ensuring both the legend items and any other hardcoded
labels in the component are wired through t(...) for full localization.
| function formatRelativeTime(dateStr: string | null, t: (key: string, options?: { count: number }) => string): string { | ||
| if (!dateStr) { | ||
| return 'Never' | ||
| return t('mobile.time_never') | ||
| } | ||
| const date = new Date(dateStr) | ||
| const now = new Date() | ||
| const diffMs = now.getTime() - date.getTime() |
There was a problem hiding this comment.
Guard malformed timestamps in formatRelativeTime.
If dateStr is invalid, the current path can surface "Invalid Date" to users.
🛡️ Proposed fix
function formatRelativeTime(dateStr: string | null, t: (key: string, options?: { count: number }) => string): string {
if (!dateStr) {
return t('mobile.time_never')
}
const date = new Date(dateStr)
+ if (Number.isNaN(date.getTime())) {
+ return t('mobile.time_never')
+ }
const now = new Date()
const diffMs = now.getTime() - date.getTime()📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function formatRelativeTime(dateStr: string | null, t: (key: string, options?: { count: number }) => string): string { | |
| if (!dateStr) { | |
| return 'Never' | |
| return t('mobile.time_never') | |
| } | |
| const date = new Date(dateStr) | |
| const now = new Date() | |
| const diffMs = now.getTime() - date.getTime() | |
| function formatRelativeTime(dateStr: string | null, t: (key: string, options?: { count: number }) => string): string { | |
| if (!dateStr) { | |
| return t('mobile.time_never') | |
| } | |
| const date = new Date(dateStr) | |
| if (Number.isNaN(date.getTime())) { | |
| return t('mobile.time_never') | |
| } | |
| const now = new Date() | |
| const diffMs = now.getTime() - date.getTime() |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/routes/_authed/settings/mobile-devices.tsx` around lines 35 -
41, formatRelativeTime currently constructs a Date from dateStr and may return
"Invalid Date" to users; update the function (formatRelativeTime) to validate
the parsed date by checking date.getTime() for NaN (or use Number.isFinite)
after creating const date = new Date(dateStr) and if invalid return
t('mobile.time_never'); keep the existing null check for dateStr and proceed to
compute diffMs only when the date is valid.
| { | ||
| accessorKey: 'source', | ||
| header: 'Status', | ||
| header: t('target_status', { defaultValue: 'Status' }), | ||
| enableSorting: false, | ||
| cell: ({ row }) => | ||
| row.original.source ? ( | ||
| <span className="flex items-center gap-1 text-muted-foreground text-xs"> | ||
| <Lock aria-hidden="true" className="size-3" /> | ||
| {row.original.source_name ?? t('preset')} | ||
| {row.original.source_name ?? t('builtin', { defaultValue: 'Built-in' })} | ||
| </span> | ||
| ) : ( | ||
| <span className="text-muted-foreground text-xs">{t('custom')}</span> | ||
| ) |
There was a problem hiding this comment.
Rename this header to match the values it renders.
This column shows whether a target is built-in or custom, not its runtime status. Labeling it as Status is misleading in every locale and makes the table harder to scan; a dedicated source/origin key would fit the rendered values.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/routes/_authed/settings/network-probes.tsx` around lines 231 -
243, The column with accessorKey 'source' currently uses header:
t('target_status', ...) but displays "Built-in"/"Custom"; change the header to
use a matching i18n key (e.g. t('source' or t('origin') with defaultValue
'Source'/'Origin') so the label reflects the rendered values, update the
translation files for that new key, and ensure the header replacement is applied
where the column is defined (the header property on the column with accessorKey
'source') to keep locales consistent.
| // --------------------------------------------------------------------------- | ||
|
|
||
| function TrafficPage() { | ||
| const { t } = useTranslation('servers') |
There was a problem hiding this comment.
Chart legend labels are still hardcoded English.
You introduced t in this page, but Line 81 and Line 82 still use "Inbound" / "Outbound", so localized UIs can still leak English text.
🌐 Proposed fix
-const trendConfig = {
- bytes_in: { label: 'Inbound', color: 'var(--chart-1)' },
- bytes_out: { label: 'Outbound', color: 'var(--chart-2)' }
-} satisfies ChartConfig
-
function TrafficPage() {
const { t } = useTranslation('servers')
+ const trendConfig = useMemo<ChartConfig>(
+ () => ({
+ bytes_in: { label: t('traffic_inbound'), color: 'var(--chart-1)' },
+ bytes_out: { label: t('traffic_outbound'), color: 'var(--chart-2)' }
+ }),
+ [t]
+ )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/routes/_authed/traffic/index.tsx` at line 188, The chart legend
labels are hardcoded as "Inbound"/"Outbound" despite bringing in useTranslation;
update the legend labels in the chart configuration (where "Inbound" and
"Outbound" are used) to use the translation function `t` instead (for example
`t('inbound')` / `t('outbound')` or the appropriate keys in the 'servers'
namespace) so the chart uses localized strings; ensure the same translation keys
are added to the i18n resource files and that the component still passes the
translated values to the chart's legend/series labels (look for the
variables/props that currently contain "Inbound"/"Outbound").
There was a problem hiding this comment.
♻️ Duplicate comments (1)
apps/web/src/routes/_authed/settings/network-probes.tsx (1)
234-244:⚠️ Potential issue | 🟡 MinorRename the
sourcecolumn header; it still says “Status.”Line 234 labels provenance values (
Built-in/Custom) as status, which is misleading.Suggested adjustment
- header: t('target_status', { defaultValue: 'Status' }), + header: t('target_source', { defaultValue: 'Source' }),Also add
target_sourcetoapps/web/src/locales/en/network.jsonandapps/web/src/locales/zh/network.json.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/routes/_authed/settings/network-probes.tsx` around lines 234 - 244, The column header is mislabeled as "Status"; update the column definition's header call from t('target_status', { defaultValue: 'Status' }) to t('target_source', { defaultValue: 'Source' }) so it correctly reflects provenance, and add the new translation key "target_source" with appropriate English and Chinese values into the network.json locale files to ensure the label resolves in both languages.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@apps/web/src/routes/_authed/settings/network-probes.tsx`:
- Around line 234-244: The column header is mislabeled as "Status"; update the
column definition's header call from t('target_status', { defaultValue: 'Status'
}) to t('target_source', { defaultValue: 'Source' }) so it correctly reflects
provenance, and add the new translation key "target_source" with appropriate
English and Chinese values into the network.json locale files to ensure the
label resolves in both languages.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1fd93ea3-b0a1-4e83-83a3-e068dd5eb619
📒 Files selected for processing (7)
apps/web/src/components/network/latency-chart.tsxapps/web/src/components/server/server-card.tsxapps/web/src/lib/network-i18n.tsapps/web/src/routes/_authed/network/$server-id.test.tsxapps/web/src/routes/_authed/network/$serverId.tsxapps/web/src/routes/_authed/settings/network-probes.test.tsxapps/web/src/routes/_authed/settings/network-probes.tsx
✅ Files skipped from review due to trivial changes (3)
- apps/web/src/lib/network-i18n.ts
- apps/web/src/components/server/server-card.tsx
- apps/web/src/routes/_authed/network/$server-id.test.tsx
Summary
dockerandservice-monitorslocale namespaces and wire them into the i18n resource setupTest Plan
make web-typecheckbun x ultracite check apps/web/srcSummary by CodeRabbit
New Features
Bug Fixes / Improvements
Tests