i18n: chrome visible del Video Editor - #105
Conversation
PR Review — Loreframe StudioRisk: low Automated review from Findings
Changed files
CONTRIBUTING checklist
Posted by the repo PR review workflow. Re-runs on each push to the PR. |
Code health
Markdown, JSON catalogs and tests are out of this table. Only Most complex functions
Trend vs baseline
Warnings
Ratchet passed. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Language switch reprocesses editor handoff
- processPendingHandoff now reads translations from a tRef so a language change no longer recreates the callback or re-run the localStorage hand-off effect.
- ✅ Fixed: Language switch resets export status
- pollExport and the reconnect effect no longer depend on t, so switching language does not cancel polling or reset export status.
Or push these changes by commenting:
@cursor push 3740d1c4dd
Preview (3740d1c4dd)
diff --git a/ui/src/features/video-editor/VideoEditorPanel.tsx b/ui/src/features/video-editor/VideoEditorPanel.tsx
--- a/ui/src/features/video-editor/VideoEditorPanel.tsx
+++ b/ui/src/features/video-editor/VideoEditorPanel.tsx
@@ -670,6 +670,8 @@
export function VideoEditorPanel() {
const { t } = useUiTranslation('videoEditor')
const { t: tCommon } = useUiTranslation('common')
+ const tRef = useRef(t)
+ tRef.current = t
const refreshOutputs = useStore(s => s.refreshOutputs)
const activeWorkspace = useStore(s => s.activeWorkspace)
const [draft] = useState(() => loadEditorDraft(activeWorkspace))
@@ -876,7 +878,7 @@
}]
: [])
if (!pendingSources.length) {
- setError(t('errors.handoffEmpty'))
+ setError(tRef.current('errors.handoffEmpty'))
return
}
@@ -894,20 +896,20 @@
for (let index = 0; index < pendingSources.length; index++) {
const item = pendingSources[index]
setAddProgress(kind === 'sequence'
- ? t('status.openingSeriesShot', { current: index + 1, total: pendingSources.length })
- : t('status.openingNamed', { name: item.name || 'comic animatic' }))
+ ? tRef.current('status.openingSeriesShot', { current: index + 1, total: pendingSources.length })
+ : tRef.current('status.openingNamed', { name: item.name || 'comic animatic' }))
nextClips.push(await createClipFromSource(item.url, item.url, item.name || `Series shot ${index + 1}`))
}
if (clips.length && !window.confirm(
kind === 'sequence'
- ? t('confirm.replaceMontage', { count: clips.length })
- : t('confirm.addHandoff', { count: clips.length }),
+ ? tRef.current('confirm.replaceMontage', { count: clips.length })
+ : tRef.current('confirm.addHandoff', { count: clips.length }),
)) return
const committedClips = kind === 'sequence' ? nextClips : [...clips, ...nextClips]
if (!persistEditorDraft(committedClips, nextProjectName, nextResolution, fps, draftWorkspaceRef.current)) {
- throw new Error(t('errors.draftSave'))
+ throw new Error(tRef.current('errors.draftSave'))
}
setClips(committedClips)
setSelectedId((kind === 'sequence' ? committedClips[0] : nextClips[0])?.id || null)
@@ -919,13 +921,13 @@
setPendingHandoff(null)
setError(null)
} catch (reason) {
- setError(t('errors.handoffOpen', { message: (reason as Error).message }))
+ setError(tRef.current('errors.handoffOpen', { message: (reason as Error).message }))
} finally {
handoffProcessingRef.current = false
setAdding(false)
setAddProgress('')
}
- }, [clips, createClipFromSource, fps, projectName, resolution, t])
+ }, [clips, createClipFromSource, fps, projectName, resolution])
useEffect(() => {
let pending: PendingEditorSource | null = null
@@ -1864,24 +1866,24 @@
}
} catch (reason) {
if (mountedRef.current && epoch === exportPollEpochRef.current) {
- setError(t('errors.reconnectExport', { jobId, message: (reason as Error).message }))
+ setError(tRef.current('errors.reconnectExport', { jobId, message: (reason as Error).message }))
}
} finally {
if (exportPollingRef.current === jobId) exportPollingRef.current = null
}
- }, [activeWorkspace, refreshOutputs, t])
+ }, [activeWorkspace, refreshOutputs])
useEffect(() => {
exportPollEpochRef.current += 1
exportPollingRef.current = null
const jobId = readVideoEditorExportId(activeWorkspace)
- setExportJob(jobId ? pendingVideoEditorExport(jobId, t('status.reconnecting')) : null)
+ setExportJob(jobId ? pendingVideoEditorExport(jobId, tRef.current('status.reconnecting')) : null)
if (jobId) void pollExport(jobId)
return () => {
exportPollEpochRef.current += 1
exportPollingRef.current = null
}
- }, [activeWorkspace, pollExport, t])
+ }, [activeWorkspace, pollExport])
const startExport = async () => {
if (!clips.length || exportSubmittingRef.current || isVideoEditorJobActive(exportJob)) returnYou can send follow-ups to the cloud agent here.
…ches A language change used to recreate t-dependent callbacks, reprocessing a stored hand-off and restarting export polling.
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 0456d53. Configure here.

Zona i18n
Boy-scout del chrome visible de Video Editor (
ui/src/features/video-editor/VideoEditorPanel.tsx). Namespace nuevovideoEditor(EN+ES, 89 claves, misma forma). El panel usauseUiTranslation('videoEditor'); no se llama auseTranslationde react-i18next.Entra al catálogo:
Video Editor se queda como nombre de producto (igual que Story Lab). Workspace no se traduce; Output folder sigue el glosario.
Qué se deja hardcoded a propósito
comic animatic/Series shot Ncomo fallback de nombreeditorClipRecoveryMessagey mensajes crudos del backendEl contrato Python de reemplazo ahora apunta a
t('inspector.redoInVideoCreation')y al catálogo ES.Cómo probar
En la app: Video Editor → cambiar idioma en Ajustes. Toolbar/empty/picker/timeline deben pasar a ES; nombres de clip y transiciones no.
No mezclar.
Note
Low Risk
UI copy and test updates only; no API, auth, or export pipeline logic changes beyond localized user-visible strings.
Overview
Moves Video Editor visible chrome from hardcoded strings into the
videoEditori18n namespace (EN+ES), wiringVideoEditorPanelthroughuseUiTranslation('videoEditor')and sharedcommonfor Cancel.Toolbar, empty states, HocusPocus picker, playback controls, export preview labels, inspector output settings, timeline chrome, and user-facing status/errors (import/export, hand-off, replace) now use catalog keys with interpolation and plurals. Hand-off and export reconnect paths use a
tRefso async callbacks stay on the active language.Docs and
check-i18n-catalogs.mjsexpand thevideoEditornamespace description, add glossary Video Editor, and forbid returning migrated literals in the panel. Tests cover catalog parity, panel usage, and language switching in export/handoff flows plus a newvideoEditorI18ntest.Intentionally not migrated in this PR (per scope): trim bar Spanish text, transition inspector labels (e.g. “Preview this transition”), mute/fit/duplicate/EEarlier/Later, clip names, and raw backend messages.
Reviewed by Cursor Bugbot for commit 0456d53. Configure here.