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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ coverage.xml
.pytest_cache/

# Translations
*.mo
# *.mo
*.pot

# Django stuff:
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## Unreleased

### Added

- Support for more languages in toggle button text (see README for full list) [#66](https://github.com/executablebooks/sphinx-togglebutton/pull/66).
- Added new functions (syncToggleHint, syncAllToggleHints) to dynamically update toggle button hints based on the current state [#66](https://github.com/executablebooks/sphinx-togglebutton/pull/66). This allows developers to change the open or closed state of an element and have the button hint text update accordingly automatically.

### Fixed

- Update togglebutton.js to use an admonition's existing ID if present, instead of always generating a new one [#66](https://github.com/executablebooks/sphinx-togglebutton/pull/66).

## v0.3.2 - 2022-07-15

([full changelog](https://github.com/executablebooks/sphinx-togglebutton/compare/v0.3.1...347b1ad3a093afad0f0d4c0041249f09f39afab2))
Expand Down
8 changes: 8 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
graft doc/

recursive-include sphinx_togglebutton *.js
recursive-include sphinx_togglebutton *.css

recursive-include sphinx_togglebutton *.json
recursive-include sphinx_togglebutton *.mo
recursive-include sphinx_togglebutton *.po
recursive-include sphinx_togglebutton *.py
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,10 @@ here's a container with an image inside:
.. image:: https://media.giphy.com/media/mW05nwEyXLP0Y/giphy.gif
```

Internationalization (i18n)
============

The texts `Click to hide` and `Click to show` used in the toggle buttons are internationalized by using the set language from Sphinx's configuration.

Supported languages are English (default), Chinese (Simplified), Chinese (Traditional), Hindi, Spanish, French, Arabic, Bengali, Russian, Portuguese, Indonesian, Japanese, German, Korean, Turkish, Vietnamese, Tamil, Italian, Thai, Dutch, Greek, Polish, Ukrainian, Persian, Malay, Swahili, Romanian, Czech, Hungarian, Hebrew, Swedish, and Norwegian.
7 changes: 6 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@


# -- General configuration ---------------------------------------------------
extensions = ["myst_nb", "sphinx_examples", "sphinx_design", "sphinx_togglebutton"]
extensions = [
"myst_nb",
"sphinx_examples",
"sphinx_design",
"sphinx_togglebutton",
]
templates_path = ["_templates"]
source_suffix = ".rst"
main_doc = "index"
Expand Down
11 changes: 10 additions & 1 deletion docs/use.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,16 @@ Here is my toggle-able content!
## Change the button hint text

You can control the "hint" text that is displayed next to togglebuttons.
To do so, use the following configuration variable in your `conf.py` file:

To use the default values for your language, set the language in your Sphinx configuration:

```python
language = "nl" # or "de", "es", etc.
```

Supported languages are English (default), Chinese (Simplified), Chinese (Traditional), Hindi, Spanish, French, Arabic, Bengali, Russian, Portuguese, Indonesian, Japanese, German, Korean, Turkish, Vietnamese, Tamil, Italian, Thai, Dutch, Greek, Polish, Ukrainian, Persian, Malay, Swahili, Romanian, Czech, Hungarian, Hebrew, Swedish, and Norwegian.

If you prefer to have custom hint texts instead of the defaults for your language, use the following configuration variable in your `conf.py` file:

```python
togglebutton_hint = "Displayed when the toggle is closed."
Expand Down
3 changes: 3 additions & 0 deletions readthedocs.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
version: 2
sphinx:
# Path to your Sphinx configuration file.
configuration: docs/conf.py
build:
os: "ubuntu-22.04"
tools:
Expand Down
25 changes: 21 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os
from pathlib import Path
from setuptools import setup, find_packages

from setuptools import find_packages, setup

version = [
line
Expand All @@ -24,9 +24,26 @@
license="MIT License",
packages=find_packages(),
package_data={
"sphinx_togglebutton": ["_static/togglebutton.css", "_static/togglebutton.js", "_static/togglebutton-chevron.svg"]
"sphinx_togglebutton": [
"_static/togglebutton.css",
"_static/togglebutton.js",
"_static/togglebutton-chevron.svg",
"translations/README.md",
"translations/_convert.py",
"translations/jsons/*.json",
"translations/locales/**/*",
]
},
install_requires=["setuptools", "wheel", "sphinx", "docutils"],
extras_require={"sphinx": ["matplotlib", "numpy", "myst_nb", "sphinx_book_theme", "sphinx_design", "sphinx_examples"]},
extras_require={
"sphinx": [
"matplotlib",
"numpy",
"myst_nb",
"sphinx_book_theme",
"sphinx_design",
"sphinx_examples",
]
},
classifiers=["License :: OSI Approved :: MIT License"],
)
49 changes: 41 additions & 8 deletions sphinx_togglebutton/__init__.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,44 @@
"""A small sphinx extension to add "toggle" buttons to items."""

import os
from docutils.parsers.rst import Directive, directives

from docutils import nodes
from docutils.parsers.rst import Directive, directives
from sphinx.locale import get_translation

MESSAGE_CATALOG_NAME = "togglebutton"
translate = get_translation(MESSAGE_CATALOG_NAME)

__version__ = "0.3.2"


def st_static_path(app):
static_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "_static"))
static_path = os.path.abspath(
os.path.join(os.path.dirname(__file__), "_static")
)
app.config.html_static_path.append(static_path)


def initialize_js_assets(app, config):
# Update the global context
app.add_js_file(None, body=f"let toggleHintShow = '{config.togglebutton_hint}';")
app.add_js_file(None, body=f"let toggleHintHide = '{config.togglebutton_hint_hide}';")

app.add_js_file(
None, body=f"let toggleHintShow = '{config.togglebutton_hint}';"
)
app.add_js_file(
None, body=f"let toggleHintHide = '{config.togglebutton_hint_hide}';"
)
open_print = str(config.togglebutton_open_on_print).lower()
app.add_js_file(None, body=f"let toggleOpenOnPrint = '{open_print}';")
app.add_js_file("togglebutton.js")


# This function reads in a variable and inserts it into JavaScript


def insert_custom_selection_config(app):
# This is a configuration that you've specified for users in `conf.py`

selector = app.config["togglebutton_selector"]
js_text = "var togglebuttonSelector = '%s';" % selector
app.add_js_file(None, body=js_text)
Expand All @@ -42,28 +58,45 @@ def run(self):
classes = ["toggle"]
if "show" in self.options:
classes.append("toggle-shown")

parent = nodes.container(classes=classes)
self.state.nested_parse(self.content, self.content_offset, parent)
return [parent]


# We connect this function to the step after the builder is initialized


def setup(app):
# add translations

package_dir = os.path.abspath(os.path.dirname(__file__))
locale_dir = os.path.join(package_dir, "translations", "locales")
app.add_message_catalog(MESSAGE_CATALOG_NAME, locale_dir)

# Add our static path

app.connect("builder-inited", st_static_path)

# Add relevant code to headers

app.add_css_file("togglebutton.css")

# Add the string we'll use to select items in the JS
# Tell Sphinx about this configuration variable
app.add_config_value("togglebutton_selector", ".toggle, .admonition.dropdown", "html")
app.add_config_value("togglebutton_hint", "Click to show", "html")
app.add_config_value("togglebutton_hint_hide", "Click to hide", "html")

app.add_config_value(
"togglebutton_selector", ".toggle, .admonition.dropdown", "html"
)
app.add_config_value(
"togglebutton_hint", f"{translate('Click to show')}", "html"
)
app.add_config_value(
"togglebutton_hint_hide", f"{translate('Click to hide')}", "html"
)
app.add_config_value("togglebutton_open_on_print", True, "html")

# Run the function after the builder is initialized

app.connect("builder-inited", insert_custom_selection_config)
app.connect("config-inited", initialize_js_assets)
app.add_directive("toggle", Toggle)
Expand Down
55 changes: 53 additions & 2 deletions sphinx_togglebutton/_static/togglebutton.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,13 @@ var initToggleItems = () => {
itemsToToggle.forEach((item, index) => {
if (item.classList.contains("admonition")) {
// If it's an admonition block, then we'll add a button inside
// Generate unique IDs for this item
var toggleID = `toggle-${index}`;
// Generate unique IDs for this item,
// IF AND ONLY IF THE ITEM DOESN'T ALREADY HAVE AN ID
if (!item.id) {
var toggleID = `toggle-${index}`;
} else {
var toggleID = item.id;
}
var buttonID = `button-${toggleID}`;

item.setAttribute("id", toggleID);
Expand Down Expand Up @@ -123,6 +128,25 @@ var toggleHidden = (button) => {
}
};

// Function to synchronize the data-toggle-hint with the current state
var syncToggleHint = (button) => {
const target = button.dataset["target"];
const itemToToggle = document.getElementById(target);

if (itemToToggle && itemToToggle.classList.contains("toggle-hidden")) {
button.dataset.toggleHint = toggleHintShow;
button.setAttribute("aria-expanded", false);
} else if (itemToToggle) {
button.dataset.toggleHint = toggleHintHide;
button.setAttribute("aria-expanded", true);
}
};

// Function to sync all toggle buttons - can be called by external extensions
var syncAllToggleHints = () => {
document.querySelectorAll('.toggle-button').forEach(syncToggleHint);
};

var toggleClickHandler = (click) => {
// Be cause the admonition title is clickable and extends to the whole admonition
// We only look for a click event on this title to trigger the toggle.
Expand Down Expand Up @@ -170,6 +194,33 @@ const sphinxToggleRunWhenDOMLoaded = (cb) => {
sphinxToggleRunWhenDOMLoaded(addToggleToSelector);
sphinxToggleRunWhenDOMLoaded(initToggleItems);

// Set up MutationObserver to watch for external changes to toggle states
sphinxToggleRunWhenDOMLoaded(() => {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
const target = mutation.target;
// Check if this is a toggle item that had its class changed
if (target.classList.contains('toggle')) {
// Find the associated toggle button and sync its hint
const button = target.querySelector('.toggle-button');
if (button) {
syncToggleHint(button);
}
}
}
});
});

// Start observing class changes on all toggle elements
document.querySelectorAll('.toggle').forEach((toggleElement) => {
observer.observe(toggleElement, {
attributes: true,
attributeFilter: ['class']
});
});
});

/** Toggle details blocks to be open when printing */
if (toggleOpenOnPrint == "true") {
window.addEventListener("beforeprint", () => {
Expand Down
3 changes: 3 additions & 0 deletions sphinx_togglebutton/translations/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
JSONs created using GitHub Copilot Pro.

To convert to locale files run `_convert.py` in this folder.
65 changes: 65 additions & 0 deletions sphinx_togglebutton/translations/_convert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import json
import os
import subprocess
from pathlib import Path

MESSAGE_CATALOG_NAME = "togglebutton"


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

# remove exising
for path in (folder / "locales").glob(f"**/{MESSAGE_CATALOG_NAME}.po"):
path.unlink()

# compile po
for path in (folder / "jsons").glob("*.json"):
data = json.loads(path.read_text("utf8"))
assert data[0]["symbol"] == "en"
english = data[0]["text"]
for item in data[1:]:
language = item["symbol"]
out_path = (
folder
/ "locales"
/ language
/ "LC_MESSAGES"
/ f"{MESSAGE_CATALOG_NAME}.po"
)
if not out_path.parent.exists():
out_path.parent.mkdir(parents=True)
if not out_path.exists():
header = f"""
msgid ""
msgstr ""
"Project-Id-Version: Sphinx-ToggleButton\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"
"Language: {language}\\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\\n"
"""
out_path.write_text(header)

with out_path.open("a", encoding="utf8") as f:
f.write("\n")
f.write(f'msgid "{english}"\n')
text = item["text"].replace('"', '\\"')
f.write(f'msgstr "{text}"\n')

# compile mo
for path in (folder / "locales").glob(f"**/{MESSAGE_CATALOG_NAME}.po"):
print(path)
subprocess.check_call(
[
"msgfmt",
os.path.abspath(path),
"-o",
os.path.abspath(path.parent / f"{MESSAGE_CATALOG_NAME}.mo"),
]
)


if __name__ == "__main__":
convert_json()
Loading