Skip to content

🐛 Keep buttons intact under gettext translation - #270

Merged
chrisjsewell merged 3 commits into
mainfrom
brief/03-button-i18n
Jul 12, 2026
Merged

🐛 Keep buttons intact under gettext translation#270
chrisjsewell merged 3 commits into
mainfrom
brief/03-button-i18n

Conversation

@chrisjsewell

@chrisjsewellchrisjsewell commented Jul 12, 2026

Copy link
Copy Markdown
Member

Carries #264 by @sneakers-the-rat (commit preserved) with hardening and test coverage on top. Closes#96, closes#44, closes#263.

From #264 (the core fix): gettext now targets only the button text via a translatable inline (translatable=True) instead of the whole directive paragraph (translatable=False), so translation no longer replaces the styled button with plain text.

Added on top:

  • Crash fix: Sphinx's RemoveTranslatableInline transform unwraps the translatable placeholder, leaving a bare Text as the reference/xref child — an unresolvedbutton-ref in a translated project then crashed the build (Losing "classes" attribute assertion during reference resolution). The placeholder is now nested inside a plain inline wrapper, so an element child always remains. This also restores the pre-existing doctree shape, keeping HTML output identical to released behaviour.
  • Tests: .pot extraction regression; a translated-build round-trip (compiled .mo, language="de") asserting both button types keep classes and links and resolve refs with translated text, plus a badge control; rst snippet aligned with the myst one (they share regression files).
  • .gitignore exception for the new pot regression dir; changelog entry.

Known gap, deliberately out of scope: button-ref text is absent from .pot extraction because the gettext builder extracts from the resolved doctree, and std-domain xref resolution flattens explicit-title content (rebuilds it as a plain std std-ref inline), discarding the translatable marker. Translation itself works for both button types since the Locale transform runs before resolution. Documented in the test docstring; follow-up tracked in #265.

Validation: tox -e py311 (113 passed), py311-no-myst, mypy, ruff — all green.

@chrisjsewell
chrisjsewellforce-pushed the brief/03-button-i18n branch 2 times, most recently from 0a1ccad to f8792baCompareJuly 12, 2026 13:53
@codecov

codecovBot commented Jul 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.39%. Comparing base (ce1e0a9) to head (d3bbb47).

Additional details and impacted files
@@ Coverage Diff @@## main #270 +/- ##
==========================================
+ Coverage 88.08% 88.39% +0.31% 
==========================================
Files 11 11 Lines 1041 1043 +2 ==========================================
+ Hits 917 922 +5 + Misses 124 121 -3 
FlagCoverage Δ
pytests88.39% <100.00%> (+0.31%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

sneakers-the-ratand others added 3 commits July 12, 2026 14:00
Builds on the approach from #264: gettext targets only the button text
via a translatable inline, so translated button-link/button-ref keep
their classes and links (closes#96, #44, #263).
Additional hardening and coverage on top of #264:
- wrap the translatable placeholder inline in a plain inline, so the
reference always retains an element child after sphinx unwraps the
placeholder (an unresolved button-ref otherwise crashed the build
during reference resolution in translated projects)
- this also restores the pre-existing doctree shape (inline wrapper),
keeping HTML output identical to previous releases
- add gettext extraction regression (pot) and a translated-build
round-trip test proving classes/links/refs survive with translated
text, for both button types, plus a badge control
- align the rst button-link snippet with the myst one (both feed the
same regression files)
- note: button-ref text is still missing from pot extraction because
std-domain xref resolution flattens explicit-title content before the
gettext builder extracts; translation itself works (documented in the
test, follow-up tracked on the roadmap)
- add 'translatable'/'translated' to the boolean-attribute
normalization list, so regressions are stable across docutils
0.22 (1/0) and earlier (True/False) serializations
- define an 'issue' extlink for changelog references
@chrisjsewell
chrisjsewell merged commit 834eebe into mainJul 12, 2026
25 checks passed
@chrisjsewell
chrisjsewell deleted the brief/03-button-i18n branch July 12, 2026 14:05
chrisjsewell added a commit that referenced this pull request Jul 14, 2026
Two `button-ref` bugs, plus a parity improvement:
## Changes
- **#110 — targets no longer whitespace-stripped**: both button
directives ran their argument through `directives.uri`, which deletes
all whitespace — correct for `button-link` URLs, but it turned
multi-word ref targets (exactly what `autosectionlabel` generates, e.g.
`my article section`) into `myarticlesection` → `undefined label`. Each
directive now normalizes its own argument: `button-link` keeps
`directives.uri`; `button-ref` collapses whitespace runs like Sphinx's
`XRefRole` does. Additionally, `ref-type: ref` targets are lowercased to
match the `:ref:` role (`XRefRole(lowercase=True)` — std labels are
stored lowercased), so the visible Title-Case heading text can be pasted
verbatim; `doc`/`myst` targets stay case-sensitive.
- **#228 — rich content preserved through resolution**: the std-domain
resolvers rebuild explicit-title refs from `astext()`, flattening nested
inline markup (emphasis, icons rendered as escaped text). Fixed with a
stash/graft pair of post-transforms using only stable APIs: content is
stashed and the `pending_xref` tagged with a transient marker class
before any resolver runs (priority 8 — below myst-parser's resolver at 9
and the built-in at 10), then grafted back onto the resolved reference
(priority 11). Buttons whose content itself contains a cross-reference
role are deliberately left on the old flatten path — regrafting would
re-insert an unresolved `pending_xref` and crash the writer (and a link
inside a button link is invalid HTML anyway).
- Content-less `button-ref`s keep the resolver-generated section title
(guarded by `refexplicit`).
- Unresolved `button-ref`s degrade to a standard `undefined label`
warning with unlinked content — no traceback (locking in the hardening
from #270). Docs updated: the "bold text will be rendered without bold"
caveat is gone (it now renders), and targets may contain spaces.
## Verification
- New `tests/test_buttons.py` (11 tests, rst + myst): multi-word target
resolution (verified red on the old code: `undefined label:
'myarticlesection'`), Title-Case + `ref-type: ref`, nested-content
parity with `button-link`, nested-xref-in-content build safety (verified
red — reproduced the writer crash pre-fix), content-less title
rendering, unresolved warning path, and marker-leak assertions (HTML +
doctree) on every path.
- Independent adversarial review: marker-leak sweep across
HTML/latex/text/xml/gettext outputs *and* pickled doctrees/env (clean,
including incremental rebuilds), resolver-path matrix (std ref/doc, any,
myst resolving + failing, missing-ref), `sd-btn` class output
byte-identical to main outside the intended fixes, nitpicky-mode parity.
- Full suite green on pinned Sphinx 7.2.6 / 7.4.7 / 8.2.3 (145 passed
each, versions verified in-venv) and the standard matrix on 9.0.4. An
earlier draft used a private resolver API that doesn't exist before
Sphinx 8.2 — the final design deliberately uses only stable public
mechanics.
Closes#110Closes#228
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants

@chrisjsewell@sneakers-the-rat@claude