Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add full i18n support #1192

Merged
merged 20 commits into from
Feb 23, 2023
Merged
Show file tree
Hide file tree
Changes from 17 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
3 changes: 3 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: "3.9"
- name: Install gettext for translations
run: |
sudo apt-get install gettext
- name: Build package
run: |
python -m pip install -U pip build
Expand Down
12 changes: 6 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,6 @@ coverage.xml
*.cover
.hypothesis/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
Expand Down Expand Up @@ -113,11 +109,15 @@ node_modules/
.vscode
.idea

# MacOSX store files
**/.DS_Store

# THEME FILES
# files from the gallery screenshots
docs/_static/gallery

# Our site profile tests
profile.svg

# MacOSX store files
**/.DS_Store
# Compiled translation files (are compiled at build time)
src/pydata_sphinx_theme/locale/*/*/*.mo
5 changes: 5 additions & 0 deletions babel.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# See https://github.com/sphinx-doc/sphinx/blob/4.x/babel.cfg
[jinja2: **.html]
encoding = utf-8
ignore_tags = script,style
include_attrs = alt title summary placeholder
Comment on lines +2 to +5
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

current (6.1.x) sphinx has this:

[jinja2: **/themes/**.html]
encoding = utf-8
ignore_tags = script,style
include_attrs = alt title summary

IDK though which is correct for us?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have strong opinions on this, I suggest we just pick one and see if it has any tangible impact, then change it later if we want. IMO unless we have a strong rationale for excluding placeholder, we should just keep it 🤷

38 changes: 36 additions & 2 deletions docs/community/topics/i18n.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,25 @@ These steps cover how to add or change text that has been marked as translateabl

pybabel extract . -F babel.cfg -o src/pydata_sphinx_theme/locale/sphinx.pot -k '_ __ l_ lazy_gettext'

**To run this in ``.nox``**: ``nox -s translate -- extract``.

#. Update the message catalogs (``PO`` files) with `the PyBabel update command <https://babel.pocoo.org/en/latest/cmdline.html#update>`__:

.. code-block:: bash

pybabel update -i src/pydata_sphinx_theme/locale/sphinx.pot -d src/pydata_sphinx_theme/locale -D sphinx

**To run this in ``.nox``**: ``nox -s translate -- update``.


This will update these files with new information about the position and text of the language you have modified.
If you change non-translatable text, you can run pybabel and it will update the line numbers.
However, this step is optional - the line numbers are to inform the human translator, not to perform the translation.

If you change translatable text, pybabel will try to fuzzy match the new text with an existing translation.
If so it will add a comment like `#, fuzzy`.
Otherwise, the new text will simply be untranslated in the file.


.. _translating-the-theme:

Expand All @@ -92,14 +104,36 @@ This section covers how to do so.

pybabel init -i src/pydata_sphinx_theme/locale/sphinx.pot -d src/pydata_sphinx_theme/locale -D sphinx -l es

**To run this in ``.nox``**: ``nox -s translate -- init es``

#. Edit the language's message catalog at ``pydata_sphinx_theme/locale/es/LC_MESSAGES/sphinx.po``. For each source string introduced by the ``msgid`` keyword, add its translation after the ``msgstr`` keyword.

#. Compile the message catalogs of every language. This creates or updates the MO files with `PyBabel compile <https://babel.pocoo.org/en/latest/cmdline.html#compile>`__:

.. code-block:: bash
.. code-block:: bash

pybabel compile -d src/pydata_sphinx_theme/theme/pydata_sphinx_theme/static/locale -D sphinx

**To run this in ``.nox``**: ``nox -s translate -- compile``.

Translation tips
----------------

Translating words vs phrases
````````````````````````````

Full sentences and clauses must always be a single translatable string.
Otherwise, you get ``next page`` translated as ``suivant page`` instead of ``page suivante`` etc.

Dealing with names and non-translateable text in translations
`````````````````````````````````````````````````````````````

pybabel compile -d src/pydata_sphinx_theme/locale -D sphinx
In some cases we must include non-translateable text within translations (e.g. ``GitHub``).
It's important to include them in the translatable phrase so that word ordering is accounted for in the translations.
To do so, use Jinja syntax to insert the name as a variable (``{{ variablename }}``).

For example, Jinja like ```{% trans %}Hello {{ name }}{% endtrans %}``, is extracted as the plain Python format string ``Hello %(name)s``, such that it can be used in both Jinja templates and Python code.
The translated text in each language must contain the ``%(name)s`` verbatim to be inserted.

References
----------
Expand Down
39 changes: 39 additions & 0 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"""
import nox
from pathlib import Path
from shlex import split

nox.options.reuse_existing_virtualenvs = True

Expand Down Expand Up @@ -35,6 +36,10 @@ def _should_install(session):
return should_install


def _compile_translations(session):
session.run(*split("pybabel compile -d src/pydata_sphinx_theme/locale -D sphinx"))


@nox.session(name="compile")
def compile(session):
"""Compile the theme's web assets with sphinx-theme-builder."""
Expand All @@ -55,6 +60,7 @@ def docs(session):
@nox.session(name="docs-live")
def docs_live(session):
"""Build the docs with a live server that re-loads as you make changes."""
_compile_translations(session)
if _should_install(session):
session.install("-e", ".[doc]")
session.install("sphinx-theme-builder[cli]")
Expand All @@ -66,6 +72,7 @@ def test(session):
"""Run the test suite."""
if _should_install(session):
session.install("-e", ".[test]")
_compile_translations(session)
session.run("pytest", *session.posargs)


Expand All @@ -79,6 +86,38 @@ def test_sphinx(session, sphinx):
session.run("pytest", *session.posargs)


@nox.session()
def translate(session):
"""Translation commands. Available commands after `--` : extract, update, compile"""
session.install("Babel")
if "extract" in session.posargs:
session.run(
*split(
"pybabel extract . -F babel.cfg -o src/pydata_sphinx_theme/locale/sphinx.pot -k '_ __ l_ lazy_gettext'"
)
)
elif "update" in session.posargs:
session.run(
*split(
"pybabel update -i src/pydata_sphinx_theme/locale/sphinx.pot -d src/pydata_sphinx_theme/locale -D sphinx"
)
)
elif "compile" in session.posargs:
_compile_translations(session)
elif "init" in session.posargs:
language = session.posargs[-1]
session.run(
*split(
f"pybabel init -i src/pydata_sphinx_theme/locale/sphinx.pot -d src/pydata_sphinx_theme/locale -D sphinx -l {language}"
)
)
else:
print(
"No translate command found. Use like: `nox -s translate -- COMMAND`."
"\n\n Available commands: extract, update, compile, init"
)


@nox.session(name="profile")
def profile(session):
"""Generate a profile chart with py-spy. The chart will be placed at profile.svg."""
Expand Down
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ additional-compiled-static-assets = [
"webpack-macros.html",
"vendor/",
"styles/bootstrap.css",
"scripts/bootstrap.js"
"scripts/bootstrap.js",
"locale/"
]

[project]
Expand All @@ -24,6 +25,7 @@ dependencies = [
"beautifulsoup4",
"docutils!=0.17.0",
"packaging",
"Babel",
"pygments>=2.7",
"accessible-pygments"
]
Expand Down
3 changes: 3 additions & 0 deletions src/pydata_sphinx_theme/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1153,6 +1153,9 @@ def setup(app):
app.connect("build-finished", _overwrite_pygments_css)
app.connect("build-finished", copy_logo_images)

# https://www.sphinx-doc.org/en/master/extdev/i18n.html#extension-internationalization-i18n-and-localization-l10n-using-i18n-api
app.add_message_catalog("sphinx", here / "locale")

# Include component templates
app.config.templates_path.append(str(theme_path / "components"))

Expand Down
158 changes: 158 additions & 0 deletions src/pydata_sphinx_theme/locale/en/LC_MESSAGES/sphinx.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# English translations for PROJECT.
# Copyright (C) 2023 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2023.
#
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2023-02-16 14:32-0500\n"
"PO-Revision-Date: 2023-02-16 13:19-0500\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: en\n"
"Language-Team: en <[email protected]>\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.11.0\n"

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/layout.html:50
msgid "Skip to main content"
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/search-button.html:7
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/search.html:5
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/search.html:28
msgid "Search"
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/search.html:8
msgid "Error"
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/search.html:9
msgid "Please activate JavaScript to enable the search functionality."
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/breadcrumbs.html:12
msgid "Breadcrumbs"
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/breadcrumbs.html:13
msgid "Breadcrumb"
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/breadcrumbs.html:16
msgid "Home"
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/copyright.html:4
#, python-format
msgid "© <a href=\"%(path)s\">Copyright</a> %(copyright)s."
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/copyright.html:7
#, python-format
msgid "© Copyright %(copyright)s."
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/edit-this-page.html:9
#, python-format
msgid "Edit on %(provider)s"
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/edit-this-page.html:11
msgid "Edit"
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/icon-links.html:31
msgid "GitHub"
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/icon-links.html:32
msgid "GitLab"
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/icon-links.html:33
msgid "Bitbucket"
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/icon-links.html:34
msgid "Twitter"
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/indices.html:2
msgid "Indices"
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/indices.html:9
msgid "General Index"
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/indices.html:13
msgid "Global Module Index"
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/indices.html:17
msgid "Python Module Index"
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/last-updated.html:2
#, python-format
msgid "Last updated on %(last_updated)s."
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/navbar-nav.html:5
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/navbar-nav.html:6
msgid "Site Navigation"
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/page-toc.html:4
msgid "On this page"
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/sidebar-nav-bs.html:2
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/sidebar-nav-bs.html:3
msgid "Section Navigation"
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/sourcelink.html:4
msgid "Show Source"
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/sphinx-version.html:3
#, python-format
msgid ""
"Created using <a href=\"https://sphinx-doc.org/\">Sphinx</a> "
"%(sphinx_version)s."
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/theme-switcher.html:5
msgid "light/dark"
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/theme-version.html:2
#, python-format
msgid ""
"Built with the <a href=\"https://pydata-sphinx-"
"theme.readthedocs.io/en/stable/index.html\">PyData Sphinx Theme</a> "
"%(theme_version)s."
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/footer-article/prev-next.html:6
msgid "previous page"
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/footer-article/prev-next.html:9
msgid "previous"
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/footer-article/prev-next.html:17
msgid "next page"
msgstr ""

#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/footer-article/prev-next.html:19
msgid "next"
msgstr ""
Loading