Localization #1

Merged
LeoVasanko merged 31 commits from localization into main 2026-09-03 03:54:28 +00:00
Owner

Implement comprehensive content localization, admin panels for editing each language, AI translation interface with automatic updates when base language version is changed.

  • SEO tags for all language URLs
  • Uses accept-language by default, ?lang=en overrides temporarily
  • User edits patched on top of translations
  • RTL language support
Implement comprehensive content localization, admin panels for editing each language, AI translation interface with automatic updates when base language version is changed. - SEO tags for all language URLs - Uses accept-language by default, ?lang=en overrides temporarily - User edits patched on top of translations - RTL language support
LeoVasanko added 31 commits 2026-09-03 03:54:00 +00:00
- 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
- 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/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).
- 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/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.
- segments.py: a block of plain text with inline links now stays ONE wire
  segment (Mark/Span) — the link label crosses in sentence context, so
  translations come back grammatically coherent instead of incompatible
  snippets. join() re-inserts the link markdown into the translated block
  at weight-mapped positions (word units; CJK ideographs and kana runs
  count one unit each), degrading to the source label on empty slices and
  still rejecting markup injection. No sentinels on the wire: boundaries
  are found by text processing alone.
- translate.py: the Dispatcher tracks Span (offsets + link marks) for the
  in-flight job.
- LangSelect.vue: one language selector for the page and structure tabs
  (small flag, clean dropdown), v-modeled on the shell-wide editorLang —
  switching in either panel switches both AND the page preview. While the
  panel is open the selection overrides the normal language preferences
  (swapdoc.setLangOverride: loadPlain and pagerite.js fetches/prefetches
  pin ?lang=, the primary language by its own code); closing restores.
- reconnect.js: every socket (page/banner editors, analytics view, the
  activity channel) now connects through a staggered slot — simultaneous
  attempts at page load (Vite's HMR socket plus ours, repeated on every
  refresh) trip the browser's WebSocket throttling, leaving all sockets
  to the host "pending" for minutes, which was the recurring empty
  editor. A watchdog closes sockets stuck CONNECTING so they reschedule
  through the policy (jittered exponential backoff, reset only by a
  healthy connection) instead of hanging forever; a reconnected editor
  re-opens its document when the previous open died with its socket.
- ConnNote.vue: a status strip for the WebSocket-backed panels (page and
  banner editors, analytics view) — "connecting…" while the socket is
  pending (the staggered slot included), "reconnecting in ~N s…" during
  backoff, hidden once open. A pending/lost socket no longer reads as a
  silently empty or stale panel.
- PageEditor/BannerEditor: CodeMirror stays non-editable until the
  page/banner doc has been accepted — typing before it would be clobbered
  by the accept. A mere disconnect keeps the editor live: text stashes
  and pending saves flush on reconnect.
- Node.language sets an article's primary language, inherited down the
  hierarchy, configured per row in the structure tab; English is now a
  regular language in the lang tab, still the root fallback
- Flags map a base tag to the language's home country (en → GB,
  pt → PT); explicit variant tags resolve their own region
- Lang tab flag grid: geographic group rows, shading-only selection
The service stays connected full time; the GPU is held only while
translating. Loads at startup (backlog is likely after a downtime).
The model mangles Markdown it can see (Seed-X dropped the ** and the
glued-on colon in '**Pagerite**: ...'), so emphasis now crosses stripped,
inline in its sentence, and the syntax is weight-mapped back like links.
Also fix _place_marks swallowing trailing punctuation into the inner text
(end at the slice's last unit) — same-language round trips are now exact.
LeoVasanko merged commit d3e2196c83 into main 2026-09-03 03:54:28 +00:00
LeoVasanko deleted branch localization 2026-09-03 03:54:28 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: LeoVasanko/pagerite#1