diff --git a/CHANGES b/CHANGES index 2e914cae9..77cf9c567 100644 --- a/CHANGES +++ b/CHANGES @@ -26,12 +26,54 @@ Bugs fixed Testing -------- -Release 4.3.0 (in development) +Release 4.4.0 (in development) ============================== Dependencies ------------ +Incompatible changes +-------------------- + +Deprecated +---------- + +Features added +-------------- + +Bugs fixed +---------- + +Testing +-------- + +Release 4.3.1 (in development) +============================== + +Dependencies +------------ + +Incompatible changes +-------------------- + +Deprecated +---------- + +Features added +-------------- + +Bugs fixed +---------- + +Testing +-------- + +Release 4.3.0 (released Nov 11, 2021) +===================================== + +Dependencies +------------ + * Support Python 3.10 Incompatible changes @@ -51,7 +93,7 @@ Incompatible changes * #9695: The rendering of Javascript domain declarations is implemented with more docutils nodes to allow better CSS styling. It may break existing styling. - +* #9450: mathjax: Load MathJax via "defer" strategy Deprecated ---------- @@ -71,6 +113,8 @@ Features added * #9691: C, added new info-field ``retval`` for :rst:dir:`c:function` and :rst:dir:`c:macro`. * C++, added new info-field ``retval`` for :rst:dir:`cpp:function`. +* #9618: i18n: Add :confval:`gettext_allow_fuzzy_translations` to allow "fuzzy" + messages for translation * #9672: More CSS classes on Python domain descriptions * #9695: More CSS classes on Javascript domain descriptions * #9683: Revert the removal of ``add_stylesheet()`` API. It will be kept until @@ -80,6 +124,8 @@ Features added inventory specification. Specific types of cross-references can be disabled, e.g., ``std:doc`` or all cross-references in a specific domain, e.g., ``std:*``. +* #9623: Allow to suppress "toctree contains reference to excluded document" + warnings using :confval:`suppress_warnings` Bugs fixed ---------- @@ -123,30 +169,6 @@ Bugs fixed * Intersphinx, for unresolved references with an explicit inventory, e.g., ``proj:myFunc``, leave the inventory prefix in the unresolved text. -Testing --------- - -Release 4.2.1 (in development) -============================== - -Dependencies ------------- - -Incompatible changes --------------------- - -Deprecated ----------- - -Features added --------------- - -Bugs fixed ----------- - -Testing --------- - Release 4.2.0 (released Sep 12, 2021) ===================================== diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index 81e0577d4..b1efe4a16 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -329,6 +329,8 @@ General configuration * ``ref.python`` * ``misc.highlighting_failure`` * ``toc.circular`` + * ``toc.excluded`` + * ``toc.not_readable`` * ``toc.secnum`` * ``epub.unknown_project_files`` * ``epub.duplicated_toc_entry`` @@ -360,6 +362,10 @@ General configuration Added ``epub.duplicated_toc_entry`` + .. versionchanged:: 4.3 + + Added ``toc.excluded`` and ``toc.not_readable`` + .. confval:: needs_sphinx If set to a ``major.minor`` version string like ``'1.1'``, Sphinx will @@ -802,6 +808,13 @@ documentation on :ref:`intl` for details. .. versionchanged:: 1.5 Use ``locales`` directory as a default value +.. confval:: gettext_allow_fuzzy_translations + + If true, "fuzzy" messages in the message catalogs are used for translation. + The default is ``False``. + + .. versionadded:: 4.3 + .. confval:: gettext_compact .. versionadded:: 1.1 diff --git a/sphinx/__init__.py b/sphinx/__init__.py index b8865f4e1..fbbd0d908 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -38,7 +38,11 @@ __released__ = '5.0.0' # used when Sphinx builds its own docs #: #: .. versionadded:: 1.2 #: Before version 1.2, check the string ``sphinx.__version__``. +<<<<<<< HEAD version_info = (5, 0, 0, 'final', 0) +======= +version_info = (4, 4, 0, 'beta', 0) +>>>>>>> 4.x package_dir = path.abspath(path.dirname(__file__)) diff --git a/sphinx/application.py b/sphinx/application.py index 4a75a83fe..ec0234a4e 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -284,7 +284,8 @@ class Sphinx: self.config.language, self.config.source_encoding) for catalog in repo.catalogs: if catalog.domain == 'sphinx' and catalog.is_outdated(): - catalog.write_mo(self.config.language) + catalog.write_mo(self.config.language, + self.config.gettext_allow_fuzzy_translations) locale_dirs: List[Optional[str]] = list(repo.locale_dirs) locale_dirs += [None] diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index 5ad989539..64c621a1c 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -217,7 +217,8 @@ class Builder: for catalog in status_iterator(catalogs, __('writing output... '), "darkgreen", len(catalogs), self.app.verbosity, stringify_func=cat2relpath): - catalog.write_mo(self.config.language) + catalog.write_mo(self.config.language, + self.config.gettext_allow_fuzzy_translations) def compile_all_catalogs(self) -> None: repo = CatalogRepository(self.srcdir, self.config.locale_dirs, diff --git a/sphinx/config.py b/sphinx/config.py index 2ca20cb48..05bcdeccc 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -103,6 +103,7 @@ class Config: 'language': (None, 'env', [str]), 'locale_dirs': (['locales'], 'env', []), 'figure_language_filename': ('{root}.{language}{ext}', 'env', [str]), + 'gettext_allow_fuzzy_translations': (False, 'gettext', []), 'master_doc': ('index', 'env', []), 'root_doc': (lambda config: config.master_doc, 'env', []), diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index e131fe820..03eb6d3d2 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -18,8 +18,8 @@ from docutils.parsers.rst.directives.misc import Include as BaseInclude from sphinx import addnodes from sphinx.domains.changeset import VersionChange # NOQA # for compatibility -from sphinx.locale import _ -from sphinx.util import docname_join, url_re +from sphinx.locale import _, __ +from sphinx.util import docname_join, logging, url_re from sphinx.util.docutils import SphinxDirective from sphinx.util.matching import Matcher, patfilter from sphinx.util.nodes import explicit_title_re @@ -30,6 +30,7 @@ if TYPE_CHECKING: glob_re = re.compile(r'.*[*?\[].*') +logger = logging.getLogger(__name__) def int_or_nothing(argument: str) -> int: @@ -106,9 +107,8 @@ class TocTree(SphinxDirective): toctree['entries'].append((None, docname)) toctree['includefiles'].append(docname) if not docnames: - ret.append(self.state.document.reporter.warning( - 'toctree glob pattern %r didn\'t match any documents' - % entry, line=self.lineno)) + logger.warning(__('toctree glob pattern %r didn\'t match any documents'), + entry, location=toctree) else: if explicit: ref = explicit.group(2) @@ -128,20 +128,21 @@ class TocTree(SphinxDirective): toctree['entries'].append((title, ref)) elif docname not in self.env.found_docs: if excluded(self.env.doc2path(docname, None)): - message = 'toctree contains reference to excluded document %r' + message = __('toctree contains reference to excluded document %r') + subtype = 'excluded' else: - message = 'toctree contains reference to nonexisting document %r' + message = __('toctree contains reference to nonexisting document %r') + subtype = 'not_readable' - ret.append(self.state.document.reporter.warning(message % docname, - line=self.lineno)) + logger.warning(message, docname, type='toc', subtype=subtype, + location=toctree) self.env.note_reread() else: if docname in all_docnames: all_docnames.remove(docname) else: - message = 'duplicated entry found in toctree: %s' - ret.append(self.state.document.reporter.warning(message % docname, - line=self.lineno)) + logger.warning(__('duplicated entry found in toctree: %s'), docname, + location=toctree) toctree['entries'].append((title, docname)) toctree['includefiles'].append(docname) @@ -250,8 +251,9 @@ class Acks(SphinxDirective): self.state.nested_parse(self.content, self.content_offset, node) if len(node.children) != 1 or not isinstance(node.children[0], nodes.bullet_list): - reporter = self.state.document.reporter - return [reporter.warning('.. acks content is not a list', line=self.lineno)] + logger.warning(__('.. acks content is not a list'), + location=(self.env.docname, self.lineno)) + return [] return [node] @@ -274,8 +276,9 @@ class HList(SphinxDirective): self.state.nested_parse(self.content, self.content_offset, node) if len(node.children) != 1 or not isinstance(node.children[0], nodes.bullet_list): - reporter = self.state.document.reporter - return [reporter.warning('.. hlist content is not a list', line=self.lineno)] + logger.warning(__('.. hlist content is not a list'), + location=(self.env.docname, self.lineno)) + return [] fulllist = node.children[0] # create a hlist node where the items are distributed npercol, nmore = divmod(len(fulllist), ncolumns) diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index fd6a78892..b932a9ea5 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -26,7 +26,7 @@ from sphinx import addnodes from sphinx.addnodes import desc_signature, pending_xref, pending_xref_condition from sphinx.application import Sphinx from sphinx.builders import Builder -from sphinx.deprecation import RemovedInSphinx50Warning +from sphinx.deprecation import RemovedInSphinx50Warning, RemovedInSphinx60Warning from sphinx.directives import ObjectDescription from sphinx.domains import Domain, Index, IndexEntry, ObjType from sphinx.environment import BuildEnvironment @@ -495,7 +495,17 @@ class PyObject(ObjectDescription[Tuple[str, str]]): sig_prefix = self.get_signature_prefix(sig) if sig_prefix: - signode += addnodes.desc_annotation(str(sig_prefix), '', *sig_prefix) + if type(sig_prefix) is str: + warnings.warn( + "Python directive method get_signature_prefix()" + " returning a string is deprecated." + " It must now return a list of nodes." + " Return value was '{}'.".format(sig_prefix), + RemovedInSphinx60Warning) + signode += addnodes.desc_annotation(sig_prefix, '', # type: ignore + nodes.Text(sig_prefix)) # type: ignore + else: + signode += addnodes.desc_annotation(str(sig_prefix), '', *sig_prefix) if prefix: signode += addnodes.desc_addname(prefix, prefix) diff --git a/sphinx/ext/mathjax.py b/sphinx/ext/mathjax.py index 46ca3b332..eb06908d3 100644 --- a/sphinx/ext/mathjax.py +++ b/sphinx/ext/mathjax.py @@ -81,7 +81,7 @@ def install_mathjax(app: Sphinx, pagename: str, templatename: str, context: Dict domain = cast(MathDomain, app.env.get_domain('math')) if app.registry.html_assets_policy == 'always' or domain.has_equations(pagename): # Enable mathjax only if equations exists - options = {'async': 'async'} + options = {'defer': 'defer'} if app.config.mathjax_options: options.update(app.config.mathjax_options) app.add_js_file(app.config.mathjax_path, **options) # type: ignore diff --git a/sphinx/highlighting.py b/sphinx/highlighting.py index 329561df0..addd0d45c 100644 --- a/sphinx/highlighting.py +++ b/sphinx/highlighting.py @@ -8,11 +8,11 @@ :license: BSD, see LICENSE for details. """ -from distutils.version import LooseVersion from functools import partial from importlib import import_module from typing import Any, Dict +from packaging import version from pygments import __version__ as pygmentsversion from pygments import highlight from pygments.filters import ErrorToken @@ -64,7 +64,7 @@ _LATEX_ADD_STYLES_FIXPYG = r''' {\let\fcolorbox\spx@fixpyg@fcolorbox\PYG@do{#2}}} \makeatother ''' -if tuple(LooseVersion(pygmentsversion).version) <= (2, 7, 4): +if version.parse(pygmentsversion).release <= (2, 7, 4): _LATEX_ADD_STYLES += _LATEX_ADD_STYLES_FIXPYG diff --git a/sphinx/util/docutils.py b/sphinx/util/docutils.py index 047a0faa6..c3d385306 100644 --- a/sphinx/util/docutils.py +++ b/sphinx/util/docutils.py @@ -12,7 +12,6 @@ import os import re from contextlib import contextmanager from copy import copy -from distutils.version import LooseVersion from os import path from types import ModuleType from typing import (IO, TYPE_CHECKING, Any, Callable, Dict, Generator, List, Optional, Set, @@ -26,6 +25,7 @@ from docutils.parsers.rst import Directive, directives, roles from docutils.parsers.rst.states import Inliner from docutils.statemachine import State, StateMachine, StringList from docutils.utils import Reporter, unescape +from packaging import version from sphinx.errors import SphinxError from sphinx.locale import _ @@ -41,7 +41,7 @@ if TYPE_CHECKING: from sphinx.environment import BuildEnvironment -__version_info__ = tuple(LooseVersion(docutils.__version__).version) +__version_info__ = version.parse(docutils.__version__).release additional_nodes: Set[Type[Element]] = set() diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py index e82e33f57..02b42cf0b 100644 --- a/sphinx/util/i18n.py +++ b/sphinx/util/i18n.py @@ -59,7 +59,7 @@ class CatalogInfo(LocaleFileInfoBase): not path.exists(self.mo_path) or path.getmtime(self.mo_path) < path.getmtime(self.po_path)) - def write_mo(self, locale: str) -> None: + def write_mo(self, locale: str, use_fuzzy: bool = False) -> None: with open(self.po_path, encoding=self.charset) as file_po: try: po = read_po(file_po, locale) @@ -69,7 +69,7 @@ class CatalogInfo(LocaleFileInfoBase): with open(self.mo_path, 'wb') as file_mo: try: - write_mo(file_mo, po) + write_mo(file_mo, po, use_fuzzy) except Exception as exc: logger.warning(__('writing error: %s, %s'), self.mo_path, exc) diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 2e9a43f51..106ad0470 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -10,13 +10,13 @@ import os import re -from distutils.version import LooseVersion from itertools import chain, cycle from unittest.mock import ANY, call, patch import pygments import pytest from html5lib import HTMLParser +from packaging import version from sphinx.builders.html import validate_html_extra_path, validate_html_static_path from sphinx.errors import ConfigError @@ -30,6 +30,9 @@ else: FIGURE_CAPTION = ".//figure/figcaption/p" +PYGMENTS_VERSION = version.parse(pygments.__version__).release + + ENV_WARNINGS = """\ %(root)s/autodoc_fodder.py:docstring of autodoc_fodder.MarkupError:\\d+: \ WARNING: Explicit markup ends without a blank line; unexpected unindent. @@ -1576,8 +1579,7 @@ def test_html_codeblock_linenos_style_table(app): app.build() content = (app.outdir / 'index.html').read_text() - pygments_version = tuple(LooseVersion(pygments.__version__).version) - if pygments_version >= (2, 8): + if PYGMENTS_VERSION >= (2, 8): assert ('
1\n'
                 '2\n'
                 '3\n'
@@ -1592,8 +1594,7 @@ def test_html_codeblock_linenos_style_inline(app):
     app.build()
     content = (app.outdir / 'index.html').read_text()
 
-    pygments_version = tuple(LooseVersion(pygments.__version__).version)
-    if pygments_version > (2, 7):
+    if PYGMENTS_VERSION > (2, 7):
         assert '1' in content
     else:
         assert '1 ' in content
diff --git a/tests/test_ext_math.py b/tests/test_ext_math.py
index 973fc3699..7c78954b7 100644
--- a/tests/test_ext_math.py
+++ b/tests/test_ext_math.py
@@ -71,7 +71,7 @@ def test_mathjax_options(app, status, warning):
     app.builder.build_all()
 
     content = (app.outdir / 'index.html').read_text()
-    assert ('' in content)
 
diff --git a/tests/test_intl.py b/tests/test_intl.py
index 30beb1135..3b54063ef 100644
--- a/tests/test_intl.py
+++ b/tests/test_intl.py
@@ -1301,6 +1301,44 @@ def getwarning(warnings):
     return strip_escseq(warnings.getvalue().replace(os.sep, '/'))
 
 
+@pytest.mark.sphinx('html', testroot='basic',
+                    srcdir='gettext_allow_fuzzy_translations',
+                    confoverrides={
+                        'language': 'de',
+                        'gettext_allow_fuzzy_translations': True
+                    })
+def test_gettext_allow_fuzzy_translations(app):
+    locale_dir = app.srcdir / 'locales' / 'de' / 'LC_MESSAGES'
+    locale_dir.makedirs()
+    with (locale_dir / 'index.po').open('wb') as f:
+        catalog = Catalog()
+        catalog.add('features', 'FEATURES', flags=('fuzzy',))
+        pofile.write_po(f, catalog)
+
+    app.build()
+    content = (app.outdir / 'index.html').read_text()
+    assert 'FEATURES' in content
+
+
+@pytest.mark.sphinx('html', testroot='basic',
+                    srcdir='gettext_disallow_fuzzy_translations',
+                    confoverrides={
+                        'language': 'de',
+                        'gettext_allow_fuzzy_translations': False
+                    })
+def test_gettext_disallow_fuzzy_translations(app):
+    locale_dir = app.srcdir / 'locales' / 'de' / 'LC_MESSAGES'
+    locale_dir.makedirs()
+    with (locale_dir / 'index.po').open('wb') as f:
+        catalog = Catalog()
+        catalog.add('features', 'FEATURES', flags=('fuzzy',))
+        pofile.write_po(f, catalog)
+
+    app.build()
+    content = (app.outdir / 'index.html').read_text()
+    assert 'FEATURES' not in content
+
+
 @pytest.mark.sphinx('html', testroot='basic', confoverrides={'language': 'de'})
 def test_customize_system_message(make_app, app_params, sphinx_test_tempdir):
     try: