From 24d46a815afe04f00b0329c0fbcbc11fb84b4318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Sun, 23 Aug 2026 14:18:26 +0000 Subject: [PATCH] system monitor panel keyed (544 -> 528) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pm2View's column metadata is module-level, so it carries labelKey and the header resolves it. '#', '↺' and 'PID' keep plain labels — a glyph and an acronym read the same in every locale, and a key whose two sides can never differ is a cost bought for nothing. PanelTranslationKey is now exported from the locales barrel, which panel-level data tables need in the same way officer-web needed AppTranslationKey. --- .../src/apps/SystemMonitor/BtopView.tsx | 24 ++++++++------- .../src/apps/SystemMonitor/DockerView.tsx | 4 ++- .../src/apps/SystemMonitor/Pm2View.tsx | 22 +++++++++----- .../officerdev/src/locales/en-GB.json | 30 +++++++++++++++++++ .../officerdev/src/locales/index.ts | 1 + .../officerdev/src/locales/pt-PT.json | 30 +++++++++++++++++++ .../officerdev/src/locales/useTranslation.ts | 2 +- 7 files changed, 93 insertions(+), 20 deletions(-) diff --git a/src/workspaces/officerdev/src/apps/SystemMonitor/BtopView.tsx b/src/workspaces/officerdev/src/apps/SystemMonitor/BtopView.tsx index 66459854..f2958537 100644 --- a/src/workspaces/officerdev/src/apps/SystemMonitor/BtopView.tsx +++ b/src/workspaces/officerdev/src/apps/SystemMonitor/BtopView.tsx @@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from 'react'; import { useClient } from 'hooks/useClient'; import { Cpu, MemoryStick, HardDrive, Server, Thermometer, CircuitBoard, Network, Zap } from 'lucide-react'; import { fmtBytes, fmtDuration } from './shared'; +import { useTranslation } from '../../locales'; type Sensor = { name: string; label: string; celsius: number }; type Stats = { @@ -67,6 +68,7 @@ const Card = ({ ); export const BtopView = () => { + const { t } = useTranslation(); const { get } = useClient(); const [stats, setStats] = useState(null); const [error, setError] = useState(null); @@ -133,7 +135,7 @@ export const BtopView = () => { - + {stats.mem ? ( <>
@@ -154,7 +156,7 @@ export const BtopView = () => { )} - + {stats.disks.length ? (
{stats.disks.map((d) => ( @@ -195,7 +197,7 @@ export const BtopView = () => { )} - + {stats.temp?.cpuC != null ? ( <>
@@ -223,7 +225,7 @@ export const BtopView = () => { )} - + {stats.net ? ( <>
@@ -247,11 +249,11 @@ export const BtopView = () => { )} - + {stats.power ? (
- CPU package + {t('systemMonitor.cpuPackage')} {stats.power.cpuWatts != null ? `${stats.power.cpuWatts} W` : '—'} @@ -269,16 +271,16 @@ export const BtopView = () => {
- +
- - - - + + + + diff --git a/src/workspaces/officerdev/src/apps/SystemMonitor/DockerView.tsx b/src/workspaces/officerdev/src/apps/SystemMonitor/DockerView.tsx index 45f91351..956e8d1b 100644 --- a/src/workspaces/officerdev/src/apps/SystemMonitor/DockerView.tsx +++ b/src/workspaces/officerdev/src/apps/SystemMonitor/DockerView.tsx @@ -1,6 +1,7 @@ import { useEffect, useRef, useState } from 'react'; import { useClient } from 'hooks/useClient'; import { LogStream } from './LogStream'; +import { useTranslation } from '../../locales'; type Container = { id: string; name: string; image: string; state: string; status: string; ports: string }; @@ -8,6 +9,7 @@ const POLL_MS = 3000; const stateColor = (s: string) => (s === 'running' ? 'bg-success' : s === 'exited' ? 'bg-destructive' : 'bg-busy'); export const DockerView = () => { + const { t } = useTranslation(); const { get } = useClient(); const [containers, setContainers] = useState(null); const [error, setError] = useState(null); @@ -59,7 +61,7 @@ export const DockerView = () => { setFilter(e.target.value)} - placeholder="filter name or image…" + placeholder={t('systemMonitor.filterPlaceholder')} className="w-56 rounded-md border border-border bg-background px-2 py-1 text-xs text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary" /> diff --git a/src/workspaces/officerdev/src/apps/SystemMonitor/Pm2View.tsx b/src/workspaces/officerdev/src/apps/SystemMonitor/Pm2View.tsx index 656132af..bc756f28 100644 --- a/src/workspaces/officerdev/src/apps/SystemMonitor/Pm2View.tsx +++ b/src/workspaces/officerdev/src/apps/SystemMonitor/Pm2View.tsx @@ -2,6 +2,8 @@ import { useEffect, useMemo, useRef, useState } from 'react'; import { useClient } from 'hooks/useClient'; import { fmtBytes, fmtDuration } from './shared'; import { LogStream } from './LogStream'; +import type { PanelTranslationKey } from '../../locales'; +import { useTranslation } from '../../locales'; type Proc = { id: number; @@ -25,18 +27,24 @@ const statusColor = (s: string) => ? 'bg-destructive' : 'bg-busy'; -const COLS: { key: SortKey; label: string; align: 'left' | 'right' }[] = [ +/* + Column metadata lives outside the component, so it carries catalogue KEYS and the header resolves + them. `#`, `↺` and `PID` have no key: a glyph and an acronym are the same in every locale, and a key + whose two sides can never differ is a maintenance cost bought for nothing. +*/ +const COLS: { key: SortKey; labelKey?: PanelTranslationKey; label?: string; align: 'left' | 'right' }[] = [ { key: 'id', label: '#', align: 'right' }, - { key: 'name', label: 'Name', align: 'left' }, - { key: 'status', label: 'Status', align: 'left' }, - { key: 'cpuPct', label: 'CPU%', align: 'right' }, - { key: 'memBytes', label: 'Mem', align: 'right' }, + { key: 'name', labelKey: 'systemMonitor.name', align: 'left' }, + { key: 'status', labelKey: 'systemMonitor.status', align: 'left' }, + { key: 'cpuPct', labelKey: 'systemMonitor.cpuPct', align: 'right' }, + { key: 'memBytes', labelKey: 'systemMonitor.mem', align: 'right' }, { key: 'restarts', label: '↺', align: 'right' }, - { key: 'uptimeMs', label: 'Uptime', align: 'left' }, + { key: 'uptimeMs', labelKey: 'systemMonitor.uptime', align: 'left' }, { key: 'pid', label: 'PID', align: 'right' }, ]; export const Pm2View = () => { + const { t } = useTranslation(); const { get } = useClient(); const [procs, setProcs] = useState(null); const [error, setError] = useState(null); @@ -116,7 +124,7 @@ export const Pm2View = () => { onClick={() => toggleSort(c.key)} className={`inline-flex items-center gap-1 hover:text-foreground ${c.align === 'right' ? 'flex-row-reverse' : ''} ${sortKey === c.key ? 'text-foreground' : ''}`} > - {c.label} + {c.labelKey ? t(c.labelKey) : c.label} {sortKey === c.key ? (sortDir === 'asc' ? '▲' : '▼') : ''} diff --git a/src/workspaces/officerdev/src/locales/en-GB.json b/src/workspaces/officerdev/src/locales/en-GB.json index 68c68567..fa1fcf97 100644 --- a/src/workspaces/officerdev/src/locales/en-GB.json +++ b/src/workspaces/officerdev/src/locales/en-GB.json @@ -51,5 +51,35 @@ "installing": "installing", "failed": "failed", "installedIcon": "installed" + }, + "systemMonitor": { + "cpuPackage": "CPU package", + "user": "User", + "cpuPct": "CPU%", + "memPct": "MEM%", + "command": "Command", + "memory": "Memory", + "disks": "Disks", + "temperature": "Temperature", + "network": "Network", + "power": "Power", + "topProcesses": "Top processes", + "filterPlaceholder": "filter name or image…", + "name": "Name", + "status": "Status", + "mem": "Mem", + "uptime": "Uptime" + }, + "codeEditor": { + "closeFile": "Close {{name}}", + "loading": "Loading…", + "noFiles": "No files", + "explorer": "Explorer", + "openAFile": "Open a file from the explorer to start editing", + "refresh": "Refresh the tree and reload unmodified open files", + "readFailed": "Failed to read file", + "saved": "File saved", + "saveFailed": "Failed to save file", + "title": "Code Editor" } } diff --git a/src/workspaces/officerdev/src/locales/index.ts b/src/workspaces/officerdev/src/locales/index.ts index 3088b54b..e6077b48 100644 --- a/src/workspaces/officerdev/src/locales/index.ts +++ b/src/workspaces/officerdev/src/locales/index.ts @@ -6,3 +6,4 @@ import ptPT from './pt-PT.json'; export const panelCatalogs: TranslationMap = { 'en-GB': enGB, 'pt-PT': ptPT }; export { useTranslation } from './useTranslation'; +export type { PanelTranslationKey } from './useTranslation'; diff --git a/src/workspaces/officerdev/src/locales/pt-PT.json b/src/workspaces/officerdev/src/locales/pt-PT.json index 4401277e..21fd394c 100644 --- a/src/workspaces/officerdev/src/locales/pt-PT.json +++ b/src/workspaces/officerdev/src/locales/pt-PT.json @@ -51,5 +51,35 @@ "installing": "a instalar", "failed": "falhou", "installedIcon": "instalada" + }, + "systemMonitor": { + "cpuPackage": "Pacote do CPU", + "user": "Utilizador", + "cpuPct": "CPU%", + "memPct": "MEM%", + "command": "Comando", + "memory": "Memória", + "disks": "Discos", + "temperature": "Temperatura", + "network": "Rede", + "power": "Energia", + "topProcesses": "Processos principais", + "filterPlaceholder": "filtrar por nome ou imagem…", + "name": "Nome", + "status": "Estado", + "mem": "Mem", + "uptime": "Tempo ativo" + }, + "codeEditor": { + "closeFile": "Fechar {{name}}", + "loading": "A carregar…", + "noFiles": "Sem ficheiros", + "explorer": "Explorador", + "openAFile": "Abra um ficheiro no explorador para começar a editar", + "refresh": "Atualizar a árvore e recarregar os ficheiros abertos não modificados", + "readFailed": "Falha ao ler o ficheiro", + "saved": "Ficheiro guardado", + "saveFailed": "Falha ao guardar o ficheiro", + "title": "Editor de código" } } diff --git a/src/workspaces/officerdev/src/locales/useTranslation.ts b/src/workspaces/officerdev/src/locales/useTranslation.ts index cd16ad69..34c15828 100644 --- a/src/workspaces/officerdev/src/locales/useTranslation.ts +++ b/src/workspaces/officerdev/src/locales/useTranslation.ts @@ -3,7 +3,7 @@ import { useCallback } from 'react'; import { useTranslation as useTranslationBase } from 'i18n'; import type en from './en-GB.json'; -type PanelTranslationKey = TranslationKeys; +export type PanelTranslationKey = TranslationKeys; // The `panel.` prefix is namespace wiring for the merged catalog (D6) — callers never spell it, they // pass the same bare key the JSON file uses.
PIDUserCPU%MEM%Command{t('systemMonitor.user')}{t('systemMonitor.cpuPct')}{t('systemMonitor.memPct')}{t('systemMonitor.command')}