A production-grade Next.js app that translates .srt subtitle files through any OpenAI-compatible provider — with a fully deterministic pipeline where the LLM never rebuilds the SRT.
-
⚙️ Deterministic SRT Pipeline — The LLM Never Rebuilds the File:
- Structural control is always deterministic:
parse → internal model → batch → context → LLM → validate → persist → serialize. - Every structural element (timestamps, indices, tags, line breaks, music/SDH markers, technical placeholders) is preserved by code, not by the model.
- Original subtitle file is immutable — export always writes a new file and never overwrites the import.
- Structural control is always deterministic:
-
🛡️ Zero-Trust Response Validation:
- The LLM must answer strict structured JSON
{ translations: [{id, text}] }, validated byTranslationResponseValidator(schema, IDs present, no duplicates, no empty text). - HTTP 200 is not trusted. Missing IDs are repaired by re-requesting only those entries (
maxRepairAttempts = 2), then the batch is markedFAILED.
- The LLM must answer strict structured JSON
-
🧠 Layered Architecture (Presentation → Application → Domain → Infrastructure):
- Clean separation enforced by convention:
src/app(Next.js routes),src/application(translation runner),src/domain(batching/context/window),src/infrastructure(LLM provider, retry, rate limiting),src/lib(SRT parsing, serialization, tokens, validation). - Domain never imports Drizzle directly; all DB access is isolated behind
src/db/to keep the SQLite→Postgres swap possible.
- Clean separation enforced by convention:
-
🔀 Token-Based Batching + Context Window Management:
BatchManagergroups subtitles by token budget (default target 1800), not by count, with min/max subtitle guards.ContextManagerbuilds before/after reference context (default 10 before / 5 after), andContextWindowManagershrinks it if it exceeds the model window.
-
⏱️ Resilient Execution — Concurrency, Retry, Checkpoint:
- Configurable concurrency (1–16, default conservative),
RateLimiterper-minute andConcurrencyLimiter. - Exponential backoff with jitter on retryable statuses (408/409/425/429/500/502/503/504/timeout); never retries permanent errors (401/403/invalid key/model/malformed).
- Idempotent checkpoint after each batch: pause/cancel/resume never destroys translated content.
- Configurable concurrency (1–16, default conservative),
-
🔒 Server-Only Credentials:
- API keys are always server-side, masked in the UI (
sk-••••…1234), never in logs, client components, or the exported SRT. The LLM flow isBrowser → Next Server → LLM Provider, never direct.
- API keys are always server-side, masked in the UI (
bun install # bun.lock is the source of truth (versioned)bun run dev # http://localhost:3000 (Next 16 Turbopack)Runtime note:
next dev/next buildstill run on the Node runtime (Next.js 16 has no stable Bun runtime). Bun is the package manager and runs every script that is not Next itself (vitest,tsx,drizzle-kit, the smoke script).
- Open Settings, register a provider (
Base URL,API Key,Default Model) → Test Connection → List Models. - On the project page use Start Translation (provider, model, style, priority, concurrency, glossary).
- Edit side-by-side, then Export as
.srt(UTF-8 / UTF-8 BOM).
Credentials are server-only and masked in the UI.
srt-translator-ai/
├── AGENTS.md # Agent law (English; canonical requirements source)
├── README.md # Master English documentation
├── README.pt-BR.md # Brazilian Portuguese documentation (secondary)
├── SPECIFICATION.md # Formal technical specification
├── LICENSE # MIT
├── package.json # Next.js 16 scripts & dependencies (Bun is the package manager)
├── bun.lock # Bun lockfile (source of truth, versioned)
├── drizzle.config.ts # Drizzle config
├── src/
│ ├── app/ # Next.js App Router — pages and API routes
│ │ ├── layout.tsx page.tsx
│ │ ├── projects/[id]/ # Project detail + editor
│ │ ├── settings/ # Provider configuration
│ │ └── api/ # parse, projects, providers, translations
│ ├── application/
│ │ └── translation/runner.ts # Translation orchestration (concurrency pool)
│ ├── components/
│ │ ├── ui/ # shadcn primitives (Button, Card, Badge, Select, Tooltip…)
│ │ ├── ds/ # Design-system layout primitives (Stack, Cluster, Grid, Box, Container)
│ │ ├── layout/ projects/ subtitles/ translation/
│ ├── config/ # languages.ts, profiles.ts
│ ├── db/ # Drizzle schema + node:sqlite access (isolated)
│ │ └── schema.ts index.ts migrate.ts
│ ├── domain/translation/ # batch-manager, context-manager, window-manager
│ ├── infrastructure/llm/ # provider, factory, prompt, validator, retry, rate-limiter
│ ├── i18n/ # dictionaries.ts (pt-BR/en), context.tsx
│ ├── lib/ # srt/, tokens/, cost/, validation/, format, status, notify
│ ├── types/ # shared domain types
│ └── globals.css # design tokens + dark mode
├── tests/ # Vitest unit + integration suite
│ ├── domain/ llm/ srt/ tokens/
└── scripts/smoke-i18n.cjs # i18n dictionary smoke check
- 📋 Master Technical Specification: Formal architecture & technical spec derived from the codebase.
- 🎨 Design System: Primitives, tokens, and UI rules.
- 🤖 Agent Law: The repository's convention contract — pipeline, boundary, security, DoR.
bun test # vitest run (parser, serializer, markup, validator, batch, estimate)
bun run lint # eslint
bun run build # production build (Next, on Node runtime)This project is open-source and licensed under the MIT License.
