diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/VideoDownloadDialog.tsx b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/VideoDownloadDialog.tsx index 50d11480..e3474894 100644 --- a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/VideoDownloadDialog.tsx +++ b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/VideoDownloadDialog.tsx @@ -4,6 +4,7 @@ import { Checkbox } from '@/components/ui/checkbox'; import { Download, Loader2, ChevronLeft, AlertCircle, Check, Music } from 'lucide-react'; import type { VideoInfo } from '../../../../hooks/useFilesAPI'; import type { UseFileBrowserAppType } from '../useFileBrowserApp'; +import { useTranslation } from '../../../../locales'; type VideoDownloadDialogProps = { fileBrowserManager: UseFileBrowserAppType; @@ -37,6 +38,7 @@ const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); const sanitizeFolder = (name: string) => name.replace(/[/\\]/g, '').replace(/^\.+/, '').trim(); export const VideoDownloadDialog = ({ fileBrowserManager }: VideoDownloadDialogProps) => { + const { t } = useTranslation(); const { showVideoDownload, setShowVideoDownload, currentPath, refresh, files } = fileBrowserManager; const [phase, setPhase] = useState<'input' | 'preview'>('input'); @@ -94,7 +96,9 @@ export const VideoDownloadDialog = ({ fileBrowserManager }: VideoDownloadDialogP // Sequentially (ReClip does yt-dlp per video — parallel would hammer it); cards fill in as they resolve. for (let i = 0; i < urls.length; i++) { - const info = await files.videoInfo(urls[i]!).catch((): VideoInfo => ({ error: 'Could not fetch info' })); + const info = await files + .videoInfo(urls[i]!) + .catch((): VideoInfo => ({ error: t('videoDownload.couldNotFetchInfo') })); if (info.error) patch(i, { status: 'error', error: info.error }); else patch(i, { @@ -121,11 +125,12 @@ export const VideoDownloadDialog = ({ fileBrowserManager }: VideoDownloadDialogP const { jobId } = await files.downloadVideo(entryUrl, targetDir(), audioOnly); const deadline = Date.now() + 60 * 60 * 1000; for (;;) { - if (Date.now() > deadline) return patch(i, { dl: 'error', dlError: 'Timed out' }); + if (Date.now() > deadline) return patch(i, { dl: 'error', dlError: t('videoDownload.timedOut') }); await sleep(2000); const st = await files.downloadVideoStatus(jobId).catch(() => null); if (!st) continue; - if (st.status === 'error') return patch(i, { dl: 'error', dlError: st.error || 'Download failed' }); + if (st.status === 'error') + return patch(i, { dl: 'error', dlError: st.error || t('videoDownload.downloadFailed') }); if (st.status === 'transferring') patch(i, { dl: 'saving' }); if (st.status === 'done') { patch(i, { dl: 'done', filename: st.filename }); @@ -134,7 +139,7 @@ export const VideoDownloadDialog = ({ fileBrowserManager }: VideoDownloadDialogP } } } catch { - patch(i, { dl: 'error', dlError: 'Could not start the download' }); + patch(i, { dl: 'error', dlError: t('videoDownload.couldNotStart') }); } }; @@ -152,17 +157,21 @@ export const VideoDownloadDialog = ({ fileBrowserManager }: VideoDownloadDialogP }; const dlLabel = (e: Entry) => - e.dl === 'downloading' ? 'Downloading…' : e.dl === 'saving' ? 'Saving…' : e.dl === 'done' ? 'Saved' : ''; + e.dl === 'downloading' + ? t('videoDownload.downloading') + : e.dl === 'saving' + ? t('videoDownload.saving') + : e.dl === 'done' + ? t('videoDownload.saved') + : ''; return ( !open && handleClose()}> - Download video + {t('videoDownload.title')} - {phase === 'input' - ? 'Paste a video or playlist URL — it fetches details before downloading.' - : 'Review and download.'} + {phase === 'input' ? t('videoDownload.descriptionInput') : t('videoDownload.descriptionPreview')} @@ -178,12 +187,12 @@ export const VideoDownloadDialog = ({ fileBrowserManager }: VideoDownloadDialogP autoFocus value={url} onChange={(ev) => setUrl(ev.target.value)} - placeholder="https://www.youtube.com/watch?v=… or …/playlist?list=…" + placeholder={t('videoDownload.urlPlaceholder')} className="h-10 w-full rounded-md border border-duck-dark/20 bg-background/60 px-3 text-sm text-duck-dark outline-none placeholder:text-duck-dark/40 focus:border-duck-teal/50" />
@@ -211,11 +220,11 @@ export const VideoDownloadDialog = ({ fileBrowserManager }: VideoDownloadDialogP onClick={() => setPhase('input')} className="flex cursor-pointer items-center gap-1 text-sm text-duck-dark/60 hover:text-duck-dark" > - Back + {t('videoDownload.back')} - {audioOnly ? 'Audio only' : 'Video'} - {isPlaylist ? ` · ${entries.length} items` : ''} + {audioOnly ? t('videoDownload.audioOnlyLabel') : t('videoDownload.video')} + {isPlaylist ? ` · ${t('videoDownload.itemCount', { count: entries.length })}` : ''} @@ -224,7 +233,7 @@ export const VideoDownloadDialog = ({ fileBrowserManager }: VideoDownloadDialogP setSubfolder(ev.target.value)} - placeholder="Subfolder (optional) — leave blank for this folder" + placeholder={t('videoDownload.subfolderPlaceholder')} className="h-9 min-w-0 flex-1 rounded-md border border-duck-dark/20 bg-background/60 px-3 text-sm text-duck-dark outline-none placeholder:text-duck-dark/40 focus:border-duck-teal/50" /> )} @@ -265,7 +274,9 @@ export const VideoDownloadDialog = ({ fileBrowserManager }: VideoDownloadDialogP ) : e.status === 'error' ? ( <> -

Could not fetch

+

+ {t('videoDownload.couldNotFetch')} +

{e.error || e.url}

) : ( @@ -285,7 +296,7 @@ export const VideoDownloadDialog = ({ fileBrowserManager }: VideoDownloadDialogP
{e.dl === 'done' ? ( - Saved + {t('videoDownload.saved')} ) : e.dl === 'downloading' || e.dl === 'saving' ? ( @@ -298,7 +309,7 @@ export const VideoDownloadDialog = ({ fileBrowserManager }: VideoDownloadDialogP className="flex cursor-pointer items-center gap-1.5 rounded-md border border-duck-teal/40 px-2.5 py-1.5 text-xs font-medium text-duck-teal transition-colors hover:bg-duck-teal/10" > - {e.dl === 'error' ? 'Retry' : 'Download'} + {e.dl === 'error' ? t('videoDownload.retry') : t('videoDownload.download')} )}
diff --git a/src/workspaces/officerdev/src/locales/en-GB.json b/src/workspaces/officerdev/src/locales/en-GB.json index 37e1dc0d..20778e1b 100644 --- a/src/workspaces/officerdev/src/locales/en-GB.json +++ b/src/workspaces/officerdev/src/locales/en-GB.json @@ -575,5 +575,32 @@ "placeholder": "https://github.com/owner/repo.git", "submit": "Clone" } + }, + "videoDownload": { + "title": "Download video", + "descriptionInput": "Paste a video or playlist URL — it fetches details before downloading.", + "descriptionPreview": "Review and download.", + "urlPlaceholder": "https://www.youtube.com/watch?v=… or …/playlist?list=…", + "audioOnly": "Extract audio only (mp3)", + "fetch": "Fetch", + "back": "Back", + "audioOnlyLabel": "Audio only", + "video": "Video", + "itemCount": { + "one": "{{count}} item", + "other": "{{count}} items" + }, + "subfolderPlaceholder": "Subfolder (optional) — leave blank for this folder", + "downloadAll": "Download all", + "couldNotFetch": "Could not fetch", + "downloading": "Downloading…", + "saving": "Saving…", + "saved": "Saved", + "retry": "Retry", + "download": "Download", + "couldNotFetchInfo": "Could not fetch info", + "timedOut": "Timed out", + "downloadFailed": "Download failed", + "couldNotStart": "Could not start the download" } } diff --git a/src/workspaces/officerdev/src/locales/pt-PT.json b/src/workspaces/officerdev/src/locales/pt-PT.json index 87f61239..6d3df556 100644 --- a/src/workspaces/officerdev/src/locales/pt-PT.json +++ b/src/workspaces/officerdev/src/locales/pt-PT.json @@ -575,5 +575,32 @@ "placeholder": "https://github.com/owner/repo.git", "submit": "Clonar" } + }, + "videoDownload": { + "title": "Descarregar vídeo", + "descriptionInput": "Cole um URL de vídeo ou de lista de reprodução — os detalhes são obtidos antes de descarregar.", + "descriptionPreview": "Reveja e descarregue.", + "urlPlaceholder": "https://www.youtube.com/watch?v=… ou …/playlist?list=…", + "audioOnly": "Extrair apenas o áudio (mp3)", + "fetch": "Obter", + "back": "Voltar", + "audioOnlyLabel": "Apenas áudio", + "video": "Vídeo", + "itemCount": { + "one": "{{count}} item", + "other": "{{count}} itens" + }, + "subfolderPlaceholder": "Subpasta (opcional) — deixe em branco para esta pasta", + "downloadAll": "Descarregar tudo", + "couldNotFetch": "Não foi possível obter", + "downloading": "A descarregar…", + "saving": "A guardar…", + "saved": "Guardado", + "retry": "Tentar novamente", + "download": "Descarregar", + "couldNotFetchInfo": "Não foi possível obter informação", + "timedOut": "Tempo esgotado", + "downloadFailed": "Falha ao descarregar", + "couldNotStart": "Não foi possível iniciar o descarregamento" } }