diff --git a/README.md b/README.md
index beb055ca7..1809cbc90 100644
--- a/README.md
+++ b/README.md
@@ -60,6 +60,28 @@ To build live documentation that updates when you update local files, run the fo
$ nox -s docs-live
```
+### Building for release
+
+When building for release, the docs are built multiple times for each translation,
+but translations are only included in the production version of the guide after some completion threshold.
+
+The sphinx build environment is controlled by an environment variable `SPHINX_ENV`
+
+- when `SPHINX_ENV=development` (default), sphinx assumes all languages are built,
+ and includes them in the language selector
+- when `SPHINX_ENV=production`, only those languages in `release_languages` (set in `conf.py`)
+ are built and included in the language selector.
+
+Most of the time you should not need to set `SPHINX_ENV`,
+as it is forced by the primary nox sessions intended to be used for release or development:
+
+`SPHINX_ENV=development`
+- `docs-live` - autobuild english
+- `docs-live-lang` - autobuild a single language
+- `docs-live-langs` - autobuild all languages
+
+`SPHINX_ENV=production`
+- `build-test` - build all languages for production
## Contributing to this guide
diff --git a/_static/language_select.js b/_static/language_select.js
new file mode 100644
index 000000000..e1cae4c67
--- /dev/null
+++ b/_static/language_select.js
@@ -0,0 +1,13 @@
+document.addEventListener("DOMContentLoaded", () => {
+ let selectors = document.querySelectorAll("#language-selector");
+ selectors.forEach((selector) => {
+ selector.addEventListener("change", (event) => {
+ let target = event.target.value;
+ if (target.startsWith("https")) {
+ window.location.href = target;
+ } else {
+ window.location.pathname = target;
+ }
+ });
+ });
+});
diff --git a/_static/pyos.css b/_static/pyos.css
index ff33950e4..7a467b8d7 100644
--- a/_static/pyos.css
+++ b/_static/pyos.css
@@ -46,6 +46,10 @@ body {
margin-right: auto !important;
}
+.navbar-persistent--mobile {
+ margin-left: unset !important;
+}
+
/* custom fonts */
html,
@@ -414,3 +418,7 @@ th {
border: 1px solid #ccc; /* Light gray border */
padding: 8px; /* Add some padding for better readability */
}
+
+/* ----------------- */
+/* Language Selector */
+/* ----------------- */
diff --git a/_templates/language-selector.html b/_templates/language-selector.html
new file mode 100644
index 000000000..02d037424
--- /dev/null
+++ b/_templates/language-selector.html
@@ -0,0 +1,30 @@
+{%- macro langlink(lang, selected=False) -%} {%- if lang == "en" %}
+
+{%- else %}
+
+{%- endif -%} {%- endmacro -%}
+
diff --git a/conf.py b/conf.py
index e4123823c..49c3ec4e7 100644
--- a/conf.py
+++ b/conf.py
@@ -15,10 +15,15 @@
# sys.path.insert(0, os.path.abspath('.'))
from datetime import datetime
import subprocess
+import os
current_year = datetime.now().year
organization_name = "pyOpenSci"
+# env vars
+sphinx_env = os.environ.get("SPHINX_ENV", "development")
+language_env = os.environ.get("SPHINX_LANG", "en")
+
# -- Project information -----------------------------------------------------
@@ -26,6 +31,24 @@
copyright = f"{current_year}, {organization_name}"
author = "pyOpenSci Community"
+# Language of the current build
+# language can later be overridden (eg with the -D flag)
+# but we need it set here so it can make it into the html_context
+language = language_env
+# all languages that have .po files generated for them
+# (excluding english)
+languages = ["es", "ja"]
+# the languages that will be included in a production build
+# (also excluding english)
+release_languages = ["ja"]
+
+# languages that will be included in the language dropdown
+# (ie. all that are being built in this nox build session)
+if sphinx_env == "production":
+ build_languages = ["en"] + release_languages
+else:
+ build_languages = ["en"] + languages
+
# Get the latest Git tag - there might be a prettier way to do this but...
try:
release_value = (
@@ -72,7 +95,11 @@
{"href": "https://www.pyopensci.org/images/favicon.ico"},
]
-# Link to our repo for easy PR/ editing
+html_baseurl = "https://www.pyopensci.org/python-package-guide/"
+if not sphinx_env == "production":
+ # for links in language selector when developing locally
+ html_baseurl = "/"
+
html_theme_options = {
"announcement": "
",
# "navbar_center": ["nav"], this can be a way to override the default navigation structure
@@ -112,12 +139,16 @@
"github_url": "https://github.com/pyopensci/python-package-guide",
"footer_start": ["code_of_conduct", "copyright"],
"footer_end": [],
+ "navbar_persistent": ["language-selector", "search-button"]
}
html_context = {
"github_user": "pyopensci",
"github_repo": "python-package-guide",
"github_version": "main",
+ "language": language,
+ "languages": build_languages,
+ "baseurl": html_baseurl,
}
# Add any paths that contain templates here, relative to this directory.
@@ -141,7 +172,7 @@
]
# For sitemap generation
-html_baseurl = "https://www.pyopensci.org/python-package-guide/"
+
sitemap_url_scheme = "{link}"
# -- Options for HTML output -------------------------------------------------
@@ -153,7 +184,7 @@
html_static_path = ["_static"]
html_css_files = ["pyos.css"]
html_title = "Python Packaging Guide"
-html_js_files = ["matomo.js"]
+html_js_files = ["matomo.js", "language_select.js"]
# Social cards
diff --git a/noxfile.py b/noxfile.py
index fd13ce257..fe70c4820 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -2,6 +2,12 @@
import pathlib
import shutil
import nox
+import sys
+import subprocess
+
+# for some reason necessary to correctly import conf from cwd
+sys.path.insert(0, str(pathlib.Path(__file__).parent.absolute()))
+import conf
## Sphinx related options
@@ -23,7 +29,7 @@
BUILD_PARAMETERS = ["-b", "html"]
# Sphinx parameters used to test the build of the guide
-TEST_PARAMETERS = ['-W', '--keep-going', '-E', '-a']
+TEST_PARAMETERS = ['--keep-going', '-E', '-a']
# Sphinx parameters to generate translation templates
TRANSLATION_TEMPLATE_PARAMETERS = ["-b", "gettext"]
@@ -42,16 +48,19 @@
## Localization options (translations)
# List of languages for which locales will be generated in (/locales/)
-LANGUAGES = ["es", "ja"]
+LANGUAGES = conf.languages
# List of languages that should be built when releasing the guide (docs or docs-test sessions)
-RELEASE_LANGUAGES = []
+RELEASE_LANGUAGES = conf.release_languages
+# allowable values of `SPHINX_ENV`
+SPHINX_ENVS = ('production', 'development')
@nox.session
def docs(session):
"""Build the packaging guide."""
session.install("-e", ".")
+ sphinx_env = _sphinx_env(session)
session.run(SPHINX_BUILD, *BUILD_PARAMETERS, SOURCE_DIR, OUTPUT_DIR, *session.posargs)
# When building the guide, also build the translations in RELEASE_LANGUAGES
session.notify("build-release-languages", session.posargs)
@@ -65,11 +74,18 @@ def docs_test(session):
Note: this is the session used in CI/CD to release the guide.
"""
session.install("-e", ".")
- session.run(SPHINX_BUILD, *BUILD_PARAMETERS, *TEST_PARAMETERS, SOURCE_DIR, OUTPUT_DIR, *session.posargs)
+ session.run(SPHINX_BUILD, *BUILD_PARAMETERS, *TEST_PARAMETERS, SOURCE_DIR, OUTPUT_DIR, *session.posargs,
+ env={'SPHINX_ENV': 'production'})
# When building the guide with additional parameters, also build the translations in RELEASE_LANGUAGES
# with those same parameters.
session.notify("build-release-languages", [*TEST_PARAMETERS, *session.posargs])
+def _autobuild_cmd(posargs: list[str], output_dir = OUTPUT_DIR) -> list[str]:
+ cmd = [SPHINX_AUTO_BUILD, *BUILD_PARAMETERS, str(SOURCE_DIR), str(output_dir), *posargs]
+ for folder in AUTOBUILD_IGNORE:
+ cmd.extend(["--ignore", f"*/{folder}/*"])
+ return cmd
+
@nox.session(name="docs-live")
def docs_live(session):
@@ -87,13 +103,11 @@ def docs_live(session):
so they don't need to remember the specific sphinx-build parameters to build a different language.
"""
session.install("-e", ".")
- cmd = [SPHINX_AUTO_BUILD, *BUILD_PARAMETERS, SOURCE_DIR, OUTPUT_DIR, *session.posargs]
- for folder in AUTOBUILD_IGNORE:
- cmd.extend(["--ignore", f"*/{folder}/*"])
+ cmd = _autobuild_cmd(session.posargs)
# This part was commented in the previous version of the nox file, keeping the same here
# for folder in AUTOBUILD_INCLUDE:
# cmd.extend(["--watch", folder])
- session.run(*cmd)
+ session.run(*cmd, env={'SPHINX_ENV': "development"})
@nox.session(name="docs-live-lang")
@@ -127,6 +141,37 @@ def docs_live_lang(session):
f"where LANG is one of: {LANGUAGES}"
)
+@nox.session(name="docs-live-langs")
+def docs_live_langs(session):
+ """
+ Like docs-live but build all languages simultaneously
+
+ Requires concurrently to run (npm install -g concurrently)
+ """
+ try:
+ subprocess.check_call(['concurrently'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+ except subprocess.CalledProcessError:
+ # handle errors in the called executable
+ # (aka, was found)
+ pass
+ except OSError:
+ session.error('docs-live-langs requires concurrently (npm install -g concurrently)')
+
+ session.install("-e", ".")
+
+ cmds = ['"' + " ".join(["SPHINX_ENV=development"] + _autobuild_cmd(session.posargs) + ['--open-browser']) + '"']
+ for language in LANGUAGES:
+ cmds.append(
+ '"' + " ".join(
+ [f"SPHINX_LANG={language}", "SPHINX_ENV=development"] +
+ _autobuild_cmd(
+ session.posargs + ["-D", f"language={language}"],
+ output_dir=OUTPUT_DIR / language
+ ) + ["--port=0"]
+ ) + '"'
+ )
+ cmd = ['concurrently', '--kill-others', '-n', ','.join(["en"] + LANGUAGES), '-c', 'auto', *cmds]
+ session.run(*cmd)
@nox.session(name="docs-clean")
def clean_dir(session):
@@ -187,6 +232,13 @@ def update_language(session):
"nox -s update-language -- LANG\n\n "
f" where LANG is one of: {LANGUAGES}"
)
+ if not session.posargs:
+ session.error("Please provide the list of languages to build the translation for")
+
+ sphinx_env = _sphinx_env(session)
+
+ languages_to_build = session.posargs.pop(0)
+
@nox.session(name="build-language")
def build_language(session):
@@ -215,6 +267,7 @@ def build_release_languages(session):
"""
Build the translations of the guide for the languages in RELEASE_LANGUAGES.
"""
+ sphinx_env = _sphinx_env(session)
if not RELEASE_LANGUAGES:
session.warn("No release languages defined in RELEASE_LANGUAGES")
return
@@ -222,6 +275,12 @@ def build_release_languages(session):
for lang in RELEASE_LANGUAGES:
session.log(f"Building [{lang}] guide")
session.run(SPHINX_BUILD, *BUILD_PARAMETERS, "-D", f"language={lang}", ".", OUTPUT_DIR / lang, *session.posargs)
+ if lang == 'en':
+ out_dir = OUTPUT_DIR
+ else:
+ out_dir = OUTPUT_DIR / lang
+ session.run(SPHINX_BUILD, *BUILD_PARAMETERS, "-D", f"language={lang}", ".", out_dir, *session.posargs,
+ env={"SPHINX_LANG": lang, "SPHINX_ENV": sphinx_env})
session.log(f"Translations built for {RELEASE_LANGUAGES}")
@nox.session(name="build-all-languages")
@@ -237,6 +296,19 @@ def build_all_languages(session):
session.log(f"Building [{lang}] guide")
session.run(SPHINX_BUILD, *BUILD_PARAMETERS, "-D", f"language={lang}", ".", OUTPUT_DIR / lang, *session.posargs)
session.log(f"Translations built for {LANGUAGES}")
+ sphinx_env = _sphinx_env(session)
+
+ # if running from the docs or docs-test sessions, build only release languages
+ BUILD_LANGUAGES = RELEASE_LANGUAGES if sphinx_env == "production" else LANGUAGES
+ # only build languages that have a locale folder
+ BUILD_LANGUAGES = [lang for lang in BUILD_LANGUAGES if (TRANSLATION_LOCALES_DIR / lang).exists()]
+ session.log(f"Declared languages: {LANGUAGES}")
+ session.log(f"Release languages: {RELEASE_LANGUAGES}")
+ session.log(f"Building languages{' for release' if sphinx_env == 'production' else ''}: {BUILD_LANGUAGES}")
+ if not BUILD_LANGUAGES:
+ session.warn("No translations to build")
+ else:
+ session.notify("build-languages", [sphinx_env, BUILD_LANGUAGES, *session.posargs])
@nox.session(name="build-all-languages-test")
@@ -248,3 +320,14 @@ def build_all_languages_test(session):
in the same way docs-test does for the English version.
"""
session.notify("build-all-languages", [*TEST_PARAMETERS])
+
+
+def _sphinx_env(session) -> str:
+ """
+ Get the sphinx env, from the first positional argument if present or from the
+ ``SPHINX_ENV`` environment variable, defaulting to "development"
+ """
+ if session.posargs and session.posargs[0] in SPHINX_ENVS:
+ return session.posargs.pop(0)
+ else:
+ return os.environ.get('SPHINX_ENV', 'development')