Add the translation layer to arr-subs: a trait every backend implements, and
the chunking and validation that sit above all of them. Backends themselves are
separate issues (#191, #192, #193).
The unit sent to a backend is a batch of cues, not a whole file. A movie
subtitle is 40-80k characters and a season several hundred thousand; one
request per file is fragile and one per cue loses the context that makes
dialogue translate well.
Timing data never leaves arr. Only text goes out, and the translated text is
reassembled onto the original timings.
A reply whose cue count or numbering does not match the batch is rejected.
A backend that silently merges or drops lines must fail loudly rather than
produce a subtitle whose lines have drifted out of sync with the picture.
Source and target language are explicit. pt-PT and pt-BR are distinct
targets; a backend that cannot express the difference says so rather than
quietly returning the wrong one.
Every backend sits behind its own cargo feature. Which backend runs is a
database setting (#198), so switching between compiled-in backends needs no
rebuild.
Non-goals: any specific backend, any decision about when to translate — that
is the reconcile loop's (#196).
Add the translation layer to `arr-subs`: a trait every backend implements, and
the chunking and validation that sit above all of them. Backends themselves are
separate issues (#191, #192, #193).
From DESIGN.md §15 (added by #183):
- The unit sent to a backend is a batch of cues, not a whole file. A movie
subtitle is 40-80k characters and a season several hundred thousand; one
request per file is fragile and one per cue loses the context that makes
dialogue translate well.
- Timing data never leaves arr. Only text goes out, and the translated text is
reassembled onto the original timings.
- A reply whose cue count or numbering does not match the batch is rejected.
A backend that silently merges or drops lines must fail loudly rather than
produce a subtitle whose lines have drifted out of sync with the picture.
- Source and target language are explicit. pt-PT and pt-BR are distinct
targets; a backend that cannot express the difference says so rather than
quietly returning the wrong one.
- Every backend sits behind its own cargo feature. Which backend runs is a
database setting (#198), so switching between compiled-in backends needs no
rebuild.
Non-goals: any specific backend, any decision about *when* to translate — that
is the reconcile loop's (#196).
Depends on: #184
naps62-yolo
added this to the Subtitles milestone 2026-08-24 21:28:32 +01:00
Implemented the translation layer in arr-subs on branch subtitles/190-translate. A new translate module carries the object-safe Backend trait (boxed futures, same shape as Provider), an id type for the database setting, and the shared machinery no backend can skip: chunking cues into character-budgeted batches (default 4096 chars, overridable per backend via batch_budget()), a supports(target) check so a backend that cannot express pt-PT vs pt-BR refuses before anything is sent, validation that rejects any reply whose cue count or numbering does not match the batch, and reassembly of translated text onto the original timings — timings never leave arr, and any failed batch fails the whole call so a partially translated subtitle never exists. A companion srt module provides the Cue type with tolerant SRT parsing and strict rendering, since reassembly onto original timings needs cues in structured form and sidecars are SRT per §15. Backend errors get their own translate::Error (transport / unauthorized / rate-limited / unsupported target / malformed / cue mismatch) with the same is_transient() split as the provider error.
just ci passed (511 tests).
Findings
No issue owns decoding downloaded subtitle bytes to UTF-8. Fetched.content is deliberately raw bytes (providers serve Latin-1/Windows-1252), and srt::parse deliberately takes &str; something between #187/#188 and #194/#195/#196 has to do the encoding detection, and no issue in the milestone mentions it.
Nothing converts non-SRT downloads (SubtitleFormat::Ass/Vtt) to SRT before syncing/writing. §15 says sidecars are SRT and model.rs notes converting "is not this crate's decision to make silently", but no issue decides it either.
Implemented the translation layer in `arr-subs` on branch `subtitles/190-translate`. A new `translate` module carries the object-safe `Backend` trait (boxed futures, same shape as `Provider`), an id type for the database setting, and the shared machinery no backend can skip: chunking cues into character-budgeted batches (default 4096 chars, overridable per backend via `batch_budget()`), a `supports(target)` check so a backend that cannot express pt-PT vs pt-BR refuses before anything is sent, validation that rejects any reply whose cue count or numbering does not match the batch, and reassembly of translated text onto the original timings — timings never leave arr, and any failed batch fails the whole call so a partially translated subtitle never exists. A companion `srt` module provides the `Cue` type with tolerant SRT parsing and strict rendering, since reassembly onto original timings needs cues in structured form and sidecars are SRT per §15. Backend errors get their own `translate::Error` (transport / unauthorized / rate-limited / unsupported target / malformed / cue mismatch) with the same `is_transient()` split as the provider error.
`just ci` passed (511 tests).
## Findings
- No issue owns decoding downloaded subtitle bytes to UTF-8. `Fetched.content` is deliberately raw bytes (providers serve Latin-1/Windows-1252), and `srt::parse` deliberately takes `&str`; something between #187/#188 and #194/#195/#196 has to do the encoding detection, and no issue in the milestone mentions it.
- Nothing converts non-SRT downloads (`SubtitleFormat::Ass`/`Vtt`) to SRT before syncing/writing. §15 says sidecars are SRT and `model.rs` notes converting "is not this crate's decision to make silently", but no issue decides it either.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Add the translation layer to
arr-subs: a trait every backend implements, andthe chunking and validation that sit above all of them. Backends themselves are
separate issues (#191, #192, #193).
From DESIGN.md §15 (added by #183):
subtitle is 40-80k characters and a season several hundred thousand; one
request per file is fragile and one per cue loses the context that makes
dialogue translate well.
reassembled onto the original timings.
A backend that silently merges or drops lines must fail loudly rather than
produce a subtitle whose lines have drifted out of sync with the picture.
targets; a backend that cannot express the difference says so rather than
quietly returning the wrong one.
database setting (#198), so switching between compiled-in backends needs no
rebuild.
Non-goals: any specific backend, any decision about when to translate — that
is the reconcile loop's (#196).
Depends on: #184
Implemented the translation layer in
arr-subson branchsubtitles/190-translate. A newtranslatemodule carries the object-safeBackendtrait (boxed futures, same shape asProvider), an id type for the database setting, and the shared machinery no backend can skip: chunking cues into character-budgeted batches (default 4096 chars, overridable per backend viabatch_budget()), asupports(target)check so a backend that cannot express pt-PT vs pt-BR refuses before anything is sent, validation that rejects any reply whose cue count or numbering does not match the batch, and reassembly of translated text onto the original timings — timings never leave arr, and any failed batch fails the whole call so a partially translated subtitle never exists. A companionsrtmodule provides theCuetype with tolerant SRT parsing and strict rendering, since reassembly onto original timings needs cues in structured form and sidecars are SRT per §15. Backend errors get their owntranslate::Error(transport / unauthorized / rate-limited / unsupported target / malformed / cue mismatch) with the sameis_transient()split as the provider error.just cipassed (511 tests).Findings
Fetched.contentis deliberately raw bytes (providers serve Latin-1/Windows-1252), andsrt::parsedeliberately takes&str; something between #187/#188 and #194/#195/#196 has to do the encoding detection, and no issue in the milestone mentions it.SubtitleFormat::Ass/Vtt) to SRT before syncing/writing. §15 says sidecars are SRT andmodel.rsnotes converting "is not this crate's decision to make silently", but no issue decides it either.