Skip to content

Commit 8598864

Browse files
jpmckinneydrammockcholdgraf
authored
feat: Add full i18n support (#1192)
Co-authored-by: Daniel McCloy <[email protected]> Co-authored-by: James McKinney <[email protected]> Co-authored-by: Chris Holdgraf <[email protected]> Co-authored-by: Chris Holdgraf <[email protected]>
1 parent 9819171 commit 8598864

File tree

23 files changed

+780
-47
lines changed

23 files changed

+780
-47
lines changed

.github/workflows/publish.yml

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ jobs:
2020
uses: actions/setup-python@v4
2121
with:
2222
python-version: "3.9"
23+
- name: Install gettext for translations
24+
run: |
25+
sudo apt-get install gettext
2326
- name: Build package
2427
run: |
2528
python -m pip install -U pip build

.github/workflows/tests.yml

+4
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ jobs:
8484
python -m pip install -e .[test] sphinx==${{ matrix.sphinx-version }}
8585
- name: Show installed versions
8686
run: python -m pip list
87+
- name: Compile MO files
88+
run: |
89+
pip install nox
90+
nox -s compile
8791
- name: Run tests
8892
run: pytest --color=yes --cov pydata_sphinx_theme --cov-branch --cov-report term-missing:skip-covered --cov-fail-under ${{ env.COVERAGE_THRESHOLD }}
8993

.gitignore

+6-6
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,6 @@ coverage.xml
4646
*.cover
4747
.hypothesis/
4848

49-
# Translations
50-
*.mo
51-
*.pot
52-
5349
# Django stuff:
5450
*.log
5551
local_settings.py
@@ -113,11 +109,15 @@ node_modules/
113109
.vscode
114110
.idea
115111

112+
# MacOSX store files
113+
**/.DS_Store
114+
115+
# THEME FILES
116116
# files from the gallery screenshots
117117
docs/_static/gallery
118118

119119
# Our site profile tests
120120
profile.svg
121121

122-
# MacOSX store files
123-
**/.DS_Store
122+
# Compiled translation files (are compiled at build time)
123+
src/pydata_sphinx_theme/locale/*/*/*.mo

babel.cfg

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# See https://github.com/sphinx-doc/sphinx/blob/6.1.x/babel.cfg
2+
[jinja2: **.html]
3+
encoding = utf-8
4+
ignore_tags = script,style
5+
include_attrs = alt title summary placeholder

docs/community/topics/i18n.rst

+42-2
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,26 @@ These steps cover how to add or change text that has been marked as translateabl
6565
6666
pybabel extract . -F babel.cfg -o src/pydata_sphinx_theme/locale/sphinx.pot -k '_ __ l_ lazy_gettext'
6767
68+
**To run this in ``.nox``**: ``nox -s translate -- extract``.
69+
6870
#. Update the message catalogs (``PO`` files) with `the PyBabel update command <https://babel.pocoo.org/en/latest/cmdline.html#update>`__:
6971

7072
.. code-block:: bash
7173
7274
pybabel update -i src/pydata_sphinx_theme/locale/sphinx.pot -d src/pydata_sphinx_theme/locale -D sphinx
7375
76+
**To run this in ``.nox``**: ``nox -s translate -- update``.
77+
78+
7479
This will update these files with new information about the position and text of the language you have modified.
7580

81+
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.
82+
83+
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.
84+
If there is a fuzzy match, a comment like `#, fuzzy` is added before the matched entry.
85+
Otherwise, a new entry is added and needs to be translated.
86+
87+
7688
.. _translating-the-theme:
7789

7890
Add translations to translateable text
@@ -92,13 +104,41 @@ This section covers how to do so.
92104
93105
pybabel init -i src/pydata_sphinx_theme/locale/sphinx.pot -d src/pydata_sphinx_theme/locale -D sphinx -l es
94106
107+
**To run this in ``.nox``**: ``nox -s translate -- init es``
108+
95109
#. 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.
96110

97111
#. 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>`__:
98112

99-
.. code-block:: bash
113+
.. code-block:: bash
114+
115+
pybabel compile -d src/pydata_sphinx_theme/locale -D sphinx
116+
117+
**To run this in ``.nox``**: ``nox -s translate -- compile``.
118+
119+
Translation tips
120+
----------------
121+
122+
Translate phrases, not words
123+
````````````````````````````
124+
125+
Full sentences and clauses must always be a single translatable string.
126+
Otherwise, you can get ``next page`` translated as ``suivant page`` instead of as ``page suivante``, etc.
127+
128+
Deal with variables and markup in translations
129+
`````````````````````````````````````````````````````````````
130+
131+
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.
132+
Otherwise, the word order in other languages can be incorrect.
133+
In a Jinja template, simply surround the translatable string with ``{% trans variable=variable %}`` and ``{% endtrans %}}`.
134+
For example: ``{% trans provider=provider %}Edit on {{ provider }}{% endtrans %}``
135+
The translatable string is extracted as the Python format string ``Edit on %(provider)s``.
136+
This is so that the same translatable string can be used in both Python code and Jinja templates.
137+
It is the translator's responsibility to use ``%(provider)s`` verbatim in the translation.
100138

101-
pybabel compile -d src/pydata_sphinx_theme/locale -D sphinx
139+
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.
140+
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 %}``
141+
It is the translator's responsibility to use the HTML markup verbatim in the translation.
102142

103143

104144
References

noxfile.py

+39
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"""
99
import nox
1010
from pathlib import Path
11+
from shlex import split
1112

1213
nox.options.reuse_existing_virtualenvs = True
1314

@@ -35,6 +36,10 @@ def _should_install(session):
3536
return should_install
3637

3738

39+
def _compile_translations(session):
40+
session.run(*split("pybabel compile -d src/pydata_sphinx_theme/locale -D sphinx"))
41+
42+
3843
@nox.session(name="compile")
3944
def compile(session):
4045
"""Compile the theme's web assets with sphinx-theme-builder."""
@@ -55,6 +60,7 @@ def docs(session):
5560
@nox.session(name="docs-live")
5661
def docs_live(session):
5762
"""Build the docs with a live server that re-loads as you make changes."""
63+
_compile_translations(session)
5864
if _should_install(session):
5965
session.install("-e", ".[doc]")
6066
session.install("sphinx-theme-builder[cli]")
@@ -66,6 +72,7 @@ def test(session):
6672
"""Run the test suite."""
6773
if _should_install(session):
6874
session.install("-e", ".[test]")
75+
_compile_translations(session)
6976
session.run("pytest", *session.posargs)
7077

7178

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

8188

89+
@nox.session()
90+
def translate(session):
91+
"""Translation commands. Available commands after `--` : extract, update, compile"""
92+
session.install("Babel")
93+
if "extract" in session.posargs:
94+
session.run(
95+
*split(
96+
"pybabel extract . -F babel.cfg -o src/pydata_sphinx_theme/locale/sphinx.pot -k '_ __ l_ lazy_gettext'"
97+
)
98+
)
99+
elif "update" in session.posargs:
100+
session.run(
101+
*split(
102+
"pybabel update -i src/pydata_sphinx_theme/locale/sphinx.pot -d src/pydata_sphinx_theme/locale -D sphinx"
103+
)
104+
)
105+
elif "compile" in session.posargs:
106+
_compile_translations(session)
107+
elif "init" in session.posargs:
108+
language = session.posargs[-1]
109+
session.run(
110+
*split(
111+
f"pybabel init -i src/pydata_sphinx_theme/locale/sphinx.pot -d src/pydata_sphinx_theme/locale -D sphinx -l {language}"
112+
)
113+
)
114+
else:
115+
print(
116+
"No translate command found. Use like: `nox -s translate -- COMMAND`."
117+
"\n\n Available commands: extract, update, compile, init"
118+
)
119+
120+
82121
@nox.session(name="profile")
83122
def profile(session):
84123
"""Generate a profile chart with py-spy. The chart will be placed at profile.svg."""

pyproject.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ additional-compiled-static-assets = [
99
"webpack-macros.html",
1010
"vendor/",
1111
"styles/bootstrap.css",
12-
"scripts/bootstrap.js"
12+
"scripts/bootstrap.js",
13+
"locale/"
1314
]
1415

1516
[project]
@@ -24,6 +25,7 @@ dependencies = [
2425
"beautifulsoup4",
2526
"docutils!=0.17.0",
2627
"packaging",
28+
"Babel",
2729
"pygments>=2.7",
2830
"accessible-pygments"
2931
]

src/pydata_sphinx_theme/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -1160,6 +1160,9 @@ def setup(app):
11601160
app.connect("build-finished", _overwrite_pygments_css)
11611161
app.connect("build-finished", copy_logo_images)
11621162

1163+
# https://www.sphinx-doc.org/en/master/extdev/i18n.html#extension-internationalization-i18n-and-localization-l10n-using-i18n-api
1164+
app.add_message_catalog("sphinx", here / "locale")
1165+
11631166
# Include component templates
11641167
app.config.templates_path.append(str(theme_path / "components"))
11651168

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# English translations for pydata-sphinx-theme.
2+
# Copyright (C) 2023 PyData developers
3+
# This file is distributed under the same license as the pydata-sphinx-theme project.
4+
#
5+
msgid ""
6+
msgstr ""
7+
"Project-Id-Version: PROJECT VERSION\n"
8+
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
9+
"POT-Creation-Date: 2023-02-16 14:32-0500\n"
10+
"PO-Revision-Date: 2023-02-16 13:19-0500\n"
11+
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
12+
"Language: en\n"
13+
"Language-Team: en <[email protected]>\n"
14+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
15+
"MIME-Version: 1.0\n"
16+
"Content-Type: text/plain; charset=utf-8\n"
17+
"Content-Transfer-Encoding: 8bit\n"
18+
"Generated-By: Babel 2.11.0\n"
19+
20+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/layout.html:50
21+
msgid "Skip to main content"
22+
msgstr ""
23+
24+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/search-button.html:7
25+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/search.html:5
26+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/search.html:28
27+
msgid "Search"
28+
msgstr ""
29+
30+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/search.html:8
31+
msgid "Error"
32+
msgstr ""
33+
34+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/search.html:9
35+
msgid "Please activate JavaScript to enable the search functionality."
36+
msgstr ""
37+
38+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/breadcrumbs.html:12
39+
msgid "Breadcrumbs"
40+
msgstr ""
41+
42+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/breadcrumbs.html:13
43+
msgid "Breadcrumb"
44+
msgstr ""
45+
46+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/breadcrumbs.html:16
47+
msgid "Home"
48+
msgstr ""
49+
50+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/copyright.html:4
51+
#, python-format
52+
msgid "© <a href=\"%(path)s\">Copyright</a> %(copyright)s."
53+
msgstr ""
54+
55+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/copyright.html:7
56+
#, python-format
57+
msgid "© Copyright %(copyright)s."
58+
msgstr ""
59+
60+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/edit-this-page.html:9
61+
#, python-format
62+
msgid "Edit on %(provider)s"
63+
msgstr ""
64+
65+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/edit-this-page.html:11
66+
msgid "Edit"
67+
msgstr ""
68+
69+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/icon-links.html:31
70+
msgid "GitHub"
71+
msgstr ""
72+
73+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/icon-links.html:32
74+
msgid "GitLab"
75+
msgstr ""
76+
77+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/icon-links.html:33
78+
msgid "Bitbucket"
79+
msgstr ""
80+
81+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/icon-links.html:34
82+
msgid "Twitter"
83+
msgstr ""
84+
85+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/indices.html:2
86+
msgid "Indices"
87+
msgstr ""
88+
89+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/indices.html:9
90+
msgid "General Index"
91+
msgstr ""
92+
93+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/indices.html:13
94+
msgid "Global Module Index"
95+
msgstr ""
96+
97+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/indices.html:17
98+
msgid "Python Module Index"
99+
msgstr ""
100+
101+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/last-updated.html:2
102+
#, python-format
103+
msgid "Last updated on %(last_updated)s."
104+
msgstr ""
105+
106+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/navbar-nav.html:5
107+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/navbar-nav.html:6
108+
msgid "Site Navigation"
109+
msgstr ""
110+
111+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/page-toc.html:4
112+
msgid "On this page"
113+
msgstr ""
114+
115+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/sidebar-nav-bs.html:2
116+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/sidebar-nav-bs.html:3
117+
msgid "Section Navigation"
118+
msgstr ""
119+
120+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/sourcelink.html:4
121+
msgid "Show Source"
122+
msgstr ""
123+
124+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/sphinx-version.html:3
125+
#, python-format
126+
msgid ""
127+
"Created using <a href=\"https://sphinx-doc.org/\">Sphinx</a> "
128+
"%(sphinx_version)s."
129+
msgstr ""
130+
131+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/theme-switcher.html:5
132+
msgid "light/dark"
133+
msgstr ""
134+
135+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/theme-version.html:2
136+
#, python-format
137+
msgid ""
138+
"Built with the <a href=\"https://pydata-sphinx-"
139+
"theme.readthedocs.io/en/stable/index.html\">PyData Sphinx Theme</a> "
140+
"%(theme_version)s."
141+
msgstr ""
142+
143+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/footer-article/prev-next.html:6
144+
msgid "previous page"
145+
msgstr ""
146+
147+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/footer-article/prev-next.html:9
148+
msgid "previous"
149+
msgstr ""
150+
151+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/footer-article/prev-next.html:17
152+
msgid "next page"
153+
msgstr ""
154+
155+
#: src/pydata_sphinx_theme/theme/pydata_sphinx_theme/components/footer-article/prev-next.html:19
156+
msgid "next"
157+
msgstr ""

0 commit comments

Comments
 (0)