> ## Documentation Index
> Fetch the complete documentation index at: https://help.pixwel.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Subtitler internals

> How the in-app Subtitler is built — its state, translation layering, provenance, live rendering, formats, and the submit/approval flow.

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](/features/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 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:

| `translationFrom` | Label    | Color              |
| ----------------- | -------- | ------------------ |
| `custom`          | Custom   | `#2F9E44` (green)  |
| `tm`              | Memories | `#9D4EDD` (violet) |
| `mt`              | Machine  | `#F08C00` (orange) |
| `ov`              | Original | (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](/features/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.
