🌐 News Translate

July 23, 2026 Β· View on GitHub

This workflow is the sole writer of analysis/daily/$DATE/$SUB/executive-brief_<lang>.md files. It translates the English-master executive brief (executive-brief.md, produced by the per-type news workflows) into 13 non-English sibling files β€” together with the English source these give "all 14 languages". It never generates original analysis and never modifies executive-brief.md itself.

πŸ”— Authoritative content contract: TRANSLATION_GUIDE.md Β§Executive Brief Markdown Translations. Read this in full before producing any translation β€” it defines verbatim-preserve blocks, always-translate blocks, structural parity, tone register per language, RTL rules, and the validator acceptance checklist.

Pipeline

  1. Wall-clock checkpoint (mandatory) β€” as the very first bash call in this run, record JOB_START=$(date +%s) and export it. After every source completes Pass 2, re-read NOW=$(date +%s) and compute ELAPSED_MIN=$(( (NOW - JOB_START) / 60 )); print it. If ELAPSED_MIN >= 35, stop selecting new sources and proceed directly to the validate β†’ stage β†’ commit β†’ safeoutputs___create_pull_request flow regardless of remaining $TRANSLATION_WORKLIST entries. If ELAPSED_MIN >= 42, halt all translation work immediately and execute the 07-commit-and-pr.md Β§Emergency deadline order of operations bash block to ship a partial PR before Timer A (60-min job timeout-minutes) and Timer B (~60-min Copilot API session) fire. A partial PR is always better than zero output β€” see run #26633644372, where source #2 failed with CAPIError: 429 Maximum effective tokens exceeded.
  2. The Build executive-brief translation work list pre-flight step has already exported four env vars into the agent sandbox: $TRANSLATION_WORKLIST (comma-separated repo-relative paths β€” read this verbatim, or parse the same content line-by-line from the file at $EXEC_BRIEF_WORKLIST_FILE at ${GITHUB_WORKSPACE}/.exec-brief-worklist.txt), $TRANSLATION_LANGS (comma-separated language codes β€” this is the per-run language batch, already capped at inputs.max_langs (default 13) and pre-selected greenfield-first from the languages that still need work; translate exactly and only these codes β€” do not expand to the full 13-language set), $MAX_BRIEFS / $MAX_BRIEFS_RESOLVED (1–7, clamp-applied), and the audit counters $MISSING_COUNT / $DRIFT_COUNT. Do not re-scan the filesystem to rebuild this list β€” the pre-flight step has already honoured inputs.article_date, inputs.subfolder, inputs.languages, inputs.max_briefs, inputs.max_langs, and inputs.force_retranslate. Languages omitted from $TRANSLATION_LANGS this run are intentionally deferred to the next scheduled run (the source stays MISSING/DRIFT until all requested languages are present and current) β€” this per-run cap is what keeps the session under the Copilot weighted effective-token cap that aborted run #26641603577. The selector is greenfield-first: sources with one or more missing target files (MISSING) always win the max_briefs batch slots over sources that only need drift-fixes (DRIFT); DRIFT is only consulted when MISSING is empty. If $TRANSLATION_WORKLIST is empty (so $MISSING_COUNT=0 and $DRIFT_COUNT=0), nothing is pending β€” proceed to improvement-mode (re-validate every existing translation against the current <!-- source-sha: ... --> trailer and fix any drift the validator flags). If still nothing changes after improvement-mode, follow 07-commit-and-pr.md Β§No-op policy.
  3. For each source path in $TRANSLATION_WORKLIST (or each line of $EXEC_BRIEF_WORKLIST_FILE):
    1. Pass 1 β€” translate: Read the source executive-brief.md in full once. For every language in TRANSLATION_LANGS, produce analysis/daily/$DATE/$SUB/executive-brief_<lang>.md following the TRANSLATION_GUIDE rules β€” write each file with one edit tool call per language (never via python3, bash heredocs, or shell redirection β€” see 01-bash-and-shell-safety.md Β§File creation & overwrite strategy). Apply the per-language tone register from TRANSLATION_GUIDE at write time (so Pass 2 never needs a full re-read just to adjust register), and for ar/he start the file with <!-- dir: rtl -->. Preserve every verbatim block (YAML, HTML comments except source-sha, dok_id codes, Mermaid DSL bodies, code fences, URLs, file paths, evidence-anchor canonical column values). Translate every always-translate block (prose, headings, list items, table cell text, image alt-text, BLUF, decisions, link text). Do not read a freshly written translation back into model context to "confirm" it β€” the validator in Pass 2 is the authoritative gate.
    2. Pass 2 β€” validate-first & targeted refine: This is the primary token-efficiency control. Run npx tsx scripts/validate-executive-brief-translations.ts --source <source> --lang "$TRANSLATION_LANGS" in the runtime shell (the --lang scope restricts validation to this run's language batch so deferred languages are not reported as missing). That validator already performs every structural-parity check at near-zero model-token cost: heading / table-row / code-fence / Mermaid-block count parity, dok_id and URL set equality, banned-English-phrase detection (Executive Brief, Decisions, Confidence, BLUF, …), the <!-- dir: rtl --> marker for ar/he, and the <!-- source-sha: --> trailer. Do not read passing translations back into model context β€” reading every target file back in full is exactly what drove run #26633644372 to 7.6M effective tokens and a 429 Maximum effective tokens exceeded abort. Only read back (and re-translate / fix) the specific executive-brief_<lang>.md files the validator reports as failing, then re-run the validator until it is clean for the requested languages.
    3. Append the source-revision marker: compute SRC_SHA=$(git log -1 --format=%H -- <source>) in the runtime shell. Use the edit tool to add or replace the final <!-- source-sha: $SRC_SHA --> line at the end of every translation file whose language is in $TRANSLATION_LANGS (consistent with the file-write contract β€” no >> or echo). Do not touch translation files for languages outside the current batch β€” their marker must remain stale (or absent) so the next run correctly classifies them as DRIFT/MISSING. This is the drift signal future runs use to decide whether to retranslate.
    4. Title post-processing: for each language code $L in $TRANSLATION_LANGS, run npx tsx scripts/postprocess-translated-brief.ts analysis/daily/$DATE/$SUB/executive-brief_$L.md to re-apply the renderer's cleanArticleTitle pipeline on the translated H1. This strips locale-specific boilerplate prefixes (Exekutiv sammanfattning β€” , Zusammenfassung β€” , ζ‰§θ‘Œζ‘˜θ¦οΌšβ€¦) and trailing date suffixes that the Pass 1 translation may have left in place. The helper only rewrites when the cleaned title differs from the source; it never falls back to a BLUF synthesis. Verify the script reports βœ“ or ✏️ for every file (no βœ—). Do not pass executive-brief_*.md as a glob β€” that would rewrite deferred-language files outside $TRANSLATION_LANGS without subsequent validation, defeating the per-run language cap.
    5. Final validate: re-run npx tsx scripts/validate-executive-brief-translations.ts --source <source> --lang "$TRANSLATION_LANGS" (when available) to confirm the title post-processing in sub-step 4 did not break parity, or fall back to the structural sanity checks listed in TRANSLATION_GUIDE Β§Acceptance checklist. Fix any failures by re-translating only the offending language (read back only that file). Do not commit a file that fails validation.
    6. Re-evaluate the wall-clock checkpoint (from step 1) before starting the next source. Never start a new source after ELAPSED_MIN >= 35.
  4. Stage every newly written / refreshed executive-brief_<lang>.md file. Do not touch executive-brief.md itself, any HTML under news/, any file under analysis/daily/*/article.md, or any non-translation file.
  5. Call safeoutputs___create_pull_request exactly once covering the whole batch, by agent minute 42 at the latest (hard deadline 45 per 07-commit-and-pr.md Β§Deadline enforcement). The branch prefix news/translate/briefs/ is enforced by the safe-outputs config (07-commit-and-pr.md).

Inputs

  • article_date β€” optional. Restrict scanning to a single date folder.
  • subfolder β€” optional. Restrict scanning to a single document type folder.
  • languages β€” default all-extra (= sv,da,no,fi,de,fr,es,nl,ar,he,ja,ko,zh). Aliases: nordic-extra, eu-extra, cjk, rtl, all-extra. Comma list also accepted.
  • max_briefs β€” default 1, range 1–7. Caps the number of source files processed in this run; total file output = max_briefs Γ— |TRANSLATION_LANGS| (= 1Γ—13=131 \times 13 = 13 at the defaults; hard-cap worst case 7Γ—13=917 \times 13 = 91, safely under the 200-file safe-outputs cap). The default was lowered from 3 to 1 after run #26633644372 hit the Copilot effective-token limit before finishing source #2.
  • max_langs β€” default 13 (= a full source in one run), range 1–13. Caps the number of target languages translated this run. The builder pre-selects the still-needed languages greenfield-first (missing files first, then drift) and truncates to this many. Run #26641603577 hit 27.0M weighted effective tokens (> the 25M cap) at 13 languages, but that was while the heavy github toolsets:[all] + three data-MCP server schemas were re-sent every turn; that dead-weight surface has been removed (this workflow only uses bash/edit/safe-outputs), collapsing the per-turn footprint so a full 13-language source now fits in one run. Lower this first if an unusually large brief approaches the cap.
  • force_retranslate β€” default false. When true, every requested language is rewritten even if the <!-- source-sha: --> marker matches.
  • analysis_depth β€” default standard. Echoed into the validator output for parity with content workflows; does not change translation behaviour.

Batch-size rationale

QuantityValueSource
Average source size~650 words (range 232–2040)wc -w over analysis/daily/**/executive-brief.md
Per-language Pass 1 write + validator gate~45–75 sSonnet-class translation budget; Pass 2 is bash-validated, not re-read into context
Per-source wall time (≀max_langs languages, sequential)~15–25 min at max_langs=13derived
Default max_briefs / max_langs1 source / 13 languagesa slim per-turn MCP/tool surface keeps the weighted effective-token total under Copilot's per-session cap even at a full 13-language source
Per-session token driverper-turn tool-schema surface Γ— turn countbefore the fix the dominant cost was the fixed per-turn schema (github toolsets:[all] + three data-MCP servers) re-sent every turn β€” the ~3.5Γ— weighting multiplier. The translate agent calls none of those tools, so the surface was stripped to bash/edit/safe-outputs (+ one riksdag health-gate tool); the per-turn footprint now collapses toward the raw token count
Run #26641603577 (pre-fix)13 langs β†’ 27.0M weighted / 25M cap β†’ 429 before PRgh-aw raw 7.67M; the weighted metric was ~3.5Γ— raw and exceeded the cap β€” driven by the dead-weight per-turn schema, now removed; no PR shipped
Primary token leversstrip unused per-turn MCP/tool schema + validator-first Pass 2removing the data-MCP servers + github toolsets:[all] cuts the fixed per-turn cost so the 25M cap stops being the limiter; structural parity stays in the bash validator
Per-run file outputmax_briefs Γ— max_langs = 1Γ—131 \times 13 = 13 files (default)well under safe-outputs max-patch-files: 100
Daily throughput (3 runs)up to 39 language-files / day at defaultswall-clock (Timer A/B) and the 100-file cap, not the token budget, are now the governing limits
Current backlog drain (β‰₯159 untranslated sources)greenfield-first, oldest source + missing languages firstlinear

If a run is behind schedule at agent minute 30 with translations still pending, the agent MUST trim max_briefs downward (drop the last selected source) rather than skip the Pass 2 validator gate β€” quality over completeness. A partial PR is always better than missing Timer A (job timeout-minutes: 60). The default max_briefs=1 was set after run #26633644372 repeatedly hit CAPIError: 429 Maximum effective tokens exceeded while attempting source #2; the validator-first Pass 2 above removes the read-back token sink, and stripping the unused per-turn MCP/tool schema (github toolsets:[all] + the scb/world-bank data servers β€” none of which this workflow ever calls) collapses the fixed per-turn cost that drove the ~3.5Γ— weighting, so the weighted effective-token total stays under the 25M cap and the PR safe-output is reached even at the default 13-language batch.

Rules specific to this workflow

  • Never generate original analysis or write files outside analysis/daily/**/executive-brief_<lang>.md.
  • Never modify the English source executive-brief.md β€” it is owned by per-type news workflows.
  • Validate every translation with scripts/validate-executive-brief-translations.ts before commit. Re-translate (do not commit) any file that fails.
  • Keep the PR under the safe-outputs 200-file cap. The default max_briefs=1 Γ— 13 languages = 13 files leaves ample headroom; the hard cap max_briefs=7 Γ— 13 = 91 stays well under 200. The previous default of 3 was reduced after run #26633644372 hit the Copilot effective-token cap before the PR call.
  • Per-language idempotency, not workflow-level no-op:
    • The pre-flight selector is greenfield-first: it classifies every candidate source as MISSING (β‰₯1 requested language file absent or empty) or DRIFT (every requested language file present but β‰₯1 has a stale <!-- source-sha: --> trailer), then fills the max_briefs batch slots with MISSING sources oldest-first and only falls back to DRIFT once MISSING is empty. Drift-fix work never displaces an untranslated brief while the backlog still has missing files.
    • A target language is skipped for that language only when all three hold:
      1. executive-brief_<lang>.md exists and is non-empty,
      2. its <!-- source-sha: <sha> --> trailer matches the current git log -1 --format=%H -- executive-brief.md, and
      3. scripts/validate-executive-brief-translations.ts passes for that language.
    • When all requested languages for all candidate sources satisfy the three conditions, the run enters translation-improvement mode: re-run the validator across every translation, fix any drift / structural-parity regressions / RTL-marker omissions the validator flags, refresh <!-- source-sha: --> trailers if the source has been recommitted since, and commit the resulting changes. Append the improvement-mode rerun marker per 07-commit-and-pr.md Β§No-op policy.
    • safeoutputs___noop is only allowed under the conditions in 07-commit-and-pr.md Β§No-op policy. "All translations already exist and are valid" is never a noop trigger; it is an improvement trigger.

Time budget

🟑 Plan to call safeoutputs___create_pull_request by agent minute 42 (hard deadline 45) to reserve job-level headroom for setup variance and the safe-outputs runner. The operative constraint is Timer A (job timeout-minutes: 60) and Timer B (~60-min Copilot API session). See 00-base-contract.md §Session timing and 07-commit-and-pr.md §Deadline enforcement. The default max_briefs=1 is sized to keep runs below the Copilot effective-token ceiling; exceeding it risks repeating run #26633644372, where the session aborted on source #2 with CAPIError: 429 Maximum effective tokens exceeded.

Single run (target ~36–40 agent minutes in a 60-min job, hard deadline 45 agent minutes for the PR call):

MinutesPhase
0–3MCP pre-warm; work-list resolved into $TRANSLATION_WORKLIST env var + $EXEC_BRIEF_WORKLIST_FILE (${GITHUB_WORKSPACE}/.exec-brief-worklist.txt); record JOB_START=$(date +%s)
3–5Read TRANSLATION_GUIDE Β§Executive Brief Markdown Translations + selected sources
5–32Pass 1 write + validator-first Pass 2 Γ— max_briefs sources Γ— all requested languages; re-evaluate ELAPSED_MIN after every source and stop selecting new sources once ELAPSED_MIN >= 35
32–38Final validation with scripts/validate-executive-brief-translations.ts; append <!-- source-sha: --> trailers; stage scoped files
38–42One safeoutputs___create_pull_request call β€” HARD DEADLINE agent minute 45; if ELAPSED_MIN >= 42 at any point, switch to the 07-commit-and-pr.md Β§Emergency deadline order of operations bash block immediately

If the batch cannot finish under this budget, commit the translations completed so far and call safeoutputs___create_pull_request with label partial; the next scheduled run picks up the remaining work. A partial PR is always better than losing the whole batch to Timer A.

All non-workflow-specific rules are in the imported modules β€” do not restate them here.