feat: add translators theme option alongside authors - #427
Conversation
Adds a `translators` option, parallel to the existing `authors`, taking a
list of `{name, url}` entries and rendering them below the page title so
translated editions can credit the people who produced them.
Translators render behind a visible label because two adjacent unlabelled
name lists would be ambiguous. 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; the
matching `authors_label` exists but defaults to empty, so a site that
sets neither option renders byte-identical HTML.
Either list can 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.
Front matter is read from the source file rather than through
`app.env.metadata`. docutils treats `authors` as a bibliographic field,
so a value routed through that path arrives smart-quoted and split on
commas, and in a different shape again once `language` is set to a locale
whose bibliographic fields do not include `authors`. A feature whose
purpose is non-English editions cannot sit on a code path that changes
type when you set `language`. Verified identical across en, zh_CN, fa, es
and ja builds.
For the same reason front matter is stricter than `html_theme_options`:
only the documented list-of-mappings form counts as an override. A page
already carrying an `authors` value written for docutils or for
nbformat's notebook schema keeps the project credit instead of having it
rendered badly or dropped.
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. `page-header.js` no longer returns early when a page has no
authors paragraph, which is newly possible, so the landing page keeps the
styling paragraph and its accent border.
Part of the cross-theme attribution work tracked in QuantEcon/meta#360.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #427 +/- ##
=======================================
Coverage ? 53.62%
=======================================
Files ? 2
Lines ? 511
Branches ? 0
=======================================
Hits ? 274
Misses ? 237
Partials ? 0
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR adds first-class translator attribution to the QuantEcon Book Theme, mirroring the existing authors byline while supporting per-page overrides via MyST/notebook front matter and preserving backwards-compatible output for sites that don’t configure the new options.
Changes:
- Added
translators/translators_labeltheme options (plusauthors_label) and rendered translators as a distinct, labelled byline under the page title. - Implemented front-matter reading from source files (Markdown YAML block / notebook metadata) and resolution logic so page-level values replace project-level values, with explicit empty lists suppressing output.
- Added styling for translators, adjusted landing-page header JS behavior, and introduced a comprehensive test suite for attribution behavior.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
tests/test_translators.py |
New unit + end-to-end Sphinx-build tests covering attribution normalization, resolution, and rendering. |
src/quantecon_book_theme/theme/quantecon_book_theme/theme.conf |
Declares new theme options and defaults (authors_label, translators, translators_label). |
src/quantecon_book_theme/theme/quantecon_book_theme/layout.html |
Renders authors/translators blocks and adds rel="author" to author links. |
src/quantecon_book_theme/assets/styles/_page.scss |
Adds translators byline styling (spacing, font size, color). |
src/quantecon_book_theme/assets/styles/_dark-theme.scss |
Sets dark-mode translators text color via --qe-dark-text-muted. |
src/quantecon_book_theme/assets/scripts/page-header.js |
Ensures landing page styling paragraph is inserted even when authors are absent/suppressed. |
src/quantecon_book_theme/__init__.py |
Adds normalization + front-matter reading/caching and context resolution for authors/translators and labels. |
pyproject.toml |
Registers the new build pytest marker. |
docs/user/index.md |
Links new “Authors and Translators” user guide page. |
docs/user/configuration.md |
Documents the new translators configuration option. |
docs/user/authors.md |
New detailed documentation for authors/translators, labels, and per-page overrides. |
docs/developer/testing.md |
Documents the new attribution test module. |
CHANGELOG.md |
Notes the new feature and related fixes/behavior changes. |
Suppressed comments (2)
src/quantecon_book_theme/theme/quantecon_book_theme/layout.html:268
- Links opened with
target="_blank"should includerel="noopener noreferrer"to prevent reverse-tabnabbing. Since these author links already setrel, add the additional tokens here.
<a href="{{ author.url }}" rel="author" target="_blank"><span>{{ author.name }}</span></a>
src/quantecon_book_theme/theme/quantecon_book_theme/layout.html:270
- Links opened with
target="_blank"should includerel="noopener noreferrer"to prevent reverse-tabnabbing. Since these author links already setrel, add the additional tokens here.
<a href="{{ author.url }}" rel="author" target="_blank"><span>{{ author.name }}</span></a>,
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if value is None or (isinstance(value, (list, tuple, str)) and not value): | ||
| return [] |
| {% for author in theme_authors %} | ||
| {% if loop.last and theme_authors|length >= 2 %} | ||
| and <a href="{{ author.url }}" target="_blank"><span>{{ author.name }}</span></a> | ||
| and <a href="{{ author.url }}" rel="author" target="_blank"><span>{{ author.name }}</span></a> |
| {%- if theme_translators_label %}<span class="qe-page__header-translators-label">{{ theme_translators_label }}</span> {% endif -%} | ||
| {%- for translator in theme_translators -%} | ||
| {%- if not loop.first %}{% if loop.last %}{% if theme_translators|length > 2 %},{% endif %} and {% else %}, {% endif %}{% endif -%} | ||
| {%- if translator.url %}<a href="{{ translator.url }}" target="_blank"><span>{{ translator.name }}</span></a>{% else %}<span>{{ translator.name }}</span>{% endif -%} |
🎭 Visual Regression Test ResultsDetails
Skipped testsdesktop-chrome › theme.spec.ts › Visual Regression Tests › prob-matrix - full page screenshot |
Description
Adds a
translatorsoption, parallel to the existingauthors, taking a list of{name, url}entries and rendering them below the page title so translated editions can credit the people who produced them. Closes #426.Translators render behind a visible label, because two adjacent unlabelled name lists would be ambiguous. The label defaults to "Translated by" and is overridable via
translators_labelso each edition renders it in its own language. Authors keep their current label-free rendering — the matchingauthors_labelexists but defaults to empty — so a site that sets neither option renders byte-identical HTML.Either list can be overridden per page in MyST or notebook front matter. That is the case that motivated per-page support: translation is naturally per-lecture work, so the project-level option carries the default credit and each page names whoever actually produced it. A page value replaces the project value rather than merging with it, an explicit empty list leaves the block out on that page (the way to mark a page with no human translator yet), and an absent key inherits the project value.
Part of the cross-theme attribution work tracked in QuantEcon/meta#360.
Why front matter is read from the source file
Front matter is read directly from the page's source rather than through
app.env.metadata. docutils treatsauthorsas a bibliographic field, so a value routed through that path comes back smart-quoted and split on commas — and in a different shape again oncelanguageis set to a locale whose bibliographic fields do not includeauthors. Three shapes for one key, selected by configuration the theme does not control. A feature whose whole purpose is non-English editions cannot sit on a code path that changes type when you setlanguage. Verified identical across en, zh_CN, fa, es and ja builds.For the same reason front matter is stricter than
html_theme_options: only the documented list-of-mappings form counts as an override.authorsis shared ground — docutils owns 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 rather than having it rendered badly or silently dropped.Type of Change
Testing
New
tests/test_translators.py(98 tests) covers the theme.conf declarations, the template markup, the normalisation and resolution helpers as units, the front-matter readers, and end-to-end rendering through real Sphinx builds. Full suite: 180 passed.The backwards-compatibility claim was verified by diffing real builds of
tests/sites/baseagainstmain, with asset digests normalised since adding SCSS necessarily changes the bundle hash:mainauthors, notranslatorsauthorsconfiguredrel="author"on author anchors, nothing elseAlso verified: builds under
languageset to en, zh_CN, fa, es and ja all resolve page overrides identically; the docs site builds with only its three pre-existing warnings; the landing page keeps its styling paragraph and accent border when a page suppresses its authors (checked in Chromium, not just in the markup).Screenshots
The header with both lines, project-level
authorsplustranslators, and per-page localised labels (作者/译者):Authors render at the existing size and colour; translators sit below at 0.95rem in a muted tone (
#6a6a6a, 5.3:1 on white) with--qe-dark-text-mutedin dark mode. Labels are deliberately not italicised — italic on CJK text triggers synthetic oblique, which is exactly the case this feature exists for.Nothing moves for a site that does not set the new options, so no visual baselines change.
Also in this PR
Author links now carry
rel="author", which they did not before. It is invisible on screen, so it moves no visual snapshot and could otherwise slip past review as a no-op.An
authorsvalue given as a bare string used to be iterated character by character, emitting one empty anchor per character. Both lists now normalise a bare string, a list of strings, or a single mapping into the documented form. This is a silent behaviour change for anyone currently passing a bare string, and strictly better.page-header.jsno longer returns early when a page has no authors paragraph. That was previously impossible and is now reachable viaauthors: [], and the early return skipped inserting the paragraph that carries the landing page's accent border — which the function's own comment says should be inserted "even if no authors for styling".Follow-ups, deliberately not in this PR
Translators are not cloned onto the landing page, where only the authors line is moved up beneath the title. Doing so means a second paragraph plus relocating the accent border, which is a real design change. Documented as a limitation.
The list connector (
,andand) stays English regardless of the configured language, so a Chinese edition reads "A, B, and C" rather than "A、B、C". Only the labels are localisable today. Worth settling cross-theme in QuantEcon/meta#360 since all three themes will have the same gap.No fixtures demo is included. Adding
translatorsto the fixtures_config.ymlwould give a live preview and visual coverage but invalidates a large share of the visual baselines, so it is better done on its own.Checklist
docs/user/authors.md,configuration.md, testing docs, CHANGELOG)🤖 Generated with Claude Code