Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,5 @@ remix.config.js
/tests/visual/fixture/_build
/tests/visual/fixture-no-thebe/myst.yml
/tests/visual/fixture-no-thebe/_build
/tests/visual/fixture-rtl/myst.yml
/tests/visual/fixture-rtl/_build
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
the existing `--qe-literal-color`. Only the default scheme ships: no lecture
repo sets `color_scheme`, so the `gruvbox` / `none` switches and the
custom-CSS hook are deferred until a consumer asks for one. Phase 3, Goal B. ([#171](https://github.com/QuantEcon/quantecon-theme.mystmd/pull/171))
- Language switcher and `hreflang` alternates for translated editions: with
two or more entries in the new `languages` site option the toolbar gains a
globe-icon dropdown linking to the same page in each edition, the entry
matching `current_language` marked current, and every page carries
`<link rel="alternate" hreflang>` tags with the first entry as `x-default`.
Phase 4 of the book-theme parity plan
([#90](https://github.com/QuantEcon/quantecon-theme.mystmd/issues/90)). ([#174](https://github.com/QuantEcon/quantecon-theme.mystmd/pull/174))
- Right-to-left editions: `enable_rtl` sets `dir="rtl"` on the document (and
`current_language` now sets its `lang`, in place of the hard-coded `en`),
with the drawer, upstream content accents and spacing mirrored and code and
maths kept left-to-right. Phase 5
([#91](https://github.com/QuantEcon/quantecon-theme.mystmd/issues/91)). ([#174](https://github.com/QuantEcon/quantecon-theme.mystmd/pull/174))
- Translator credit in the page header from the new `translators` site option,
with a localisable `translators_label` (default "Translated by") and a
per-page override under `site:` in page frontmatter, on the shape settled
across the themes on QuantEcon/workspace-themes#3
([#143](https://github.com/QuantEcon/quantecon-theme.mystmd/issues/143)). ([#174](https://github.com/QuantEcon/quantecon-theme.mystmd/pull/174))
- The site options above are declared in `template.yml`. The MyST CLI drops
any `site.options` key the template does not declare, and can only declare
scalar types, so `languages` and `translators` are YAML written inside a
block string; see the README's "Multilingual editions". ([#174](https://github.com/QuantEcon/quantecon-theme.mystmd/pull/174))

## [2.5.0] - 2026-09-05

Expand Down
62 changes: 44 additions & 18 deletions PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ Derived from `quantecon-book-theme` v0.20.3 (see its `README.md`, `docs/user/*`,
| Launch parity — Thebe (live compute) in addition to Colab / private hub (BinderHub dropped, #26) | ✅ | ✅ | **2** |
| Code highlighting in the QE token palette (the Pygments-style toggle has no user; deferred) | ✅ | ✅ | **3** |
| Text colour scheme `seoul256` (default; `gruvbox` / `none` / custom have no user; deferred) | ✅ | ✅ | **3** |
| Language switcher (multilingual) + `hreflang` SEO tags | ✅ | ❌ | **4** |
| RTL support (`dir="rtl"`) | ✅ | ❌ | **5** |
| Language switcher (multilingual) + `hreflang` SEO tags | ✅ | ✅ | **4** |
| RTL support (`dir="rtl"`) | ✅ | ✅ | **5** |
| Translator credit (`translators` + localisable label; #143, QuantEcon/workspace-themes#3) | ✅ | ✅ | **4** |
| Collapsible stderr warnings in notebook cells | ✅ | ❓ verify | **6** |
| Full OpenGraph / Twitter card meta tags | ✅ | ⚠️ partial | **6** |
| **Already at parity:** dark mode, font scaling, fullscreen, search, "On this page" TOC + back-to-top, contents sidebar, downloads (PDF/notebook), Colab launch, edit-on-GitHub, author header, content-driven footer, responsive/mobile | ✅ | ✅ | — |
Expand All @@ -60,7 +61,8 @@ Derived from `quantecon-book-theme` v0.20.3 (see its `README.md`, `docs/user/*`,
[v2.1.0](https://github.com/QuantEcon/quantecon-theme.mystmd/releases/tag/v2.1.0)–[v2.2.0](https://github.com/QuantEcon/quantecon-theme.mystmd/releases/tag/v2.2.0); Phase 1 and the Thebe half of Phase 2
shipped in [v2.3.0](https://github.com/QuantEcon/quantecon-theme.mystmd/releases/tag/v2.3.0), and Phase 2's launch-config half shipped in
[v2.2.0](https://github.com/QuantEcon/quantecon-theme.mystmd/releases/tag/v2.2.0). Phase 3 landed on `main`
on 2026-09-07 (#89), unreleased. **Phases 4–5 are next.**
on 2026-09-10 (#89, #171), unreleased; Phases 4–5 and the translator credit (#143) follow in
#174. **Phase 6 is next.**

---

Expand Down Expand Up @@ -371,7 +373,7 @@ properly is tracked as future work — see the enhancement issues linked from #8

## Phase 3 — Code highlighting + text colour schemes

**Status: complete** *(2026-09-07, #89 via #171)* — defaults only, by design. Before building,
**Status: complete** *(2026-09-10, #89 via #171)* — defaults only, by design. Before building,
the scoping pass on #89 found that no lecture repo sets `qetheme_code_style` or
`color_scheme`: every live site runs the defaults, so parity is the default rendering,
applied unconditionally, and the switch surface (Pygments toggle, `gruvbox` / `none`,
Expand Down Expand Up @@ -410,6 +412,14 @@ between custom QuantEcon token colours and any built-in Pygments style.

## Phase 4 — Internationalisation (language switcher + hreflang)

**Status: complete** *(#90 via #174; translator credit #143 shipped with it)*. One
engine fact shaped the configuration: the MyST CLI validates `site.options` against the
template's declared options and **drops every undeclared key** (myst-templates
`validateTemplateOptions`), and it can declare only scalar types. So the options are
declared in `template.yml`, and the two lists (`languages`, `translators`) are YAML written
inside a block string, parsed by `app/i18n.ts`; a real list is accepted too. See README
"Multilingual editions".

**Goal:** book-theme v0.20.0 globe-icon dropdown to switch between translated lecture
sites, plus `<link rel="alternate" hreflang>` head tags for SEO. Only renders with 2+
languages configured.
Expand All @@ -418,27 +428,43 @@ hreflang block + language-switcher markup, `assets/scripts/language-switcher.js`
`assets/styles/_language-switcher.scss`, `docs/user/rtl-support.md`,
`docs/developer/multilingual.md` + `infrastructure.md`.

- [ ] Add a `languages` config (list of `{code, name, url}`) to `myst.yml`/site config,
surfaced to the theme via the site manifest loader (`loaders.server.ts`).
- [ ] New toolbar `LanguageSwitcher.tsx` (Radix dropdown), placed consistently in
`Toolbar.tsx` / `MobileActionsMenu.tsx`, with keyboard nav + active-language marker.
- [ ] Inject `hreflang` alternates in `root.tsx` `links`/`meta` for each language +
`x-default`.
- [x] `languages` (`{code, name, url}` list, 2+ entries to render) and `current_language`
under `site.options`, declared in `template.yml`; no loader change was needed once
declared, since the site manifest carries validated options.
- [x] `LanguageSwitcher.tsx` (Radix dropdown, globe icon) at the far end of `Toolbar.tsx` at
every width, rather than inside the overflow menu: it is the one action a reader of a
translated edition reaches for. Keyboard navigation from Radix; the current edition
carries `aria-current` and a check mark; items are real anchors with `hreflang`.
- [x] `hreflang` alternates (+ `x-default` on the first entry) from the two page routes'
`meta` (Remix v2 `tagName: 'link'`), not `root.tsx`: the alternates need the page path.
- [x] Translator credit (#143): `translators` / `translators_label` site options with a
per-page `site:` override (replace, never merge; explicit empty suppresses), rendered
at the end of the "Last changed" row as the book theme does since v0.22.0. Labels
(`translators_label`, `language_switcher_label`) are the localisation mechanism; the
list connector (", " / " and ") stays English, the same gap the book theme records.

**Effort:** M. **Risk:** low. **Deps:** none (independent of 1–3).

---

## Phase 5 — RTL support

**Status: complete** *(#91 via #174)*.

**Goal:** book-theme `enable_rtl` sets `dir="rtl"` on `<body>` and ships `_rtl.scss`.
**Reference:** book-theme `layout.html` `body_tag` block + `assets/styles/_rtl.scss` +
`docs/user/rtl-support.md`.

- [ ] Add a config flag; set `dir="rtl"` on the document in `root.tsx`.
- [ ] Audit Tailwind utilities for logical-property / RTL correctness (margins, the blue
left/right accents, toolbar ordering); add RTL overrides where physical properties
leak.
- [x] `enable_rtl` sets `dir="rtl"` (and `current_language` sets `lang`) on `<html>` in the
server render. Upstream's `Document` hard-codes `lang="en"` and has no `dir`, so the
theme carries a local copy (`app/components/Document.tsx`) that adds the two props; an
upstream prop is the candidate in `UPSTREAM-CANDIDATES.yml`. Radix's floating
positioning reads the CSS `direction`, so no `DirectionProvider` is needed.
- [x] Audit: the theme's own components now use logical utilities (`ms-`, `me-`, `pe-`), and
`styles/rtl.css` mirrors the physical utilities upstream content markup uses
(`border-l-4` accents, `pl-*`/`ml-*` spacing, the copy button), flips the drawer's
slide, and keeps code and maths left-to-right. Snapshot-tested on a Persian fixture
(`tests/visual/fixture-rtl`).

**Effort:** S–M. **Risk:** low. **Deps:** ideally after Phase 4 (often shipped together
for the same translated sites).
Expand Down Expand Up @@ -471,13 +497,13 @@ Phase 0 (hygiene/deploy + preview harness) ── prerequisite ✅ shipped
├─▶ Phase 1 Git history in headers ⭐ ✅ shipped v2.3.0
├─▶ Phase 2 Launch parity (Thebe + config) ✅ shipped v2.2.0 / v2.3.0
├─▶ Phase 3 Code highlight + colour schemes ✅ on main 2026-09-07 (unreleased)
├─▶ Phase 4 i18n (language switcher) ──▶ Phase 5 RTL (commonly shipped together) ← next
└─▶ Phase 6 Meta/SEO + stderr + docs
├─▶ Phase 3 Code highlight + colour schemes ✅ on main 2026-09-10 (unreleased)
├─▶ Phase 4 i18n (language switcher) ──▶ Phase 5 RTL ✅ via #174 (unreleased)
└─▶ Phase 6 Meta/SEO + stderr + docs ← next
```

Suggested order: **0 → 1 → 2 → 3 → (4 → 5) → 6**. Phases 0–2 are shipped as of
[v2.3.0](https://github.com/QuantEcon/quantecon-theme.mystmd/releases/tag/v2.3.0) (2026-08-20) and Phase 3 is on `main`, so **Phases 4–5 are next**. Phases 4–6 are largely
[v2.3.0](https://github.com/QuantEcon/quantecon-theme.mystmd/releases/tag/v2.3.0) (2026-08-20) and Phases 3–5 are on `main`, so **Phase 6 is next**. Phases 3–6 are largely
independent of one another and can be parallelised across contributors. Per open
question 4's resolution below, all of Phases 3–6 gate the all-at-once lecture
migration — they are cutover blockers, not optional polish.
Expand Down
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,52 @@ Notes:
`site.git_metadata` in the page frontmatter, which takes precedence over the
injected data (this is how the visual fixture keeps snapshots deterministic).

### Multilingual editions

A translated edition sets its language, direction and translators, and lists the
sibling editions, under `site.options` (all declared in `template.yml`):

```yaml
# myst.yml
site:
options:
current_language: fa # BCP 47 code: document lang, current entry in the switcher
enable_rtl: true # dir="rtl" on the document (Arabic, Persian, Hebrew, Urdu)
language_switcher_label: تغییر زبان # tooltip / accessible name (default "Switch language")
languages: |
- code: en
name: English
url: https://python-programming.quantecon.org
- code: fa
name: فارسی
url: https://quantecon.github.io/lecture-python-programming.fa
translators: |
- name: Adisankar Manoj Thanuja
url: https://www.linkedin.com/in/adisankar-m-t/
translators_label: ترجمهٔ # default "Translated by"; an empty string hides the label
```

- **`languages`** — with two or more entries the toolbar shows a globe-icon
switcher linking to the *same page* in each edition (that edition's `url` plus
the page path), and every page carries `<link rel="alternate" hreflang>` tags,
with the first entry as `x-default`. The entry matching `current_language` is
marked current.
- **`translators`** — credited in the page header, at the end of the "Last
changed" row, with the label in front. A page overrides the list in its own
frontmatter under `site:`; the page value replaces the site value (never
merges), and an explicitly empty value (`translators: ''`) suppresses the
credit on that page. The same values configure `quantecon-book-theme`.

`languages` and `translators` are written as YAML *inside a block string*
(`|`). The MyST CLI validates `site.options` against the template's declared
options and can only declare scalar types, so a bare list would be dropped; the
theme parses the block. A real list is accepted too, should the engine ever pass
one through.

One trap: a page whose `site:` frontmatter sets any of these keys has its whole
`site:` block replaced by the validated keys, so an undeclared `site.git_metadata`
on the same page is lost. Keep the two on different pages.

## Usage with MyST

Point your project's `site.template` at a **pinned release** zip:
Expand Down
48 changes: 42 additions & 6 deletions UPSTREAM-CANDIDATES.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,56 @@ upstream_candidates:

- id: language-switcher
title: Language switcher + hreflang alternates
status: planned
description: |
`languages` / `current_language` site options; a globe-icon Radix
dropdown in the toolbar linking to the same page in each edition
(app/components/toolbar/LanguageSwitcher.tsx), and `<link rel="alternate"
hreflang>` tags emitted from the page routes' `meta` (app/i18n.ts).
status: pending
target: jupyter-book/myst-theme
provenance: []
provenance:
- local_pr: 174
note: Phase 4 implementation, with Phase 5 and the translator credit
upstream:
pr: null
notes: |
Phase 4 (#90); not yet built. Record local PRs here as they land.
Phase 4 (#90). The option shape is constrained by the CLI: template
options are scalar-only, so the list is a YAML block in a string. An
upstream version would want a real list type on template options
(jupyter-book/mystmd) before the theme half is worth porting.

- id: rtl-support
title: RTL rendering (dir="rtl" config + logical-property audit)
status: planned
description: |
`enable_rtl` site option; `dir` and `lang` set on <html> in the server
render; styles/rtl.css mirrors the physical utilities upstream content
markup uses and keeps code and maths LTR.
status: pending
target: jupyter-book/myst-theme
provenance:
- local_pr: 174
note: Phase 5 implementation
upstream:
pr: null
notes: |
Phase 5 (#91). The most reusable piece upstream is the audit itself:
myst-to-react and @myst-theme/jupyter use physical `border-l-*` /
`pl-*` / `ml-*` utilities where logical ones (`border-s-*`, `ps-*`,
`ms-*`) would make RTL free. Shipped with language-switcher.

- id: document-lang-dir
title: Document accepts `lang` and `dir` props
description: |
@myst-theme/site's `Document` hard-codes `<html lang="en">` and sets no
`dir`. The theme carries a local copy (app/components/Document.tsx) that
adds the two props.
status: pending
target: jupyter-book/myst-theme
provenance: []
provenance:
- local_pr: 174
note: Local Document copy, taken for Phase 5
upstream:
pr: null
notes: |
Phase 5 (#91); not yet built. Commonly shipped with language-switcher.
A two-prop change to src/pages/Root.tsx upstream; once accepted, the
local copy can be deleted. Small enough to open ahead of the rest.
Loading
Loading