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 all 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
4 changes: 4 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ jobs:
python -m pip install -e .[test] sphinx==${{ matrix.sphinx-version }}
- name: Show installed versions
run: python -m pip list
- name: Compile MO files
run: |
pip install nox
nox -s compile
- name: Run tests
run: pytest --color=yes --cov pydata_sphinx_theme --cov-branch --cov-report term-missing:skip-covered --cov-fail-under ${{ env.COVERAGE_THRESHOLD }}

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/6.1.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 🤷

44 changes: 42 additions & 2 deletions docs/community/topics/i18n.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,26 @@ 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 *only* change non-translatable text (like HTML markup), the `extract` and `update` commands will only update the positions (line numbers) of the translatable strings. Updating positions is optional - the line numbers are to inform the human translator, not to perform the translation.

If you change translatable strings, the `extract` command will extract the new or updated strings to the POT file, and the `update` command will try to fuzzy match the new or updated strings with existing translations in the PO files.
If there is a fuzzy match, a comment like `#, fuzzy` is added before the matched entry.
Otherwise, a new entry is added and needs to be translated.


.. _translating-the-theme:

Add translations to translateable text
Expand All @@ -92,13 +104,41 @@ 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/locale -D sphinx

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

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

Translate phrases, not words
````````````````````````````

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

Deal with variables and markup in translations
`````````````````````````````````````````````````````````````

If a variable (like the ``edit_page_provider_name`` theme option) is used as part of a phrase, it must be included within the translatable string.
Otherwise, the word order in other languages can be incorrect.
In a Jinja template, simply surround the translatable string with ``{% trans variable=variable %}`` and ``{% endtrans %}}`.
For example: ``{% trans provider=provider %}Edit on {{ provider }}{% endtrans %}``
The translatable string is extracted as the Python format string ``Edit on %(provider)s``.
This is so that the same translatable string can be used in both Python code and Jinja templates.
It is the translator's responsibility to use ``%(provider)s`` verbatim in the translation.

pybabel compile -d src/pydata_sphinx_theme/locale -D sphinx
If a non-translatable word or token (like HTML markup) is used as part of a phrase, it must also be included within the translatable string.
For example: ``{% trans theme_version=theme_version|e %}Built with the <a href="https://pydata-sphinx-theme.readthedocs.io/en/stable/index.html">PyData Sphinx Theme</a> {{ theme_version }}.{% endtrans %}``
It is the translator's responsibility to use the HTML markup verbatim in the translation.


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
157 changes: 157 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,157 @@
# English translations for pydata-sphinx-theme.
# Copyright (C) 2023 PyData developers
# This file is distributed under the same license as the pydata-sphinx-theme project.
#
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 <LL@li.org>\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