Run candidates:
bun run i18n:translate:candidates -- --slug the-last-to-think --locale es
Candidate generation captures every successful slug+locale+model attempt as a timestamped artifact and appends it to candidates.jsonl; judging owns promotion back into src/content.
AI SDK translation and judge calls default to a 240 second timeout. Use --timeout-seconds 240 explicitly for batch work. Thinking-capable models should stay cheap: Qwen, gpt-oss, and GLM run with low reasoning effort; Gemini 3 Flash runs with minimal reasoning effort.
Article translation chunks default to 18p: eighteen paragraphs per active chunk, with one neighboring source paragraph before and after as context padding when available. Stable translation instructions, locale guidance, and article/quiz context should live in cacheable prompt blocks; dynamic prompt text should contain only per-chunk or per-question material. Quiz posts ignore chunk padding because they use structured Challenge input/output.
Each model report should include runtime seconds, input tokens, output tokens, thinking/reasoning tokens, cached input tokens, and estimated cost. Token counts are best-effort from provider output; write unknown rather than omitting unavailable fields.
OpenRouter usage metadata is read from usage.prompt_tokens, usage.completion_tokens, usage.total_tokens, usage.cost in OpenRouter credits, usage.prompt_tokens_details.cached_tokens, usage.prompt_tokens_details.cache_write_tokens, and usage.completion_tokens_details.reasoning_tokens.
For quiz posts, run competing Nitro candidates through the normal candidate wrapper so the outputs are committed and judgeable:
bun run i18n:translate:candidates -- \
--slug javascript-promises-quiz \
--locale ja \
--models openrouter/openai/gpt-oss-120b:nitro,openrouter/qwen/qwen3-32b:nitro \
--task-concurrency 12 \
--quiz-concurrency 24 \
--challenge-retries 2 \
--timeout-seconds 240
Judge only the last two or three candidate commits with --candidate-limit:
bun run i18n:judge -- \
--slug javascript-promises-quiz \
--locale ja \
--candidate-limit 2 \
--model openrouter/google/gemini-3-flash-preview \
--timeout-seconds 240
Judge calls compare at most three candidate commits at a time and include any existing translated file as <current> context. The judge wrapper appends every judge call and automatic fix application to reports/i18n/{slug}/judgements.jsonl, next to the article-level candidates.jsonl.
Automatic pre-publish fixes loop until no medium/high-priority issues remain or --fix-pass-limit is reached. The default limit is 2.
The candidate TUI can launch a parallel judge pass with --judge, or with j while the dashboard is open. It uses --task-concurrency to run separate slug/locale eval+fix loops in parallel; prefer --task-concurrency 12 for normal batch work.
For broad missing-translation sweeps, use translate:all-missing. Start with a dry run:
bun run i18n:translate:all-missing -- \
--models openrouter/openai/gpt-oss-120b:nitro,openrouter/deepseek/deepseek-v4-flash \
--min-candidates 2 \
--judge-model openrouter/google/gemini-3-flash-preview \
--judge-timeout-seconds 300 \
--timeout-seconds 300 \
--task-concurrency 12 \
--candidate-task-concurrency 12 \
--quiz-concurrency 24 \
--continue-on-error \
--dry-run
Then run the same command without --dry-run. These higher limits are the standard recommendation for translation batches when the machine/API budget can handle it. --continue-on-error is important for large batches: it lets later tasks finish when one candidate, judge, or shortfall commit fails.
For stubborn cases where one model repeatedly fails validation and one good candidate exists, judge the single good candidate explicitly:
bun run i18n:judge -- \
--slug from-zero-to-regex-hero-extract-url-like-strings \
--locale he \
--model openrouter/google/gemini-3-flash-preview \
--timeout-seconds 300 \
--allow-single-candidate