From 8148023a1e785ed9b61ad073cdc07e9b57d46f7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Sun, 23 Aug 2026 16:16:41 +0000 Subject: [PATCH] file item's row menu keyed (80 -> 67) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The single row menu shared by the dropdown and the context-menu variants: open, edit, paste-into, read aloud, extract, run task/agent, cut/copy/duplicate/rename, compress, copy path, download, pin, chat, create dashboard, delete. Four strings the scanner cannot see, converted anyway since the file was open: "Open" sits after a conditional icon expression (`{cond ? : }`) rather than directly after a tag's `>`, which is outside the jsx-text pattern's reach. "Cut" is three characters and the pattern requires four. "Open in editor"/"(too large)" and "Compress"/"Compress selection" are both ternaries, which the scanner's own docs already say it cannot see. formatDate() called toLocaleDateString(undefined, {...}) — the browser-locale contradiction D7 describes, same shape as ApiKeys and DavAppPasswords in phase 2. Replaced with useFormat().date, threaded from the exported FileItem component since the old helper was module-level and a hook cannot be called from one. Verified the two locales now genuinely differ: "14 Nov 2023" against "14/11/2023", not "whatever the browser happens to be set to". task.name, agent.name and group.category stay untouched — content, not chrome. Reused files.menu.copyPath, .createDashboardHere and .download from the previous commit rather than adding duplicates for the same words. --- .../FileBrowserApp/components/FileItem.tsx | 52 +++++++++---------- .../officerdev/src/locales/en-GB.json | 20 ++++++- .../officerdev/src/locales/pt-PT.json | 20 ++++++- 3 files changed, 62 insertions(+), 30 deletions(-) diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/FileItem.tsx b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/FileItem.tsx index d51e5195..2e486529 100644 --- a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/FileItem.tsx +++ b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/FileItem.tsx @@ -50,6 +50,8 @@ import type { DirEntry } from '../../../../hooks/useFilesAPI'; import type { TaskGroup, TaskSummary } from '../../useTasks'; import type { AgentGroup, AgentSummary } from '../../useAgents'; import { getFileType } from '../../../FileViewer'; +import { useTranslation } from '../../../../locales'; +import { useFormat } from 'i18n'; export type FileItemProps = { entry: DirEntry; @@ -91,14 +93,6 @@ function formatSize(bytes: number): string { return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } -function formatDate(ms: number): string { - return new Date(ms).toLocaleDateString(undefined, { - month: 'short', - day: 'numeric', - year: 'numeric', - }); -} - /** * Extensions that reach `getFileType`'s `text` fallback but are not text. * @@ -232,6 +226,7 @@ const MenuItems = ({ onRunAgent, onCreateDashboard, }: MenuItemsProps & { ui: MenuPrimitives }) => { + const { t } = useTranslation(); const isDir = entry.type === 'directory'; const fileType = entry.type === 'file' ? getFileType(entry.name) : null; // Same binary guard as the editor. `text` is the FALLBACK type, so without this Read Aloud offered to @@ -268,7 +263,7 @@ const MenuItems = ({ puts first. */} onOpen(entry)} className="cursor-pointer"> {isDir ? : } - Open + {t('files.menu.open')} {showEdit && ( // The first place `disabled` is used rather than hiding. `/file-browser/read` refuses anything @@ -281,7 +276,7 @@ const MenuItems = ({ className={tooBigToEdit ? '' : 'cursor-pointer'} > - {tooBigToEdit ? 'Open in editor (too large)' : 'Open in editor'} + {tooBigToEdit ? t('files.menu.openInEditorTooLarge') : t('files.menu.openInEditor')} )} {/* Paste INTO a folder. The background menu could only paste into the folder already open, so a @@ -289,27 +284,27 @@ const MenuItems = ({ {isDir && canPaste && ( onPasteInto(entry)} className="cursor-pointer"> - Paste into folder + {t('files.menu.pasteIntoFolder')} )} {showReadAloud && ( onReadAloud(entry)} className="cursor-pointer"> - Read Aloud + {t('files.menu.readAloud')} )} {showExtract && ( onExtract(entry)} className="cursor-pointer"> - Extract + {t('files.menu.extract')} )} {hasTasks && ( - Run Task + {t('files.menu.runTask')} {nestTasks @@ -337,7 +332,7 @@ const MenuItems = ({ - Run Agent + {t('files.menu.runAgent')} {nestAgents @@ -364,58 +359,58 @@ const MenuItems = ({ {hasActions && } - Cut + {t('files.menu.cut')} - Copy + {t('files.menu.copy')} {!multiSelected && ( onDuplicate(entry)} className="cursor-pointer"> - Duplicate + {t('files.menu.duplicate')} )} {!multiSelected && ( - Rename + {t('files.menu.rename')} )} {/* Compress, the missing half of Extract. `/download` already shelled out to zip but streamed the result away; nothing could make an archive and keep it. */} onCompress(entry)} className="cursor-pointer"> - {multiSelected ? 'Compress selection' : 'Compress'} + {multiSelected ? t('files.menu.compressSelection') : t('files.menu.compress')} onCopyPath(entry)} className="cursor-pointer"> - Copy path + {t('files.menu.copyPath')} onDownload(entry)} className="cursor-pointer"> - Download + {t('files.menu.download')} {/* Pinning lived only in the widget, which is not where you meet a file worth keeping to hand. */} onTogglePin(entry)} className="cursor-pointer"> {isPinned ? : } - {isPinned ? 'Unpin' : 'Pin'} + {isPinned ? t('files.menu.unpin') : t('files.menu.pin')} onChat(entry)} className="cursor-pointer"> - Chat... + {t('files.menu.chat')} {isDir && ( onCreateDashboard(entry)} className="cursor-pointer"> - Create Dashboard here + {t('files.menu.createDashboardHere')} )} onDelete(entry)} className="text-destructive cursor-pointer"> - Delete + {t('files.menu.delete')} ); @@ -593,6 +588,7 @@ export const FileItem = ({ isPinned, canPaste, }: FileItemProps) => { + const { date } = useFormat(); const [renaming, setRenaming] = useState(false); const clickTimer = useRef | null>(null); const isDir = entry.type === 'directory'; @@ -730,7 +726,7 @@ export const FileItem = ({ {isDir ? '--' : formatSize(entry.size)} - {formatDate(entry.modifiedAt)} + {date(entry.modifiedAt, { month: 'short', day: 'numeric', year: 'numeric' })}
@@ -780,7 +776,7 @@ export const FileItem = ({ {isDir ? 'Folder' : formatSize(entry.size)} {' · '} - {formatDate(entry.modifiedAt)} + {date(entry.modifiedAt, { month: 'short', day: 'numeric', year: 'numeric' })}
diff --git a/src/workspaces/officerdev/src/locales/en-GB.json b/src/workspaces/officerdev/src/locales/en-GB.json index af44d33d..46d29c6a 100644 --- a/src/workspaces/officerdev/src/locales/en-GB.json +++ b/src/workspaces/officerdev/src/locales/en-GB.json @@ -503,7 +503,25 @@ "openFolder": "Open folder", "showLocation": "Show location", "download": "Download", - "noResults": "No results found" + "noResults": "No results found", + "open": "Open", + "openInEditor": "Open in editor", + "openInEditorTooLarge": "Open in editor (too large)", + "pasteIntoFolder": "Paste into folder", + "readAloud": "Read Aloud", + "extract": "Extract", + "runTask": "Run Task", + "runAgent": "Run Agent", + "cut": "Cut", + "copy": "Copy", + "duplicate": "Duplicate", + "rename": "Rename", + "compress": "Compress", + "compressSelection": "Compress selection", + "unpin": "Unpin", + "pin": "Pin", + "chat": "Chat...", + "delete": "Delete" } }, "taskRunner": { diff --git a/src/workspaces/officerdev/src/locales/pt-PT.json b/src/workspaces/officerdev/src/locales/pt-PT.json index c8a2cfe4..078c79ca 100644 --- a/src/workspaces/officerdev/src/locales/pt-PT.json +++ b/src/workspaces/officerdev/src/locales/pt-PT.json @@ -503,7 +503,25 @@ "openFolder": "Abrir pasta", "showLocation": "Mostrar localização", "download": "Descarregar", - "noResults": "Nenhum resultado encontrado" + "noResults": "Nenhum resultado encontrado", + "open": "Abrir", + "openInEditor": "Abrir no editor", + "openInEditorTooLarge": "Abrir no editor (demasiado grande)", + "pasteIntoFolder": "Colar na pasta", + "readAloud": "Ler em voz alta", + "extract": "Extrair", + "runTask": "Executar tarefa", + "runAgent": "Executar agente", + "cut": "Cortar", + "copy": "Copiar", + "duplicate": "Duplicar", + "rename": "Renomear", + "compress": "Comprimir", + "compressSelection": "Comprimir seleção", + "unpin": "Remover fixação", + "pin": "Fixar", + "chat": "Chat...", + "delete": "Eliminar" } }, "taskRunner": {