Sticky ?lang= navigation + site-wide hreflang/canonical tags
- 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.
This commit is contained in:
+25
-13
@@ -36,15 +36,24 @@ Region tags normalize to their base subtag (`fi-FI` → `fi`).
|
||||
|
||||
- Canonical URLs stay pretty (`/some-page`). Each language version is
|
||||
addressable as `/some-page?lang=fi` so search engines can index them.
|
||||
- `<link rel="canonical">` points to the page **itself including the query**
|
||||
(each language version is its own canonical).
|
||||
- `<link rel="alternate" hreflang="…">` entries point to every other language
|
||||
version (with `?lang=`), plus `x-default` for the plain URL.
|
||||
- On page load, pagerite.js removes the `?lang=` query via
|
||||
`history.replaceState` (restoring the pretty URL) but remembers the language
|
||||
in a JS variable. All fetch-navigation and preloads it performs afterwards
|
||||
send that language in the `Accept-Language` header, so the chosen language
|
||||
sticks for the session of clicks.
|
||||
- `<link rel="canonical">` names the **actually served language**: the plain
|
||||
URL when serving the original (for SEO the non-query URL means the
|
||||
article's own language), `?lang=xx` when serving a translation — however
|
||||
the language was arrived at (query or header).
|
||||
- `<link rel="alternate" hreflang="…">` entries follow the canonical
|
||||
directly (before the social meta tags) and are the same set on every
|
||||
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.
|
||||
- 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
|
||||
authored), so plain clicks and no-JS navigation keep the language.
|
||||
pagerite.js additionally strips the query from the address bar via
|
||||
`history.replaceState` (pretty, shareable URLs), remembers the language,
|
||||
and adds it to every internal fetch that lacks one (preloads,
|
||||
fetch-navigations, history traversals); history entries stay query-less.
|
||||
- A full page refresh or a shared link resets to automatic selection (header
|
||||
only). This gives a clean one-time override without cookies.
|
||||
|
||||
@@ -53,7 +62,10 @@ Region tags normalize to their base subtag (`fi-FI` → `fi`).
|
||||
- Content responses carry `Vary: accept-language` (added to the existing
|
||||
`accept-encoding` vary).
|
||||
- `_cached_body` and the page ETag include the **selected language** (not the
|
||||
raw header, which would blow up the cache key space).
|
||||
raw header, which would blow up the cache key space) and the **replicated
|
||||
link language**: a `?lang=fi` render and a header-selected Finnish render
|
||||
of the same page differ in their navigation links, so they are cached as
|
||||
separate variants.
|
||||
- `<html lang="…">` reflects the served language.
|
||||
|
||||
### Rendering
|
||||
@@ -188,9 +200,9 @@ def get_translation(path, lang, data) -> Translation | None:
|
||||
|
||||
- Availability is an article-level index: `node.langs: dict[lang, True]`,
|
||||
maintained by the translation writers (translator job, patch saves) in the
|
||||
same transaction as their data writes — rendering, language selection and
|
||||
hreflang never probe the `trans` store chunk by chunk. A stale key is
|
||||
benign (the "translation" just renders as the original).
|
||||
same transaction as their data writes — rendering and language selection
|
||||
never probe the `trans` store chunk by chunk. A stale key is benign (the
|
||||
"translation" just renders as the original).
|
||||
- `titles` for nav/sidebar/cards: each node's translated title is
|
||||
`trans.get(hash(node.title), {}).get(lang)` with per-node fallback — one
|
||||
dict lookup per nav item at render time.
|
||||
|
||||
Reference in New Issue
Block a user