diff --git a/.copier-answers.yml b/.copier-answers.yml
index bda5cd0c..fa6c4f9c 100644
--- a/.copier-answers.yml
+++ b/.copier-answers.yml
@@ -1,5 +1,5 @@
# Changes here will be overwritten by Copier.
-_commit: 1.7.1
+_commit: 1.8.4
_src_path: gh:pawamoy/copier-uv
author_email: dev@pawamoy.fr
author_fullname: Timothée Mazzucotelli
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ed973133..658ae74f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
+## [0.30.0](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.30.0) - 2025-07-23
+
+[Compare with 0.29.1](https://github.com/mkdocstrings/mkdocstrings/compare/0.29.1...0.30.0)
+
+### Features
+
+- Add `data-skip-inventory` boolean attribute for elements to skip registration in local inventory ([f856160](https://github.com/mkdocstrings/mkdocstrings/commit/f856160b03b2c27e1d75fdf4f315c273cb9d9247) by Bartosz Sławecki). [Issue-671](https://github.com/mkdocstrings/mkdocstrings/issues/671), [PR-774](https://github.com/mkdocstrings/mkdocstrings/pull/774)
+- Add I18N support (translations) ([2b4ed54](https://github.com/mkdocstrings/mkdocstrings/commit/2b4ed541bc707e55d959092d950ebeecc4fbd136) by Nyuan Zhang). [PR-645](https://github.com/mkdocstrings/mkdocstrings/pull/645), Co-authored-by: Timothée Mazzucotelli
+
## [0.29.1](https://github.com/mkdocstrings/mkdocstrings/releases/tag/0.29.1) - 2025-03-31
[Compare with 0.29.0](https://github.com/mkdocstrings/mkdocstrings/compare/0.29.0...0.29.1)
diff --git a/docs/css/mkdocstrings.css b/docs/css/mkdocstrings.css
index 55321aa3..3447549c 100644
--- a/docs/css/mkdocstrings.css
+++ b/docs/css/mkdocstrings.css
@@ -35,3 +35,48 @@ td code {
li.md-nav__item:has(> a[href*="("]) {
display: none;
}
+
+/* Tree-like output for backlinks. */
+.doc-backlink-list {
+ --tree-clr: var(--md-default-fg-color);
+ --tree-font-size: 1rem;
+ --tree-item-height: 1;
+ --tree-offset: 1rem;
+ --tree-thickness: 1px;
+ --tree-style: solid;
+ display: grid;
+ list-style: none !important;
+}
+
+.doc-backlink-list li > span:first-child {
+ text-indent: .3rem;
+}
+.doc-backlink-list li {
+ padding-inline-start: var(--tree-offset);
+ border-left: var(--tree-thickness) var(--tree-style) var(--tree-clr);
+ position: relative;
+ margin-left: 0 !important;
+
+ &:last-child {
+ border-color: transparent;
+ }
+ &::before{
+ content: '';
+ position: absolute;
+ top: calc(var(--tree-item-height) / 2 * -1 * var(--tree-font-size) + var(--tree-thickness));
+ left: calc(var(--tree-thickness) * -1);
+ width: calc(var(--tree-offset) + var(--tree-thickness) * 2);
+ height: calc(var(--tree-item-height) * var(--tree-font-size));
+ border-left: var(--tree-thickness) var(--tree-style) var(--tree-clr);
+ border-bottom: var(--tree-thickness) var(--tree-style) var(--tree-clr);
+ }
+ &::after{
+ content: '';
+ position: absolute;
+ border-radius: 50%;
+ background-color: var(--tree-clr);
+ top: calc(var(--tree-item-height) / 2 * 1rem);
+ left: var(--tree-offset) ;
+ translate: calc(var(--tree-thickness) * -1) calc(var(--tree-thickness) * -1);
+ }
+}
diff --git a/docs/reference/mkdocstrings.md b/docs/reference/api.md
similarity index 100%
rename from docs/reference/mkdocstrings.md
rename to docs/reference/api.md
diff --git a/docs/schema.json b/docs/schema.json
index bd646f88..66197827 100644
--- a/docs/schema.json
+++ b/docs/schema.json
@@ -28,6 +28,12 @@
"type": "string",
"default": "python"
},
+ "locale": {
+ "title": "The locale to use for translations.",
+ "markdownDescription": "https://mkdocstrings.github.io/usage/#global-options",
+ "type": "string",
+ "default": null
+ },
"enable_inventory": {
"title": "Whether to enable inventory file generation.",
"markdownDescription": "https://mkdocstrings.github.io/usage/#cross-references-to-other-projects-inventories",
diff --git a/docs/usage/index.md b/docs/usage/index.md
index ea9716cc..64588fdf 100644
--- a/docs/usage/index.md
+++ b/docs/usage/index.md
@@ -16,7 +16,7 @@ The syntax is as follows:
> Here are some resources that other users found useful to better
> understand YAML's peculiarities.
>
-> - [YAML idiosyncrasies](https://docs.saltproject.io/en/3000/topics/troubleshooting/yaml_idiosyncrasies.html)
+> - [YAML idiosyncrasies](https://salt-zh.readthedocs.io/en/latest/topics/troubleshooting/yaml_idiosyncrasies.html)
> - [YAML multiline](https://yaml-multiline.info/)
The `identifier` is a string identifying the object you want to document.
@@ -113,6 +113,7 @@ The above is equivalent to:
- `handlers`: The handlers' global configuration.
- `enable_inventory`: Whether to enable inventory file generation.
See [Cross-references to other projects / inventories](#cross-references-to-other-projects-inventories)
+- `locale`: The locale used for translations. See [Internationalization](#internationalization-i18n).
- `enabled` **(New in version 0.20)**: Whether to enable the plugin. Defaults to `true`.
Can be used to reduce build times when doing local development.
Especially useful when used with environment variables (see example below).
@@ -124,6 +125,7 @@ The above is equivalent to:
enabled: !ENV [ENABLE_MKDOCSTRINGS, true]
custom_templates: templates
default_handler: python
+ locale: en
handlers:
python:
options:
@@ -141,6 +143,16 @@ The above is equivalent to:
Some handlers accept additional global configuration.
Check the documentation for your handler of interest in [Handlers](handlers.md).
+## Internationalization (I18N)
+
+Some handlers support multiple languages.
+
+If the handler supports localization, the locale it uses is determined by the following order of precedence:
+
+- `locale` in [global options](#global-options)
+- `theme.language`: used by the [MkDocs Material theme](https://squidfunk.github.io/mkdocs-material/setup/changing-the-language/)
+- `theme.locale` in [MkDocs configuration](https://www.mkdocs.org/user-guide/configuration/#theme)
+
## Cross-references
Cross-references are written as Markdown *reference-style* links:
diff --git a/duties.py b/duties.py
index 6ee9b08d..b75d8b55 100644
--- a/duties.py
+++ b/duties.py
@@ -238,7 +238,7 @@ def coverage(ctx: Context) -> None:
@duty
-def test(ctx: Context, *cli_args: str, match: str = "") -> None:
+def test(ctx: Context, *cli_args: str, match: str = "") -> None: # noqa: PT028
"""Run the test suite.
Parameters:
diff --git a/mkdocs.yml b/mkdocs.yml
index 828a81a4..dc53327f 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -34,7 +34,7 @@ nav:
- Guides:
- Recipes: recipes.md
- Troubleshooting: troubleshooting.md
-- API reference: reference/mkdocstrings.md
+- API reference: reference/api.md
- Development:
- Contributing: contributing.md
- Code of Conduct: code_of_conduct.md
@@ -143,6 +143,7 @@ plugins:
- https://markupsafe.palletsprojects.com/en/stable/objects.inv
paths: [src]
options:
+ backlinks: tree
docstring_options:
ignore_init_summary: true
docstring_section_style: list
@@ -161,16 +162,17 @@ plugins:
signature_crossrefs: true
summary: true
- llmstxt:
- files:
- - output: llms-full.txt
- inputs:
+ full_output: llms-full.txt
+ sections:
+ Usage:
- index.md
- usage/index.md
- usage/handlers.md
- usage/theming.md
- recipes.md
- troubleshooting.md
- - reference/**.md
+ API:
+ - reference/api.md
- git-revision-date-localized:
enabled: !ENV [DEPLOY, false]
enable_creation_date: true
@@ -179,14 +181,15 @@ plugins:
redirect_maps:
theming.md: usage/theming.md
handlers/overview.md: usage/handlers.md
- reference/index.md: reference/mkdocstrings.md#mkdocstrings
- reference/extension.md: reference/mkdocstrings.md#mkdocstrings.extension
- reference/handlers/index.md: reference/mkdocstrings.md#mkdocstrings.handlers
- reference/handlers/base.md: reference/mkdocstrings.md#mkdocstrings.handlers.base
- reference/handlers/rendering.md: reference/mkdocstrings.md#mkdocstrings.handlers.rendering
- reference/inventory.md: reference/mkdocstrings.md#mkdocstrings.inventory
- reference/loggers.md: reference/mkdocstrings.md#mkdocstrings.loggers
- reference/plugin.md: reference/mkdocstrings.md#mkdocstrings.plugin
+ reference/mkdocstrings.md: reference/api.md
+ reference/index.md: reference/api.md#mkdocstrings
+ reference/extension.md: reference/api.md#mkdocstrings.extension
+ reference/handlers/index.md: reference/api.md#mkdocstrings.handlers
+ reference/handlers/base.md: reference/api.md#mkdocstrings.handlers.base
+ reference/handlers/rendering.md: reference/api.md#mkdocstrings.handlers.rendering
+ reference/inventory.md: reference/api.md#mkdocstrings.inventory
+ reference/loggers.md: reference/api.md#mkdocstrings.loggers
+ reference/plugin.md: reference/api.md#mkdocstrings.plugin
- minify:
minify_html: !ENV [DEPLOY, false]
- group:
diff --git a/pyproject.toml b/pyproject.toml
index 29aff0d9..4db21462 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -66,7 +66,7 @@ getter = "scripts.get_version:get_version"
[tool.pdm.build]
# Include as much as possible in the source distribution, to help redistributors.
-excludes = ["**/.pytest_cache"]
+excludes = ["**/.pytest_cache", "**/.mypy_cache"]
source-includes = [
"config",
"docs",
@@ -112,7 +112,7 @@ ci = [
"mkdocs>=1.6",
"mkdocs-coverage>=1.0",
"mkdocs-git-revision-date-localized-plugin>=1.2",
- "mkdocs-llmstxt>=0.1",
+ "mkdocs-llmstxt>=0.2",
"mkdocs-material>=9.5",
"mkdocs-minify-plugin>=0.8",
"mkdocs-redirects>=1.2.1",
diff --git a/src/mkdocstrings/_internal/extension.py b/src/mkdocstrings/_internal/extension.py
index 83421ff8..00b112d3 100644
--- a/src/mkdocstrings/_internal/extension.py
+++ b/src/mkdocstrings/_internal/extension.py
@@ -23,6 +23,8 @@
from __future__ import annotations
import re
+from functools import partial
+from inspect import signature
from typing import TYPE_CHECKING, Any
from warnings import warn
from xml.etree.ElementTree import Element
@@ -197,8 +199,12 @@ def _process_block(
self._updated_envs.add(handler_name)
_logger.debug("Rendering templates")
+ if "locale" in signature(handler.render).parameters:
+ render = partial(handler.render, locale=self._handlers._locale)
+ else:
+ render = handler.render # type: ignore[assignment]
try:
- rendered = handler.render(data, options)
+ rendered = render(data, options)
except TemplateNotFound as exc:
_logger.error( # noqa: TRY400
"Template '%s' not found for '%s' handler and theme '%s'.",
@@ -245,6 +251,15 @@ def _process_headings(self, handler: BaseHandler, element: Element) -> None:
for heading in headings:
rendered_id = heading.attrib["id"]
+
+ skip_inventory = "data-skip-inventory" in heading.attrib
+ if skip_inventory:
+ _logger.debug(
+ "Skipping heading with id %r because data-skip-inventory is present",
+ rendered_id,
+ )
+ continue
+
# The title is registered to be used as tooltip by autorefs.
self._autorefs.register_anchor(page, rendered_id, title=heading.text, primary=True)
diff --git a/src/mkdocstrings/_internal/handlers/base.py b/src/mkdocstrings/_internal/handlers/base.py
index 3d5852c5..60910436 100644
--- a/src/mkdocstrings/_internal/handlers/base.py
+++ b/src/mkdocstrings/_internal/handlers/base.py
@@ -319,20 +319,29 @@ def collect(self, identifier: str, options: HandlerOptions) -> CollectorItem:
"""
raise NotImplementedError
- def render(self, data: CollectorItem, options: HandlerOptions) -> str:
+ def render(self, data: CollectorItem, options: HandlerOptions, *, locale: str | None = None) -> str:
"""Render a template using provided data and configuration options.
Arguments:
data: The collected data to render.
options: The final configuration options.
+ locale: The locale to use for translations, if any.
Returns:
The rendered template as HTML.
"""
raise NotImplementedError
- def render_backlinks(self, backlinks: Mapping[str, Iterable[Backlink]]) -> str: # noqa: ARG002
- """Render backlinks."""
+ def render_backlinks(self, backlinks: Mapping[str, Iterable[Backlink]], *, locale: str | None = None) -> str: # noqa: ARG002
+ """Render backlinks.
+
+ Parameters:
+ backlinks: A mapping of identifiers to backlinks.
+ locale: The locale to use for translations, if any.
+
+ Returns:
+ The rendered backlinks as HTML.
+ """
return ""
def teardown(self) -> None:
@@ -359,7 +368,7 @@ def get_templates_dir(self, handler: str | None = None) -> Path:
"""
handler = handler or self.name
try:
- import mkdocstrings_handlers
+ import mkdocstrings_handlers # noqa: PLC0415
except ModuleNotFoundError as error:
raise ModuleNotFoundError(f"Handler '{handler}' not found, is it installed?") from error
@@ -450,6 +459,7 @@ def do_heading(
role: str | None = None,
hidden: bool = False,
toc_label: str | None = None,
+ skip_inventory: bool = False,
**attributes: str,
) -> Markup:
"""Render an HTML heading and register it for the table of contents. For use inside templates.
@@ -460,6 +470,7 @@ def do_heading(
role: An optional role for the object bound to this heading.
hidden: If True, only register it for the table of contents, don't render anything.
toc_label: The title to use in the table of contents ('data-toc-label' attribute).
+ skip_inventory: Flag element to not be registered in the inventory (by setting a `data-skip-inventory` attribute).
**attributes: Any extra HTML attributes of the heading.
Returns:
@@ -479,6 +490,8 @@ def do_heading(
if toc_label is None:
toc_label = content.unescape() if isinstance(content, Markup) else content
el.set("data-toc-label", toc_label)
+ if skip_inventory:
+ el.set("data-skip-inventory", "true")
if role:
el.set("data-role", role)
if content:
@@ -578,6 +591,7 @@ def __init__(
custom_templates: str | None = None,
mdx: Sequence[str | Extension] | None = None,
mdx_config: Mapping[str, Any] | None = None,
+ locale: str = "en",
tool_config: Any,
) -> None:
"""Initialize the object.
@@ -591,6 +605,7 @@ def __init__(
custom_templates: The path to custom templates.
mdx: A list of Markdown extensions to use.
mdx_config: Configuration for the Markdown extensions.
+ locale: The locale to use for translations.
tool_config: Tool configuration to pass down to handlers.
"""
self._theme = theme
@@ -600,6 +615,7 @@ def __init__(
self._mdx = mdx or []
self._mdx_config = mdx_config or {}
self._handlers: dict[str, BaseHandler] = {}
+ self._locale = locale
self._tool_config = tool_config
self.inventory: Inventory = Inventory(project=inventory_project, version=inventory_version)
diff --git a/src/mkdocstrings/_internal/plugin.py b/src/mkdocstrings/_internal/plugin.py
index afc94490..2af14a7a 100644
--- a/src/mkdocstrings/_internal/plugin.py
+++ b/src/mkdocstrings/_internal/plugin.py
@@ -15,6 +15,8 @@
import os
import re
+from functools import partial
+from inspect import signature
from re import Match
from typing import TYPE_CHECKING, Any
from warnings import catch_warnings, simplefilter
@@ -73,6 +75,8 @@ class PluginConfig(Config):
"""Whether to enable object inventory creation."""
enabled = opt.Type(bool, default=True)
"""Whether to enable the plugin. Default is true. If false, *mkdocstrings* will not collect or render anything."""
+ locale = opt.Optional(opt.Type(str))
+ """The locale to use for translations."""
class MkdocstringsPlugin(BasePlugin[PluginConfig]):
@@ -131,6 +135,9 @@ def on_config(self, config: MkDocsConfig) -> MkDocsConfig | None:
return config
_logger.debug("Adding extension to the list")
+ locale = self.config.locale or config.theme.get("language") or config.theme.get("locale") or "en"
+ locale = str(locale).replace("_", "-")
+
handlers = Handlers(
default=self.config.default_handler,
handlers_config=self.config.handlers,
@@ -140,6 +147,7 @@ def on_config(self, config: MkDocsConfig) -> MkDocsConfig | None:
mdx_config=config.mdx_configs,
inventory_project=config.site_name,
inventory_version="0.0.0", # TODO: Find a way to get actual version.
+ locale=locale,
tool_config=config,
)
@@ -234,7 +242,12 @@ def repl(match: Match) -> str:
if not backlinks:
return ""
- return handler.render_backlinks(backlinks)
+ if "locale" in signature(handler.render_backlinks).parameters:
+ render_backlinks = partial(handler.render_backlinks, locale=self.handlers._locale)
+ else:
+ render_backlinks = handler.render_backlinks # type: ignore[assignment]
+
+ return render_backlinks(backlinks)
for file in files:
if file.page and file.page.content:
diff --git a/tests/test_api.py b/tests/test_api.py
index 4b13aac7..57f0ce20 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -155,7 +155,11 @@ def test_inventory_matches_api(
public_api_paths = {obj.path for obj in public_objects}
public_api_paths.add("mkdocstrings")
for item in inventory.values():
- if item.domain == "py" and "(" not in item.name:
+ if (
+ item.domain == "py"
+ and "(" not in item.name
+ and (item.name == "mkdocstrings" or item.name.startswith("mkdocstrings."))
+ ):
obj = loader.modules_collection[item.name]
# YORE: Bump 1: Remove block.
if any(obj.path.startswith(f"mkdocstrings.{module}") for module in deprecated_modules):
diff --git a/tests/test_extension.py b/tests/test_extension.py
index dd3d7028..c283f9c0 100644
--- a/tests/test_extension.py
+++ b/tests/test_extension.py
@@ -198,7 +198,6 @@ def test_removing_duplicated_headings(ext_markdown: Markdown) -> None:
assert output.count(">Heading one<") == 1
assert output.count(">Heading two<") == 1
assert output.count(">Heading three<") == 1
- assert output.count('class="mkdocstrings') == 0
def _assert_contains_in_order(items: list[str], string: str) -> None:
diff --git a/tests/test_plugin.py b/tests/test_plugin.py
index af8a5594..acb9556c 100644
--- a/tests/test_plugin.py
+++ b/tests/test_plugin.py
@@ -57,6 +57,7 @@ def test_plugin_default_config(tmp_path: Path) -> None:
"custom_templates": None,
"enable_inventory": None,
"enabled": True,
+ "locale": None,
}
@@ -77,4 +78,5 @@ def test_plugin_config_custom_templates(tmp_path: Path) -> None:
"custom_templates": str(template_dir),
"enable_inventory": None,
"enabled": True,
+ "locale": None,
}