Skip to content

Add i18n support for exercise and solution labels#75

Merged
mmcky merged 9 commits intoexecutablebooks:mainfrom
TeachBooks:main
Oct 21, 2025
Merged

Add i18n support for exercise and solution labels#75
mmcky merged 9 commits intoexecutablebooks:mainfrom
TeachBooks:main

Conversation

@douden
Copy link
Contributor

@douden douden commented Jul 30, 2025

This PR references and solves issue #73.
Closes #73

This pull request introduces internationalization (i18n) support for the sphinx_exercise extension, enabling translations for exercise-related text. It includes changes to add translation files, update the codebase to use translatable strings, and provide a mechanism for compiling and managing translation files.

Internationalization Support:

  • Added translation infrastructure:

    • Introduced MESSAGE_CATALOG_NAME and get_translation for retrieving localized strings in sphinx_exercise/__init__.py, directive.py, and nodes.py. [1] [2] [3]
    • Updated default titles and labels to use translatable strings (e.g., "Exercise" and "Solution to"). [1] [2] [3] [4] [5]
    • Registered translation message catalogs and locale directories in the setup function.
  • Added translation files:

    • Created JSON files for translations of "Exercise" and "Solution to" in multiple languages. [1] [2]
    • Added .po files for supported languages in the translations/locales directory. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12]

Translation Management:

  • Added a script (_convert.py) to convert JSON translation files into .po and .mo files for use in the extension. The script handles file creation, updates, and compilation.
  • Documented the translation process in a new README.md file within the translations directory.

Packaging Updates:

  • Updated MANIFEST.in to include translation files and other necessary assets in the package distribution.

douden added 9 commits July 30, 2025 14:41
Introduces internationalization for 'Exercise' and 'Solution to' labels using Sphinx's message catalog system. Adds translation JSONs, conversion script, and locale files for multiple languages. Updates code to use translated labels and registers message catalog in Sphinx extension setup.
Changed the project name to 'teachbooks-sphinx-exercise' and updated the description to indicate this is a TeachBooks temporary version. Added a new author to the authors list.
Removed the old CI workflow and introduced a new Python publishing workflow for building, publishing, and signing releases. Added initial files for Dutch translation tests under the tests/books/test-dutch directory.
Added [tool.hatch.build.targets.wheel] section to specify the package directory for building wheels, ensuring correct packaging of 'src/sphinx_exercise'.
Renamed all occurrences of the 'sphinx_exercise' package to 'teachbooks_sphinx_exercise', including source files, assets, translations, and configuration in pyproject.toml. Updated optional dependencies and version path in pyproject.toml to reflect the new package name.
@mmcky mmcky requested a review from Copilot October 21, 2025 00:07
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces internationalization (i18n) support for the sphinx_exercise extension, enabling translations of "Exercise" and "Solution to" labels across 21 languages. The implementation uses Sphinx's built-in translation infrastructure with .po and .mo files.

Key changes:

  • Added translation infrastructure using Sphinx's get_translation API
  • Created translation files (.po) for 21 languages with corresponding JSON source files
  • Implemented a conversion script to generate .po/.mo files from JSON translations

Reviewed Changes

Copilot reviewed 28 out of 48 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
sphinx_exercise/init.py Registers message catalog and locale directory; uses translated strings for numfig format
sphinx_exercise/directive.py Updates default title text to use translated strings
sphinx_exercise/nodes.py Modifies title validation to check against translated strings
sphinx_exercise/translations/_convert.py Provides script to convert JSON translations to .po/.mo files
sphinx_exercise/translations/README.md Documents the translation conversion process
sphinx_exercise/translations/jsons/*.json Source translation data for Exercise and Solution labels
sphinx_exercise/translations/locales/*/LC_MESSAGES/exercise.po Generated .po files for 21 languages
MANIFEST.in Includes translation files in package distribution

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

def convert_json(folder=None):
folder = folder or Path(__file__).parent

# remove exising
Copy link

Copilot AI Oct 21, 2025

Choose a reason for hiding this comment

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

Corrected spelling of 'exising' to 'existing'.

Suggested change
# remove exising
# remove existing

Copilot uses AI. Check for mistakes.
def default_title(self):
title_text = self.children[0].astext()
if title_text == "Exercise" or title_text == "Exercise %s":
if title_text == f"{translate('Exercise')}" or title_text == f"{translate('Exercise')} %s":
Copy link

Copilot AI Oct 21, 2025

Choose a reason for hiding this comment

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

The f-string formatting is unnecessary when wrapping a single function call. The code can be simplified to if title_text == translate('Exercise') or title_text == f"{translate('Exercise')} %s": for better readability.

Suggested change
if title_text == f"{translate('Exercise')}" or title_text == f"{translate('Exercise')} %s":
if title_text == translate('Exercise') or title_text == translate('Exercise') + " %s":

Copilot uses AI. Check for mistakes.
def default_title(self):
title_text = self.children[0].astext()
if title_text == "Solution to":
if title_text == f"{translate('Solution to')}":
Copy link

Copilot AI Oct 21, 2025

Choose a reason for hiding this comment

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

The f-string formatting is unnecessary when wrapping a single function call. The code can be simplified to if title_text == translate('Solution to'): for better readability.

Suggested change
if title_text == f"{translate('Solution to')}":
if title_text == translate('Solution to'):

Copilot uses AI. Check for mistakes.

def run(self) -> List[Node]:
self.defaults = {"title_text": "Exercise"}
self.defaults = {"title_text": f"{translate('Exercise')}"}
Copy link

Copilot AI Oct 21, 2025

Choose a reason for hiding this comment

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

The f-string formatting is unnecessary when wrapping a single function call. The code can be simplified to self.defaults = {"title_text": translate('Exercise')} for better readability.

Suggested change
self.defaults = {"title_text": f"{translate('Exercise')}"}
self.defaults = {"title_text": translate('Exercise')}

Copilot uses AI. Check for mistakes.

def run(self) -> List[Node]:
self.defaults = {"title_text": "Solution to"}
self.defaults = {"title_text": f"{translate('Solution to')}"}
Copy link

Copilot AI Oct 21, 2025

Choose a reason for hiding this comment

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

The f-string formatting is unnecessary when wrapping a single function call. The code can be simplified to self.defaults = {"title_text": translate('Solution to')} for better readability.

Suggested change
self.defaults = {"title_text": f"{translate('Solution to')}"}
self.defaults = {"title_text": translate('Solution to')}

Copilot uses AI. Check for mistakes.

config["numfig"] = True
numfig_format = {"exercise": "Exercise %s"}
numfig_format = {"exercise": f"{translate('Exercise')} %s"}
Copy link

Copilot AI Oct 21, 2025

Choose a reason for hiding this comment

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

The f-string can be simplified by removing unnecessary braces. Change to numfig_format = {"exercise": f"{translate('Exercise')} %s"} or use string concatenation: numfig_format = {"exercise": translate('Exercise') + " %s"} for better readability.

Suggested change
numfig_format = {"exercise": f"{translate('Exercise')} %s"}
numfig_format = {"exercise": translate('Exercise') + " %s"}

Copilot uses AI. Check for mistakes.
@mmcky
Copy link
Member

mmcky commented Oct 21, 2025

Thanks @douden -- this is a great addition to this extension. Again apologies for the delay in review, appreciate the contribution.

I have done some local testing and it is looking pretty good on my end, I will take a look at the copilot review as a final check.

I will add a few languages, but I will do that in a separate PR.

@mmcky
Copy link
Member

mmcky commented Oct 21, 2025

Actually I just realised this is from a fork @douden so those suggestions can't be easily committed unless you do so upstream. I will include some of them in a new PR.

@mmcky mmcky merged commit 568df6c into executablebooks:main Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for Internationalisation

2 participants