- translate.py: new "nav" job mode (Hello.modes opt-in) — the whole
navigation hierarchy crosses as one nested Markdown list of pending
titles, decomposed back by align_nav: item count/depth must match or
the job is rejected wholesale (titles fall back to scoped jobs);
items failing title checks individually are skipped to scoped jobs.
Dispatched ahead of per-title jobs; a lone pending title stays scoped.
- article jobs carry the already-translated menu title and parent title
as contexts, so the injected heading can match the menu while the
model may adapt the in-article title to the content.
- llm_translator.py: nav mode + nav_prompt; article prompt takes the
title/location context; API keys from per-provider env vars only
(KIMI/MOONSHOT/OPENAI_API_KEY, each sent only to its own host;
LLM_API_KEY generic) — no CLI flag, no config file; Kimi Code /coding
endpoint support (sampling fields dropped, reasoning_effort from
config, field-proven with k3-256k at low effort); errors include the
response body; verbose per-job logging with the raw response incl.
thinking (stripped from results); Kimi models announce all languages.
When the render would inject the page title as an h1 (markdown.has_h1 is
False for the body), an article job's text carries the same '# {title}'
line: the title translates in document context and the opening paragraphs
see the heading (works both ways). The heading's pair in the decomposed
result becomes the title fragment — heading text only, pure prose, never
stored as a body chunk; a demoted or merged heading skips the title,
which stays pending for a scoped title job. The job covers the title key
so it is not double-dispatched afterwards.
Prompt: prefer established technical loanwords with English roots over
forced localizations (frontend -> frontti in Finnish, not etupääte).
Verified offline (title extraction, demoted-heading skip, no-injection
path) and live against ollama qwen3.8:27b: the standalone title job gave
'Alkuun pääseminen', the article job then overwrote it with the
in-context 'Aloitus' matching the body's translated heading.
Hello gains model + modes; Job gains mode. Dispatch routes per connection
capability: article jobs only to article-capable connections and only
while a page is mostly pending; titles/chunks go as markdown (whole
fragment, hybrid-neighbor contexts) or segments jobs. Validation skip
list is now (lang, key, mode). align_article decomposes a whole-article
result into per-chunk stores: non-translatable blocks (fences, HTML,
containers) are verbatim anchors, regions between anchors pair
positionally, mismatched regions and blocks with altered link
destinations/placeholders stay pending.
Verified offline against /tmp/llmtrial outputs: qwen3.8:27b runs pair
105/105 and 43/43 blocks; qwen3:30b-instruct's translated code comments,
qwen3-next's degenerate runs and the structurally broken seedx trial
files (a dropped ::: fence) are all rejected.