Skip to content

Commit 717b38b

Browse files
kwagyemanclaude
andcommitted
docs: add RTL (Arabic/Hebrew) layout support, disabled pending native QA
A working first pass at right-to-left layout for ar/he: - static/rtl.css mirrors the Shibuya chrome the flexbox layout can't (lists, admonitions, blockquotes, sidebar chevron, copy-page dropdown, search box, prev/next chevrons, mobile drawers) and keeps code blocks + API signatures left-to-right. - templates/partials/extra-head.html emits <html dir="rtl"> before paint. - conf.py exposes text_direction per build. It is turned OFF (RTL_ENABLED = False): the theme has no native RTL support so this is an open-ended retrofit, it touches only 2 of 21 languages, and it needs a native Arabic/Hebrew reader to QA. ar/he ship with the LTR layout (their text still reads RTL via the browser's bidi algorithm). Kept in-tree so re-enabling is a one-line flag. See the RTL note block in conf.py. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent f1d976c commit 717b38b

3 files changed

Lines changed: 217 additions & 0 deletions

File tree

docs/conf.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,9 @@ def _render_landing_code(src):
550550
# Add a custom CSS file for HTML generation
551551
html_css_files = [
552552
"custom.css",
553+
# Right-to-left mirroring for Arabic/Hebrew. Every rule is scoped to
554+
# [dir="rtl"] (set by partials/extra-head.html), so it is inert otherwise.
555+
"rtl.css",
553556
]
554557
# Client-side locale auto-selection: redirect first-time visitors to the
555558
# build matching their browser language (English at root is the fallback),
@@ -706,6 +709,26 @@ def _render_landing_code(src):
706709
intersphinx_mapping = {"python": ("https://docs.python.org/3", None)}
707710

708711

712+
# =============================================================================
713+
# Right-to-left layout (Arabic, Hebrew) -- DISABLED, future work
714+
# =============================================================================
715+
# A working first pass at RTL mirroring exists in the tree -- static/rtl.css and
716+
# templates/partials/extra-head.html (emits <html dir="rtl">) -- but it is
717+
# turned OFF here. Reasons it is parked rather than shipped:
718+
# * The Shibuya theme has no native RTL support, so this is an open-ended set
719+
# of overrides on its (minified) chrome; new edge cases keep surfacing
720+
# (mobile drawers, dropdown spacing, chevrons, ...), and a theme update can
721+
# silently re-break them.
722+
# * It only affects 2 of the 21 translations, and neither maintainer reads
723+
# Arabic/Hebrew, so the layout cannot be properly QA'd here.
724+
# * Arabic/Hebrew already read correctly with the LTR layout: the browser's
725+
# bidi algorithm flows the translated text right-to-left within the page.
726+
# To revive it: set RTL_ENABLED = True, rebuild ar/he, and have a native
727+
# Arabic/Hebrew speaker review the result. rtl.css / extra-head.html are kept
728+
# in place (inert) so re-enabling is just this flag.
729+
RTL_ENABLED = False
730+
731+
709732
# =============================================================================
710733
# Per-language build date
711734
# =============================================================================
@@ -719,6 +742,7 @@ def setup(app):
719742
date (month names, ordering, and digits) in the hero badge and footer
720743
instead of "17 Jun 2026" everywhere.
721744
"""
745+
from babel.core import Locale
722746
from babel.dates import format_date
723747

724748
def localize_build_date(app, pagename, templatename, context, doctree):
@@ -729,6 +753,22 @@ def localize_build_date(app, pagename, templatename, context, doctree):
729753
)
730754
except Exception:
731755
pass # unknown locale -> keep the English default already in context
756+
# Expose the writing direction so partials/extra-head.html can flip the
757+
# document to RTL for Arabic/Hebrew (the Shibuya theme does not emit a
758+
# dir attribute itself). rtl.css then mirrors the chrome.
759+
#
760+
# DISABLED (future work): the RTL layout retrofit is intentionally
761+
# parked -- see RTL_ENABLED below. While disabled this always resolves
762+
# to "ltr", so Arabic/Hebrew ship with the left-to-right layout (their
763+
# text still reads right-to-left via the browser's bidi algorithm) like
764+
# every other translation.
765+
direction = "ltr"
766+
if RTL_ENABLED:
767+
try:
768+
direction = Locale.parse(lang).text_direction
769+
except Exception:
770+
direction = "ltr"
771+
context["text_direction"] = direction
732772

733773
def localize_nav_links(app, pagename, templatename, context, doctree):
734774
# The navbar (Shibuya's ``theme_nav_links``) renders the configured

docs/static/rtl.css

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
* Right-to-left (Arabic, Hebrew) layout for the Shibuya theme.
3+
*
4+
* partials/extra-head.html sets <html dir="rtl"> for RTL builds. The page body
5+
* (.sy-page) is a flexbox row [left sidebar | main | right TOC], so dir="rtl"
6+
* already mirrors the column order automatically -- the nav tree moves to the
7+
* right and the "on this page" panel to the left. These rules fix the
8+
* remaining details that use *physical* left/right and therefore do not follow
9+
* dir on their own, and -- importantly -- keep code blocks and API signatures
10+
* left-to-right.
11+
*
12+
* This file is loaded for every language but every rule is scoped to
13+
* [dir="rtl"], so it is completely inert for left-to-right builds.
14+
*
15+
* STATUS: parked / future work. The RTL layout is currently DISABLED via
16+
* RTL_ENABLED = False in conf.py, so <html dir="rtl"> is never emitted and none
17+
* of these rules apply. See the "Right-to-left layout ... DISABLED" note in
18+
* conf.py for why. Kept here so re-enabling is a one-line change.
19+
*/
20+
21+
/* ── Keep code and API signatures left-to-right ──────────────────────────
22+
Source code, REPL output, and Python signatures are always LTR, even on an
23+
RTL page. Inline <code> inside prose is left alone; the bidi algorithm
24+
already renders it correctly within the right-to-left text run. */
25+
[dir="rtl"] .highlight,
26+
[dir="rtl"] .highlight pre,
27+
[dir="rtl"] pre,
28+
[dir="rtl"] .highlighttable,
29+
[dir="rtl"] .doctest,
30+
[dir="rtl"] .sig {
31+
direction: ltr;
32+
text-align: left;
33+
}
34+
/* Signature hanging-indent is authored for LTR; restore its physical paddings. */
35+
[dir="rtl"] .sig {
36+
text-indent: -2.4rem;
37+
padding: 0.25rem 0.5rem 0.25rem 3rem;
38+
}
39+
40+
/* ── List indentation moves to the inline-start (right) side ─────────────── */
41+
[dir="rtl"] .yue ul,
42+
[dir="rtl"] .yue ol {
43+
padding-left: 0;
44+
padding-right: 1.625em;
45+
}
46+
[dir="rtl"] .yue ol > li,
47+
[dir="rtl"] .yue ul > li {
48+
padding-left: 0;
49+
padding-right: 0.375em;
50+
}
51+
52+
/* ── Admonitions: accent border + title bar on the right edge ────────────── */
53+
[dir="rtl"] .admonition {
54+
border-left: 0;
55+
border-right: 4px solid var(--color-3);
56+
}
57+
[dir="rtl"] .admonition-title {
58+
margin-left: -1rem;
59+
margin-right: -19px;
60+
}
61+
[dir="rtl"] .admonition.deprecated::before,
62+
[dir="rtl"] .deprecated::before {
63+
left: auto;
64+
right: -12px;
65+
}
66+
67+
/* ── Block quotes: quote bar on the right ────────────────────────────────── */
68+
[dir="rtl"] blockquote {
69+
border-left: 0;
70+
border-right: 0.25rem solid var(--yue-c-quote-border);
71+
padding-left: 0;
72+
padding-right: 1rem;
73+
}
74+
75+
/* ── Left navigation tree: expand chevron + its reserved gutter flip ──────── */
76+
[dir="rtl"] .globaltoc li > button {
77+
right: auto;
78+
left: 0;
79+
}
80+
[dir="rtl"] .globaltoc li:has(> button) > a {
81+
padding-right: 0;
82+
padding-left: 1.6rem;
83+
}
84+
85+
/* ── Horizontal chevrons point the other way ─────────────────────────────
86+
The prev/next page navigation uses chevron-left (Previous) and
87+
chevron-right (Next); in RTL "previous" is to the right and "next" to the
88+
left, so mirror these horizontal chevrons. Vertical chevrons (down/up, used
89+
by dropdowns and the sidebar expanders) are left alone. */
90+
[dir="rtl"] .i-lucide.chevron-left,
91+
[dir="rtl"] .i-lucide.chevron-right {
92+
transform: scaleX(-1);
93+
}
94+
95+
/* ── "Copy page" dropdown + button align to the inline-start edge ─────────── */
96+
[dir="rtl"] #copy-page-content {
97+
right: auto;
98+
left: 0;
99+
}
100+
[dir="rtl"] .copy-page-wrapper.lg\:absolute {
101+
right: auto;
102+
left: 1.5rem;
103+
}
104+
/* The H1 reserves space for the copy-page button with padding-right (button
105+
on the right in LTR). In RTL the button is on the left, so reserve there
106+
instead or the right-aligned title runs underneath it. */
107+
[dir="rtl"] .copy-page-wrapper + .yue > section > h1:first-of-type {
108+
padding-right: 0;
109+
padding-left: 180px;
110+
}
111+
/* Dropdown rows: the icon's gap is authored as margin-right (icon-on-left);
112+
in RTL the icon is on the right, so move the gap to its inline-start side
113+
or the label collides with the icon. Same for the trailing external-link. */
114+
[dir="rtl"] #copy-page-content .iconify-icon,
115+
[dir="rtl"] #copy-page-content iconify-icon {
116+
margin-right: 0;
117+
margin-left: 0.5rem;
118+
}
119+
[dir="rtl"] #copy-page-content a::after {
120+
margin-left: 0;
121+
margin-right: 0.1rem;
122+
}
123+
124+
/* ── Search box: the keyboard-shortcut hint moves to the inline-start edge ──
125+
It is pinned right:0, which is where the right-aligned RTL query text
126+
starts, so the text runs under it. Move it to the left in RTL. */
127+
[dir="rtl"] .searchbox kbd,
128+
[dir="rtl"] .searchbox button,
129+
[dir="rtl"] .searchform input[name="q"] + button {
130+
right: auto;
131+
left: 0;
132+
}
133+
134+
/* ── Mobile off-canvas drawers come in from the mirrored side ─────────────
135+
Scoped to the mobile breakpoint only. On desktop these sidebars are static
136+
flex items that dir="rtl" already mirrors, so touching their transform/
137+
position here would push them off-canvas. */
138+
@media (max-width: 1279px) {
139+
[dir="rtl"] #lside {
140+
left: auto;
141+
right: 0;
142+
transform: translate(100%);
143+
}
144+
[dir="rtl"] #rside {
145+
right: auto;
146+
left: 0;
147+
transform: translate(-110%);
148+
box-shadow: 12px 0 16px var(--gray-a1);
149+
}
150+
/* Open state: slide the drawer in. The theme's #lside[aria-hidden=false]
151+
rule has the same specificity as our closed-state rule above and, being
152+
loaded first, would lose by source order -- leaving the drawer off-screen
153+
(a visible overlay but no content). Re-assert the open transform here. */
154+
[dir="rtl"] #lside[aria-hidden="false"],
155+
[dir="rtl"] #rside[aria-hidden="false"] {
156+
transform: translate(0);
157+
}
158+
[dir="rtl"] .rside-close {
159+
right: auto;
160+
left: 16px;
161+
}
162+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{#-
2+
Set the document direction to RTL for right-to-left languages (Arabic,
3+
Hebrew). The Shibuya theme renders <html> without a dir attribute, so we add
4+
it here. Done with a tiny head script -- which runs before first paint, the
5+
same pattern the theme uses for its color-mode script -- so there is no flash
6+
of left-to-right layout. rtl.css (loaded for every build, scoped to
7+
[dir="rtl"]) mirrors the chrome once this attribute is set.
8+
text_direction is provided by conf.py's html-page-context hook.
9+
10+
STATUS: parked / future work. RTL is currently DISABLED (RTL_ENABLED = False
11+
in conf.py), so text_direction is always "ltr" and this script never runs.
12+
-#}
13+
{%- if text_direction == "rtl" -%}
14+
<script>document.documentElement.setAttribute("dir", "rtl");</script>
15+
{%- endif -%}

0 commit comments

Comments
 (0)