diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index fdeb0382..a9058e36 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- - uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4
+ - uses: j178/prek-action@4e14d07f9231acabce116ccfca13b13dd9755ece # v3.0.0
diff --git a/.github/workflows/pypi-package.yml b/.github/workflows/pypi-package.yml
index c1d86b97..0c788ff7 100644
--- a/.github/workflows/pypi-package.yml
+++ b/.github/workflows/pypi-package.yml
@@ -20,12 +20,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
- - uses: hynek/build-and-inspect-python-package@d44ca7d91762de7a7d5436ddae667c6da6d1c3df # v2.18.0
+ - uses: hynek/build-and-inspect-python-package@2abe76da66d0a6a4a227101f9348ee855797cfa5 # v3.0.1
# Upload to real PyPI on GitHub Releases.
release-pypi:
@@ -49,4 +49,4 @@ jobs:
path: dist
- name: Upload package to PyPI
- uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 58a53f40..75f3844a 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -19,10 +19,10 @@ jobs:
- python-version: "3.15"
branch: "main"
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
@@ -66,10 +66,10 @@ jobs:
# Test minimum supported and latest stable from 3.x series
python-version: ["3.12", "3"]
steps:
- - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dbf11f5e..98fb5ee4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
# Changelog
+## [2026.9](https://github.com/python/python-docs-theme/releases/tag/2026.9)
+
+* Add support for right-to-left page layout by @sepehr-rs in https://github.com/python/python-docs-theme/pull/324
+* Improve contrast between unvisited and visited links in dark mode
+ by @StanFromIreland in https://github.com/python/python-docs-theme/pull/329
+* Keep breadcrumb navigation visible on mobile by @undeMalum in
+ https://github.com/python/python-docs-theme/pull/326
+* Add Persian translation by @sepehr-rs in https://github.com/python/python-docs-theme/pull/322
+
## [2026.7](https://github.com/python/python-docs-theme/releases/tag/2026.7)
* Add Vietnamese translation by @StanFromIreland in https://github.com/python/python-docs-theme/pull/309
diff --git a/README.md b/README.md
index 6d89793a..0e4d779b 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,8 @@ To use this theme, add the following to `conf.py`:
- `html_sidebars`, defaults taken from https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-html_sidebars
+- To lay pages out right-to-left for languages such as Persian, Arabic, or Hebrew,
+ set `html_theme_options = {"is_rtl": True}`. Defaults to `false`.
## Translation
diff --git a/python_docs_theme/__init__.py b/python_docs_theme/__init__.py
index f0ddc066..454d4499 100644
--- a/python_docs_theme/__init__.py
+++ b/python_docs_theme/__init__.py
@@ -11,7 +11,7 @@
from sphinx.application import Sphinx
from sphinx.util.typing import ExtensionMetadata
-__version__ = "2026.7"
+__version__ = "2026.9"
THEME_PATH = Path(__file__).resolve().parent
LOCALE_DIR = THEME_PATH / "locale"
diff --git a/python_docs_theme/layout.html b/python_docs_theme/layout.html
index 1903fad4..10232585 100644
--- a/python_docs_theme/layout.html
+++ b/python_docs_theme/layout.html
@@ -6,7 +6,7 @@
{{ _('Navigation') }}
{%- for rellink in rellinks %}
- -
+
-
{{ rellink[3] }}
@@ -14,7 +14,7 @@
{{ _('Navigation') }}
{%- endfor %}
{%- block rootrellink %}
-  }})
+  }})
- {{theme_root_name}}{{ reldelim1 }}
-
@@ -89,7 +89,7 @@
{{ _('Navigation') }}
{%- endblock -%}
{%- block body_tag %}
-{{ super() }}
+
{%- if builder != 'htmlhelp' %}
, 2026.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: python-docs-theme 2026.7\n"
+"Report-Msgid-Bugs-To: https://github.com/python/python-docs-theme/"
+"issues\n"
+"POT-Creation-Date: 2026-08-16 15:22+0330\n"
+"PO-Revision-Date: 2026-08-16 15:54+0330\n"
+"Last-Translator: Sepehr Rasouli
, 2026\n"
+"Language-Team: Persian (https://github.com/revisto/python-docs-fa/)\n"
+"Language: fa\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"Generated-By: Babel 2.18.0\n"
+"X-Generator: Poedit 3.6\n"
+
+#: python_docs_theme/footerdonate.html:1
+msgid "The Python Software Foundation is a non-profit corporation."
+msgstr "بنیاد نرمافزار پایتون یک شرکت غیرانتفاعی است."
+
+#: python_docs_theme/footerdonate.html:2
+msgid "Please donate."
+msgstr "لطفاً کمک مالی کنید."
+
+#: python_docs_theme/layout.html:6
+msgid "Navigation"
+msgstr "ناوبری"
+
+#: python_docs_theme/layout.html:51 python_docs_theme/layout.html:111
+msgid "Quick search"
+msgstr "جستوجوی سریع"
+
+#: python_docs_theme/layout.html:52 python_docs_theme/layout.html:112
+msgid "Go"
+msgstr "برو"
+
+#: python_docs_theme/layout.html:60
+msgid "Theme"
+msgstr "پوسته"
+
+#: python_docs_theme/layout.html:62
+msgid "Auto"
+msgstr "خودکار"
+
+#: python_docs_theme/layout.html:63
+msgid "Light"
+msgstr "روشن"
+
+#: python_docs_theme/layout.html:64
+msgid "Dark"
+msgstr "تاریک"
+
+#: python_docs_theme/layout.html:96
+msgid "Menu"
+msgstr "منو"
+
+#: python_docs_theme/layout.html:142
+msgid "Copyright"
+msgstr "حق نشر"
+
+#: python_docs_theme/layout.html:147
+msgid ""
+"This page is licensed under the Python Software Foundation License "
+"Version 2."
+msgstr "این صفحه تحت مجوز بنیاد نرمافزار پایتون نسخه ۲ منتشر شده است."
+
+#: python_docs_theme/layout.html:149
+msgid ""
+"Examples, recipes, and other code in the documentation are additionally "
+"licensed under the Zero Clause BSD License."
+msgstr ""
+"مثالها، دستورالعملها، و سایر کدهای موجود در مستندات همچنین تحت مجوز "
+"بدون بند BSD منتشر شدهاند."
+
+#: python_docs_theme/layout.html:152
+#, python-format
+msgid ""
+"See History and License for more "
+"information."
+msgstr ""
+"برای اطلاعات بیشتر به تاریخچه و مجوز "
+"مراجعه کنید."
+
+#: python_docs_theme/layout.html:155
+#, python-format
+msgid "Hosted on %(hosted_on)s."
+msgstr "میزبانی شده روی %(hosted_on)s."
+
+#: python_docs_theme/layout.html:163
+#, python-format
+msgid "Last updated on %(last_updated)s."
+msgstr "آخرین بهروزرسانی در %(last_updated)s."
+
+#: python_docs_theme/layout.html:166
+#, python-format
+msgid "Found a bug?"
+msgstr "آیا اشکالی یافتید؟"
+
+#: python_docs_theme/layout.html:170
+#, python-format
+msgid ""
+"Created using Sphinx "
+"%(sphinx_version)s."
+msgstr ""
+"ساخته شده با Sphinx "
+"%(sphinx_version)s."
+
+#: python_docs_theme/static/copybutton.js:30
+#: python_docs_theme/static/copybutton.js:55
+msgid "Copy"
+msgstr "کپی"
+
+#: python_docs_theme/static/copybutton.js:31
+msgid "Copy to clipboard"
+msgstr "کپی به تختهگیره"
+
+#: python_docs_theme/static/copybutton.js:53
+msgid "Copied!"
+msgstr "کپی شد!"
diff --git a/python_docs_theme/static/pydoctheme.css b/python_docs_theme/static/pydoctheme.css
index aa9622d0..44e5ce51 100644
--- a/python_docs_theme/static/pydoctheme.css
+++ b/python_docs_theme/static/pydoctheme.css
@@ -108,7 +108,7 @@ div.related a:hover {
}
.switchers > div {
- margin-right: 5px;
+ margin-inline-end: 5px;
}
div.related ul::after {
@@ -117,6 +117,17 @@ div.related ul::after {
display: block;
}
+div.related ul {
+ padding: 0;
+ padding-inline-start: 10px;
+}
+
+body[dir='rtl'] div.related ul li.right {
+ float: left;
+ margin-right: 0;
+ margin-left: 5px;
+}
+
.inline-search,
form.inline-search input {
display: inline;
@@ -142,6 +153,9 @@ span.pre {
div.sphinxsidebar {
display: flex;
width: min(25vw, 350px);
+ flex-shrink: 0;
+ margin: 0;
+ order: 0;
float: none;
position: sticky;
top: 0;
@@ -158,8 +172,14 @@ div.sphinxsidebar h4 {
margin-top: 1.5em;
}
-div.bodywrapper {
- margin-left: min(25vw, 350px);
+div.document div.bodywrapper {
+ margin: 0;
+}
+
+div.documentwrapper {
+ flex: 1;
+ min-width: 0;
+ order: 1;
}
div.sphinxsidebarwrapper {
@@ -179,6 +199,11 @@ div.sphinxsidebarwrapper > ul > li > ul > li {
margin-bottom: 0.4em;
}
+div.sphinxsidebar ul ul {
+ margin-left: 0;
+ margin-inline-start: 20px;
+}
+
div.sphinxsidebar a:hover {
color: #0095c4;
}
@@ -206,16 +231,23 @@ div.sphinxsidebar input[type='text'] {
border-left: none;
}
+body[dir='rtl'] #sidebarbutton {
+ border-radius: 5px 0 0 5px;
+ border-right: none;
+ border-left: none;
+}
+
#sidebarbutton:hover {
background-color: #AAAAAA;
}
div.body {
- padding: 0 0 0 1.2em;
+ padding: 0;
+ padding-inline-start: 1.2em;
}
div.body p, div.body dd, div.body li, div.body blockquote {
- text-align: left;
+ text-align: start;
line-height: 1.6;
}
div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 {
@@ -225,6 +257,10 @@ div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 {
}
div.body dl.field-list > dt {
line-height: 1.6;
+ padding-left: 0;
+ padding-right: 0;
+ padding-inline-start: 0.5em;
+ padding-inline-end: 5px;
}
div.body hr {
@@ -233,6 +269,22 @@ div.body hr {
height: 1px;
}
+body[dir='rtl'] {
+ & pre,
+ & code,
+ & div.highlight,
+ & dl > dt span ~ em,
+ & .sig,
+ & .sig-param {
+ direction: ltr;
+ unicode-bidi: isolate;
+ }
+}
+
+body[dir='rtl'] .sig {
+ text-align: right;
+}
+
div.body pre {
border-radius: 3px;
border: 1px solid #ac9;
@@ -402,7 +454,7 @@ table.docutils th {
table p,
table li {
- text-align: left !important;
+ text-align: start !important;
}
table.docutils th {
@@ -414,11 +466,37 @@ table.footnote td {
border: 0 !important;
}
+body[dir='rtl'] div#search-results,
+body[dir='rtl'] div#search-results ul.search li {
+ direction: rtl;
+ text-align: right;
+ unicode-bidi: isolate;
+}
+
+body[dir='rtl'] div#search-results ul.search li p {
+ text-align: right;
+}
+
+body[dir='rtl'] div.citation > span {
+ float: right;
+}
+
+body[dir='rtl'] aside.footnote > span {
+ float: right;
+ padding-right: 0;
+ padding-left: 0.5rem;
+}
+
+body[dir='rtl'] aside.footnote > p {
+ margin-left: 0;
+ margin-right: 2rem;
+}
+
div.footer {
line-height: 150%;
- text-align: right;
+ text-align: end;
width: auto;
- margin-right: 10px;
+ margin-inline-end: 10px;
}
div.footer a {
@@ -476,18 +554,17 @@ div.footer a:hover {
color: var(--threadsafety-atomic);
}
-
dl > dt span ~ em,
.sig {
font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace;
}
.toctree-wrapper ul {
- padding-left: 20px;
+ padding-inline-start: 20px;
}
.theme-selector {
- margin-left: .5em;
+ margin-inline-start: .5em;
}
div.genindex-jumpbox,
@@ -506,17 +583,17 @@ div.genindex-jumpbox a {
cursor: pointer;
position: absolute;
top: 0;
- right: 0;
+ right: 0; /* physical right; always, even in RTL, since code is LTR */
font-family: Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace;
font-size: 80%;
padding-left: .5em;
padding-right: .5em;
height: 100%;
max-height: min(100%, 2.4em);
- border-radius: 0 3px 0 0;
+ border-radius: 0 3px 0 0; /* physical — top-right corner, always */
color: #000;
background-color: #fff;
- border: 1px solid #ac9; /* follows div.body pre */
+ border: 1px solid #ac9;
display: none;
}
@@ -564,10 +641,16 @@ div.genindex-jumpbox a {
margin-inline-start: 1rem;
margin-inline-end: 0;
}
- /* Remove sidebar and top related bar */
- div.related, div.sphinxsidebar {
+ /* Remove sidebar and desktop-only related controls */
+ div.related li.right, div.related li.switchers, div.sphinxsidebar {
+ display: none;
+ }
+ div.related .root-icon, div.related .root-icon + li {
display: none;
}
+ div.related {
+ line-height: 1.3;
+ }
/* Anchorlinks are not hidden by fixed-positioned navbar when scrolled to */
html {
scroll-padding-top: 40px;
@@ -583,7 +666,7 @@ div.genindex-jumpbox a {
width: 100%;
position: fixed;
top: 0;
- left: 0;
+ inset-inline-start: 0;
box-shadow: rgba(0, 0, 0, 0.25) 0 0 2px 0;
z-index: 1;
}
@@ -605,7 +688,7 @@ div.genindex-jumpbox a {
align-items: stretch;
}
.nav-logo {
- margin-right: 1rem;
+ margin-inline-end: 1rem;
flex-shrink: 0;
align-self: center;
}
@@ -614,7 +697,7 @@ div.genindex-jumpbox a {
width: 20px;
}
.version_switcher_placeholder {
- margin-right: 1rem;
+ margin-inline-end: 1rem;
}
.version_switcher_placeholder > select {
height: 100%;
@@ -624,10 +707,11 @@ div.genindex-jumpbox a {
flex: auto;
border: 1px solid #a9a9a9;
align-items: stretch;
+ position: relative;
}
.nav-content .search input[type=search] {
border: 0;
- padding-left: 24px;
+ padding-inline-start: 24px;
width: 100%;
flex: 1;
}
@@ -635,14 +719,15 @@ div.genindex-jumpbox a {
height: 100%;
box-shadow: none;
border: 0;
- border-left: 1px solid #a9a9a9;
+ border-inline-start: 1px solid #a9a9a9;
cursor: pointer;
- margin-right: 0;
+ margin-inline-end: 0;
}
.nav-content .search svg {
position: absolute;
- align-self: center;
- padding-left: 4px;
+ top: 50%;
+ transform: translateY(-50%);
+ inset-inline-start: 4px;
}
.toggler__input {
display: none;
@@ -674,7 +759,7 @@ div.genindex-jumpbox a {
width: 100%;
background: inherit;
position: absolute;
- left: 0;
+ inset-inline-start: 0;
top: -8px;
}
.toggler__label > span::after {
@@ -695,7 +780,7 @@ div.genindex-jumpbox a {
}
.toggler__input:checked ~ .menu-wrapper {
visibility: visible;
- left: 0;
+ transform: translateX(0);
}
/* Sliding side menu */
@@ -703,8 +788,9 @@ div.genindex-jumpbox a {
display: block;
position: fixed;
top: 0;
- transition: left 400ms ease;
- left: -310px;
+ inset-inline-start: 0;
+ transition: transform 400ms ease;
+ transform: translateX(calc(-100% - 10px));
width: 300px;
height: 100%;
background-color: #eee;
@@ -712,12 +798,18 @@ div.genindex-jumpbox a {
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
overflow-y: auto;
}
+ body[dir='rtl'] .menu-wrapper {
+ transform: translateX(100%);
+ }
.menu-wrapper.open {
visibility: visible;
- left: 0;
+ transform: translateX(0);
}
.menu {
- padding: 40px 10px 30px 20px;
+ padding-block-start: 40px;
+ padding-inline-end: 10px;
+ padding-block-end: 30px;
+ padding-inline-start: 20px;
}
.menu-wrapper h3,
.menu-wrapper h4 {
@@ -742,10 +834,10 @@ div.genindex-jumpbox a {
list-style: none;
line-height: 1.4;
overflow-wrap: break-word;
- padding-left: 0;
+ padding-inline-start: 0;
}
.menu ul ul {
- margin-left: 20px;
+ margin-inline-start: 20px;
list-style: square;
}
.menu ul li {
@@ -801,26 +893,26 @@ div.versionadded,
div.versionchanged,
div.deprecated,
div.deprecated-removed {
- border-left: 3px solid;
+ border-inline-start: 3px solid;
padding: 0 1rem;
}
div.versionadded {
- border-left-color: var(--versionadded-border);
+ border-inline-start-color: var(--versionadded-border);
}
div.versionchanged {
- border-left-color: var(--versionchanged-border);
+ border-inline-start-color: var(--versionchanged-border);
}
div.deprecated,
div.deprecated-removed,
div.versionremoved {
- border-left-color: var(--deprecated-border);
+ border-inline-start-color: var(--deprecated-border);
}
div.soft-deprecated {
- border-left-color: var(--soft-deprecated-border);
+ border-inline-start-color: var(--soft-deprecated-border);
}
div.versionadded .versionmodified {
diff --git a/python_docs_theme/static/pydoctheme_dark.css b/python_docs_theme/static/pydoctheme_dark.css
index df91a2ab..2139108c 100644
--- a/python_docs_theme/static/pydoctheme_dark.css
+++ b/python_docs_theme/static/pydoctheme_dark.css
@@ -49,7 +49,7 @@ div.body a {
}
div.body a:visited {
- color: #09e;
+ color: #bf95f9;
}
a.headerlink:hover {
diff --git a/python_docs_theme/theme.toml b/python_docs_theme/theme.toml
index 1d3cdbda..c0f9c79c 100644
--- a/python_docs_theme/theme.toml
+++ b/python_docs_theme/theme.toml
@@ -27,6 +27,7 @@ headbgcolor = "white"
headlinkcolor = "#aaaaaa"
codebgcolor = "#eeffcc"
codetextcolor = "#333333"
+is_rtl = "false"
hosted_on = ""
issues_url = ""