diff --git a/web/pandas/_templates/layout.html b/web/pandas/_templates/layout.html
index c26b093b0c4ba..eeefc715b1853 100644
--- a/web/pandas/_templates/layout.html
+++ b/web/pandas/_templates/layout.html
@@ -1,5 +1,5 @@
-
+
pandas - Python Data Analysis Library
@@ -15,6 +15,38 @@
href="{{ base_url }}{{ stylesheet }}">
{% endfor %}
+
@@ -28,7 +60,7 @@
diff --git a/web/pandas/config.yml b/web/pandas/config.yml
index cb5447591dab6..b0a4bc8f5cf7e 100644
--- a/web/pandas/config.yml
+++ b/web/pandas/config.yml
@@ -1,6 +1,7 @@
main:
templates_path: _templates
base_template: "layout.html"
+ navbar_fname: "navbar.yml"
production_url: "https://pandas.pydata.org/"
ignore:
- _templates/layout.html
@@ -8,7 +9,10 @@ main:
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
@@ -25,39 +29,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
@@ -204,3 +175,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
diff --git a/web/pandas/navbar.yml b/web/pandas/navbar.yml
new file mode 100644
index 0000000000000..07ff0a64314a5
--- /dev/null
+++ b/web/pandas/navbar.yml
@@ -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
diff --git a/web/pandas_web.py b/web/pandas_web.py
index b3872b829c73a..77da821cc98c5 100755
--- a/web/pandas_web.py
+++ b/web/pandas_web.py
@@ -28,6 +28,7 @@
import collections
import datetime
import importlib
+import io
import itertools
import json
import operator
@@ -36,6 +37,7 @@
import re
import shutil
import sys
+import tarfile
import time
import typing
@@ -65,7 +67,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.
@@ -74,15 +76,69 @@ 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()),
@@ -90,7 +146,40 @@ def navbar_add_info(context):
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
@@ -162,7 +251,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.
@@ -212,7 +301,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"]
@@ -272,7 +361,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
@@ -386,7 +475,7 @@ def get_callable(obj_as_str: str) -> object:
return obj
-def get_context(config_fname: str, **kwargs):
+def get_context(config_fname: str, **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.
@@ -441,19 +530,20 @@ def main(
For ``.md`` and ``.html`` files, render them with the context
before copying them. ``.md`` files are transformed to HTML.
"""
- config_fname = os.path.join(source_path, "config.yml")
-
shutil.rmtree(target_path, ignore_errors=True)
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 = os.path.join(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 os.path.normpath(fname) in context["main"]["ignore"]:
continue
@@ -484,6 +574,7 @@ def main(
shutil.copy(
os.path.join(source_path, fname), os.path.join(target_path, dirname)
)
+ return 0
if __name__ == "__main__":