Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 50 additions & 2 deletions web/pandas/_templates/layout.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html lang="{{ lang }}">
<head>
<script defer data-domain="pandas.pydata.org" src="https://views.scientific-python.org/js/script.js"></script>
<title>pandas - Python Data Analysis Library</title>
Expand All @@ -15,6 +15,38 @@
href="{{ base_url }}{{ stylesheet }}">
{% endfor %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css">
<script type="text/javascript">
function changeLanguage(lang) {
var absBaseUrl = document.baseURI;
var baseUrl = location.protocol + "//" + location.hostname
var currentLanguage = document.documentElement.lang;
var languages = [
{% for lang, name in translations["languages"].items() -%}
"{{ lang }}",
{% endfor -%}
]

if (location.port) {
baseUrl = baseUrl + ":" + location.port
}

// Handle preview URLs on github
// If preview URL changes, this regex will need to be updated
var re = /preview\/pandas-dev\/pandas\/(?<pr>[0-9]*)\//g;
var previewUrl = '';
for (const match of absBaseUrl.matchAll(re)) {
previewUrl = `/preview/pandas-dev/pandas/${match.groups.pr}`;
}
var pathName = location.pathname.replace(previewUrl, '')
var urlLanguage = '';
if (lang !== 'en') {
urlLanguage = '/' + lang;
}
pathName = pathName.replace('/' + currentLanguage + '/', '/')
var newUrl = baseUrl + previewUrl + urlLanguage + pathName
window.location.href = newUrl;
}
</script>
</head>
<body>
<header>
Expand All @@ -28,7 +60,7 @@

<div class="collapse navbar-collapse" id="nav-content">
<ul class="navbar-nav ms-auto">
{% for item in navbar %}
{% for item in navbar[lang] %}
{% if not item.has_subitems %}
<li class="nav-item">
<a class="nav-link" href="{% if not item.target.startswith("http") %}{{ base_url }}{% endif %}{{ item.target }}">{{ item.name }}</a>
Expand All @@ -50,6 +82,22 @@
</li>
{% endif %}
{% endfor %}
<!-- Language switcher -->
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle"
data-bs-toggle="dropdown"
href="#"
role="button"
aria-haspopup="true"
aria-expanded="false">{{ translations["languages"][lang] }}</a>
<div class="dropdown-menu">
{% for language, name in translations["languages"].items() -%}
<a class="dropdown-item"
href="#"
onclick="changeLanguage('{{ language }}')">{{ name }}</a>
{% endfor -%}
</div>
</li>
</ul>
</div>
</div>
Expand Down
44 changes: 11 additions & 33 deletions web/pandas/config.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
main:
templates_path: _templates
base_template: "layout.html"
navbar_fname: "navbar.yml"
production_url: "https://pandas.pydata.org/"
ignore:
- _templates/layout.html
- config.yml
github_repo_url: pandas-dev/pandas
context_preprocessors:
- pandas_web.Preprocessors.current_year
- pandas_web.Preprocessors.download_translated_content
- pandas_web.Preprocessors.add_navbar_content
- pandas_web.Preprocessors.navbar_add_info
- pandas_web.Preprocessors.navbar_add_translated_info
- pandas_web.Preprocessors.blog_add_posts
- pandas_web.Preprocessors.maintainers_add_info
- pandas_web.Preprocessors.home_add_releases
Expand All @@ -24,39 +28,6 @@ static:
css:
- static/css/pandas.css
- static/css/codehilite.css
navbar:
- name: "About us"
target:
- name: "About pandas"
target: about/
- name: "Project roadmap"
target: about/roadmap.html
- name: "Governance"
target: about/governance.html
- name: "Team"
target: about/team.html
- name: "Sponsors"
target: about/sponsors.html
- name: "Citing and logo"
target: about/citing.html
- name: "Getting started"
target: getting_started.html
- name: "Documentation"
target: docs/
- name: "Community"
target:
- name: "Blog"
target: community/blog/
- name: "Ask a question (StackOverflow)"
target: https://stackoverflow.com/questions/tagged/pandas
- name: "Code of conduct"
target: community/coc.html
- name: "Ecosystem"
target: community/ecosystem.html
- name: "Benchmarks"
target: community/benchmarks.html
- name: "Contribute"
target: contribute.html
blog:
num_posts: 50
posts_path: community/blog
Expand Down Expand Up @@ -203,3 +174,10 @@ sponsors:
kind: partner
roadmap:
pdeps_path: pdeps
translations:
url: https://github.com/Scientific-Python-Translations/pandas-translations/archive/refs/heads/main.tar.gz
source_path: pandas-translations-main/web/pandas/
languages:
en: English
es: Español
pt: Português
34 changes: 34 additions & 0 deletions web/pandas/navbar.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
navbar:
- name: "About us"
target:
- name: "About pandas"
target: about/
- name: "Project roadmap"
target: about/roadmap.html
- name: "Governance"
target: about/governance.html
- name: "Team"
target: about/team.html
- name: "Sponsors"
target: about/sponsors.html
- name: "Citing and logo"
target: about/citing.html
- name: "Getting started"
target: getting_started.html
- name: "Documentation"
target: docs/
translated: false
- name: "Community"
target:
- name: "Blog"
target: community/blog/
- name: "Ask a question (StackOverflow)"
target: https://stackoverflow.com/questions/tagged/pandas
- name: "Code of conduct"
target: community/coc.html
- name: "Ecosystem"
target: community/ecosystem.html
- name: "Benchmarks"
target: community/benchmarks.html
- name: "Contribute"
target: contribute.html
119 changes: 107 additions & 12 deletions web/pandas_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import collections
import datetime
import importlib
import io
import itertools
import json
import operator
Expand All @@ -36,6 +37,7 @@
import re
import shutil
import sys
import tarfile
import time
import typing

Expand Down Expand Up @@ -66,7 +68,7 @@ class Preprocessors:
"""

@staticmethod
def current_year(context):
def current_year(context: dict) -> dict:
"""
Add the current year to the context, so it can be used for the copyright
note, or other places where it is needed.
Expand All @@ -75,23 +77,110 @@ def current_year(context):
return context

@staticmethod
def navbar_add_info(context):
def download_translated_content(context: dict) -> dict:
"""
Download the translations from the mirror translations repository.
https://github.com/Scientific-Python-Translations/pandas-translations

All translated languages are downloaded, extracted and place inside the
``pandas`` folder in a separate folder for each language (e.g. ``pandas/es``).

The extracted folder and the translations folders are deleted before
downloading the information, so the translations are always up to date.
"""
base_folder = os.path.dirname(__file__)
extract_path = os.path.join(base_folder, context["translations"]["source_path"])
shutil.rmtree(extract_path, ignore_errors=True)
response = requests.get(context["translations"]["url"])
if response.status_code == 200:
with tarfile.open(None, "r:gz", io.BytesIO(response.content)) as tar:
tar.extractall(base_folder)
else:
raise Exception(f"Failed to download translations: {response.status_code}")

for lang in list(context["translations"]["languages"].keys())[1:]:
shutil.rmtree(os.path.join(base_folder, "pandas", lang), ignore_errors=True)
shutil.move(
os.path.join(extract_path, lang),
os.path.join(base_folder, "pandas", lang),
)
return context

@staticmethod
def add_navbar_content(context: dict) -> dict:
"""
Add the navbar content to the context.

The navbar content is loaded for all available languages.
"""
context["navbar"] = {}
for lang in context["translations"]["languages"]:
path = os.path.join(
context["source_path"],
"" if lang == "en" else f"{lang}",
context["main"]["navbar_fname"],
)
if os.path.exists(path):
with open(
path,
encoding="utf-8",
) as f:
navbar_lang = yaml.safe_load(f)
context["navbar"][lang] = navbar_lang["navbar"]

return context

@staticmethod
def navbar_add_info(context: dict) -> dict:
"""
Items in the main navigation bar can be direct links, or dropdowns with
subitems. This context preprocessor adds a boolean field
``has_subitems`` that tells which one of them every element is. It
also adds a ``slug`` field to be used as a CSS id.
"""
for i, item in enumerate(context["navbar"]):
context["navbar"][i] = dict(
for i, item in enumerate(context["navbar"]["en"]):
context["navbar"]["en"][i] = dict(
item,
has_subitems=isinstance(item["target"], list),
slug=(item["name"].replace(" ", "-").lower()),
)
return context

@staticmethod
def blog_add_posts(context):
def navbar_add_translated_info(context: dict) -> dict:
"""
Prepare the translated navbar information for the template.

Items in the main navigation bar can be direct links, or dropdowns with
subitems. This context preprocessor adds a boolean field
``has_subitems`` that tells which one of them every element is. It
also adds a ``slug`` field to be used as a CSS id.
"""

def update_target(item: dict, prefix: str) -> None:
if item.get("translated", True):
item["target"] = f"{prefix}/{item['target']}"
else:
item["target"] = f"../{item['target']}"

for lang in list(context["translations"]["languages"].keys())[1:]:
for i, item in enumerate(context["navbar"][lang]):
has_subitems = isinstance(item["target"], list)
if has_subitems:
for sub_item in item["target"]:
update_target(sub_item, lang)
else:
update_target(item, lang)

context["navbar"][lang][i] = dict(
item,
has_subitems=has_subitems,
slug=(item["name"].replace(" ", "-").lower()),
)
return context

@staticmethod
def blog_add_posts(context: dict) -> dict:
"""
Given the blog feed defined in the configuration yaml, this context
preprocessor fetches the posts in the feeds, and returns the relevant
Expand Down Expand Up @@ -158,7 +247,7 @@ def blog_add_posts(context):
return context

@staticmethod
def maintainers_add_info(context):
def maintainers_add_info(context: dict) -> dict:
"""
Given the active maintainers defined in the yaml file, it fetches
the GitHub user information for them.
Expand Down Expand Up @@ -208,7 +297,7 @@ def maintainers_add_info(context):
return context

@staticmethod
def home_add_releases(context):
def home_add_releases(context: dict) -> dict:
context["releases"] = []

github_repo_url = context["main"]["github_repo_url"]
Expand Down Expand Up @@ -268,7 +357,7 @@ def home_add_releases(context):
return context

@staticmethod
def roadmap_pdeps(context):
def roadmap_pdeps(context: dict) -> dict:
"""
PDEP's (pandas enhancement proposals) are not part of the bar
navigation. They are included as lists in the "Roadmap" page
Expand Down Expand Up @@ -382,7 +471,7 @@ def get_callable(obj_as_str: str) -> object:
return obj


def get_context(config_fname: pathlib.Path, **kwargs):
def get_context(config_fname: pathlib.Path, **kwargs: dict) -> dict:
"""
Load the config yaml as the base context, and enrich it with the
information added by the context preprocessors defined in the file.
Expand Down Expand Up @@ -453,13 +542,16 @@ def main(
os.makedirs(target_path, exist_ok=True)

sys.stderr.write("Generating context...\n")
context = get_context(config_fname, target_path=target_path)
context = get_context(
os.path.join(source_path, "config.yml"),
target_path=target_path,
)
sys.stderr.write("Context generated\n")

templates_path = source_path / context["main"]["templates_path"]
jinja_env = jinja2.Environment(loader=jinja2.FileSystemLoader(templates_path))

for fname in get_source_files(source_path):
context["lang"] = fname[0:2] if fname[2] == "/" else "en"
if fname.as_posix() in context["main"]["ignore"]:
continue
sys.stderr.write(f"Processing {fname}\n")
Expand Down Expand Up @@ -489,7 +581,10 @@ def main(
with (target_path / dirname / fname_html).open("w", encoding="utf-8") as f:
f.write(content)
else:
shutil.copy(source_path / fname, target_path / fname)
shutil.copy(
os.path.join(source_path, fname), os.path.join(target_path, dirname)
)
return 0


if __name__ == "__main__":
Expand Down
Loading