- pagerite/segments.py replaces masking.py: a fragment is parsed with the
project's own markdown-it (markdown.make_md(verbatim=True), byte-identical
tokens) and split into pure-prose segments with source spans; only the
segments plus per-segment context surrounds cross the wire (Job.texts /
contexts / Result.texts) and translations splice back by offset — markup
can no longer break, it never leaves the server. Count/empty/non-prose
results are rejected and skipped for the run.
- Localization machinery out of app.py: the translator dispatcher (clients,
job pipeline, validation skip-list) moves into translate.Dispatcher;
translated-edit recording moves into i18n (add_patch, set_title_translation,
clear_translations). app.py keeps only the routes.
- Structure editor localized: flag strip switches the language titles are
shown/edited in (GET /_api/pages?lang= flags translated rows, originals
dimmed); retitling in a translation writes a per-language title fragment
via StructureOp.lang — slugs, order and hierarchy stay language-independent.
- Localization tab: refresh-all button (DELETE /_api/translations) drops
machine translations, keeps user patches and clears the skip-list so the
dispatcher re-translates everything.
- PageEditor always opens in the primary language; editor socket gets
reconnect/doc-mismatch logging. Reference translator: per-segment calls
with context prompts, deterministic punctuation matching and the "<"
markup-bleed cut.
- New editor-shell tab (lang): translatable languages as clickable flag
tiles (shared langs.js mapping of the 28 Seed-X languages to flags),
saved via the settings round-trip; translator service URL(s) shown.
- Bootstrap seeds translate_langs with Spanish and Chinese (the original
language is never a target).
- GET /_api/settings also returns primary_lang.
- PageEditor's flag/name helpers move to the shared langs.js.
- pagerite/masking.py: technical spans (code, URLs, {placeholders}, attrs,
footnote/link labels, container names, HTML tags) become numbered sentinels
for the LLM round trip; results are restored by number and rejected when a
sentinel is mangled (skipped for the rest of the run, stays pending).
Chunks with no prose left after masking are never dispatched.
- Data.translate_key -> translate_keys dict (key -> name); the first key is
generated at bootstrap, result transactions record the key as user=, and
startup logs the service URL(s) via translate.log_service_urls.
- Fix /_translate proxying through the Vite dev server (missing slash).
- Server replicates a ?lang= override onto the navigation links it
renders (nav, sidebar, cards, brand), so clicks and prefetches stay in
the chosen language even without JS; link_lang is part of the ETag and
body cache key (query and header renders of the same language differ
in their links).
- pagerite.js drops the Accept-Language header hack: the remembered
language rides internal fetches as ?lang= instead (added when a link
lacks one), the page cache keys on path+query, and history/address bar
keep the pretty query-less URL.
- Canonical names the actually served language (plain URL for the
original, ?lang= for translations); hreflang alternates are site-wide
from translate_langs, identical on every page: x-default (the plain
autodetecting URL) first, then every language explicitly, default
included, emitted right after canonical before the social tags.
- pagerite/i18n.py: parse_accept_language, select_language, Translation struct,
get_translation/available_languages stubs
- app.py: language threaded through show_page/_render_html/_cached_body,
ETag and Vary: accept-language
- views.py: <html lang>, translated nav titles with fallback, canonical
with ?lang=, hreflang alternates
- pagerite.js: strip ?lang= via replaceState, send it as Accept-Language
on fetch-navigation/preloads, lang-aware page cache
- docs/localization.md: negotiation + URL scheme + phase-2 fragment model
- docs/migrate.md: migrate_v3 content-addressed chunk storage plan