Skip to content

fix(translate): harden CapCut cloud translation from #169 - #170

Merged
renezander030 merged 1 commit into
masterfrom
fix/cloud-translate-hardening
Nov 4, 2026
Merged

renezander030 merged 1 commit into
masterfrom
fix/cloud-translate-hardening

Conversation

@renezander030

Copy link
Copy Markdown
Owner

Follow-up to #169 (CapCut cloud translation, contributed by datnt-97), the way #158 followed #157. Neither flag has been released yet.

What changes

  • No misaligned translations. Translations pair with the lines sent by position, so a reply with more or fewer lines than were sent is refused instead of putting text on the wrong timings.
  • Untranslated lines are visible. A line that comes back without a translation keeps its original text (and, for captions, its word timings) and is counted in kept_original. Before, an empty translation blanked the line, and a missing one passed silently.
  • --dry-run sends nothing. caption --translate --dry-run called ByteDance's servers, because only the draft write is gated by a dry run. It now reports the lines it would send under translation (sent: false); caption --cloud --dry-run --translate reports the same.
  • Same warning as caption --cloud. Every run says on stderr how many lines go to ByteDance's servers, and the help text and command reference call both flags experimental and unofficial.
  • One install identity. The headers both cloud clients replay now come from src/capcut-cloud-headers.ts: the identity (app id, device id, locale, user agent) is written once, and each client keeps the signature captured with its own requests. The header objects are byte-identical to before, values and order.
  • Offline tests. test/cloud-translate.test.mjs runs against a stand-in fetch, the pattern of cloud-asr-flow.test.mjs: request shape, 50-line batches, refused and partial replies, the service's error codes, retries, cue word timings, and both dry runs. The calls to ByteDance run only with CAPCUT_LIVE=1, and fail loudly there instead of passing on any error.
  • CI. The Python client's 3.9 job runs on ubuntu-24.04: ubuntu-latest is now 26.04, which has no 3.9 build for setup-python, so that job has failed on master since the image moved. The package still declares requires-python >= 3.9.
  • CHANGELOG: one Unreleased entry for the feature as it now ships, crediting feat: add zero-dep ByteDance CapCut cloud subtitle translation #169, and one for the CI change.

Evidence

  • npm test on a clean checkout of master with this change: 1,385 passed, 0 failed, 2 skipped (the live suite and one existing skip). The commit went through the repo's pre-commit gate (lint-staged, then build and the full suite).
  • biome check --error-on-warnings src/ test/: clean.
  • Header check: both DEFAULT_CAPCUT_HEADERS and DEFAULT_CAPCUT_TRANSLATE_HEADERS compared entry by entry against the literals they replace: identical, same key order.
  • docs/command-reference.json regenerated from capcut describe; only the two flag descriptions change.

Door

Two-way. Every change is local to the two unreleased cloud translation flags, the shared header constant and one CI matrix entry; reverting the commit restores #169 as merged.

Blast radius

translate --cloud and caption --translate (unreleased). caption --cloud gets its headers from the shared builder with unchanged values. No other command, no runtime dependency and no published API changes; translateTextsCapcutCloud (added in #169, never released or exported from the package entry) is removed in favour of translateUtterancesCapcutCloud. CI: one Python job moves runner image.

translate --cloud and caption --translate (#169) now:

- refuse a reply with more or fewer lines than were sent, instead of
  pairing translations with the wrong timings;
- keep the original text, and its word timings, for a line that comes
  back without a translation, and count it in kept_original (an empty
  translation used to blank the text);
- send nothing under --dry-run: caption --translate --dry-run reports the
  lines it would send under `translation`, and --cloud --dry-run says so;
- warn on stderr how many lines go to ByteDance's servers, and say in the
  help and command reference that both flags are experimental and
  unofficial, as caption --cloud does.

The install identity both cloud clients replay (app id, device id,
locale, user agent) now lives once in capcut-cloud-headers.ts. Each
client keeps the signature captured with its own requests, and the
header sets are byte-identical to before, in the same order.

The translation tests run offline against a stand-in fetch (request
shape, 50-line batches, refused and partial replies, retries, cue word
timings, the dry runs). The calls to ByteDance run only with
CAPCUT_LIVE=1 and fail loudly there, instead of passing on any error.

CI: the Python client's 3.9 job runs on ubuntu-24.04; ubuntu-latest
(26.04) has no Python 3.9 build for setup-python.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@renezander030
renezander030 merged commit 845c83a into master Nov 4, 2026
15 checks passed
@renezander030
renezander030 deleted the fix/cloud-translate-hardening branch November 4, 2026 08:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant