Skip to content
Open
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
50 changes: 31 additions & 19 deletions sphinx/transforms/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,14 +282,21 @@ def update_refnamed_references(self) -> None:
is_refnamed_ref = NodeMatcher(nodes.reference, refname=Any)
old_refs = list(is_refnamed_ref.findall(self.node))
new_refs = list(is_refnamed_ref.findall(self.patch))
self.compare_references(
old_refs,
new_refs,
__(
'inconsistent references in translated message.'
' original: {0}, translated: {1}'
),
)
# Only compare the count of references, not their content.
# Translators are allowed to change display text (which affects rawsource comparison),
# and the fixup mechanism below will correct the refnames if needed.
if not self.noqa and len(old_refs) != len(new_refs):
old_ref_rawsources = [ref.rawsource for ref in old_refs]
new_ref_rawsources = [ref.rawsource for ref in new_refs]
logger.warning(
__(
'inconsistent references in translated message.'
' original: {0}, translated: {1}'
).format(old_ref_rawsources, new_ref_rawsources),
location=self.node,
type='i18n',
subtype='inconsistent_references',
)
old_ref_names = [r['refname'] for r in old_refs]
new_ref_names = [r['refname'] for r in new_refs]
orphans = [*({*old_ref_names} - {*new_ref_names})]
Expand Down Expand Up @@ -354,17 +361,22 @@ def update_pending_xrefs(self) -> None:
# This code restricts to change ref-targets in the translation.
old_xrefs = [*self.node.findall(addnodes.pending_xref)]
new_xrefs = [*self.patch.findall(addnodes.pending_xref)]
self.compare_references(
old_xrefs,
new_xrefs,
__(
'inconsistent term references in translated message.'
' original: {0}, translated: {1}'
),
# Compare by reftarget only, allowing translated display text.
key_func=lambda ref: ref.get('reftarget'),
)

# Only compare the count of cross-references, not their targets.
# For term references, translators may translate both display text and
# the term name itself (when the glossary is also translated).
# For other xrefs, the fixup mechanism below handles target corrections.
if not self.noqa and len(old_xrefs) != len(new_xrefs):
old_xref_rawsources = [ref.rawsource for ref in old_xrefs]
new_xref_rawsources = [ref.rawsource for ref in new_xrefs]
logger.warning(
__(
'inconsistent term references in translated message.'
' original: {0}, translated: {1}'
).format(old_xref_rawsources, new_xref_rawsources),
location=self.node,
type='i18n',
subtype='inconsistent_references',
)
xref_reftarget_map: dict[tuple[str, str, str] | None, dict[str, Any]] = {}

def get_ref_key(node: addnodes.pending_xref) -> tuple[str, str, str] | None:
Expand Down
52 changes: 52 additions & 0 deletions tests/roots/test-intl/refs_translated_display_text.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
:tocdepth: 2

i18n with translated display text for references
=================================================

.. _vectorcall: https://peps.python.org/pep-0590/
.. _Documentation bugs: https://github.com/sphinx-doc/sphinx/issues

.. glossary::

locale encoding
The encoding used for the locale.

abstract base class
A base class that is abstract.

text encoding
The encoding used for text.

decorator
A function decorator.

abstrakcyjna klasa bazowa
Translated: A base class that is abstract.

kodowanie tekstu
Translated: The encoding used for text.

dekorator
Translated: A function decorator.

.. rubric:: Test cases from issue #14162

1. Add translated display text for hyperlink (case 2): vectorcall_.

2. Translated display text for hyperlink (case 3): `Improved suggestions <https://github.com/example/pr/1>`_.

3. Use translated hyperlink tag (case 4): `Documentation bugs`_.

4. Translated glossary term (case 5): :term:`locale encoding`.

5. Multiple translated refs: vectorcall_ and `Documentation bugs`_.

.. rubric:: Additional term reference cases

6. Term with explicit target and newline in source: :term:`virtual <abstract base class>`.

7. Term without explicit target: :term:`text encoding`.

8. Simple term reference: :term:`decorator`.

9. Multiple term refs: :term:`text encoding` and :term:`decorator`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Test for translated display text in references.
# These should NOT trigger inconsistency warnings (issue #14162).
#
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 translated display text for references"
msgstr "I18N WITH TRANSLATED DISPLAY TEXT FOR REFERENCES"

msgid "The encoding used for the locale."
msgstr "THE ENCODING USED FOR THE LOCALE."

msgid "A base class that is abstract."
msgstr "A BASE CLASS THAT IS ABSTRACT."

msgid "The encoding used for text."
msgstr "THE ENCODING USED FOR TEXT."

msgid "A function decorator."
msgstr "A FUNCTION DECORATOR."

msgid "Translated: A base class that is abstract."
msgstr "TRANSLATED: A BASE CLASS THAT IS ABSTRACT."

msgid "Translated: The encoding used for text."
msgstr "TRANSLATED: THE ENCODING USED FOR TEXT."

msgid "Translated: A function decorator."
msgstr "TRANSLATED: A FUNCTION DECORATOR."

msgid "Test cases from issue #14162"
msgstr "TEST CASES FROM ISSUE #14162"

# Case 2: Add translated display text for hyperlink
# Original: vectorcall_
# Translated: add display text but keep same target
msgid "Add translated display text for hyperlink (case 2): vectorcall_."
msgstr "ADD TRANSLATED DISPLAY TEXT FOR HYPERLINK (CASE 2): `VECTORCALL <vectorcall_>`_."

# Case 3: Translated display text for hyperlink
# Original: `Improved suggestions <URL>`_
# Translated: translate display text but keep same URL
msgid "Translated display text for hyperlink (case 3): `Improved suggestions <https://github.com/example/pr/1>`_."
msgstr "TRANSLATED DISPLAY TEXT FOR HYPERLINK (CASE 3): `SUGGERIMENTI MIGLIORATI <https://github.com/example/pr/1>`_."

# Case 4: Use translated hyperlink tag
# Original: `Documentation bugs`_
# Translated: translate tag name - the fixup mechanism will correct the refname
msgid "Use translated hyperlink tag (case 4): `Documentation bugs`_."
msgstr "USE TRANSLATED HYPERLINK TAG (CASE 4): `TRANSLATED DOCUMENTATION BUGS`_."

# Case 5: Translated glossary term
# Original: :term:`locale encoding`
# Translated: translate display text using explicit target syntax
msgid "Translated glossary term (case 5): :term:`locale encoding`."
msgstr "TRANSLATED GLOSSARY TERM (CASE 5): :term:`CODIFICAÇÃO DA LOCALIDADE <locale encoding>`."

# Multiple refs: should allow translating display text and rely on fixup
msgid "Multiple translated refs: vectorcall_ and `Documentation bugs`_."
msgstr "MULTIPLE TRANSLATED REFS: `TRANSLATED DOCUMENTATION BUGS`_ AND `VECTORCALL <vectorcall_>`_."

msgid "Additional term reference cases"
msgstr "ADDITIONAL TERM REFERENCE CASES"

# Term with explicit target - simulating newline in original (from PO wrapping)
# and translated glossary term name as target
msgid ""
"Term with explicit target and newline in source: :term:`virtual <abstract "
"base class>`."
msgstr ""
"TERM WITH EXPLICIT TARGET AND NEWLINE IN SOURCE: :term:`wirtualną "
"<abstrakcyjna klasa bazowa>`."

# Term without explicit target - translate the term name itself
# This simulates when the glossary is also translated
msgid "Term without explicit target: :term:`text encoding`."
msgstr "TERM WITHOUT EXPLICIT TARGET: :term:`kodowanie tekstu`."

# Simple term reference - translate the term name
msgid "Simple term reference: :term:`decorator`."
msgstr "SIMPLE TERM REFERENCE: :term:`dekorator`."

# Multiple term refs with translated names
msgid "Multiple term refs: :term:`text encoding` and :term:`decorator`."
msgstr "MULTIPLE TERM REFS: :term:`kodowanie tekstu` AND :term:`dekorator`."
39 changes: 39 additions & 0 deletions tests/test_intl/test_intl.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,45 @@ def test_text_refs_reordered_no_warning(app: SphinxTestApp) -> None:
)


@sphinx_intl
@pytest.mark.sphinx('text', testroot='intl')
@pytest.mark.test_params(shared_result='test_intl_basic')
def test_text_refs_translated_display_text_no_warning(app: SphinxTestApp) -> None:
"""Test that translated display text in references doesn't trigger warnings.

This test covers cases 2-5 from issue #14162 plus additional term reference cases:
- Case 2: Add translated display text for hyperlink
- Case 3: Translated display text for hyperlink
- Case 4: Use translated hyperlink tag
- Case 5: Translated glossary term (with explicit target syntax)
- Additional: Term references with translated glossary term names
- Additional: Term references with newlines from PO file wrapping.
"""
app.build()
result = (app.outdir / 'refs_translated_display_text.txt').read_text(
encoding='utf8'
)

# Verify the translations were applied
assert 'I18N WITH TRANSLATED DISPLAY TEXT FOR REFERENCES' in result
assert 'TEST CASES FROM ISSUE #14162' in result
assert 'ADD TRANSLATED DISPLAY TEXT FOR HYPERLINK (CASE 2)' in result
assert 'TRANSLATED DISPLAY TEXT FOR HYPERLINK (CASE 3)' in result
assert 'USE TRANSLATED HYPERLINK TAG (CASE 4)' in result
assert 'TRANSLATED GLOSSARY TERM (CASE 5)' in result
assert 'MULTIPLE TRANSLATED REFS' in result
assert 'ADDITIONAL TERM REFERENCE CASES' in result

warnings = getwarning(app.warning)
# Should NOT have any inconsistent_references warnings for refs_translated_display_text.txt
unexpected_warning_expr = (
'.*/refs_translated_display_text.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')
Expand Down
Loading