55import contextlib
66from re import DOTALL , match
77from textwrap import indent
8+ from types import SimpleNamespace
89from typing import TYPE_CHECKING , Any , TypeVar
910
1011from docutils import nodes
1314from sphinx import addnodes
1415from sphinx .domains .std import make_glossary_term , split_term_classifiers
1516from sphinx .errors import ConfigError
17+ from sphinx .io import SphinxBaseReader
1618from sphinx .locale import __
1719from sphinx .locale import init as init_locale
18- from sphinx .transforms import SphinxTransform
20+ from sphinx .transforms import AutoIndexUpgrader , DoctreeReadEvent , SphinxTransform
21+ from sphinx .transforms .references import SphinxDomains
1922from sphinx .util import get_filetype , logging
2023from sphinx .util .i18n import docname_to_domain
2124from sphinx .util .index_entries import split_index_msg
2629 extract_messages ,
2730 traverse_translatable_index ,
2831)
32+ from sphinx .versioning import UIDTransform
2933
3034if TYPE_CHECKING :
3135 from collections .abc import Sequence
3236
3337 from sphinx .application import Sphinx
3438 from sphinx .config import Config
39+ from sphinx .environment import BuildEnvironment
40+ from sphinx .registry import SphinxComponentRegistry
3541 from sphinx .util .typing import ExtensionMetadata
3642
3743
4753N = TypeVar ('N' , bound = nodes .Node )
4854
4955
56+ class _SphinxI18nReader (SphinxBaseReader ):
57+ """A document reader for internationalisation (i18n).
58+
59+ This returns the source line number of the original text
60+ as the current source line number to let users know where
61+ the error happened, because the translated texts are
62+ partial and they don't have correct line numbers.
63+ """
64+
65+ def __init__ (
66+ self , * args : Any , registry : SphinxComponentRegistry , ** kwargs : Any
67+ ) -> None :
68+ super ().__init__ (* args , ** kwargs )
69+ unused = frozenset ({
70+ PreserveTranslatableMessages ,
71+ Locale ,
72+ RemoveTranslatableInline ,
73+ AutoIndexUpgrader ,
74+ SphinxDomains ,
75+ DoctreeReadEvent ,
76+ UIDTransform ,
77+ })
78+ transforms = self .transforms + registry .get_transforms ()
79+ self .transforms = [
80+ transform for transform in transforms if transform not in unused
81+ ]
82+
83+
5084def publish_msgstr (
51- app : Sphinx ,
5285 source : str ,
5386 source_path : str ,
5487 source_line : int ,
5588 config : Config ,
5689 settings : Any ,
90+ * ,
91+ env : BuildEnvironment ,
92+ registry : SphinxComponentRegistry ,
5793) -> nodes .Element :
5894 """Publish msgstr (single line) into docutils document
5995
60- :param sphinx.application.Sphinx app: sphinx application
6196 :param str source: source text
6297 :param str source_path: source path for warning indication
6398 :param source_line: source line for warning indication
6499 :param sphinx.config.Config config: sphinx config
65100 :param docutils.frontend.Values settings: docutils settings
66101 :return: document
67102 :rtype: docutils.nodes.document
103+ :param sphinx.environment.BuildEnvironment env: sphinx environment
104+ :param sphinx.registry.SphinxComponentRegistry registry: sphinx registry
68105 """
69106 try :
70107 # clear rst_prolog temporarily
71108 rst_prolog = config .rst_prolog
72109 config .rst_prolog = None
73110
74- from sphinx .io import SphinxI18nReader
75-
76- reader = SphinxI18nReader ()
77- reader .setup (app )
111+ reader = _SphinxI18nReader (registry = registry )
112+ app = SimpleNamespace (config = config , env = env , registry = registry )
78113 filetype = get_filetype (config .source_suffix , source_path )
79- parser = app . registry .create_source_parser (app , filetype )
114+ parser = registry .create_source_parser (app , filetype ) # type: ignore[arg-type]
80115 doc = reader .read (
81116 source = StringInput (
82117 source = source , source_path = f'{ source_path } :{ source_line } :<translated>'
@@ -436,12 +471,13 @@ def apply(self, **kwargs: Any) -> None:
436471 msgstr = '::\n \n ' + indent (msgstr , ' ' * 3 )
437472
438473 patch = publish_msgstr (
439- self .app ,
440474 msgstr ,
441475 source ,
442476 node .line , # type: ignore[arg-type]
443477 self .config ,
444478 settings ,
479+ env = self .env ,
480+ registry = self .env ._registry ,
445481 )
446482 # FIXME: no warnings about inconsistent references in this part
447483 # XXX doctest and other block markup
@@ -456,12 +492,13 @@ def apply(self, **kwargs: Any) -> None:
456492 for _id in node ['ids' ]:
457493 term , first_classifier = split_term_classifiers (msgstr )
458494 patch = publish_msgstr (
459- self .app ,
460495 term or '' ,
461496 source ,
462497 node .line , # type: ignore[arg-type]
463498 self .config ,
464499 settings ,
500+ env = self .env ,
501+ registry = self .env ._registry ,
465502 )
466503 updater .patch = make_glossary_term (
467504 self .env ,
@@ -533,12 +570,13 @@ def apply(self, **kwargs: Any) -> None:
533570 msgstr = msgstr + '\n ' + '=' * len (msgstr ) * 2
534571
535572 patch = publish_msgstr (
536- self .app ,
537573 msgstr ,
538574 source ,
539575 node .line , # type: ignore[arg-type]
540576 self .config ,
541577 settings ,
578+ env = self .env ,
579+ registry = self .env ._registry ,
542580 )
543581 # Structural Subelements phase2
544582 if isinstance (node , nodes .title ):
@@ -612,7 +650,7 @@ class TranslationProgressTotaliser(SphinxTransform):
612650 def apply (self , ** kwargs : Any ) -> None :
613651 from sphinx .builders .gettext import MessageCatalogBuilder
614652
615- if isinstance (self .app . builder , MessageCatalogBuilder ):
653+ if issubclass (self .env . _builder_cls , MessageCatalogBuilder ):
616654 return
617655
618656 total = translated = 0
@@ -635,7 +673,7 @@ class AddTranslationClasses(SphinxTransform):
635673 def apply (self , ** kwargs : Any ) -> None :
636674 from sphinx .builders .gettext import MessageCatalogBuilder
637675
638- if isinstance (self .app . builder , MessageCatalogBuilder ):
676+ if issubclass (self .env . _builder_cls , MessageCatalogBuilder ):
639677 return
640678
641679 if not self .config .translation_progress_classes :
@@ -673,7 +711,7 @@ class RemoveTranslatableInline(SphinxTransform):
673711 def apply (self , ** kwargs : Any ) -> None :
674712 from sphinx .builders .gettext import MessageCatalogBuilder
675713
676- if isinstance (self .app . builder , MessageCatalogBuilder ):
714+ if issubclass (self .env . _builder_cls , MessageCatalogBuilder ):
677715 return
678716
679717 matcher = NodeMatcher (nodes .inline , translatable = Any )
0 commit comments