Skip to content

Commit

Permalink
feat: Add full i18n support (#1192)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel McCloy <dan@mccloy.info>
Co-authored-by: James McKinney <26463+jpmckinney@users.noreply.github.com>
Co-authored-by: Chris Holdgraf <choldgraf@berkeley.edu>
Co-authored-by: Chris Holdgraf <choldgraf@gmail.com>
  • Loading branch information
4 people authored Feb 23, 2023
1 parent 9819171 commit 8598864
Show file tree
Hide file tree
Showing 23 changed files with 780 additions and 47 deletions.
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
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 @@ -1160,6 +1160,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

0 comments on commit 8598864

Please sign in to comment.