mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2026-10-13 17:02:12 -05:00
Allow reordered refs and translated display text in pending_xref (#14144)
Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
This commit is contained in:
1 parent
f666208d78
commit
88a4a97873
6 files changed
+99
-5
No files matched your search
@@ -4,6 +4,9 @@ Release 9.0.4 (in development)
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
* #14143: Fix spurious build warnings when translators reorder references
|
||||
in strings, or use translated display text in references.
|
||||
Patch by Matt Wang.
|
||||
|
||||
Release 9.0.3 (released Dec 04, 2025)
|
||||
=====================================
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from operator import attrgetter
|
||||
from re import DOTALL, match
|
||||
from textwrap import indent
|
||||
from typing import TYPE_CHECKING, Any, TypeVar
|
||||
@@ -28,7 +29,7 @@ from sphinx.util.nodes import (
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
from collections.abc import Callable, Sequence
|
||||
|
||||
from docutils.frontend import Values
|
||||
|
||||
@@ -133,11 +134,30 @@ class _NodeUpdater:
|
||||
old_refs: Sequence[nodes.Element],
|
||||
new_refs: Sequence[nodes.Element],
|
||||
warning_msg: str,
|
||||
*,
|
||||
key_func: Callable[[nodes.Element], Any] = attrgetter('rawsource'),
|
||||
ignore_order: bool = False,
|
||||
) -> None:
|
||||
"""Warn about mismatches between references in original and translated content."""
|
||||
old_ref_rawsources = [ref.rawsource for ref in old_refs]
|
||||
new_ref_rawsources = [ref.rawsource for ref in new_refs]
|
||||
if not self.noqa and old_ref_rawsources != new_ref_rawsources:
|
||||
"""Warn about mismatches between references in original and translated content.
|
||||
|
||||
:param key_func: A function to extract the comparison key from each reference.
|
||||
Defaults to extracting the ``rawsource`` attribute.
|
||||
:param ignore_order: If True, ignore the order of references when comparing.
|
||||
This allows translators to reorder references while still catching
|
||||
missing or extra references.
|
||||
"""
|
||||
old_ref_keys = list(map(key_func, old_refs))
|
||||
new_ref_keys = list(map(key_func, new_refs))
|
||||
|
||||
if ignore_order:
|
||||
# The ref_keys lists may contain ``None``, so compare hashes.
|
||||
# Recall objects which compare equal have the same hash value.
|
||||
old_ref_keys.sort(key=hash)
|
||||
new_ref_keys.sort(key=hash)
|
||||
|
||||
if not self.noqa and old_ref_keys != new_ref_keys:
|
||||
old_ref_rawsources = [ref.rawsource for ref in old_refs]
|
||||
new_ref_rawsources = [ref.rawsource for ref in new_refs]
|
||||
logger.warning(
|
||||
warning_msg.format(old_ref_rawsources, new_ref_rawsources),
|
||||
location=self.node,
|
||||
@@ -347,6 +367,10 @@ class _NodeUpdater:
|
||||
'inconsistent term references in translated message.'
|
||||
' original: {0}, translated: {1}'
|
||||
),
|
||||
# Compare by reftarget only, allowing translated display text.
|
||||
# Ignore order since translators may legitimately reorder references.
|
||||
key_func=lambda ref: ref.get('reftarget'),
|
||||
ignore_order=True,
|
||||
)
|
||||
|
||||
xref_reftarget_map: dict[tuple[str, str, str] | None, dict[str, Any]] = {}
|
||||
|
||||
@@ -33,6 +33,7 @@ CONTENTS
|
||||
topic
|
||||
markup
|
||||
backslashes
|
||||
refs_reordered
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
:tocdepth: 2
|
||||
|
||||
i18n with reordered and translated references
|
||||
==============================================
|
||||
|
||||
.. glossary::
|
||||
|
||||
term one
|
||||
First glossary term
|
||||
|
||||
term two
|
||||
Second glossary term
|
||||
|
||||
1. Multiple refs reordered: :term:`term one` and :term:`term two`.
|
||||
|
||||
2. Single ref with translated display text: :term:`term one`.
|
||||
@@ -0,0 +1,31 @@
|
||||
# Test for reordered and translated references.
|
||||
# These should NOT trigger inconsistency warnings.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: sphinx 1.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-01-01 00:00+0000\n"
|
||||
"PO-Revision-Date: 2024-01-01 00:00+0000\n"
|
||||
"Last-Translator: Test\n"
|
||||
"Language-Team: xx\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "i18n with reordered and translated references"
|
||||
msgstr "I18N WITH REORDERED AND TRANSLATED REFERENCES"
|
||||
|
||||
msgid "First glossary term"
|
||||
msgstr "FIRST GLOSSARY TERM"
|
||||
|
||||
msgid "Second glossary term"
|
||||
msgstr "SECOND GLOSSARY TERM"
|
||||
|
||||
# Reordered references - should NOT warn because targets are the same
|
||||
msgid "Multiple refs reordered: :term:`term one` and :term:`term two`."
|
||||
msgstr "MULTIPLE REFS REORDERED: :term:`term two` AND :term:`term one`."
|
||||
|
||||
# Translated display text - should NOT warn because reftarget is the same
|
||||
msgid "Single ref with translated display text: :term:`term one`."
|
||||
msgstr "SINGLE REF WITH TRANSLATED DISPLAY TEXT: :term:`TRANSLATED TERM ONE <term one>`."
|
||||
@@ -378,6 +378,25 @@ def test_text_glossary_term_inconsistencies(app):
|
||||
)
|
||||
|
||||
|
||||
@sphinx_intl
|
||||
@pytest.mark.sphinx('text', testroot='intl')
|
||||
@pytest.mark.test_params(shared_result='test_intl_basic')
|
||||
def test_text_refs_reordered_no_warning(app):
|
||||
app.build()
|
||||
# --- refs_reordered: verify no inconsistency warnings
|
||||
result = (app.outdir / 'refs_reordered.txt').read_text(encoding='utf8')
|
||||
# Verify the translation was applied
|
||||
assert 'MULTIPLE REFS REORDERED' in result
|
||||
assert 'SINGLE REF WITH TRANSLATED DISPLAY TEXT' in result
|
||||
|
||||
warnings = getwarning(app.warning)
|
||||
# Should NOT have any inconsistent_references warnings for refs_reordered.txt
|
||||
unexpected_warning_expr = '.*/refs_reordered.txt.*inconsistent.*references'
|
||||
assert not re.search(unexpected_warning_expr, warnings), (
|
||||
f'Unexpected warning found: {warnings!r}'
|
||||
)
|
||||
|
||||
|
||||
@sphinx_intl
|
||||
@pytest.mark.sphinx('gettext', testroot='intl')
|
||||
@pytest.mark.test_params(shared_result='test_intl_gettext')
|
||||
|
||||
Reference in new issue
Block a user