ui/3x for translating, timing, and positioning subtitles on a subtitled work request. This page covers how it’s put together. For the user-facing feature, see Subtitler.
Where it lives
| Concern | Location |
|---|---|
| Page / loader | pages/subtitler/index.js (SubtitlerLoader → Subtitler) |
| Line editor, player, waveform, print preview | modules/components/subtitler/* |
| State | stores/use-subtitler-store.js (Zustand) |
| Data fetching | modules/hooks/use-subtitler-queries.js (React Query) |
| Translation logic | modules/services/translation-service.js, subtitle-service.js, work-request-service.js |
| Provenance colors | constants/provenance.js |
When it opens
The loader admits editing only when the work request is subtitled (workRequest.translation or graphicsTranslation) and its status is submitted / incomplete (or awaiting-materials when the asset has a workflow). Once a vendor has started the order it’s read-only — except auto / autosubs orders, which stay editable.
Data model: lines and provenance
Each subtitle is a segment (line) carrying: a 1-indexed line number, the read-only OV text, the editable translation text, in/out timecodes, and atranslationFrom value — one of ov, custom, tm, or mt.
translationFrom is the single source of truth for the provenance color and for the “completed” counter (which counts lines where translationFrom !== 'ov'). SubtitleService.isCellTranslated() compares the stripped OV against the translation to decide whether an edit is custom.
Layering: memories + machine + custom
translation-service.autoTranslate() recomputes each line’s text and translationFrom from the available sources with a fixed precedence:
- Translation Memories load via
loadTranslationMemories()intosubtitles.tmTranslations, matched per line index on an exact match. - Machine Translations load via
loadMachineTranslations()intosubtitles.machineTranslations. - A hand-edited line (
custom) is never overwritten when a toggle re-runs — that’s what makes “preserve custom edits” hold while TM/MT layer underneath.
Provenance colors
constants/provenance.js defines the source colors, applied as a 3px left border on each line via .source-* classes in segment.css.js, plus a key in the header and a per-line badge while editing:
translationFrom | Label | Color |
|---|---|---|
custom | Custom | #2F9E44 (green) |
tm | Memories | #9D4EDD (violet) |
mt | Machine | #F08C00 (orange) |
ov | Original | (uncolored) |
Rendering and timing
The live preview is driven by the video element’s text tracks: subtitles are serialized to WebVTT (SubtitleService builds a base64 WEBVTT payload) and handed to the player. The player applies the selected aspect ratio (text-tracks.css) to reflow size and margins, and maps top/bottom alignment to the cue’s line position. Timing is frame-based (24fps default) with bounds checks that prevent a line from overlapping its neighbors.
Split, merge, reset
use-subtitler-store owns structural edits: Split divides a line, Merge Down combines it with the next, and Reset to OV reverts text and undoes a split/merge. Split/merge also re-align the per-index tmTranslations / machineTranslations arrays, which is why enabling those auto-fills after restructuring requires reloading them (the UI warns about this).
Formats
subtitle-service.js detects an imported file by extension (srt, sub, ass). Export goes through SRTService.export() (application/x-subrip) or ASSService.export() (application/x-ass), the latter taking the language’s translationFont and an isTiktok flag for vertical. WebVTT is used only for in-app preview, never as an export. See Subtitler → Subtitle formats.
Dual-language orders
FormultiTranslations (e.g. a combined PFR-FLE order), the TM request is split per language and the results joined with a <span></span> separator; the segment renders a field per language, and the header’s language picker scopes to the translator’s assigned languages.
Submit and approvals
work-request-service.js gates the submit path:
shouldSubmitTranslationForApproval()— true when the translation requires approval and the user is not the owner/admin → the action is Submit for Approval, moving status tofor-review.shouldApproveTranslation()— true when approval is required and the user is the owner/admin → the action is Approve (or Approve (with changes)), moving status tosubmitted.
translation.lines back to the API, clears the unsaved-changes flag, and — when the order came from the work-request queue — offers to continue with the queue or open the order. On submit, any lines still ov trigger a confirmation modal.
Modes
The editor adapts to the order: script, graphics-only, and script + graphics filters; a print mode (print-preview) for print assets; and a hidden waveform in print, graphics-only, and dual-language layouts.