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": "

We run peer review of scientific Python software. Learn more.

", # "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')