From fa32c82e0dd33b5d293823e9fb81d148f48d19dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Sun, 23 Aug 2026 14:49:20 +0000 Subject: [PATCH] file viewer panel keyed (445 -> 414) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VideoRenderer used t as its track loop variable in two maps, so keying the panel shadowed the translation function — renamed to track. That is the third panel with this collision; it is worth knowing that a one-letter loop variable is the main mechanical hazard in this phase. The useFileViewer context Error stays English: thrown, not rendered. --- .../src/apps/FileViewer/FileViewerBody.tsx | 6 ++-- .../src/apps/FileViewer/FileViewerHeader.tsx | 16 ++++++----- .../apps/FileViewer/FileViewerProvider.tsx | 18 ++++++------ .../FileViewer/renderers/AudioRenderer.tsx | 4 ++- .../FileViewer/renderers/EditorRenderer.tsx | 6 ++-- .../FileViewer/renderers/ImageRenderer.tsx | 4 ++- .../renderers/JsonEditorRenderer.tsx | 13 ++++----- .../apps/FileViewer/renderers/PdfRenderer.tsx | 9 ++++-- .../renderers/ScrollToTopButton.tsx | 4 ++- .../FileViewer/renderers/VideoRenderer.tsx | 28 ++++++++++++------- .../officerdev/src/locales/en-GB.json | 28 +++++++++++++++++++ .../officerdev/src/locales/pt-PT.json | 28 +++++++++++++++++++ 12 files changed, 121 insertions(+), 43 deletions(-) diff --git a/src/workspaces/officerdev/src/apps/FileViewer/FileViewerBody.tsx b/src/workspaces/officerdev/src/apps/FileViewer/FileViewerBody.tsx index 4aa9816a..f3bbe113 100644 --- a/src/workspaces/officerdev/src/apps/FileViewer/FileViewerBody.tsx +++ b/src/workspaces/officerdev/src/apps/FileViewer/FileViewerBody.tsx @@ -12,8 +12,10 @@ import { TextRenderer } from './renderers/TextRenderer'; import { ScrollToTopButton } from './renderers/ScrollToTopButton'; import { EditorRenderer } from './renderers/EditorRenderer'; import { JsonEditorRenderer } from './renderers/JsonEditorRenderer'; +import { useTranslation } from '../../locales'; export const FileViewerBody = () => { + const { t } = useTranslation(); const { filePath, fileName, root, fileType, content, loading, error, autoPlay, editing, setContent, handleDownload } = useFileViewer(); const scrollRef = useRef(null); @@ -49,7 +51,7 @@ export const FileViewerBody = () => { className="inline-flex items-center gap-1.5 rounded-md border border-duck-dark/20 px-3 py-1.5 text-xs text-duck-dark transition-colors hover:bg-duck-dark/5 cursor-pointer" > - Download instead + {t('fileViewer.downloadInstead')} ) : fileType === 'pdf' ? ( @@ -57,7 +59,7 @@ export const FileViewerBody = () => { ) : fileType === 'archive' ? (
- Archive file + {t('fileViewer.archiveFile')} {getArchiveBaseName(fileName)}
) : fileType === 'image' ? ( diff --git a/src/workspaces/officerdev/src/apps/FileViewer/FileViewerHeader.tsx b/src/workspaces/officerdev/src/apps/FileViewer/FileViewerHeader.tsx index 6bbce036..bc1e28f3 100644 --- a/src/workspaces/officerdev/src/apps/FileViewer/FileViewerHeader.tsx +++ b/src/workspaces/officerdev/src/apps/FileViewer/FileViewerHeader.tsx @@ -17,8 +17,10 @@ import { import { getIcon } from 'material-file-icons'; import { getLang } from './file-types'; import { useFileViewer } from './FileViewerContext'; +import { useTranslation } from '../../locales'; export const FileViewerHeader = () => { + const { t } = useTranslation(); const { fileName, fileType, @@ -81,7 +83,7 @@ export const FileViewerHeader = () => { onClick={handleReadAloud} disabled={ttsLoading} className="p-1 rounded hover:bg-current/10 disabled:opacity-40 transition-colors cursor-pointer" - title="Read Aloud" + title={t('fileViewer.readAloud')} > {ttsLoading ? : } @@ -91,7 +93,7 @@ export const FileViewerHeader = () => { onClick={handleOcr} disabled={ocrLoading} className="p-1 rounded hover:bg-current/10 disabled:opacity-40 transition-colors cursor-pointer" - title="Extract Text (OCR)" + title={t('fileViewer.ocr')} > {ocrLoading ? : } @@ -101,7 +103,7 @@ export const FileViewerHeader = () => { onClick={handleTranscribe} disabled={transcribeLoading} className="p-1 rounded hover:bg-current/10 disabled:opacity-40 transition-colors cursor-pointer" - title="Transcribe" + title={t('fileViewer.transcribe')} > {transcribeLoading ? : } @@ -111,7 +113,7 @@ export const FileViewerHeader = () => { onClick={handleExtractAudio} disabled={extractAudioLoading} className="p-1 rounded hover:bg-current/10 disabled:opacity-40 transition-colors cursor-pointer" - title="Extract Audio" + title={t('fileViewer.extractAudio')} > {extractAudioLoading ? ( @@ -125,7 +127,7 @@ export const FileViewerHeader = () => { onClick={handleExtract} disabled={extractLoading} className="p-1 rounded hover:bg-current/10 disabled:opacity-40 transition-colors cursor-pointer" - title="Extract" + title={t('fileViewer.extract')} > {extractLoading ? ( @@ -139,7 +141,7 @@ export const FileViewerHeader = () => { onClick={handleSaveResult} disabled={saveResultLoading} className="p-1 rounded hover:bg-current/10 disabled:opacity-40 transition-colors cursor-pointer" - title="Save next to original" + title={t('fileViewer.saveNextTo')} > {saveResultLoading ? : } @@ -148,7 +150,7 @@ export const FileViewerHeader = () => { diff --git a/src/workspaces/officerdev/src/apps/FileViewer/FileViewerProvider.tsx b/src/workspaces/officerdev/src/apps/FileViewer/FileViewerProvider.tsx index b486ee08..9799bca9 100644 --- a/src/workspaces/officerdev/src/apps/FileViewer/FileViewerProvider.tsx +++ b/src/workspaces/officerdev/src/apps/FileViewer/FileViewerProvider.tsx @@ -5,6 +5,7 @@ import { useFilesRefresh } from '../../channels'; import { toast } from 'sonner'; import { getFileType } from './file-types'; import { FileViewerContext } from './FileViewerContext'; +import { useTranslation } from '../../locales'; type FileViewerProviderProps = { filePath: string; @@ -48,6 +49,7 @@ export const FileViewerProvider = ({ autoPlay = false, children, }: FileViewerProviderProps) => { + const { t } = useTranslation(); const [content, setContent] = useState(directContent ?? null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); @@ -104,7 +106,7 @@ export const FileViewerProvider = ({ bumpFilesRefresh(); onReplaceView?.(filePath, root, audioPath, audioRoot); } catch { - toast.error('Failed to generate speech audio'); + toast.error(t('fileViewer.speechFailed')); } finally { setTtsLoading(false); } @@ -117,7 +119,7 @@ export const FileViewerProvider = ({ bumpFilesRefresh(); onReplaceView?.(filePath, root, ocrPath, ocrRoot); } catch { - toast.error('Failed to extract text from image'); + toast.error(t('fileViewer.ocrFailed')); } finally { setOcrLoading(false); } @@ -130,7 +132,7 @@ export const FileViewerProvider = ({ bumpFilesRefresh(); onReplaceView?.(filePath, root, transcriptionPath, transcriptionRoot); } catch { - toast.error('Failed to transcribe audio'); + toast.error(t('fileViewer.transcribeFailed')); } finally { setTranscribeLoading(false); } @@ -142,7 +144,7 @@ export const FileViewerProvider = ({ const { audioPath, audioRoot } = await files.extractAudio(filePath); onOpenFile?.(audioPath, audioRoot); } catch { - toast.error('Failed to extract audio from video'); + toast.error(t('fileViewer.extractAudioFailed')); } finally { setExtractAudioLoading(false); } @@ -157,9 +159,9 @@ export const FileViewerProvider = ({ const { savedPath } = await files.saveResult(filePath); const savedName = savedPath.split('/').pop() ?? savedPath; bumpFilesRefresh(); - toast.success(`Saved as "${savedName}"`); + toast.success(t('fileViewer.savedAs', { name: savedName })); } catch { - toast.error('Failed to save file'); + toast.error(t('fileViewer.saveFailed')); } finally { setSaveResultLoading(false); } @@ -171,9 +173,9 @@ export const FileViewerProvider = ({ try { const { extractedPath } = await files.extract(filePath); const folderName = extractedPath.split('/').pop() ?? extractedPath; - toast.success(`Extracted to "${folderName}"`); + toast.success(t('fileViewer.extractedTo', { name: folderName })); } catch { - toast.error('Failed to extract archive'); + toast.error(t('fileViewer.extractFailed')); } finally { setExtractLoading(false); } diff --git a/src/workspaces/officerdev/src/apps/FileViewer/renderers/AudioRenderer.tsx b/src/workspaces/officerdev/src/apps/FileViewer/renderers/AudioRenderer.tsx index 75cb1c22..08bcfc83 100644 --- a/src/workspaces/officerdev/src/apps/FileViewer/renderers/AudioRenderer.tsx +++ b/src/workspaces/officerdev/src/apps/FileViewer/renderers/AudioRenderer.tsx @@ -2,6 +2,7 @@ import { useState, useEffect, useRef } from 'react'; import { Loader2, Music, Play, Pause, Volume2, VolumeX } from 'lucide-react'; import { formatTime } from '../file-types'; import { useSeekBar, SeekBar } from './SeekBar'; +import { useTranslation } from '../../../locales'; type AudioRendererProps = { src: string; @@ -10,6 +11,7 @@ type AudioRendererProps = { }; export const AudioRenderer = ({ src, fileName, autoPlay = false }: AudioRendererProps) => { + const { t } = useTranslation(); const audioRef = useRef(null); const [playing, setPlaying] = useState(false); const [currentTime, setCurrent] = useState(0); @@ -80,7 +82,7 @@ export const AudioRenderer = ({ src, fileName, autoPlay = false }: AudioRenderer if (error) { return (
- Failed to load audio file + {t('fileViewer.audioFailed')}
); } diff --git a/src/workspaces/officerdev/src/apps/FileViewer/renderers/EditorRenderer.tsx b/src/workspaces/officerdev/src/apps/FileViewer/renderers/EditorRenderer.tsx index 60d02c44..0d4f8f28 100644 --- a/src/workspaces/officerdev/src/apps/FileViewer/renderers/EditorRenderer.tsx +++ b/src/workspaces/officerdev/src/apps/FileViewer/renderers/EditorRenderer.tsx @@ -4,6 +4,7 @@ import type { editor as MonacoEditor } from 'monaco-editor'; import { toast } from 'sonner'; import { getLanguage } from '../../CodeEditor/language-map'; import { useFilesAPI } from '../../../hooks/useFilesAPI'; +import { useTranslation } from '../../../locales'; type EditorRendererProps = { content: string; @@ -14,6 +15,7 @@ type EditorRendererProps = { }; export const EditorRenderer = ({ content, fileName, filePath, root, onSave }: EditorRendererProps) => { + const { t } = useTranslation(); const editorRef = useRef(null); const { writeFile } = useFilesAPI(root); @@ -24,9 +26,9 @@ export const EditorRenderer = ({ content, fileName, filePath, root, onSave }: Ed try { await writeFile(filePath, newContent); onSave?.(newContent); - toast.success('File saved'); + toast.success(t('fileViewer.saved')); } catch { - toast.error('Failed to save file'); + toast.error(t('fileViewer.saveFailed')); } }, [filePath, writeFile, onSave]); diff --git a/src/workspaces/officerdev/src/apps/FileViewer/renderers/ImageRenderer.tsx b/src/workspaces/officerdev/src/apps/FileViewer/renderers/ImageRenderer.tsx index 19116237..951c7d9d 100644 --- a/src/workspaces/officerdev/src/apps/FileViewer/renderers/ImageRenderer.tsx +++ b/src/workspaces/officerdev/src/apps/FileViewer/renderers/ImageRenderer.tsx @@ -1,5 +1,6 @@ import { useState, useEffect, useRef } from 'react'; import { Loader2, ZoomIn, ZoomOut, RotateCw } from 'lucide-react'; +import { useTranslation } from '../../../locales'; const ZOOM_STEPS = [0.25, 0.5, 0.75, 1, 1.5, 2, 3, 5]; const MIN_ZOOM = ZOOM_STEPS[0]!; @@ -11,6 +12,7 @@ type ImageRendererProps = { }; export const ImageRenderer = ({ src, fileName }: ImageRendererProps) => { + const { t } = useTranslation(); const containerRef = useRef(null); const [zoom, setZoom] = useState(1); const [rotation, setRotation] = useState(0); @@ -78,7 +80,7 @@ export const ImageRenderer = ({ src, fileName }: ImageRendererProps) => { if (error) { return (
- Failed to load image + {t('fileViewer.imageFailed')}
); } diff --git a/src/workspaces/officerdev/src/apps/FileViewer/renderers/JsonEditorRenderer.tsx b/src/workspaces/officerdev/src/apps/FileViewer/renderers/JsonEditorRenderer.tsx index 0f6dbcf0..5e50c442 100644 --- a/src/workspaces/officerdev/src/apps/FileViewer/renderers/JsonEditorRenderer.tsx +++ b/src/workspaces/officerdev/src/apps/FileViewer/renderers/JsonEditorRenderer.tsx @@ -3,6 +3,7 @@ import { JsonEditor } from '@visual-json/react'; import type { JsonValue } from '@visual-json/core'; import { toast } from 'sonner'; import { useFilesAPI } from '../../../hooks/useFilesAPI'; +import { useTranslation } from '../../../locales'; type JsonEditorRendererProps = { content: string; @@ -12,6 +13,7 @@ type JsonEditorRendererProps = { }; export const JsonEditorRenderer = ({ content, filePath, root, onSave }: JsonEditorRendererProps) => { + const { t } = useTranslation(); const { writeFile } = useFilesAPI(root); const valueRef = useRef(JSON.parse(content)); @@ -24,9 +26,9 @@ export const JsonEditorRenderer = ({ content, filePath, root, onSave }: JsonEdit try { await writeFile(filePath, newContent); onSave?.(newContent); - toast.success('File saved'); + toast.success(t('fileViewer.saved')); } catch { - toast.error('Failed to save file'); + toast.error(t('fileViewer.saveFailed')); } }, [filePath, writeFile, onSave]); @@ -40,12 +42,7 @@ export const JsonEditorRenderer = ({ content, filePath, root, onSave }: JsonEdit } }} > - + ); }; diff --git a/src/workspaces/officerdev/src/apps/FileViewer/renderers/PdfRenderer.tsx b/src/workspaces/officerdev/src/apps/FileViewer/renderers/PdfRenderer.tsx index 263a1fb3..fd6bdc9c 100644 --- a/src/workspaces/officerdev/src/apps/FileViewer/renderers/PdfRenderer.tsx +++ b/src/workspaces/officerdev/src/apps/FileViewer/renderers/PdfRenderer.tsx @@ -1,3 +1,6 @@ -export const PdfRenderer = ({ src }: { src: string }) => ( -