Skip to main content
The Subtitler is a React editor in 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

ConcernLocation
Page / loaderpages/subtitler/index.js (SubtitlerLoaderSubtitler)
Line editor, player, waveform, print previewmodules/components/subtitler/*
Statestores/use-subtitler-store.js (Zustand)
Data fetchingmodules/hooks/use-subtitler-queries.js (React Query)
Translation logicmodules/services/translation-service.js, subtitle-service.js, work-request-service.js
Provenance colorsconstants/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 a translationFrom 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:
custom  >  tm  >  mt  >  ov
  • Translation Memories load via loadTranslationMemories() into subtitles.tmTranslations, matched per line index on an exact match.
  • Machine Translations load via loadMachineTranslations() into subtitles.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:
translationFromLabelColor
customCustom#2F9E44 (green)
tmMemories#9D4EDD (violet)
mtMachine#F08C00 (orange)
ovOriginal(uncolored)
The palette is intentionally not themeable — it’s a semantic signal, chosen for separation in hue and lightness so it survives colorblind and grayscale viewing.

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

For multiTranslations (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 to for-review.
  • shouldApproveTranslation() — true when approval is required and the user is the owner/admin → the action is Approve (or Approve (with changes)), moving status to submitted.
Saving writes 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.