From caee86e168b2a3f21d7f0cd640617bc86ea9fe61 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Fri, 25 Sep 2026 21:21:18 +0200 Subject: [PATCH] fix(i18n): apply the name rule to the core domain too The two domains are built apart: docs passes through sphinx, where the transform withholds a message that is nothing but a name, while core builds its template straight from role descriptions, categories and menu labels. A sphinx extension never reaches it, so the same message could be withheld from one catalog and carried by the other. build_template now applies the same predicate. Its effect today is nil: counted directly, none of core's 421 messages is withheld, and the extract reports 0 catalogs changed. This is an assurance for what gets added rather than a repair of what is there - a role description that is only a product name would have slipped through before. Co-Authored-By: Claude Opus 5 (1M context) --- tests/unit/python/utils/i18n/test_catalog.py | 19 +++++++++++++++++++ utils/i18n/catalog.py | 11 +++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/tests/unit/python/utils/i18n/test_catalog.py b/tests/unit/python/utils/i18n/test_catalog.py index 7224f51e67..40c62d2700 100644 --- a/tests/unit/python/utils/i18n/test_catalog.py +++ b/tests/unit/python/utils/i18n/test_catalog.py @@ -12,6 +12,25 @@ from utils.i18n.catalog import ( from utils.i18n.translate import pending +class TestBuildTemplate(unittest.TestCase): + def test_a_message_that_is_only_a_name_never_enters_the_template(self) -> None: + template = build_template( + [("a", "Mastodon"), ("b", "Mastodon is a federated network.")], "core" + ) + + self.assertEqual( + [message.id for message in template if message.id], + ["Mastodon is a federated network."], + "core never passes through sphinx, so without this the docs domain " + "withholds a name its own catalog still carries", + ) + + def test_a_message_without_a_word_never_enters_the_template(self) -> None: + template = build_template([("a", "`x`"), ("b", "Real prose.")], "core") + + self.assertEqual([message.id for message in template if message.id], ["Real prose."]) + + class TestMerge(unittest.TestCase): def test_changed_source_comes_back_empty_for_the_translator(self): catalog = merge(build_template([("a", "Hello world")], "core"), None, "de") diff --git a/utils/i18n/catalog.py b/utils/i18n/catalog.py index 333fb0ffb8..9aab2448a7 100644 --- a/utils/i18n/catalog.py +++ b/utils/i18n/catalog.py @@ -11,6 +11,7 @@ from babel.messages.catalog import Catalog from babel.messages.pofile import read_po, write_po from utils.i18n.placeholders import carries_placeholder +from utils.i18n.untranslatable import untranslatable from utils.software import ( SOFTWARE_AUTHOR, SOFTWARE_CONTACT, @@ -65,7 +66,12 @@ def new_catalog(domain: str, code: str | None = None) -> Catalog: def build_template(messages: Iterable[tuple[str, str]], domain: str) -> Catalog: - """Return a template catalog holding ``messages``. + """Return a template catalog holding the translatable part of ``messages``. + + This is the ``core`` domain's counterpart to the sphinx transform the + ``docs`` domain carries: ``core`` never passes through sphinx, so a message + that is nothing but a name would otherwise reach its catalog while the same + message is withheld from the other domain. Args: messages: ``(msgctxt, msgid)`` pairs; ``msgctxt`` may be ``None``. @@ -73,7 +79,8 @@ def build_template(messages: Iterable[tuple[str, str]], domain: str) -> Catalog: """ template = new_catalog(domain) for context, msgid in messages: - template.add(msgid, context=context) + if not untranslatable(msgid): + template.add(msgid, context=context) return template