From 9714677740e7c44dd2717081613d6389dcfb96d1 Mon Sep 17 00:00:00 2001 From: Jean Abou Samra Date: Tue, 11 Jan 2022 22:40:01 +0100 Subject: [PATCH] In translated docs, sort glossaries by translated terms This is done by moving the sorting from the glossary directive to a transform operating after the i18n transform. Closes #9827 --- doc/usage/restructuredtext/directives.rst | 3 + sphinx/addnodes.py | 2 +- sphinx/domains/std.py | 16 +--- sphinx/transforms/__init__.py | 18 ++++ tests/roots/test-intl/glossary_terms.txt | 15 +++ .../xx/LC_MESSAGES/glossary_terms.po | 94 ++++++++++++------- tests/test_intl.py | 30 ++++-- 7 files changed, 123 insertions(+), 55 deletions(-) diff --git a/doc/usage/restructuredtext/directives.rst b/doc/usage/restructuredtext/directives.rst index 2a9743e948..bd1b1a3c66 100644 --- a/doc/usage/restructuredtext/directives.rst +++ b/doc/usage/restructuredtext/directives.rst @@ -831,6 +831,9 @@ Glossary .. versionchanged:: 1.4 Index key for glossary term should be considered *experimental*. + .. versionchanged:: 4.4 + In internationalized documentation, the ``:sorted:`` flag sorts + according to translated terms. Meta-information markup ----------------------- diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py index 5b63d22f55..0623047fbc 100644 --- a/sphinx/addnodes.py +++ b/sphinx/addnodes.py @@ -421,7 +421,7 @@ class compact_paragraph(nodes.paragraph): class glossary(nodes.Element): """Node to insert a glossary.""" - + sorted: bool class only(nodes.Element): """Node for "only" directives (conditional inclusion based on tags).""" diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 11a95e13b1..55203b3ccc 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -9,7 +9,6 @@ """ import re -import unicodedata import warnings from copy import copy from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterable, Iterator, List, Optional, @@ -336,6 +335,7 @@ class Glossary(SphinxDirective): def run(self) -> List[Node]: node = addnodes.glossary() node.document = self.state.document + node.sorted = ('sorted' in self.options) # This directive implements a custom format of the reST definition list # that allows multiple lines of terms before the definition. This is @@ -400,9 +400,8 @@ def run(self) -> List[Node]: was_empty = False # now, parse all the entries into a big definition list - items = [] + items: List[nodes.definition_list_item] = [] for terms, definition in entries: - termtexts: List[str] = [] termnodes: List[Node] = [] system_messages: List[Node] = [] for line, source, lineno in terms: @@ -416,7 +415,6 @@ def run(self) -> List[Node]: node_id=None, document=self.state.document) term.rawsource = line system_messages.extend(sysmsg) - termtexts.append(term.astext()) termnodes.append(term) termnodes.extend(system_messages) @@ -426,16 +424,10 @@ def run(self) -> List[Node]: self.state.nested_parse(definition, definition.items[0][1], defnode) termnodes.append(defnode) - items.append((termtexts, - nodes.definition_list_item('', *termnodes))) + items.append(nodes.definition_list_item('', *termnodes)) - if 'sorted' in self.options: - items.sort(key=lambda x: - unicodedata.normalize('NFD', x[0][0].lower())) - - dlist = nodes.definition_list() + dlist = nodes.definition_list('', *items) dlist['classes'].append('glossary') - dlist.extend(item[1] for item in items) node += dlist return messages + [node] diff --git a/sphinx/transforms/__init__.py b/sphinx/transforms/__init__.py index f1359dfadc..e82f5bb1dc 100644 --- a/sphinx/transforms/__init__.py +++ b/sphinx/transforms/__init__.py @@ -9,6 +9,7 @@ """ import re +import unicodedata import warnings from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Tuple @@ -404,6 +405,22 @@ def apply(self, **kwargs: Any) -> None: info = r.groupdict() node.attributes.update(info) +class GlossarySorter(SphinxTransform): + """Sort glossaries that have the ``sorted`` flag.""" + # Must be done after i18n. + default_priority = 30 + + def apply(self, **kwargs: Any) -> None: + for glossary in self.document.findall(addnodes.glossary): + if glossary.sorted: + definition_list = glossary[0] + definition_list[:] = sorted( + definition_list, + key=lambda item: unicodedata.normalize( + 'NFD', + item[0].astext().lower()) + ) + def setup(app: "Sphinx") -> Dict[str, Any]: app.add_transform(ApplySourceWorkaround) @@ -420,6 +437,7 @@ def setup(app: "Sphinx") -> Dict[str, Any]: app.add_transform(SphinxSmartQuotes) app.add_transform(DoctreeReadEvent) app.add_transform(ManpageLink) + app.add_transform(GlossarySorter) return { 'version': 'builtin', diff --git a/tests/roots/test-intl/glossary_terms.txt b/tests/roots/test-intl/glossary_terms.txt index a6e16c948b..473d857e7a 100644 --- a/tests/roots/test-intl/glossary_terms.txt +++ b/tests/roots/test-intl/glossary_terms.txt @@ -12,3 +12,18 @@ i18n with glossary terms The corresponding glossary #2 link to :term:`Some term`. + +Translated glossary should be sorted by translated terms: + +.. glossary:: + :sorted: + + AAA + Define AAA + + CCC + EEE + Define CCC + + BBB + Define BBB diff --git a/tests/roots/test-intl/xx/LC_MESSAGES/glossary_terms.po b/tests/roots/test-intl/xx/LC_MESSAGES/glossary_terms.po index 2746655eea..83542f1c38 100644 --- a/tests/roots/test-intl/xx/LC_MESSAGES/glossary_terms.po +++ b/tests/roots/test-intl/xx/LC_MESSAGES/glossary_terms.po @@ -1,35 +1,59 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) 2012, foof -# This file is distributed under the same license as the foo package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: sphinx 1.0\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2013-01-29 14:10+0000\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" - -msgid "i18n with glossary terms" -msgstr "I18N WITH GLOSSARY TERMS" - -msgid "Some term" -msgstr "SOME NEW TERM" - -msgid "The corresponding glossary" -msgstr "THE CORRESPONDING GLOSSARY" - -msgid "Some other term" -msgstr "SOME OTHER NEW TERM" - -msgid "The corresponding glossary #2" -msgstr "THE CORRESPONDING GLOSSARY #2" - -msgid "link to :term:`Some term`." -msgstr "LINK TO :term:`SOME NEW TERM`." +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2012, foof +# This file is distributed under the same license as the foo package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: sphinx 1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-01-29 14:10+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "i18n with glossary terms" +msgstr "I18N WITH GLOSSARY TERMS" + +msgid "Some term" +msgstr "SOME NEW TERM" + +msgid "The corresponding glossary" +msgstr "THE CORRESPONDING GLOSSARY" + +msgid "Some other term" +msgstr "SOME OTHER NEW TERM" + +msgid "The corresponding glossary #2" +msgstr "THE CORRESPONDING GLOSSARY #2" + +msgid "link to :term:`Some term`." +msgstr "LINK TO :term:`SOME NEW TERM`." + +msgid "Translated glossary should be sorted by translated terms:" +msgstr "TRANSLATED GLOSSARY SHOULD BE SORTED BY TRANSLATED TERMS:" + +msgid "BBB" +msgstr "TRANSLATED TERM XXX" + +msgid "Define BBB" +msgstr "DEFINE XXX" + +msgid "AAA" +msgstr "TRANSLATED TERM YYY" + +msgid "Define AAA" +msgstr "DEFINE YYY" + +msgid "CCC" +msgstr "TRANSLATED TERM ZZZ" + +msgid "EEE" +msgstr "VVV" + +msgid "Define CCC" +msgstr "DEFINE ZZZ" diff --git a/tests/test_intl.py b/tests/test_intl.py index f84e72a848..92d7badf46 100644 --- a/tests/test_intl.py +++ b/tests/test_intl.py @@ -241,13 +241,29 @@ def test_text_glossary_term(app, warning): app.build() # --- glossary terms: regression test for #1090 result = (app.outdir / 'glossary_terms.txt').read_text() - expect = ("18. I18N WITH GLOSSARY TERMS" - "\n****************************\n" - "\nSOME NEW TERM" - "\n THE CORRESPONDING GLOSSARY\n" - "\nSOME OTHER NEW TERM" - "\n THE CORRESPONDING GLOSSARY #2\n" - "\nLINK TO *SOME NEW TERM*.\n") + expect = (r"""18. I18N WITH GLOSSARY TERMS +**************************** + +SOME NEW TERM + THE CORRESPONDING GLOSSARY + +SOME OTHER NEW TERM + THE CORRESPONDING GLOSSARY #2 + +LINK TO *SOME NEW TERM*. + +TRANSLATED GLOSSARY SHOULD BE SORTED BY TRANSLATED TERMS: + +TRANSLATED TERM XXX + DEFINE XXX + +TRANSLATED TERM YYY + DEFINE YYY + +TRANSLATED TERM ZZZ +VVV + DEFINE ZZZ +""") assert result == expect warnings = getwarning(warning) assert 'term not in glossary' not in warnings