read.markets/app
Giorgio Gilestro ee8384f1ba i18n: bilingual landing page (EN / IT) with auto-detect routing
Public landing page is now served in English and Italian via path-
prefixed URLs (/en/ and /it/), with the bare / detecting the
visitor's language and 302-ing to the right one.

Routing
-------
* GET / : if authed → dashboard (unchanged). Otherwise the visitor's
  language is resolved from (in priority order) the rtm.lang cookie,
  the Accept-Language header, the cf-ipcountry geolocation header,
  and finally DEFAULT_LANG, then a 302 redirects to /<lang>/.
* GET /en/ + /it/ : render the localised landing template, set the
  rtm.lang cookie (1-year, SameSite=Lax) so the next /-visit goes
  straight to the same translation. Logged-in users with user.lang
  set bypass detection for / (same priority chain — user.lang wins
  on every public surface they touch).

Storage
-------
* app/locales/<lang>.yaml — flat-ish nested copy files. YAML chosen
  so a future translator can edit without touching Python. Strings
  containing inline HTML (<strong>, <em>, <a>) are rendered with the
  Jinja `safe` filter in the template.
* app/services/locales.py — loads at startup, exposes get_locale()
  and detect_public_lang(). Wraps each YAML tree in a small _Dotted
  view so templates can write {{ t.hero.subhead }} (deliberately NOT
  a dict subclass — dict's built-in method names would shadow YAML
  keys like `items`).

Template + chrome
-----------------
* landing.html ported in full to {{ t.<key> }} references.
* public_base.html gets <html lang="…"> + hreflang link tags (en/it/
  x-default) when a route opts in via lang_switch=true. A tiny
  EN | IT link group lands in the public header, only visible on
  surfaces that opt in.
* public.css picks up the small lang-switch widget styles.

Scope (intentionally narrow)
----------------------------
* Only the landing page is localised. Pricing, terms, privacy,
  disclaimer, login, verify all stay English-only for now; their
  header chrome stays English too because localising labels there
  while the linked content is still EN would be a worse mismatch.
* When other public pages get translated, lang_switch=true on those
  routes will surface the same widget there with no template changes.

Tests
-----
* tests/test_locales.py covers YAML load parity (every active
  language has a file), dotted access through the tree, the
  detection precedence chain, and the unknown-locale fallback.

Deps
----
* pyyaml was already in requirements.lock as a transitive but not
  declared. Added to pyproject so it stays pinned as an explicit
  direct dependency.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 17:10:45 +02:00
..
jobs review: gate strategic-log, portfolio, chat, and digest on reviewer 2026-05-29 14:40:04 +02:00
locales i18n: bilingual landing page (EN / IT) with auto-detect routing 2026-05-29 17:10:45 +02:00
routers i18n: bilingual landing page (EN / IT) with auto-detect routing 2026-05-29 17:10:45 +02:00
services i18n: bilingual landing page (EN / IT) with auto-detect routing 2026-05-29 17:10:45 +02:00
static i18n: bilingual landing page (EN / IT) with auto-detect routing 2026-05-29 17:10:45 +02:00
templates i18n: bilingual landing page (EN / IT) with auto-detect routing 2026-05-29 17:10:45 +02:00
__init__.py initial commit — cassandra v0.1 2026-05-15 21:56:10 +01:00
auth.py public: landing + pricing + legal pages, apex-ready, lawyer-reviewed 2026-05-24 00:08:02 +02:00
branding.py css: split cassandra.css into per-section files 2026-05-28 12:31:29 +02:00
cli.py docs: drop Phase D.x markers now that the referral loop is closed 2026-05-26 23:09:39 +02:00
config.py cleanup: drop stale tombstones and dead config fields 2026-05-27 19:25:33 +02:00
db.py sync: encrypted cloud backup for portfolios + settings UX rework 2026-05-23 16:15:54 +02:00
logging.py initial commit — cassandra v0.1 2026-05-15 21:56:10 +01:00
main.py i18n: bilingual landing page (EN / IT) with auto-detect routing 2026-05-29 17:10:45 +02:00
models.py models: align translation column naming + add token counts 2026-05-27 21:18:29 +02:00
redis_client.py phase G: data minimisation + passwordless auth + DeepSeek-first LLM 2026-05-18 14:16:57 +01:00
scheduler_main.py scheduler: register email_digest_job at 06:30 UTC 2026-05-25 23:20:06 +02:00
schemas.py news: auto-tag headlines + market-aware cadence + filter UI 2026-05-21 23:25:03 +01:00
templates_env.py mobile: cache-bust static assets so browser picks up CSS/JS edits 2026-05-28 19:20:49 +02:00