Skip to content
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
88 changes: 88 additions & 0 deletions .github/workflows/integration-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: Integration test

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

permissions:
contents: read

env:
FORCE_COLOR: 1

jobs:
integration:
name: Translate, build & install on Python ${{ matrix.python }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python: [3.13, 3.14, 3.15]

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ matrix.python }}
allow-prereleases: true

- name: Install build dependencies
run: python -m pip install babel hatchling build

- name: Create a new translation
run: |
python scripts/i18n.py extract
python scripts/i18n.py init eo

- name: Translate one docstring
run: |
python - <<'PY'
import sys
sys.path.insert(0, "scripts")
import i18n

path = i18n.PO_DIR / "eo.po"
catalog = i18n.read_catalog(path)
for message in catalog:
if "turtle.Turtle.forward" in message.auto_comments:
message.string = "Movu la testudon antaŭen."
break
# A nonexistent name to check it is dropped at import.
catalog.add("Not a real docstring.", "Ne vera dokumentaĵo.",
auto_comments=["turtle.Turtle.nonexistent"])
i18n.write_catalog(catalog, path)
PY
python scripts/i18n.py stats

- name: Build and install the wheel
run: |
python -m build --wheel
python -m pip install dist/*.whl

- name: Check the translated docstring is used
working-directory: ${{ runner.temp }}
run: |
echo "language = eo" > turtle.cfg
cat > check.py <<'PY'
import contextlib
import io

# turtle prints errors for names it cannot resolve.
with contextlib.redirect_stdout(io.StringIO()) as output:
import turtle
assert output.getvalue() == ""

assert turtle.Turtle.forward.__doc__ == "Movu la testudon antaŭen."
assert turtle.forward.__doc__ == "Movu la testudon antaŭen."
assert "Move the turtle backward" in turtle.back.__doc__

import turtle_docstringdict_eo
assert "Turtle.nonexistent" not in turtle_docstringdict_eo.docsdict
assert not hasattr(turtle.Turtle, "nonexistent")
PY
python check.py
6 changes: 5 additions & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
name: Lint

on: [push, pull_request, workflow_dispatch]
on:
push:
branches: [main]
pull_request:
workflow_dispatch:

permissions: {}

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ name: Upload package

on:
push:
branches: [main]
pull_request:
release:
types:
Expand Down
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# Compiled from po/*.po
/turtle_translations/*.py
!/turtle_translations/__init__.py
/turtle_docstringdict_*.py

# Byte-compiled / optimized / DLL files
__pycache__/
Expand Down
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,16 @@ pl 42/103 translated (40%), 3 fuzzy

### Compiling

Each PO file compiles to a `turtle_translations/<lang>.py` module containing a
`docsdict`, which is what `turtle` loads. The generated modules are built
automatically when the wheel is built, so you normally only need this to test
locally:
Each PO file compiles to a top-level `turtle_docstringdict_<lang>.py` module
containing a `docsdict`, which is what `turtle` imports. Untranslated and fuzzy
entries are left out so their help stays English. When imported, the module drops
entries for names that do not exist in the running version of `turtle`, so a
single module built from the newest Python works on older ones too.

The modules are built automatically when the wheel is built, so you normally
only need this to test locally:

```console
$ python scripts/i18n.py compile
Compiled: turtle_translations/pl.py
Compiled: turtle_docstringdict_pl.py
```
11 changes: 7 additions & 4 deletions scripts/hook.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Hatchling build hook to compile the PO catalogs when the wheel is built."""

import sys
import tempfile
from pathlib import Path

from hatchling.builders.hooks.plugin.interface import BuildHookInterface
Expand All @@ -11,7 +12,9 @@

class CustomBuildHook(BuildHookInterface):
def initialize(self, version, build_data):
# The generated modules are gitignored, so hatch need to be told to ship them.
build_data["artifacts"] = [
str(path.relative_to(self.root)) for path in i18n._compile_catalogs()
]
self._build_dir = tempfile.TemporaryDirectory()
for path in i18n._compile_catalogs(self._build_dir.name):
build_data["force_include"][str(path)] = path.name

def finalize(self, version, build_data, artifact_path):
self._build_dir.cleanup()
37 changes: 29 additions & 8 deletions scripts/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

ROOT = Path(__file__).resolve().parent.parent
PO_DIR = ROOT / "po"
PACKAGE_DIR = ROOT / "turtle_translations"
POT = PO_DIR / "turtle.pot"
PROJECT = "turtle-translations"
BUGS_ADDRESS = "https://github.com/python/turtle-translations/issues"
Expand Down Expand Up @@ -115,16 +114,38 @@ def _load_docsdict(path):
}


def _compile_catalogs():
MODULE_FOOTER = """
# turtle imports this module after defining its classes, so drop entries for
# names this version of turtle does not have to avoid stdout reports.
import turtle

for _key in list(docsdict):
_obj = turtle
for _attr in _key.split("."):
_obj = getattr(_obj, _attr, None)
if _obj is None:
del docsdict[_key]
Comment on lines +120 to +127

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If we were to go ahead with this, could we please cover this fragment with a test that ensures it doesn't fail in runtime?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

For now, I've added an integration test that runs with 3.13, 3.14 and 3.15 and asserts this works. I'll look into cherry-picking your (more precise) tests from the other PR and including one for this.

"""


def _render_module(source, docsdict):
lines = [f"# Generated from {source.name}.",
"",
"docsdict = {"
]
for key, doc in sorted(docsdict.items()):
lines.append(f" {key!r}: {doc!r},")
lines.append("}")
return "\n".join(lines) + "\n" + MODULE_FOOTER


def _compile_catalogs(output_dir=ROOT):
"""Write a `turtle_docstringdict_<lang>.py` module for each PO file."""
written = []
for path in po_files():
# turtle lowercases the language before importing it!
target = PACKAGE_DIR / f"{path.stem.lower()}.py"
lines = [f"# Generated from {path.name}.", "", "docsdict = {"]
for key, doc in sorted(_load_docsdict(path).items()):
lines.append(f" {key!r}: {doc!r},")
lines.append("}")
target.write_text("\n".join(lines) + "\n", encoding="utf-8")
target = Path(output_dir) / f"turtle_docstringdict_{path.stem.lower()}.py"
target.write_text(_render_module(path, _load_docsdict(path)), encoding="utf-8")
written.append(target)
return written

Expand Down
11 changes: 0 additions & 11 deletions turtle_translations/__init__.py

This file was deleted.

Loading