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
10 changes: 7 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ on:
# PRs. To bump the pin, update FIXTURES_SHA below.
env:
FIXTURES_REPO: QuantEcon/quantecon-book-theme-fixtures
# Includes the announcement-banner demo in the fixtures config (merged in
# fixtures #1), so the preview/visual build exercises the banner.
FIXTURES_SHA: 32763e43b893c1520df8e58c0c6e10d81db7f1ac
# Includes the announcement-banner demo (merged in fixtures #1) and the
# author/translator byline demo (fixtures #2), so the preview/visual build
# exercises both. Points at the fixtures #2 branch head rather than a merge
# commit: that PR's own self-check builds against the latest *released*
# theme, which does not know the `translators` option yet, so it stays red
# until this ships. Re-pin to the merge commit once it does.
FIXTURES_SHA: ab9ba206d48870e86b822c74380546e42fcba299

# Explicit least-privilege permissions:
# contents: write — checkout + upload-artifact + nwtgck/actions-netlify
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/update-snapshots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:
# "Resolve fixtures pin" step.
env:
FIXTURES_REPO: QuantEcon/quantecon-book-theme-fixtures
FIXTURES_SHA: 32763e43b893c1520df8e58c0c6e10d81db7f1ac
FIXTURES_SHA: ab9ba206d48870e86b822c74380546e42fcba299

jobs:
# /update-new-snapshots — only creates MISSING snapshots (safe for adding new tests)
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- **Dismissible announcement banner** — a new `announcement` theme option renders a notice at the top of every page (HTML allowed, so you can include emphasis and a link to changelog notes). Readers dismiss it with a `×`; the dismissal persists in `localStorage` and is keyed to a hash of the message, so editing the text re-shows the banner for everyone who dismissed the previous one. An optional `announcement_expires` ISO date (`YYYY-MM-DD`) auto-hides the banner after that day — enforced client-side (so it disappears for visitors even without a rebuild) and at build time (an already-expired notice is omitted from the HTML). An invalid expiry date logs a warning and fails open. Two looks are available via `announcement_style`: `bar` (default) — a thin full-width strip that scrolls away below the toolbar — or `callout` — a boxed in-column notice; both adapt to dark mode and RTL. The banner defaults to empty/off, so existing sites are unaffected. The renderer iterates a list of notices internally so per-page announcements can be added additively later (tracked in #403).
- **`translators` theme option** (#426) — a new `translators` option, parallel to `authors`, takes a list of `{name, url}` entries and renders them in the page header behind a visible label, so translated editions can credit the people who produced them. Authors keep the line directly under the page title and translators sit one row lower, right-aligned on the "Last changed" line and sharing its accent rule, so crediting them costs the header no extra vertical space; on narrow screens the two stack. The label defaults to "Translated by" and is overridable via `translators_label` so each edition renders it in its own language; authors keep their current label-free rendering, and the matching `authors_label` defaults to empty, so a site that sets neither renders byte-identical HTML. Either list can also be overridden per page in MyST or notebook front matter, which is how a team-translated edition credits whoever produced each lecture: a page value replaces the project value rather than merging with it, an explicit empty list leaves the block out on that page, and an absent key inherits the project value. Only the documented list-of-mappings form counts as an override, because `authors` is shared ground — docutils treats it as a bibliographic field and nbformat defines it in the notebook schema — so a page already carrying one written for something else keeps the project credit instead of rendering it badly or losing the byline. Author links now carry `rel="author"`; translator links deliberately do not, and the two lines carry distinct classes so downstream CSS can tell them apart. Front matter is read from the source file rather than through Sphinx's document metadata, because docutils treats `authors` as a bibliographic field and returns it in a shape that varies with the `language` setting — unworkable for a feature whose whole purpose is non-English editions. Part of the cross-theme attribution work tracked in QuantEcon/meta#360.

### Fixed
- **A malformed `authors` value no longer renders one empty link per character** — `authors` given as a bare string (rather than the documented list of `{name, url}` entries) was iterated character by character, emitting an empty anchor for each. Both `authors` and `translators` now normalise a bare string, a list of strings, or a single mapping into the documented form, and drop entries with no usable name.

### Documentation
- **Developer setup troubleshooting for stale `.nodeenv`** — documented the `nodeenv-version-mismatch` error (an in-repo `.nodeenv/` left over from an older pinned Node.js version) and its fix (`rm -rf .nodeenv` then rebuild), which otherwise blocks `tox` and editable installs locally. Also clarified that `tox` keeps the toolchain fully repo-local (`.tox/`, `.nodeenv/`, `node_modules/` are all git-ignored and regenerated), so nothing is installed into the base/global environment.
Expand Down
1 change: 1 addition & 0 deletions docs/developer/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ tests/
├── test_module_structure.py # Module organization tests
├── test_custom_colors.py # Color scheme tests
├── test_rtl_functionality.py # RTL language support tests
├── test_translators.py # Author and translator attribution tests
└── sites/ # Test site configurations
├── base/ # Basic test site
└── rtl_test/ # RTL-specific test site
Expand Down
138 changes: 138 additions & 0 deletions docs/user/authors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Authors and Translators

The theme renders author credits below the page title, and translated editions can
credit their translators in the page header alongside them.

```{contents}
:local:
:depth: 1
```

## Basic usage

Both options take a list of `{name, url}` entries. A translator without a `url`
renders as plain text; an author without one still renders as a link, as it always
has.

```python
html_theme_options = {
"authors": [
{"name": "Thomas J. Sargent", "url": "http://www.tomsargent.com/"},
{"name": "John Stachurski", "url": "https://johnstachurski.net/"},
],
"translators": [
{"name": "Zhang San", "url": "https://example.org/zhang"},
],
}
```

For Jupyter Book projects:

```yaml
sphinx:
config:
html_theme_options:
authors:
- name: Thomas J. Sargent
url: http://www.tomsargent.com/
translators:
- name: Zhang San
url: https://example.org/zhang
```

Authors keep the line directly beneath the page title. Translators sit one row
lower, right-aligned on the same line as the "Last changed" control and sharing its
accent rule, so crediting them does not add a fourth stacked line to the header. On
narrow screens the two stack instead. The two groups are never confused for one
another: author links carry `rel="author"`, which tells crawlers who wrote the page,
and translator links deliberately do not.

## Labels

Translators are introduced by a label, because an unlabelled second list of names
below the authors would be ambiguous. Authors have no label by default — the names
directly under the title already read as the authors.

| Option | Default | Purpose |
|--------|---------|---------|
| `translators_label` | `Translated by` | Introduces the translators line |
| `authors_label` | *(empty)* | Adds a label to the authors line when set |

Set `translators_label` to render the credit in the edition's own language:

```python
html_theme_options = {
"translators_label": "译者",
}
```

Setting `authors_label` to an empty string (the default) omits the authors label
entirely, which is how every existing site renders today.

## Per-page overrides

A page can set its own `authors` and `translators` in front matter. This matters for
a translated edition produced by a team, where each lecture is the work of a
different translator: the project-level option carries the default credit, and each
page names whoever actually did that lecture.

```yaml
---
translators:
- name: Li Si
url: https://example.org/li
---
```

A page value **replaces** the project value rather than merging with it, so a page
that wants to credit both a coordinator and its own translator must list both.

| Front matter | Result on that page |
|--------------|---------------------|
| key absent | inherits the project-level value |
| `translators: []` | the block is left out on that page |
| `translators:` (no value) or `translators: ""` | likewise left out |
| `translators: [{name: …}]` | replaces the project value entirely |
| any other shape | ignored — inherits the project-level value |

Any explicitly empty value is the way to mark a page that has no translator yet —
an untranslated or machine-translated page in an otherwise translated edition
should not carry a human's name. An empty list is the clearest form, but a bare key
and an empty string mean the same thing, since all three say "nobody" rather than
"I have nothing to say about this".

`authors_label` and `translators_label` can be overridden per page in the same way.

Overrides must use the list-of-mappings form shown above. `authors` in particular is
shared ground — docutils treats it as a bibliographic field and nbformat defines it
in the notebook schema — so a page may already carry an `authors` value written for
something else. A value this theme cannot read is left alone rather than rendered
badly or treated as suppression, and the project-level credit still shows.

Overrides are read from the source file's front matter, so they work in MyST
markdown and Jupyter notebooks. In a notebook the keys go in the notebook-level
`metadata`, which means a notebook that already sets nbformat's standard
`metadata.authors` is taken at its word. reStructuredText field lists cannot express
a list of mappings, so `.rst` pages always inherit the project-level value.

:::{note}
Quote any date-like value inside front matter (`since: "2020-01-01"`). An unquoted
YAML date anywhere in a page's front matter fails the build inside MyST, before the
theme sees it.
:::

## When nothing is configured

With no `translators` set, no translators block is rendered and the generated HTML
is unchanged from previous versions of the theme. This is fully backwards
compatible.

With no `authors` set, the theme falls back to printing Sphinx's own `author`
configuration value in the authors slot. A page that sets `authors: []` in its front
matter suppresses that fallback too, so the page shows no attribution at all.

## Limitations

Translators are shown on content pages but not on the landing page, where only the
authors line is moved up beneath the title. The list connector (`, ` and ` and `) is
English regardless of the configured language; only the labels are localisable.
17 changes: 17 additions & 0 deletions docs/user/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,23 @@ html_theme_options = {
}
```

## Add Translators

Translated editions can credit their translators alongside the authors:

```python
html_theme_options = {
...
"translators": [
{"name": "translator1", "url": "bio-link1"},
],
"translators_label": "Translated by",
...
}
```

See [Authors and Translators](authors.md) for per-page overrides and label localisation.

## Plugins

Add custom JavaScript plugins to extend or modify theme features:
Expand Down
1 change: 1 addition & 0 deletions docs/user/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ html_theme_options = {

configuration
announcements
authors
layout
notebooks
launch
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ additional-compiled-static-assets = [
testpaths = [
"tests"
]
markers = [
"build: runs a full sphinx-build in a subprocess, so it is slow",
]

[project]
name = "quantecon-book-theme"
Expand Down
Loading
Loading