Export
This area covers 35 features. Watch the walkthrough, then use the reference below — each feature links to the exact moment it appears (▶).
Who this is for: Any member (export-gated). Each feature below lists the role/permission it requires.
Features
Export dialog open/close ▶ 00:00
As a translator, I want to open and close an export dialog with a single button click, so that I can access export options on demand.
How it works. User clicks 'Export' button in the file toolbar or menu. Dialog appears with title 'Export' and format/scope selectors. Pressing Escape or clicking Cancel closes the dialog. If organization policy flips to disallow export while dialog is open, it auto-closes (FRO-253 b fix).
Key files
src/components/ExportDialog.tsx:1-45src/components/ExportDialog.tsx:172-200src/components/ExportDialog.tsx:372-375
Format selection (11 formats) ▶ 00:00
As a translator, I want to choose from multiple export formats to match my downstream tool requirements (USFM, DOCX, plain text, CSV, TSV, Markdown, XLIFF, TMX, WebVTT, audio-by-character, plain-text-dump).
How it works. Dialog shows a scrollable RadioGroup listing available formats. Each format option shows label, file extension (.ext), lossy/lossless status badge, and description. User selects one format via radio button. Pre-selection: USFM if file is USFM, DOCX if file is DOCX, otherwise TSV. Some formats are conditionally hidden: USFM only for USFM files; DOCX only for DOCX imports; plain-text-dump in Advanced section only.
Key files
src/components/ExportDialog.tsx:43-135src/components/ExportDialog.tsx:386-431src/components/ExportDialog.tsx:540-610
Export format: USFM (Universal Standard Format Markup) ▶ 00:05
As a Bible translator, I want to export my translation as USFM with translations injected back into the original markup, so that I can round-trip the file back to my source format.
How it works. User selects USFM format. When exporting file-scope: server-side route fetches raw USFM, injects client translations, returns the SFML file. When exporting project-scope: zips all exportable (USFM) files from the project. Headers are read for lossy-verse-count (FRO-276) to surface structural loss (footnotes, poetry, character markers). Files imported before round-trip support (no side-car) show 404 error; UI surfaces 're-import to enable export'. Project zip shows how many files were skipped and why.
Key files
src/components/ExportDialog.tsx:54-61src/components/ExportDialog.tsx:244-273src/lib/sync/source-export.ts:45-70src/lib/sync/source-export.ts:134-182
Export format: DOCX (Word document round-trip) ▶ 00:05
As a translator using Word documents, I want to export my translation back into the original Word file preserving paragraph/heading structure, so that I can maintain document formatting.
How it works. User selects DOCX format (only shown for files imported as .docx). Client fetches raw DOCX side-car bytes from server. Uses JSZip to open DOCX and DOMParser to parse word/document.xml. Paragraphs are matched positionally: first non-empty paragraph in DOCX → first unique group in cells. Translations injected per paragraph. Dominant run's character formatting (bold, italic, underline, font-size, font-family) is preserved. Mixed-format paragraphs lose per-run formatting—entire translated text gets first run's formatting (SWARM-TODO). Untranslated paragraphs left unchanged. Paragraph/heading style (w:pPr) preserved. Result zipped as .docx. File size > 512 KB at import has no side-car; falls back to 501 error.
Key files
src/components/ExportDialog.tsx:62-70src/components/ExportDialog.tsx:274-287src/lib/export/exporters/docx.ts:1-240
Export format: Plain text (.txt) ▶ 00:00
As a translator, I want to export translated segments as simple plain text, one line per segment, for use in text-only workflows.
How it works. User selects 'Plain text'. Client-side exporter filters cells to those with non-empty translated text, trims each, and joins with newlines. Empty/untranslated cells skipped. Result downloaded as .txt file. Marked lossy: paragraph structure, USFM markers, formatting all lost.
Key files
src/components/ExportDialog.tsx:72-77src/lib/export/exporters/plaintext.ts:1-12
Export format: Markdown (.md) ▶ 00:00
As a translator, I want to export translations as Markdown with canonical ref anchors for easy human reading and sharing.
How it works. User selects 'Markdown'. Client-side exporter iterates cells, skips empty translations, prepends each with HTML comment containing its group (canonical ref) if present, then appends the translated text with blank line separator. Result is .md file. Marked lossy: USFM heading levels, poetry, tables, footnotes not preserved—flat segment-per-paragraph output only.
Key files
src/components/ExportDialog.tsx:79-84src/lib/export/exporters/markdown.ts:1-18
Export format: Bilingual TSV (tab-separated values) ▶ 00:05
As a translator, I want to export source and target side-by-side in tab-separated format for CAT tool import or spreadsheet review.
How it works. User selects 'Bilingual TSV'. Client-side exporter generates RFC-4180-compliant output: header row 'id⇥source⇥target', then one row per cell with id (group or cell.id), original text, translated text. Fields with double-quotes, tabs, CR, or LF are wrapped in double-quotes with internal quotes doubled. Simple fields emitted bare. Result downloaded as .tsv. Marked lossy: inline markup (bold, italics, footnote markers) stripped; plain-text values only.
Key files
src/components/ExportDialog.tsx:86-91src/lib/export/exporters/tsv.ts:1-31
Export format: Bilingual CSV (comma-separated values) ▶ 00:00
As a translator, I want to export source and target in RFC 4180 CSV format for import into external translation tools.
How it works. User selects 'Bilingual CSV'. Client-side exporter generates RFC 4180-compliant output: header 'id,source,target', one row per cell. Fields quoted only if they contain comma, double-quote, or newline. Result downloaded as .csv. Marked lossy: inline markup stripped; plain-text values only.
Key files
src/components/ExportDialog.tsx:92-98src/lib/export/exporters/csv.ts:1-23
Export format: XLIFF 1.2 (XML Localization Interchange File Format) ▶ 00:00
As a translator using CAT tools, I want to export translations in XLIFF 1.2 format for standard industry-compatible exchange.
How it works. User selects 'XLIFF 1.2'. Client-side exporter generates XML with source/target language attributes, trans-unit per cell with id, source, target (state=final if validated, translated if non-empty, new if empty), and note. XML-escaped. Result downloaded as .xlf. Marked lossy: inline formatting (bold, italics, footnotes) not expressed as XLIFF <g>/<x> elements; plain-text values only.
Key files
src/components/ExportDialog.tsx:100-105src/lib/export/exporters/xliff.ts:1-43
Export format: TMX 1.4b (Translation Memory eXchange) ▶ 00:00
As a translator, I want to export translations as TMX to build or update my translation memory database.
How it works. User selects 'TMX 1.4b'. Client-side exporter generates XML with header (creationtool=Aquilla, datatype=PlainText, segtype=sentence, admin/srclang from props, creation timestamp). Body contains trans-units (tu) only for cells with both non-empty source and target. Each tu has tuid (group or cell.id) and two translation-unit variations (tuv) for source and target langs. XML-escaped. Result downloaded as .tmx. Marked lossy: inline formatting (bold, italics, footnotes) not expressed as TMX <ph>/<bpt>/<ept> elements; plain-text values only.
Key files
src/components/ExportDialog.tsx:106-112src/lib/export/exporters/tmx.ts:1-45
Export format: WebVTT (Web Video Text Tracks / subtitles) ▶ 00:00
As a translator working on video subtitles, I want to export translations as WebVTT with cast member voice tags, so that I can preserve speaker identity through the round-trip.
How it works. User selects 'WebVTT'. Client-side exporter generates WEBVTT header, then one cue per cell with startTime/endTime. Cells without timecodes skipped. Translated text (or original as fallback) is HTML-stripped. Cells explicitly assigned to a cast member get <v speaker>text</v> voice tag (matching speaker identity). Unassigned/default cells stay plain text. Result downloaded as .vtt. Marked lossy: timing/structure preserved but metadata lost.
Key files
src/components/ExportDialog.tsx:114-119src/lib/export/exporters/vtt.ts:1-29src/lib/export/vtt-voice.ts:1-20
Export format: Audio by character (.zip of WAV files) ▶ 00:00
As a voice director, I want to export audio clips grouped by cast member as individual WAV files, so that I can review each character's performance across the document.
How it works. User selects 'Audio by character'. Client-side orchestrator groups cells by cast voice (groupAudioByCharacter). For each character: fetches best-available audio (recording slot first, then generated-voice slot), decodes to mono PCM at 48kHz, concatenates all clips back-to-back in document order (no silence, no timeline). Each character's concatenated PCM is encoded as PCM16 WAV. Files disambiguated by character name + language code (e.g., 'moses_fr.wav', 'moses_fr_2.wav' for duplicates). All WAVs zipped (DEFLATE). Progress callback fires per clip decoded. Skipped clips are reported (e.g., missing audio, decode failures). Result downloaded as <safe-name>_audio-by-character.zip. Marked non-lossy (timings are full-length, concatenation preserves all audio). Inline preview shows each character's clip count and total duration. File-scope only.
Key files
src/components/ExportDialog.tsx:120-126src/components/ExportDialog.tsx:288-309src/components/ExportDialog.tsx:493-523src/lib/export/audio-by-character.ts:1-165
Export format: Plain-text dump (.txt advanced option) ▶ 00:00
As a translator, I want a quick 'content extraction only' export format that dumps every translated segment one per line with optional ref prefixes, for rapid content review.
How it works. User opens 'Advanced' section at bottom of dialog. Selects 'Plain-text dump' radio. Optional checkbox 'Prefix each line with canonical ref' toggles ref inclusion. Client-side exporter: if title option is provided, prepends it as first line followed by blank line. Filters to non-empty translated cells. For each: if includeRefs, emits 'ref⇥text'; else emits text only. Result downloaded as .txt. Marked lossy with detailed honesty bar: loses footnotes, cross-references, poetry layout, headings, paragraph markers, bold/italic, back-translations, validation state, untranslated segments, format-specific metadata. Not suitable for re-import.
Key files
src/components/ExportDialog.tsx:127-135src/components/ExportDialog.tsx:540-610src/lib/export/exporters/plain-text-dump.ts:1-48
Export scope: File vs Project ▶ 00:00
As a translator, I want to export either the current file or all files in the project at once, so that I can choose the granularity that fits my workflow.
How it works. Dialog shows RadioGroup (aria-label='Export scope') with two options: 'Current file' (default) and 'Whole project'. User toggles via visible label (radio is sr-only). Some formats (audio-by-character, vtt, docx, plain-text-dump) only support file scope; project option is disabled (opacity 40%, non-clickable, aria-disabled). When project-scope is selected and format is client-side (not usfm/docx/vtt/audio/dump), useProjectCells hook fans out over all project files (up to 40 max) and loads their cells. Large projects (>40 files) show warning: 'This project has more than 40 files — zip will include the first 40 only'. Progress shown while cells loading (skeleton loaders). Export button disabled until cells done loading.
Key files
src/components/ExportDialog.tsx:205-215src/components/ExportDialog.tsx:230-238src/components/ExportDialog.tsx:434-491src/components/ExportDialog.tsx:310-327
Lossy format warning banner ▶ 00:00
As a translator, I want to be warned when a format is lossy, so that I understand what structural information will be lost in export.
How it works. Dialog shows a note (role='note', aria-label='Lossy format warning') when selected format has lossy:true. Warning reads: 'This format is lossy — inline markup, paragraph structure, and some metadata will not round-trip back to the original format.' Warning disappears when user selects a non-lossy format (USFM, DOCX, audio-by-character).
Key files
src/components/ExportDialog.tsx:225-226src/components/ExportDialog.tsx:525-538
USFM lossy-verse warning (FRO-276) ▶ 00:00
As a Bible translator, I want to see exactly how many verses lost structural markup (footnotes, poetry, character markers) during USFM export, so that I can decide whether to re-export or accept the loss.
How it works. After USFM export completes, server returns X-Usfm-Lossy-Verse-Count header. If count > 0, status shows ok-lossy state with message and additional warning: '{N} verse(s) contained footnotes, poetry, or character markers in the source USFM — their structure is replaced by plain translated text.' If count = 0 or header absent, shows simple ok message.
Key files
src/components/ExportDialog.tsx:261-272src/components/ExportDialog.tsx:637-644src/lib/sync/source-export.ts:37-43src/lib/sync/source-export.ts:63-65
Project-scope zip file aggregation (client-side) ▶ 00:00
As a translator, I want to export all files in my project as a single zip with one file per format, so that I can download everything in one click.
How it works. User selects project scope and client-side format (txt/md/tsv/csv/xlf/tmx). useProjectCells fans out to load all files' cells. Once loaded, buildProjectZip runs each file's cells through the format exporter. Zip entries named '<fileName without ext>.<formatExt>' (e.g., 'gen.tsv', 'exo.tsv'). Entries added even if cells empty (exporters handle gracefully). Zip compressed with DEFLATE. Filename: '<safe-project-name>.<format-ext>.zip' (special chars stripped to dashes). Progress shows 'Building zip for N files…'. On success: 'Downloaded N files' + truncation note if >40 files.
Key files
src/components/ExportDialog.tsx:310-327src/lib/export/project-zip-export.ts:1-92
Lossy-verse count display formatting ▶ 00:00
As a Bible translator, I want the lossy-verse count message to be grammatically correct whether it's 1 verse or many.
How it works. USFM export status displays pluralized: '1 verse contained…' vs '{N} verses contained…'
Key files
src/components/ExportDialog.tsx:640-642
Export progress feedback ▶ 00:00
As a translator, I want to see real-time progress during long exports, so that I know the operation is not stuck.
How it works. During export, status shows 'Exporting…' then format-specific messages: 'Downloading X/Y…' (USFM project), 'Fetching original document…' (DOCX), 'Injecting translations…' (DOCX), 'Decoding audio…' (audio-by-character), 'Decoding X/Y…' (audio clips), 'Building zip for N files…' (project-scope). Export button shows spinner and is disabled while busy. Cancel button disabled during export.
Key files
src/components/ExportDialog.tsx:240-370src/components/ExportDialog.tsx:626-664
Export error handling and user feedback ▶ 00:00
As a translator, I want clear error messages when export fails, so that I understand what went wrong.
How it works. If export throws an error, status shows 'error' state with error message (from exception or generic 'Export failed.'). Message displayed in red warning box with AlertTriangle icon. Export button re-enabled so user can retry. Specific errors: SourceExportError with status 404 means 're-import to enable export' (FRO-331 UI surfaces this).
Key files
src/components/ExportDialog.tsx:367-370src/components/ExportDialog.tsx:612-646src/lib/sync/source-export.ts:27-35
Export button toolbar entry point (FRO-331) ▶ 00:00
As a translator, I want to access the export dialog from the file toolbar, so that I can quickly export the current file.
How it works. Header overflow menu has 'Export' button that opens ExportDialog. Menu only shown for exportable file types (USFM). Button disabled if org policy disallows export (FRO-253).
Key files
src/components/ExpandableFileList.tsx:EXPORTABLE_FILE_TYPESsrc/components/ProjectWorkspace.tsx
Organization policy export gating (FRO-253) ▶ 00:00
As an organization administrator, I want to control whether members can export files, so that I can enforce IP protection or compliance policies.
How it works. canExportByOrgPolicy prop (defaults to true) controls whether export button is shown and whether export dialog can stay open. If false: export button hidden from menu, export dialog auto-closes when this prop flips false after opening (FRO-253 b fix).
Key files
src/components/ExportDialog.tsx:146-198src/components/ExpandableFileList.tsx:canExportByOrgPolicysrc/components/ProjectWorkspace.tsx
USFM file-side export (per-file server route) ▶ 00:00
As a Bible translator, I want to export a single USFM file with translations injected, so that I can quickly get a round-trip file.
How it works. User selects USFM format, file scope. Export triggers downloadSourceFile which: fetches GET /api/v1/projects/:id/files/:fileId/source (requires sync token). Server returns USFM with translations substituted. Client receives raw USFM bytes, reads X-Usfm-Lossy-Verse-Count header, triggers browser download with .SFM extension. If 404: SourceExportError surfaces 'file imported before round-trip support — re-import to enable'.
Key files
src/components/ExportDialog.tsx:258-272src/lib/sync/source-export.ts:45-70
USFM project-scope export (all files zipped server-side) ▶ 00:00
As a Bible translator, I want to export all USFM files in my project as a single zip with translations, so that I can archive or share the complete translation.
How it works. User selects USFM format, project scope. Export triggers downloadProjectZip which: iterates project files, filters to EXPORTABLE (usfm only), fans out sequential per-file fetch of GET /api/v1/projects/:id/files/:fileId/source. Each file's response is added to a JSZip with name matching original filename (or +.SFM if no ext). Files with 404 are skipped and reported with reason. On completion: 'Downloaded N files' message + truncation note if >40 files skipped or error note. No files exported = error thrown. Result zipped with DEFLATE and downloaded.
Key files
src/components/ExportDialog.tsx:245-257src/lib/sync/source-export.ts:110-182
DOCX file-side export (fetch + client-side injection) ▶ 00:00
As a translator working with Word documents, I want to export my translation back into the original Word file with structure preserved, so that I can use the translated document downstream.
How it works. User selects DOCX format, file scope. Export triggers fetchSourceSidecar to GET /api/v1/projects/:id/files/:fileId/source (with X-Export-Mode: raw-sidecar header implicitly expected). Server returns raw DOCX bytes as ArrayBuffer. Client uses JSZip.loadAsync to parse DOCX, extracts word/document.xml. DOMParser parses XML. Paragraphs matched positionally to groups. Translations injected via injectTranslationIntoParagraph: dominant run's w:rPr (formatting) cloned and applied to single replacement run. Result XMLSerializer'd back and zipped. Downloaded as <filename>.docx. Message shows injection count ('N paragraph(s) translated') or '(no translations to inject — download original structure)' if none.
Key files
src/components/ExportDialog.tsx:274-287src/lib/export/exporters/docx.ts:1-240
Audio decode and WAV encode for audio-by-character ▶ 00:00
As a voice director, I want audio clips decoded to a consistent format and concatenated losslessly, so that I can review the final audio output.
How it works. Audio-by-character export: production wires decodeToMono48k (Web Audio decode) and fetchCellAudio (R2 fetch). Each clip: parsed via parseFrontierAudioUrl, fetched from R2, decoded to mono PCM at 48kHz via AudioContext + offline render, accumulated in array. After all clips for a character: concatPcm joins them back-to-back. encodeWavPcm16 encodes to WAV PCM16 (16-bit signed). Each character's WAV added to zip.
Key files
src/components/ExportDialog.tsx:288-309src/lib/export/audio-by-character.ts:35-102src/lib/export/audio-by-character.ts:125-164
Audio-by-character preview inline in dialog ▶ 00:00
As a voice director, I want to see a preview of which characters will be exported and how many clips each has, so that I can verify the grouping before export.
How it works. When audio-by-character format selected: dialog shows Preview section (below lossy warning) with each character's name, color dot (from voice.color), clip count, and total duration (MM:SS format, null if any clip missing durationMs). If no clips found: 'No cells with audio found in this file.'
Key files
src/components/ExportDialog.tsx:493-523
File-only format scope restriction UI ▶ 00:00
As a translator, I want clear indication of which export formats don't support project scope, so that I don't accidentally try an unsupported operation.
How it works. When user selects audio-by-character, vtt, docx, or plain-text-dump: Whole project radio becomes disabled (opacity 40%, non-clickable, cursor-not-allowed). Scope auto-reverts to 'Current file'. Below scope selector: footnote says 'Project scope not supported for this format.'
Key files
src/components/ExportDialog.tsx:205-215src/components/ExportDialog.tsx:445-474
Plain-text-dump ref inclusion toggle ▶ 00:00
As a translator, I want to optionally include canonical refs (e.g., 'GEN 1:1') on each line of plain-text-dump export, so that I can quickly locate verses in the document.
How it works. When plain-text-dump format selected: checkbox appears 'Prefix each line with canonical ref (e.g. GEN 1:1)'. Unchecked by default. When checked, exportPlainTextDump receives includeRefs: true and outputs 'ref⇥text' per line instead of just 'text'.
Key files
src/components/ExportDialog.tsx:595-605src/lib/export/exporters/plain-text-dump.ts:29-47
Download blob utility (posthog analytics capture) ▶ 00:00
As a product analyst, I want to capture which export formats are actually downloaded, so that I can understand usage patterns.
How it works. Every export (client-side blob) calls downloadBlob which: captures 'file exported' event to PostHog with {filename, file_type: extension}. Creates object URL, appends anchor, triggers click, cleans up URL.
Key files
src/components/ExportDialog.tsx:26src/lib/export/export-service.ts:27-38
Bilingual format language tag prop passing ▶ 00:00
As a translator, I want source and target languages correctly labeled in bilingual export formats, so that CAT tools and translation memories know which language is which.
How it works. ExportDialog receives sourceLanguage and targetLanguage props (default 'und'). When exporting bilingual formats (XLIFF, TMX), these are passed to exportXliff/exportTmx. XLIFF output includes source-language and target-language attributes on <file> element. TMX output includes srclang in header and xml:lang on each <tuv>.
Key files
src/components/ExportDialog.tsx:164-165src/components/ExportDialog.tsx:320-321src/components/ExportDialog.tsx:347-350src/lib/export/exporters/xliff.ts:16-42src/lib/export/exporters/tmx.ts:15-44
Terminology export (CSV and TBX) ▶ 00:00
As a terminology manager, I want to export managed concepts as CSV or TBX, so that I can share terminology or import into external term bases.
How it works. Terminology page has 'Export CSV' and 'Export TBX' buttons (role buttons with aria-label). Buttons disabled until at least one concept is added. Clicking either downloads a file (URL.createObjectURL + anchor.click pattern). CSV and TBX formats are separate from main ExportDialog; they're a different feature area.
Key files
e2e/specs/projects/terminology-export.smoke.spec.ts
Default format pre-selection logic ▶ 00:00
As a translator, I want sensible defaults when opening the export dialog, so that I don't have to reconfigure for typical exports.
How it works. Format pre-selection on dialog open: USFM if isUsfmFile, DOCX if isDocxFile, otherwise TSV (Bilingual TSV is the safe default for most formats). Scope pre-selection: always 'Current file'. Advanced section collapsed by default.
Key files
src/components/ExportDialog.tsx:200-202
Status message polishing (plurals, articles) ▶ 00:00
As a translator, I want grammatically correct status messages, so that the UI feels professional.
How it works. Success messages use correct singular/plural: 'Downloaded {baseName}.{ext}' (file), 'Downloaded N files' (project zip), 'Downloaded {baseName}.docx (N paragraph(s) translated)', 'Exported audio by character(N clip(s) skipped)', 'Exported {N} files' (USFM project).
Key files
src/components/ExportDialog.tsx:254-257src/components/ExportDialog.tsx:282-287src/components/ExportDialog.tsx:308-309src/components/ExportDialog.tsx:327src/components/ExportDialog.tsx:365
Safe filename sanitization for zip entries ▶ 00:00
As a translator, I want export zip files and entries to have valid filesystem names, so that they download and extract reliably.
How it works. Project zip filename: projectName.replace(/[^\w.-]+/g, '-').replace(/^-+|-+$/g, '') (special chars → dashes, leading/trailing dashes removed). Per-file entry: fileName.replace(/\.[^.]+$/, '') + format-ext. Audio-by-character character names: characterKey = name.replace(/[^\w.-]+/g, '_').replace(/^_+|_+$/g, '') || 'unnamed'.
Key files
src/components/ExportDialog.tsx:323src/lib/export/project-zip-export.ts:84src/lib/export/audio-by-character.ts:106-109