diff --git a/ui/scripts/check-i18n-catalogs.mjs b/ui/scripts/check-i18n-catalogs.mjs index 5898f7e1..914e9a2e 100644 --- a/ui/scripts/check-i18n-catalogs.mjs +++ b/ui/scripts/check-i18n-catalogs.mjs @@ -4,7 +4,7 @@ import { dirname, join } from 'node:path' import { fileURLToPath } from 'node:url' const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..') -const NAMESPACES = ['common', 'navigation', 'settings', 'wizard', 'activity'] +const NAMESPACES = ['common', 'navigation', 'settings', 'wizard', 'activity', 'extraInfo'] const LANGUAGES = ['en', 'es'] function load(language, namespace) { @@ -52,6 +52,8 @@ const FORBIDDEN = [ 'Pregunta al mago', 'Carpetas de salida', 'Extra info', + 'Clip information', + 'Wait for generation to finish', ] export function forbiddenLiterals() { diff --git a/ui/src/components/MainContent/VideoExtraInfoDialog.tsx b/ui/src/components/MainContent/VideoExtraInfoDialog.tsx index b5b78d68..c93d186d 100644 --- a/ui/src/components/MainContent/VideoExtraInfoDialog.tsx +++ b/ui/src/components/MainContent/VideoExtraInfoDialog.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react' import { createPortal } from 'react-dom' +import type { TFunction } from 'i18next' import { BadgeInfo, CalendarDays, Check, Clock3, Copy, FileVideo2, Languages, Loader2, MessageSquareText, RefreshCw, SlidersHorizontal, Sparkles, X, Youtube } from 'lucide-react' import { fetchVideoExtraInfo, generateVideoExtraInfo } from '../../api/client' import { useUiTranslation } from '../../i18n' @@ -50,6 +51,7 @@ async function copyText(value: string) { } function CopyButton({ value, label }: { value: string; label: string }) { + const { t } = useUiTranslation('extraInfo') const [copied, setCopied] = useState(false) const copy = async () => { await copyText(value) @@ -61,10 +63,10 @@ function CopyButton({ value, label }: { value: string; label: string }) { type="button" onClick={copy} className="inline-flex items-center gap-1 rounded-md px-2 py-1 text-[10px] text-text-muted hover:bg-bg-hover hover:text-text-primary transition-colors" - title={`Copy ${label}`} + title={t('copy.title', { label })} > {copied ? : } - {copied ? 'Copied' : 'Copy'} + {copied ? t('copy.copied') : t('copy.action')} ) } @@ -86,32 +88,34 @@ function formatDate(timestamp: number | null) { return new Date(timestamp * 1000).toLocaleString() } -function clipInfoAsText(clip: VideoClipInfo) { +function clipInfoAsText(clip: VideoClipInfo, t: TFunction<'extraInfo'>) { const breakdown = formatGenerationBreakdown(clip.generation_timings) const lines = [ - 'CLIP INFORMATION', - `File: ${clip.name}`, - clip.created_at ? `Generated: ${formatDate(clip.created_at)}` : '', - clip.file_size_bytes ? `File size: ${formatBytes(clip.file_size_bytes)}` : '', - clip.generation_time_sec != null ? `Generation time: ${formatGenerationDuration(clip.generation_time_sec)}` : '', - breakdown ? `Timing breakdown: ${breakdown}` : '', - clip.model_type ? `Model: ${clip.model_type}` : '', - clip.resolution ? `Resolution: ${clip.resolution}` : '', - clip.video_length_frames != null ? `Frames: ${clip.video_length_frames}` : '', - clip.num_inference_steps != null ? `Inference steps: ${clip.num_inference_steps}` : '', - clip.guidance_scale != null ? `Guidance: ${clip.guidance_scale}` : '', - clip.seed != null ? `Seed: ${clip.seed}` : '', - clip.job_id ? `Job ID: ${clip.job_id}` : '', - clip.prompt ? `\nPROMPT\n${clip.prompt}` : '', - clip.audio_prompt ? `\nAUDIO PROMPT\n${clip.audio_prompt}` : '', - clip.negative_prompt ? `\nNEGATIVE PROMPT\n${clip.negative_prompt}` : '', - `\nALL SAVED METADATA\n${JSON.stringify(clip.saved_metadata, null, 2)}`, + t('clip.title').toUpperCase(), + `${t('clip.file')}: ${clip.name}`, + clip.created_at ? `${t('clip.generated')}: ${formatDate(clip.created_at)}` : '', + clip.file_size_bytes ? `${t('clip.fileSize')}: ${formatBytes(clip.file_size_bytes)}` : '', + clip.generation_time_sec != null ? `${t('clip.generationTime')}: ${formatGenerationDuration(clip.generation_time_sec)}` : '', + breakdown ? `${t('clip.timingBreakdown')}: ${breakdown}` : '', + clip.model_type ? `${t('clip.model')}: ${clip.model_type}` : '', + clip.resolution ? `${t('clip.resolution')}: ${clip.resolution}` : '', + clip.video_length_frames != null ? `${t('clip.frames')}: ${clip.video_length_frames}` : '', + clip.num_inference_steps != null ? `${t('clip.inferenceSteps')}: ${clip.num_inference_steps}` : '', + clip.guidance_scale != null ? `${t('clip.guidance')}: ${clip.guidance_scale}` : '', + clip.seed != null ? `${t('clip.seed')}: ${clip.seed}` : '', + clip.job_id ? `${t('clip.jobId')}: ${clip.job_id}` : '', + clip.prompt ? `\n${t('clip.prompt').toUpperCase()}\n${clip.prompt}` : '', + clip.audio_prompt ? `\n${t('clip.audioPrompt').toUpperCase()}\n${clip.audio_prompt}` : '', + clip.negative_prompt ? `\n${t('clip.negativePrompt').toUpperCase()}\n${clip.negative_prompt}` : '', + `\n${t('clip.allSavedMetadata').toUpperCase()}\n${JSON.stringify(clip.saved_metadata, null, 2)}`, ] return lines.filter(Boolean).join('\n') } export function VideoExtraInfoDialog({ name, onClose }: { name: string; onClose: () => void }) { - const { t } = useUiTranslation('activity') + const { t: tActivity } = useUiTranslation('activity') + const { t } = useUiTranslation('extraInfo') + const { t: tCommon } = useUiTranslation('common') const [language, setLanguage] = useState(initialLanguage) const [status, setStatus] = useState(null) const [data, setData] = useState(null) @@ -144,13 +148,13 @@ export function VideoExtraInfoDialog({ name, onClose }: { name: string; onClose: setData(result.data) }) .catch(reason => { - if (active) setError(reason instanceof Error ? reason.message : 'Failed to load extra info') + if (active) setError(reason instanceof Error ? reason.message : t('errors.loadFailed')) }) .finally(() => { if (active) setLoading(false) }) return () => { active = false } - }, [language, name]) + }, [language, name, t]) const generate = async () => { if (generating) return @@ -161,16 +165,16 @@ export function VideoExtraInfoDialog({ name, onClose }: { name: string; onClose: setData(result.data) setStatus(previous => previous ? { ...previous, available: true, data: result.data } : previous) } catch (reason) { - setError(reason instanceof Error ? reason.message : 'Failed to generate extra info') + setError(reason instanceof Error ? reason.message : t('errors.generateFailed')) } finally { setGenerating(false) } } const clip = status?.clip || null - const clipCopy = clip ? clipInfoAsText(clip) : '' + const clipCopy = clip ? clipInfoAsText(clip, t) : '' const publishingCopy = data - ? `PUBLISHING COPY\n${data.overview}\n\nYouTube\n${data.youtube.title}\n\n${data.youtube.description}\n\nX.com\n${data.x.post}` + ? `${t('publishing.heading').toUpperCase()}\n${data.overview}\n\n${t('publishing.youtube')}\n${data.youtube.title}\n\n${data.youtube.description}\n\n${t('publishing.xCom')}\n${data.x.post}` : '' const allCopy = [clipCopy, publishingCopy].filter(Boolean).join('\n\n') const generationBreakdown = clip ? formatGenerationBreakdown(clip.generation_timings) : '' @@ -194,7 +198,7 @@ export function VideoExtraInfoDialog({ name, onClose }: { name: string; onClose:
-

{t('extraInfo')}

+

{tActivity('extraInfo')}

{name}

@@ -210,7 +214,7 @@ export function VideoExtraInfoDialog({ name, onClose }: { name: string; onClose:
- +