Subtitle Translator

Translates subtitles to your language via an OpenAI-compatible LLM.

Documentation

Changelog

All notable changes to this project. Dates in YYYY-MM-DD.

[0.21.1] — 2026-08-25

Fixed

  • DeepL config validation cached a permanent failure on 429 — free-tier keys (:fx) get rate-limited when the form is submitted in bursts (every config save attempt plus every DeepL test click is one live /v2/translate call; the free tier's limits are tight). The resulting failure was then cached with no TTL, so the exact same config kept being rejected instantly until a restart even though the key was valid. Transient failures (429/5xx/network/timeout) are no longer cached — resubmitting retries live; only successes and permanent errors (401/403 etc.) are cached. The subtitle translation path was never affected (withRetry already backs off on 429: 2s, 4s).

[0.21.0] — 2026-08-25

Changed

  • Subtitle flow: every NATIVE subtitle is listed immediately, translation runs in the background — previously the subtitle list only showed a top-5 labeled with the target language (translate=true/false) and prefetched the translation of the first file. Now:
    • Every subtitle found by the search (multi-language v3 + public API) is listed in its original language (en (native · v3 en), id (native · v3 id), …) — visible in Stremio right away, no waiting for translation.
    • On play: subtitle language == target → served directly (native). Language != target → the native subtitle is served instantly (even straight from the prefetched raw cache) while a translation to the target language runs in the background (translateSubtitle, deduped via inflight). The background translate is kicked from both serve paths (cache hit and miss); the source language is derived from the v3 fileId (30949:enen) with a fallback map from the list handler, so it still works after a server restart without a prior list request.
    • When the translation finishes, the translated cache is written → a new target-language entry appears in the next subtitle list (id (translated · v3 en)) — the user just picks it. Verified live (tt0111161): 63 natives listed instantly, background translate ~4 minutes, next list 64 entries, playing the translated one = cache HIT in 15ms.
    • Raw subtitles from v3 are prefetched into the raw cache while the list is built (direct URL, no OpenSubtitles quota); public-API subtitles are cached lazily on play (5/24h quota per key).

[0.20.1] — 2026-08-25

Fixed

  • Free-plan quota eaten by FAILED translations (not successes) — previously the free-quota counter was incremented at the cache-miss gate before translating; because failures (GLM/LLM 400) never write cache, re-requesting the same file from Stremio/Smart TV stayed a cache miss → the counter kept climbing while zero translations actually succeeded. A free user could burn all 5 daily slots without a single subtitle translated. The counter is now split into two roles: the gate (freeQuotaBlocked) only READS usage (rateLimitCheck, no increment), while the increment happens only after a translation successfully writes cache (bumpFreeQuota via rateLimitHit in doTranslate). Failed translates cost 0 slots; 5 SUCCESSFUL translations per day is what blocks. A stale rate-limit row holding 5 hits from failed translates was deleted (the affected free user is no longer blocked today).
  • GLM 5.x (e.g. glm/glm-5.3 via the LLM proxy) failed hard with 400 — root cause: GLM 5 models mandate thinking (reasoning_effort must be low/high/max; none or a missing field is rejected upstream with code 1210). The proxy wraps that 1210 JSON error into an empty HTML page, so (1) the (html-proxy) tag in callLLM stopped the reasoning ladder from ever escalating, and (2) retrying with none hit 1210 again → failure forever. Two-layer fix: (a) models with glm in the name are detected up front and send reasoning_effort:"low" from the first attempt (no attempt wasted on none); (b) if an html-proxy error occurs while we asked for none, the ladder still escalates to low once — HTML can hide a 1210, not just a dead proxy. Verified live: low → 200 with a correct JSON translation.
  • Exponential backoff between LLM retries for flaky proxies (html-proxy/network/5xx) — the proxy intermittently answers 400 with an HTML page regardless of the request body (the GLM upstream is unstable), so 3 back-to-back retries all fell inside the same bad window and all failed. Infra failures now trigger an exponential sleep (1s, 2s, capped 8s) before the next attempt — the bad window can pass and the next attempt lands in a good one. The per-host circuit breaker only helped pools with >1 provider; a single provider still got retried immediately. Backoff covers both.
  • Free-plan quota counts only NEW translations, not cache hits — the 0.20.0 gate counted every request (cached or translated), so replaying an already-translated file (Stremio/Smart TV behavior: repeated requests for the same subtitle URL on load/seek/replay) drained the 5 daily slots. Since the product promises "cache hits & BYOK stay free", the quota now only consumes slots on the cache-MISS translate path (download + new translation); cache hits and native subtitles (s=1) no longer count. The gate moved to the translate path, before download (a blocked user doesn't waste OpenSubtitles download quota).

[0.20.0] — 2026-08-25

Added

  • Free-plan quota: 5 subtitles per day — free users (without an active subscription) are limited to 5 served subtitles per UTC day (cached or translated, native or not). The gate lives in serveSubtitle — one choke point for every path. The 6th+ request receives a valid subtitle cue containing the limit message + a pointer to /pricing (not an HTTP error, so players still load it). The counter uses the rate_limits table (scope sub:<userId>, 86400s window); paid plans and anonymous tokens (no owner) are unlimited.

Fixed

  • Plan edit via /admin now adjusts the credit balance — changing a plan (e.g. free → pro) previously only updated the plan column without touching credits, while the official activation path (payment webhook) always grants the new plan's credits_per_month. POST /admin/user/:id now applies a renewal top-up worth the new plan's allocation when the plan actually changes (downgrade to free = 0, no ledger row); existing balance is not clawed back — the ledger stays honest.

[0.19.1] — 2026-08-25

Fixed

  • User edit save failed with "no valid fields" — the server only mounted express.json(); the HTML form sends application/x-www-form-urlencoded, so req.body was empty and the plan/status whitelist always rejected. Added express.urlencoded({ extended: false }).

Added

  • /admin: edit user — Users table rows now have an EDIT link to /admin/user/:id (same admin guard as /admin): change plan (list from the plans table) and status (active/suspended/banned), plus email, created date, credit balance. New updateUser in db.js (partial update, unknown user → null); POST validates plan/status against whitelists then redirects back to the Users tab of the originating page.

Changed

  • /admin: Users table 10 rows per page — down from 20 to fit the tab panel width.
  • /admin: Overview cards always visible — stat cards are no longer hidden behind tabs; tabs are now Users and Usage by Model only, with the overview cards always shown above.
  • Tabs on /admin — the admin page is split into tabs: Overview (stat cards), Users (table + pagination), Usage by Model (table + pagination). The active tab is kept in ?tab= (server-rendered, pagination links carry the active tab), switching is instant without reload (tiny JS), and Users/Usage tab titles carry count badges.

Fixed

  • VTT with a blank line inside a cue — LLM translations can insert \n\n inside cue text; in WebVTT a blank line ends the cue, so strict web parsers (Chromium/Firefox <track> in Stremio web) treat the rest of the text as a cue without timing — the track fails to load / chunks go missing (found in 5 of 16 cache files; desktop players are more tolerant so it went unnoticed). toVtt now sanitizes cue bodies: per-line trim + blank lines dropped.
  • "Save failed: token failed (400)" with no explanation — the frontend discarded the error body from the save call, hiding the real cause (usually config validation failed while the LLM proxy is inside its ~30s circuit-breaker replay window after an error). The server message is now shown inline. Note: since 0.17.2, saving an unchanged config skips the round-trip entirely (frontend signature cache + stable-token fast path), so a 400 only happens when the config changed AND live validation failed — retry after ~30s.
  • Install URL still flip-flopping on back-and-forth config edits — the content-dedup path of tokenToCfg ran BEFORE the user-stable path: with legacy duplicate rows (pre-0.17.0 race), a config matching old content returned that duplicate token while a new config fell back to the user's newest token — the install URL moved on every model switch. Logged-in users now short-circuit first: the user's own token is always used (same config = fast return without live revalidation; different config = validate then update the same token). Content dedup only applies to anonymous configs.
  • Boarding check red despite HTTP 200 — the proxy appends an SSE-style data: [DONE] tail to non-streaming replies, so JSON.parse in the test endpoint failed → content treated as empty → probe reported "no JSON array" although the model answered correctly. The SSE tail is now stripped before parsing (same pattern as callLLM in translator.js).

Changed

  • One stable token per usertokenToCfg originally registered a new token every time the config changed (dedup by content, not by user), so the install URL /<token>/manifest.json changed on every config edit (made worse by a save+install race that created identical pairs). The user's existing token is now reused: the same install URL stays valid across edits, and a manifest already installed in Stremio follows the latest config without reinstalling.

Added

  • Loading states on the configure page — BROWSE models: button disabled + spinner, dialog shows a "Loading models..." row during fetch (previously an empty dialog with no feedback). Boarding check: spinner "Checking..." button disabled while the test runs.
  • Boarding check sends a real prompt — the old test was just max_tokens:5 "Say pong" (which passed even for models that then failed every translate batch, and never touched the reasoning path). The probe is now a 1-cue translate batch: the same JSON-array system prompt as the real pipeline, asserts the reply starts with [, sends reasoning_effort:"low" (catches thinking-mandatory models at install time), 30s timeout. Failures report the cause (http vs no JSON array in reply).

Fixed

  • One subtitle listed twice in Stremio — the handler served every variant as an .srt + .vtt pair; Stremio registers every subtitle object so each file showed up as two rows. Now one entry per variant (.vtt only, the disk cache is VTT anyway), on both the cache-first and no-cache paths.
  • Player sometimes failed to load .vtt — two cases in serveSubtitle: (1) an unparseable source (e.g. an expired v3 URL replying HTML) produced a header-only VTT with no cues, which players reject as invalid; now a single full-length error cue is served so it always loads. (2) a download failure (quota 406, URL lost after restart) replied 400 text/plain the player can't parse; the reply is now valid subtitle content containing the error message, consistent with the [Translating...] placeholder pattern.

[0.19.0] — 2026-08-25

Added

  • /admin: edit user — Users table rows now have an EDIT link to /admin/user/:id (same admin guard as /admin): change plan (list from the plans table) and status (active/suspended/banned), plus email, created date, credit balance. New updateUser in db.js (partial update, unknown user → null); POST validates plan/status against whitelists then redirects back to the Users tab of the originating page.

[0.15.2] — 2026-08-25

Fixed

  • Subtitles cached from another source language didn't show in the Stremio player — the subtitle handler used searchTiered, whose Tier 1 only returns target-language (native) subs. The cache-first pass then iterated that single-language list only, so preloads/translations made from en/es/… sources on the catalog page were never served (only 1–2 native entries appeared). searchTiered now includes v3All (the full multi-language list) in every tier result, and the cache-first pass iterates that list with fileId|variant dedup — every cached variant (native raw + translated) is served.

[0.15.1] — 2026-08-25

Fixed

  • GLM 1210 loop (This model always engages in thinking and cannot be disabled; please use low, high, or max) — four bugs: (1) the retry stripped reasoning_effort entirely, but GLM-style models require a level (low/high/max), so the retry 400'd again; (2) level detection matched the please use low, high, or max hint text, but proxies truncate the upstream error before that phrase — classification now keys on code 1210 + whether the field is named, never on hint phrases, with escalation ladder "none""low" → stripped; (3) the reasoningOverride map lived inside translateBatch, so every batch re-sent reasoning_effort: "none" and re-learned — it is now truly module-scoped; (4) the proxy opens a circuit breaker on the upstream 400 and replays the cached error for ~30s regardless of body, so the immediate "low" retry got a cached rejection and the ladder wrongly demoted to stripped — the handler now parses (reset after Ns) and waits out the window before retrying (verified by direct probe: after reset, reasoning_effort:"low" returns 200). The reasoning-retry inside the catch no longer propagates out of the retry loop when it fails.
  • Truncation salvage failed when the cut landed inside a string containing } — step-3 truncation salvage used a single lastIndexOf("}"); when the max_tokens cut landed right after a } INSIDE a string value (e.g. "-{alarm}"), the closed array was still invalid → the whole batch failed even though the prefix was valid. It now walks backwards from the cut position until a parse succeeds — cues before the cut are salvaged, the rest are filled with original text (playable).
  • "Cached" badge lost after page reload/api/subtitles recomputed cache paths as cache/<shard>/ but cache.js writes to cache/<APP_ENV>/<shard>/ (default production), so the existence check always missed for new files and the UI cache status vanished on reload. Added hasCache(key) in cache.js checking the same ENV-sharded path as writes; /api/subtitles uses it now.
  • ReferenceError: fileId is not defined in /api/preloadfileId was declared inside try after makeConfig; when makeConfig threw, the catch block's logError referenced an out-of-scope fileId and crashed the error handler. fileId is now hoisted above the try.

[0.15.0] — 2026-08-24

Added

  • DeepL restricted to paid plans — free tier can no longer select or submit DeepL as the translation provider. The provider dropdown hides the DeepL option for free users, applyProviderMode force-resets any restored deepl config back to openai, and the backend now rejects provider: "deepl" on free plans with 403 free_tier_no_deepl (previously only the multi-provider pool was guarded).

Fixed

  • Model selection lost on reload — picking a model/provider now persists to localStorage. The save handler only listened to the change event, but programmatic value sets (pickModel, quick provider, gate inputs) fire input, not change, so hidden fields like model were never saved. Both events now trigger a save.
  • Server config restore fills empty hidden fields — the cross-device restore previously only filled hidden provider fields (baseUrl/apiKey/model) when localStorage was completely empty; a partially-populated device never received the missing model. It now fills any hidden field that is still empty, and the persisted snapshot is taken from the current form state instead of a stale local snapshot.

[0.14.1] — 2026-08-24

Fixed

  • Config auto-restore across devices — config now restores automatically when you log in from another device. The per-user config binding was previously dead (bug: req was never passed into tokenToCfg), so restore never matched. The binding is fixed; signing in on a fresh device repopulates the configure form + provider gates automatically.

[0.14.0] — 2026-08-24

Added

  • Two-row nav (G-row & A-row) — the masthead now shows two menu rows: G (Configure, Catalog, Stats, Map) and A (Pricing, Dashboard, Admin), each sorted by gate number. The Admin item (A4) only appears for ADMIN_EMAILS operators.
  • Dashboard & Admin menu items in nav/dashboard and /admin are now reachable straight from the masthead (previously there was no menu).
  • Server-side pagination on /dashboard & /admin — the Recent usage (dashboard) and Users (admin) tables are now paginated server-side (?p=, default 20) with a flight-board style pager (/stats): Prev/Next + number window + ellipsis, always visible even with a single page.
  • Pagination for the Usage by Provider / Model tables on /admin — paginated server-side (?um=, default 25), a second pager under the table.
  • GET /api/usage — JSON endpoint for the logged-in user's usage history (Clerk session), paginated.

Changed

  • /dashboard & /admin fully server-rendered — previously fetch-JS (/api/admin/overview) + loader; now data & pager render on the server, URL-based (?p=), consistent with /stats & /errors. Pagination buttons always render (Prev/Next disabled at bounds).
  • Removed the email/plan/upgrade/sign-out rows from /dashboard — account info moved into the stat cards (Credits/Plan/Translations/Cache hits).

[0.13.0] — 2026-08-23

Changed

  • Auth fully via Clerk — the custom scrypt email/password + session cookie (src/auth.js) was replaced by Clerk: @clerk/express (clerkMiddleware, getAuth, clerkClient).
    • Removed the custom /api/register, /api/login, /api/logout — sign-in/sign-up now go through the Clerk UI (Clerk.mountSignIn at /login).
    • users.id = Clerk userId (TEXT). password_hash dropped. ensureClerkUser() creates/remaps the DB row on first login (email as the join key for legacy credits/plans data).
    • requirePageAuth/requireAdmin use getAuth(req) + ADMIN_EMAILS via clerkClient.users.getUser.
    • The masthead on every page (landing/catalog/stats/map/errors/pages) has wfAuthSlot — UserButton when signed in, Sign in button for guests.
  • .env is gitignored (Clerk keys must never enter git).

Notes

  • Accounts are created in the Clerk Dashboard (not the DB). Set ADMIN_EMAILS in .env for /admin access.
  • scripts/seed-test-accounts.js only seeds plans + placeholder DB rows; passwords/accounts go through Clerk.

[0.12.0] — 2026-08-16

Added

  • Platform UI (PRD §3, §12)/login (sign-in + register toggle), /pricing (plan cards + sandbox checkout), /dashboard (credits, usage, API key generation), /admin (control tower: users, subs, token totals, usage-by-model; ADMIN_EMAILS only), /docs (public API reference). All wayfind-styled, no build step.
  • Test accounts seed (PRD §10)seed-test-accounts.js: developer@/supporter@/pro@/[email protected] (password testpass123) + free_test/supporter_test/pro_test/power_test plans with starting credits. Idempotent, dev-only (APP_ENV !== production or FEATURE_TEST_PLANS=true).
  • Priority queue (PRD §12) — translate jobs queue on MAX_CONCURRENCY; higher plan priority (plans.priority) is dispatched first. FIFO among equal priority.
  • Cost tracking (PRD §19)translateBatch accepts onUsage({provider, model, promptTokens, completionTokens}); subtitle + API translates persist token counts in usage. Admin overview exposes token totals + per-model breakdown.
  • AnalyticsusageByModel() / sumTokens() / listUsers() / countActiveSubscriptions() queries + /api/admin/overview.
  • DEVELOPMENT banner shared component wfDevBanner() — now on landing, catalog, stats, errors, map, and all platform pages in dev env.

[0.11.0] — 2026-08-16

Added

  • Monetization platform (dev/staging) — PRD "Streamio SRT Monetization & Platform Upgrade". Every feature sits behind feature flags (default off → old production unaffected):
    • Env isolation (Phase 0)APP_ENV, CACHE_PREFIX enter the cacheKey hash, DB data/app_${APP_ENV}.db, cache path cache/${APP_ENV}/, DEVELOPMENT banner on the landing page.
    • Identity (Phase 1)users/api_keys/plans/subscriptions/credit_ledger/usage/payment_events tables; email+password auth (scrypt) + session cookie (/api/register, /api/login, /api/logout, /api/me).
    • Credits (Phase 1) — credit debit in doTranslate (managed), refund when a batch fails, cache hits free, out-of-credits placeholder.
    • Managed provider (Phase 2)MANAGED_PROVIDERS env (baseUrl|apiKey|model pool); managed-plan users translate via the operator pool, BYOK users keep using their own key.
    • Subscription stub (Phase 3) — idempotent POST /webhooks/payment (event_id UNIQUE), PAYMENT_MODE=sandbox, per-plan credit top-up.
    • Public API (Phase 4)POST /api/v1/translate, GET /api/v1/usage, GET /api/v1/me, POST /api/v1/keys; API keys sk_dev_/sk_live_ with env-prefix validation.
    • Rate limiting (Phase 5) — 60s sliding window per API key (default 60 req/min), rate_limits table.
    • Token usage capture (Phase 5)callLLM captures usage from provider responses for cost tracking.
  • docker-compose.dev.yml — isolated dev instance (internal port 7001, named volumes, dev env).
  • test-cache-env.js — child-process test: dev cacheKey ≠ prod.

Notes

  • All new features default off (FEATURE_* env). Enable per phase in dev.
  • No real payment gateway (Midtrans/Xendit) wired yet — the webhook is still a sandbox stub.

[0.10.3] — 2026-08-12

Fixed

  • Manifest validationprovider + includeCatalog select options now string[] (regression from earlier change). Stremio rejects [value,label][] in config.N.options.M (invalid_type: expected string, received array). Custom labels dropped, values rendered as-is.

[0.10.2] — 2026-08-12

Fixed

  • Gemini 400 INVALID_ARGUMENTreasoning_effort field rejected by Gemini with generic error (didn't name the field). Catch now strips field on any 400, retrying without it. reasoning_effort is the only non-standard field sent, so any 400 blaming an unknown arg is treated as it.

[0.10.1] — 2026-08-12

Fixed

  • OpenSubtitles quota circuit breaker — public-API /download allows 5/24h per Api-Key. Once exhausted (406 with reset_time_utc), all public-API download calls short-circuit until reset window lapses. Stops hammering OS + error-log spam per Stremio retry. v3 direct-URL path unaffected.
  • v3 origin logsearchV3 now logs hit count + how many have a direct URL, to diagnose files leaking from v3 result list into the public-API path (which would still 406).

[0.10.0] — 2026-08-07

Added

  • Provider pool repeater UI/configure page renders the provider pool with per-gate boarding checks (model + key validation per provider)
  • Translate-text failover/api/translate-text walks the provider pool on failure instead of single-endpoint abort

[0.9.0] — 2026-08-07

Added

  • Multi-provider LLM poolopenaiProviders config: array of {baseUrl, apiKey, model}. Batches round-robin across providers; on error, failover advances to next provider. Distributes load + isolates one dead provider from killing translation.

[0.8.0] — 2026-08-07

Added

  • DeepL provider — dedicated translation path (not OpenAI-compatible). Plain-text array translate (split_sentences=nonewlines, preserve_formatting). Free (:fx) vs Pro key auto-routed to correct host. Selected via provider: deepl config.

[0.7.3] — 2026-08-06

Added

  • Cerebras — added to quick-pick provider list on /configure

[0.7.2] — 2026-08-06

Changed

  • Concurrency auto-scalingbatchSize ≤ 20 runs batches sequential to avoid bursting past rate-limited provider TPM limits; larger batches use full MAX_CONCURRENCY

[0.7.1] — 2026-08-06

Fixed

  • reasoning_effort rejection — providers that reject the field for non-reasoning models (Groq, Cerebras) now caught by error message and retried without it
  • Missing-cue fallback — LLM skips in large batches now filled with original text instead of dropping the cue (partial result stays playable)

[0.7.0] — 2026-08-04

Added

  • includeCatalog install option — user can opt out of Stremio catalog resource (subtitles-only mode). Dummy catalog seed for empty token configs.
  • Telegram group button in donate card + overlay

Changed

  • Bumped version 0.6.70.7.0

[0.6.7] — 2026-08-03

Fixed

  • 502 → 400 status — Cloudflare intercepts 502 into HTML, breaking client JSON parse (Unexpected token '<'). All 502s now return 400 with JSON.
  • Negative cache fileId 406 — stale v3 fileIds rejected by public API (Invalid file_id) cached 120s so only first Stremio retry hits OS + logs
  • Transient upstream filter — 469/429/5xx/network failures no longer written to error_log (only real failures counted)
  • Catalog detail cast — fixed [object Object] render in cast list
  • Map page — flag + country name for all ISO2 codes
  • /configure redirect → landing page, manifest intercept uses req.url (encoded), removed double decodeURIComponent

Added

  • Telegram group donate card

[0.6.6] — 2026-08-03

Changed

  • Manifest dynamic anime day names — localized by targetLang (was hard-coded English)

[0.6.5] — 2026-08-03

Changed

  • Anime catalog day names in Bahasa Indonesia (reverted in 0.6.6 to per-user localization)

[0.6.4] — 2026-08-03

Changed

  • Anime catalog via TVMaze schedule by day (Kitsu unreliable)
  • max_tokens clamp 4096 — router model 828 rejects >4096

[0.6.3] — 2026-08-03

Changed

  • Catalog cache to DB — zero upstream API hit on request; serves cached catalog instantly

[0.6.2] — 2026-08-03

Changed

  • Anime catalog via Kitsu — AniList global outage fallback

[0.6.1] — 2026-08-03

Fixed

  • Movie gid emptyinstr() returned 0 for ids without colon, hiding movies from catalog

[0.6.0] — 2026-08-03

Added

  • Catalog rework — Trending, Last Play, Anime by day (replaces stats-derived Top/Popular from 0.5.0)
  • Web catalog reads the cached catalog data (filled by the 0.6.3 DB layer)
  • AGENTS.md — project guide for AI agents + semver versioning rules

[0.5.0] — 2026-08-02

Added

  • SQLite persistencenode:sqlite (zero deps) for stats, per-file access, geo cache and the error log. Replaces data/*.json files.
  • Airport wayfinding UI redesignwayfind.js design system (Hanken Grotesk + IBM Plex Mono, yellow signage, 3px black enamel frame). DESIGN.md spec.
  • World map (/map) — ECharts geo map tracking accessor countries via IP geolocation (ip-api.com, cached in ip_cache). Counter cards: Countries, Unique IPs, Total users, Active 24h. Hits/Users column.
  • Error logerror_log table, /errors page server-side sortable + pager, Errors counter card (clickable → /errors)
  • Country banBANNED_COUNTRIES middleware (currently IL)
  • Stremio catalog from stats — Top, Trending, Anime, Popular surfaced from real usage
  • Cast photosenrichCastPhotos pulls TMDB photos from catalog.nuvio.tv meta app_extras.cast
  • v3 in every search tier — OpenSubtitles v3 addon now primary at all tiers (no key, no rate limit)
  • /stats Updated column sortable by timestamp

Fixed

  • resolveReleaseName in cache-first path + mobile release layout
  • Russia map glitch + ECharts country-name aliases
  • Duplicate geo component removed (map was doubled)
  • Country flags use country code (CFLAGS) not language code

Changed

  • Bumped version 0.4.00.5.0

[0.4.0] — 2026-07-31

Added

  • Usage stats — requests, translates, cache hits/misses, errors, per-file access
  • Target language dropdown — 75 languages with native labels, default Indonesia
  • Test buttons/configure page card layout (OpenSubtitles, OpenAI API, Translation); Test endpoints validate credentials inline
  • Test endpoints/api/test/opensubtitles (search), /api/test/openai (chat completion)
  • Placeholder subtitle feedback — cache miss serves [Translating...] cues instead of blank/English; Cache-Control: no-cache so next play pulls the cached translation
  • README — full documentation: features, quick start, Docker, config table, how-it-works diagram, API reference, privacy
  • OpenAI curl test scripttest-openai.sh [base] [key] [model]
  • npm start script, main entry, description, keywords in package.json

Changed

  • Bumped version 0.3.00.4.0
  • Manifest config.options.targetLang now string[] (was [value,label][]) — fixes Stremio manifest validation (75 errors)
  • Landing form: card-based layout instead of flat field list
  • .gitignore: added data/
  • docker-compose.yml: mounted ./data volume

Removed

  • jQuery / Select2 / Alpine experiments (reverted to native <select>)
  • VTT progressive streaming (Stremio player buffers full response — streaming had no effect)
  • Silent .catch() on cache writes (now logs errors)

[0.3.0]

Added

  • Catalog browsing (IMDb via Cinemeta, anime via AniList)
  • Synopsis translation
  • Subtitle preview + preload endpoints
  • In-flight translation dedup
  • Tiered subtitle search (target lang > English > any)
  • SRT + VTT serving
  • Docker support (Dockerfile, docker-compose.yml)

[0.1.0]

Added

  • Initial Stremio addon: OpenSubtitles fetch → LLM batch translate → serve .srt

Additional Information

Submitted By

kurob

Version

0.23.2

Developed By

kurob

Language

Indonesian (Indonesia)

Submitted On

Last Updated

Manifest Status

Checking

Problem Reports

Is it down for everyone or just me?
... reports in the past 24 hours
We value your privacy

This site uses cookies to improve your browsing experience and show personalized content.