Skip to content

Commit c35ca29

Browse files
authored
🌐 feat: Translate Docs With Claude Haiku 5.5 and Batch Prefetch (#836)
* feat(i18n): translate docs with Claude Haiku 5.5 Switch the default translation model from gemini-3.1-flash-lite to anthropic/claude-haiku-5.5, which lists at $0.10/$0.50 per million tokens. Drop the Google AI Studio provider pin and flex service tier, which no Anthropic endpoint can satisfy, stop sending temperature (Haiku 5.5 rejects non-default sampling values), and turn reasoning off so thinking tokens are not billed on every block. * feat(i18n): prefetch translations through OpenRouter batch endpoints Add a --batch mode to the translate script. A silent dry run collects every request the real run would send, those go to the model's :batch endpoint at half the live price, and the real run consumes each answer once before falling back to the live model for validation retries, truncation, and anything a batch did not answer within TRANSLATE_BATCH_WAIT_MINUTES. Batch-side failures never fail the run; they only cost the live price. The translate workflow now runs in batch mode. * fix(i18n): submit the plain model slug to the batch API and dedupe dry-run misses OpenRouter selects the :batch endpoint from the plain model slug and rejects the suffixed one, which silently sent every request live. A dry run also reported a repeated source text once per occurrence, so batch rows were billed for repeats the real run serves from the staged map. Batch requests now carry the shared OpenRouter attribution headers. * fix(i18n): hand batches still running at the deadline to the next run OpenRouter cannot cancel a batch, so running its requests live after the wait billed them twice. Batches still running are now kept in a ledger in the translation cache; their blocks stay in English and uncached for this run, and the next run collects the answers instead of resubmitting them. * fix(i18n): buy each batch string once per locale and record batches as they are submitted Files processed concurrently can miss the same text before either commits it, with requests that differ only by context; the batch now buys one answer per string and serves a copy to every occurrence, as the TM already shares translations by text. The ledger is saved after each submit so a run that dies during the wait still hands its batches on. * fix(i18n): send validation retries live and reject non-positive batch settings A retry now carries its attempt number, so the prefetched model serves only a block's first request and never hands back another copy of an answer that just failed validation. Batch size, wait, and poll interval fall back to their defaults unless positive, so a bad value cannot loop forever or poll without pause. * fix(i18n): key batch requests by locale and text so later runs match them The ledger remembered each batch by the request of the string's first occurrence, whose context can differ from run to run, so a later run could resubmit a string a running batch was already answering. * fix(i18n): keep completed batches in the ledger until their answers are no longer needed A run that died after collecting a batch but before caching its answers left nothing for the next run to find, so it paid for those strings again. * fix(i18n): time out stalled batch requests A submit or status call that never answered kept the wait loop from reaching its deadline. * fix(i18n): keep unneeded batches for a while and bound each request by the remaining wait A run scoped with --only or --locales dropped batches it did not need, so a later full run paid for them again; such entries now stay in the ledger past the 24h completion window. Each batch request is also capped by the time left in the wait so stalled calls cannot add up past it. * fix(i18n): keep other models' batches and release batches that cannot be read Running with a model override wiped the ledger entries of the default model, and a batch whose status returned a permanent 4xx such as 403 stayed pending forever, keeping its strings untranslated on every run. * fix(i18n): write the batch ledger atomically * fix(i18n): never sleep past the batch wait deadline * fix(i18n): forget a batch only when it is gone, not on an auth failure
1 parent 3f6ed4b commit c35ca29

10 files changed

Lines changed: 931 additions & 48 deletions

File tree

‎.env.template‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ SUPABASE_SERVICE_ROLE_KEY=
3838
OPENROUTER_API_KEY=
3939
# OpenRouter model ID (default: openai/gpt-5.4-nano)
4040
OPENROUTER_MODEL=
41-
# Model used for automatic docs translation (default: openai/gpt-5.4-nano)
41+
# Model used for automatic docs translation (default: anthropic/claude-haiku-5.5)
4242
OPENROUTER_TRANSLATE_MODEL=
4343

4444
# Web Bot Auth (required in production for the public JWKS and signed agent requests)

‎.github/workflows/translate_docs.yml‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ on:
1616
# Convergence sweep. A push only translates after a content change, and adding a
1717
# new language doesn't touch content/docs at all — so a big batch (several new
1818
# locales and/or long files) can't fully land in the single push-triggered run
19-
# under the flex-tier rate limit. This run writes generated docs to
19+
# under provider rate limits. This run writes generated docs to
2020
# TRANSLATE_BRANCH and keeps translation memory in the Actions cache, so main stays
2121
# free of partial progress commits and cache files.
2222
#
@@ -175,7 +175,13 @@ jobs:
175175
if [ "${{ inputs.force }}" = "true" ]; then
176176
FLAGS="--force"
177177
fi
178-
npx tsx scripts/translate.ts $FLAGS
178+
# --batch sends every uncached block through the model's half-price
179+
# `:batch` endpoint first and runs only the leftovers live. It waits up to
180+
# TRANSLATE_BATCH_WAIT_MINUTES (default 180) so the live tail and the
181+
# commit still fit in the job's 6-hour limit. Blocks of a batch still
182+
# running then stay in English; content/.i18n-cache/batches.json carries
183+
# the batch to the next run, which collects its answers.
184+
npx tsx scripts/translate.ts --batch $FLAGS
179185
180186
# `!cancelled()` so a translate step that failed part-way still commits the
181187
# pages it did finish; gated on the prepare step so a failure in checkout or

0 commit comments

Comments
 (0)