Per-node primary language; home-country flags; grouped lang grid

- 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
This commit is contained in:
2026-09-03 03:01:13 +00:00
parent 652f1f82ac
commit fb159f0cb0
14 changed files with 310 additions and 127 deletions
+30 -9
View File
@@ -12,6 +12,18 @@ parameter or the `Accept-Language` header.
## Phase 1: negotiation and URLs
### The primary language
Each article has a primary (original) language: `Node.language`, inherited
down the tree like `banner` — "" = the nearest ancestor's, the front page
last (it doubles as the site default), with `en` as the final fallback
(`ORIGINAL_LANGUAGE`, `primary_lang()` in `pagerite/i18n.py`). It is
configured per row in the structure editor. Everything per-article keys
off the resolved value: language selection, `<html lang>`, canonical URLs,
what counts as a translation, and the translation targets (a node's own
primary is never one — so the target set may include the site default, and
a page in another language can be translated into it).
### Language selection
Deliberately simple — **q-values are ignored**:
@@ -45,7 +57,7 @@ Region tags normalize to their base subtag (`fi-FI` → `fi`).
page — the site-wide configured languages (`translate_langs`, which the
translator works to fill in): `x-default` first, pointing at the plain
autodetecting URL, then every language explicitly with `?lang=`, the
default language included.
page's own primary language included.
- The override sticks for the session of clicks: a page requested with
`?lang=` replicates the query onto the navigation links it renders (nav,
sidebar, cards, brand — in-article links are content and stay as
@@ -223,7 +235,9 @@ The page and structure editors share one language selector (`LangSelect.vue`:
a small flag button opening a dropdown; the same country-flag-icons set as
the analytics visitor cells), v-modeled on one shell-wide selection
(`editorLang.js`, `''` = the primary language). The page editor lists the
primary language and the union of the page's translations (`node.langs`) and
page's own primary language (`Node.language`, resolved through the
hierarchy and echoed in the WS doc as `primary_lang`) plus the union of
the page's translations (`node.langs`) and
the site-wide `translate_langs`; it always opens in the primary language,
even when the page itself was served in a translation. A note under the
toolbar states the blast radius:
@@ -233,9 +247,10 @@ local to that language.
While the editor panel is open, its language selection **overrides the
normal language preferences** for the page preview: EditorShell pins every
in-place re-render and pagerite.js fetch/prefetch to it (`?lang=`the
primary language pins by its own code, which `select_language` honors), and
closing the panel restores the normal preferences.
in-place re-render and pagerite.js fetch/prefetch to it (`?lang=`a
primary selection pins by the current page's own resolved primary, which
`select_language` honors), and closing the panel restores the normal
preferences.
- WS `open` with a `lang` returns the effective **hybrid** Markdown and
title for that language (ungated by `node.langs` — a language without
@@ -264,7 +279,11 @@ closing the panel restores the normal preferences.
The **structure editor** selects from the same languages with the same
`LangSelect` (the selection is shared — switching in either tab switches
both, and the preview). The tree it
both, and the preview). It is also where a page's **primary language** is
configured: each row carries a small flag dropdown (the resolved flag,
dimmed while inherited) that sets `Node.language` via a structure op —
'' = inherit, so setting it on a section covers the whole subtree. The
tree it
lists (`GET /_api/pages?lang=`) comes back with per-language titles where a
translation exists (`translated` marks those rows; untranslated rows show
the original title, dimmed). Retitling in a non-primary language posts the
@@ -308,9 +327,11 @@ Frames are JSON-encoded tagged msgspec structs (`pagerite/translate.py`;
Which languages get translated is **server-configured**:
`Data.translate_langs` (presence-key dict, bootstrapped to Spanish and
Chinese — the original language is never a target — edited in the editor
shell's localization tab or set via `/_api/settings` as `translate_langs`).
The dispatcher offers a
Chinese — edited in the editor shell's localization tab, whose flag grid
lists every language including English, or set via `/_api/settings` as
`translate_langs`). A target equal to an article's own primary language is
skipped per article (its original already is that language), so the set
may freely contain the site default. The dispatcher offers a
connection jobs only in `wanted ∩ capable`; a connection without overlap
simply stays idle.