diff --git a/sphinx/transforms/i18n.py b/sphinx/transforms/i18n.py index f6bad5143..c291d727c 100644 --- a/sphinx/transforms/i18n.py +++ b/sphinx/transforms/i18n.py @@ -353,9 +353,16 @@ class Locale(SphinxTransform): if not has_catalog: return + catalogues = [getattr(catalog, '_catalog', None)] + while (catalog := catalog._fallback) is not None: # type: ignore[attr-defined] + catalogues.append(getattr(catalog, '_catalog', None)) + merged: dict[str, str] = {} + for catalogue in filter(None, reversed(catalogues)): # type: dict[str, str] + merged |= catalogue + # phase1: replace reference ids with translated names for node, msg in extract_messages(self.document): - msgstr = catalog.gettext(msg) + msgstr = merged.get(msg, '') # There is no point in having #noqa on literal blocks because # they cannot contain references. Recognizing it would just @@ -364,10 +371,14 @@ class Locale(SphinxTransform): if not isinstance(node, LITERAL_TYPE_NODES): msgstr, _ = parse_noqa(msgstr) - if not msgstr or msgstr == msg or not msgstr.strip(): + if msgstr.strip() == '': # as-of-yet untranslated node['translated'] = False continue + if msgstr == msg: + # identical source and translated messages + node['translated'] = True + continue # Avoid "Literal block expected; none found." warnings. # If msgstr ends with '::' then it cause warning message at @@ -416,7 +427,7 @@ class Locale(SphinxTransform): if node.setdefault('translated', False): # to avoid double translation continue # skip if the node is already translated by phase1 - msgstr = catalog.gettext(msg) + msgstr = merged.get(msg, '') noqa = False # See above. @@ -510,7 +521,7 @@ class Locale(SphinxTransform): msg_parts = split_index_msg(entry_type, value) msgstr_parts = [] for part in msg_parts: - msgstr = catalog.gettext(part) + msgstr = merged.get(part, '') if not msgstr: msgstr = part msgstr_parts.append(msgstr) diff --git a/tests/roots/test-intl/translation_progress.txt b/tests/roots/test-intl/translation_progress.txt index 61f893e22..f70ab7aa6 100644 --- a/tests/roots/test-intl/translation_progress.txt +++ b/tests/roots/test-intl/translation_progress.txt @@ -17,16 +17,20 @@ Desiring this man’s art and that man’s scope, With what I most enjoy contented least; +.. idempotent translations (2 out of 14 lines): + Yet in these thoughts myself almost despising, Haply I think on thee, and then my state, +.. untranslated (2 out of 14 lines): + Like to the lark at break of day arising -.. untranslated (3 out of 14 lines): - From sullen earth, sings hymns at heaven’s gate; +.. translation missing (2 out of 14 lines): + For thy sweet love remember’d such wealth brings That then I scorn to change my state with kings. diff --git a/tests/roots/test-intl/xx/LC_MESSAGES/translation_progress.po b/tests/roots/test-intl/xx/LC_MESSAGES/translation_progress.po index a83112f48..94673d175 100644 --- a/tests/roots/test-intl/xx/LC_MESSAGES/translation_progress.po +++ b/tests/roots/test-intl/xx/LC_MESSAGES/translation_progress.po @@ -38,11 +38,20 @@ msgstr "DESIRING THIS MAN’S ART AND THAT MAN’S SCOPE," msgid "With what I most enjoy contented least;" msgstr "WITH WHAT I MOST ENJOY CONTENTED LEAST;" +# idempotent translations (2 out of 14 lines): + msgid "Yet in these thoughts myself almost despising," -msgstr "YET IN THESE THOUGHTS MYSELF ALMOST DESPISING," +msgstr "Yet in these thoughts myself almost despising," msgid "Haply I think on thee, and then my state," -msgstr "HAPLY I THINK ON THEE, AND THEN MY STATE," +msgstr "Haply I think on thee, and then my state," + +# untranslated (2 out of 14 lines): msgid "Like to the lark at break of day arising" -msgstr "LIKE TO THE LARK AT BREAK OF DAY ARISING" +msgstr "" + +msgid "From sullen earth, sings hymns at heaven’s gate;" +msgstr "" + +# translation missing (2 out of 14 lines): diff --git a/tests/test_intl.py b/tests/test_intl.py index c02036af1..a07ebfb63 100644 --- a/tests/test_intl.py +++ b/tests/test_intl.py @@ -612,24 +612,24 @@ def test_gettext_buildr_ignores_only_directive(app): @sphinx_intl def test_node_translated_attribute(app): - app.build() + app.builder.build_specific([app.srcdir / 'translation_progress.txt']) doctree = app.env.get_doctree('translation_progress') translated_nodes = sum(1 for _ in doctree.findall(NodeMatcher(translated=True))) - assert translated_nodes == 11 + 1 # 11 lines + title + assert translated_nodes == 10 + 1 # 10 lines + title untranslated_nodes = sum(1 for _ in doctree.findall(NodeMatcher(translated=False))) - assert untranslated_nodes == 3 + 1 # 3 lines + substitution reference + assert untranslated_nodes == 2 + 2 + 1 # 2 lines + 2 lines + substitution reference @sphinx_intl def test_translation_progress_substitution(app): - app.build() + app.builder.build_specific([app.srcdir / 'translation_progress.txt']) doctree = app.env.get_doctree('translation_progress') - assert doctree[0][17][0] == '75.00%' # 12 out of 16 lines are translated + assert doctree[0][19][0] == '68.75%' # 11 out of 16 lines are translated @pytest.mark.sphinx(testroot='intl', freshenv=True, confoverrides={ @@ -638,11 +638,14 @@ def test_translation_progress_substitution(app): 'translation_progress_classes': True, }) def test_translation_progress_classes_true(app): - app.build() + app.builder.build_specific([app.srcdir / 'translation_progress.txt']) doctree = app.env.get_doctree('translation_progress') + # title assert 'translated' in doctree[0][0]['classes'] + + # translated lines assert 'translated' in doctree[0][1]['classes'] assert 'translated' in doctree[0][2]['classes'] assert 'translated' in doctree[0][3]['classes'] @@ -651,21 +654,31 @@ def test_translation_progress_classes_true(app): assert 'translated' in doctree[0][6]['classes'] assert 'translated' in doctree[0][7]['classes'] assert 'translated' in doctree[0][8]['classes'] - assert 'translated' in doctree[0][9]['classes'] + + assert doctree[0][9]['classes'] == [] # comment node + + # idempotent assert 'translated' in doctree[0][10]['classes'] assert 'translated' in doctree[0][11]['classes'] assert doctree[0][12]['classes'] == [] # comment node + # untranslated assert 'untranslated' in doctree[0][13]['classes'] assert 'untranslated' in doctree[0][14]['classes'] - assert 'untranslated' in doctree[0][15]['classes'] - assert doctree[0][16]['classes'] == [] # comment node + assert doctree[0][15]['classes'] == [] # comment node + # missing + assert 'untranslated' in doctree[0][16]['classes'] assert 'untranslated' in doctree[0][17]['classes'] - assert len(doctree[0]) == 18 + assert doctree[0][18]['classes'] == [] # comment node + + # substitution reference + assert 'untranslated' in doctree[0][19]['classes'] + + assert len(doctree[0]) == 20 @sphinx_intl