summaryrefslogtreecommitdiff
path: root/sphinx/transforms/i18n.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/transforms/i18n.py')
-rw-r--r--sphinx/transforms/i18n.py199
1 files changed, 93 insertions, 106 deletions
diff --git a/sphinx/transforms/i18n.py b/sphinx/transforms/i18n.py
index 860f11606..494dff855 100644
--- a/sphinx/transforms/i18n.py
+++ b/sphinx/transforms/i18n.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
sphinx.transforms.i18n
~~~~~~~~~~~~~~~~~~~~~~
@@ -10,6 +9,8 @@
"""
from os import path
+from textwrap import indent
+from typing import Any, TypeVar
from docutils import nodes
from docutils.io import StringInput
@@ -22,27 +23,28 @@ from sphinx.transforms import SphinxTransform
from sphinx.util import split_index_msg, logging
from sphinx.util.i18n import find_catalog
from sphinx.util.nodes import (
- LITERAL_TYPE_NODES, IMAGE_TYPE_NODES,
+ LITERAL_TYPE_NODES, IMAGE_TYPE_NODES, NodeMatcher,
extract_messages, is_pending_meta, traverse_translatable_index,
)
-from sphinx.util.pycompat import indent
if False:
# For type annotation
- from typing import Any, Dict, List, Tuple # NOQA
+ from typing import Dict, List, Tuple, Type # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.config import Config # NOQA
logger = logging.getLogger(__name__)
+N = TypeVar('N', bound=nodes.Node)
+
def publish_msgstr(app, source, source_path, source_line, config, settings):
- # type: (Sphinx, unicode, unicode, int, Config, Dict) -> nodes.document
+ # type: (Sphinx, str, str, int, Config, Any) -> nodes.Element
"""Publish msgstr (single line) into docutils document
:param sphinx.application.Sphinx app: sphinx application
- :param unicode source: source text
- :param unicode source_path: source path for warning indication
+ :param str source: source text
+ :param str source_path: source path for warning indication
:param source_line: source line for warning indication
:param sphinx.config.Config config: sphinx config
:param docutils.frontend.Values settings: docutils settings
@@ -59,7 +61,7 @@ def publish_msgstr(app, source, source_path, source_line, config, settings):
settings=settings,
)
try:
- doc = doc[0]
+ doc = doc[0] # type: ignore
except IndexError: # empty node
pass
return doc
@@ -71,8 +73,8 @@ class PreserveTranslatableMessages(SphinxTransform):
"""
default_priority = 10 # this MUST be invoked before Locale transform
- def apply(self):
- # type: () -> None
+ def apply(self, **kwargs):
+ # type: (Any) -> None
for node in self.document.traverse(addnodes.translatable):
node.preserve_original_messages()
@@ -83,10 +85,10 @@ class Locale(SphinxTransform):
"""
default_priority = 20
- def apply(self):
- # type: () -> None
+ def apply(self, **kwargs):
+ # type: (Any) -> None
settings, source = self.document.settings, self.document['source']
- msgstr = u''
+ msgstr = ''
# XXX check if this is reliable
assert source.startswith(self.env.srcdir)
@@ -103,7 +105,7 @@ class Locale(SphinxTransform):
# phase1: replace reference ids with translated names
for node, msg in extract_messages(self.document):
- msgstr = catalog.gettext(msg) # type: ignore
+ msgstr = catalog.gettext(msg)
# XXX add marker to untranslated parts
if not msgstr or msgstr == msg or not msgstr.strip():
# as-of-yet untranslated
@@ -183,11 +185,8 @@ class Locale(SphinxTransform):
self.document.note_implicit_target(section_node)
# replace target's refname to new target name
- def is_named_target(node):
- # type: (nodes.Node) -> bool
- return isinstance(node, nodes.target) and \
- node.get('refname') == old_name
- for old_target in self.document.traverse(is_named_target):
+ matcher = NodeMatcher(nodes.target, refname=old_name)
+ for old_target in self.document.traverse(matcher): # type: nodes.target
old_target['refname'] = new_name
processed = True
@@ -220,7 +219,7 @@ class Locale(SphinxTransform):
if node.get('translated', False): # to avoid double translation
continue # skip if the node is already translated by phase1
- msgstr = catalog.gettext(msg) # type: ignore
+ msgstr = catalog.gettext(msg)
# XXX add marker to untranslated parts
if not msgstr or msgstr == msg: # as-of-yet untranslated
continue
@@ -231,7 +230,7 @@ class Locale(SphinxTransform):
continue
# update meta nodes
- if is_pending_meta(node):
+ if isinstance(node, nodes.pending) and is_pending_meta(node):
node.details['nodes'][0]['content'] = msgstr
continue
@@ -264,30 +263,30 @@ class Locale(SphinxTransform):
patch = patch.next_node()
# ignore unexpected markups in translation message
- if not isinstance(patch, (
- (nodes.paragraph, # expected form of translation
- nodes.title, # generated by above "Subelements phase2"
- ) +
- # following types are expected if
- # config.gettext_additional_targets is configured
- LITERAL_TYPE_NODES +
- IMAGE_TYPE_NODES
- )):
+ unexpected = (
+ nodes.paragraph, # expected form of translation
+ nodes.title # generated by above "Subelements phase2"
+ ) # type: Tuple[Type[nodes.Element], ...]
+
+ # following types are expected if
+ # config.gettext_additional_targets is configured
+ unexpected += LITERAL_TYPE_NODES
+ unexpected += IMAGE_TYPE_NODES
+
+ if not isinstance(patch, unexpected):
continue # skip
# auto-numbered foot note reference should use original 'ids'.
- def is_autofootnote_ref(node):
- # type: (nodes.Node) -> bool
- return isinstance(node, nodes.footnote_reference) and node.get('auto')
-
def list_replace_or_append(lst, old, new):
- # type: (List, Any, Any) -> None
+ # type: (List[N], N, N) -> None
if old in lst:
lst[lst.index(old)] = new
else:
lst.append(new)
- old_foot_refs = node.traverse(is_autofootnote_ref)
- new_foot_refs = patch.traverse(is_autofootnote_ref)
+
+ is_autofootnote_ref = NodeMatcher(nodes.footnote_reference, auto=Any)
+ old_foot_refs = node.traverse(is_autofootnote_ref) # type: List[nodes.footnote_reference] # NOQA
+ new_foot_refs = patch.traverse(is_autofootnote_ref) # type: List[nodes.footnote_reference] # NOQA
if len(old_foot_refs) != len(new_foot_refs):
old_foot_ref_rawsources = [ref.rawsource for ref in old_foot_refs]
new_foot_ref_rawsources = [ref.rawsource for ref in new_foot_refs]
@@ -295,45 +294,41 @@ class Locale(SphinxTransform):
' original: {0}, translated: {1}')
.format(old_foot_ref_rawsources, new_foot_ref_rawsources),
location=node)
- old_foot_namerefs = {} # type: Dict[unicode, List[nodes.footnote_reference]]
+ old_foot_namerefs = {} # type: Dict[str, List[nodes.footnote_reference]]
for r in old_foot_refs:
old_foot_namerefs.setdefault(r.get('refname'), []).append(r)
- for new in new_foot_refs:
- refname = new.get('refname')
+ for newf in new_foot_refs:
+ refname = newf.get('refname')
refs = old_foot_namerefs.get(refname, [])
if not refs:
continue
- old = refs.pop(0)
- new['ids'] = old['ids']
- for id in new['ids']:
- self.document.ids[id] = new
+ oldf = refs.pop(0)
+ newf['ids'] = oldf['ids']
+ for id in newf['ids']:
+ self.document.ids[id] = newf
- if new['auto'] == 1:
+ if newf['auto'] == 1:
# autofootnote_refs
- list_replace_or_append(self.document.autofootnote_refs, old, new)
+ list_replace_or_append(self.document.autofootnote_refs, oldf, newf)
else:
# symbol_footnote_refs
- list_replace_or_append(self.document.symbol_footnote_refs, old, new)
+ list_replace_or_append(self.document.symbol_footnote_refs, oldf, newf)
if refname:
- list_replace_or_append(
- self.document.footnote_refs.setdefault(refname, []),
- old, new)
- list_replace_or_append(
- self.document.refnames.setdefault(refname, []),
- old, new)
+ footnote_refs = self.document.footnote_refs.setdefault(refname, [])
+ list_replace_or_append(footnote_refs, oldf, newf)
+
+ refnames = self.document.refnames.setdefault(refname, [])
+ list_replace_or_append(refnames, oldf, newf)
# reference should use new (translated) 'refname'.
# * reference target ".. _Python: ..." is not translatable.
# * use translated refname for section refname.
# * inline reference "`Python <...>`_" has no 'refname'.
- def is_refnamed_ref(node):
- # type: (nodes.Node) -> bool
- return isinstance(node, nodes.reference) and \
- 'refname' in node
- old_refs = node.traverse(is_refnamed_ref)
- new_refs = patch.traverse(is_refnamed_ref)
+ is_refnamed_ref = NodeMatcher(nodes.reference, refname=Any)
+ old_refs = node.traverse(is_refnamed_ref) # type: List[nodes.reference]
+ new_refs = patch.traverse(is_refnamed_ref) # type: List[nodes.reference]
if 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]
@@ -344,27 +339,24 @@ class Locale(SphinxTransform):
old_ref_names = [r['refname'] for r in old_refs]
new_ref_names = [r['refname'] for r in new_refs]
orphans = list(set(old_ref_names) - set(new_ref_names))
- for new in new_refs:
- if not self.document.has_name(new['refname']):
+ for newr in new_refs:
+ if not self.document.has_name(newr['refname']):
# Maybe refname is translated but target is not translated.
# Note: multiple translated refnames break link ordering.
if orphans:
- new['refname'] = orphans.pop(0)
+ newr['refname'] = orphans.pop(0)
else:
# orphan refnames is already empty!
# reference number is same in new_refs and old_refs.
pass
- self.document.note_refname(new)
+ self.document.note_refname(newr)
# refnamed footnote should use original 'ids'.
- def is_refnamed_footnote_ref(node):
- # type: (nodes.Node) -> bool
- return isinstance(node, nodes.footnote_reference) and \
- 'refname' in node
+ is_refnamed_footnote_ref = NodeMatcher(nodes.footnote_reference, refname=Any)
old_foot_refs = node.traverse(is_refnamed_footnote_ref)
new_foot_refs = patch.traverse(is_refnamed_footnote_ref)
- refname_ids_map = {} # type: Dict[unicode, List[unicode]]
+ refname_ids_map = {} # type: Dict[str, List[str]]
if len(old_foot_refs) != len(new_foot_refs):
old_foot_ref_rawsources = [ref.rawsource for ref in old_foot_refs]
new_foot_ref_rawsources = [ref.rawsource for ref in new_foot_refs]
@@ -372,20 +364,17 @@ class Locale(SphinxTransform):
' original: {0}, translated: {1}')
.format(old_foot_ref_rawsources, new_foot_ref_rawsources),
location=node)
- for old in old_foot_refs:
- refname_ids_map.setdefault(old["refname"], []).append(old["ids"])
- for new in new_foot_refs:
- refname = new["refname"]
+ for oldf in old_foot_refs:
+ refname_ids_map.setdefault(oldf["refname"], []).append(oldf["ids"])
+ for newf in new_foot_refs:
+ refname = newf["refname"]
if refname_ids_map.get(refname):
- new["ids"] = refname_ids_map[refname].pop(0)
+ newf["ids"] = refname_ids_map[refname].pop(0)
# citation should use original 'ids'.
- def is_citation_ref(node):
- # type: (nodes.Node) -> bool
- return isinstance(node, nodes.citation_reference) and \
- 'refname' in node
- old_cite_refs = node.traverse(is_citation_ref)
- new_cite_refs = patch.traverse(is_citation_ref)
+ is_citation_ref = NodeMatcher(nodes.citation_reference, refname=Any)
+ old_cite_refs = node.traverse(is_citation_ref) # type: List[nodes.citation_reference] # NOQA
+ new_cite_refs = patch.traverse(is_citation_ref) # type: List[nodes.citation_reference] # NOQA
refname_ids_map = {}
if len(old_cite_refs) != len(new_cite_refs):
old_cite_ref_rawsources = [ref.rawsource for ref in old_cite_refs]
@@ -394,29 +383,29 @@ class Locale(SphinxTransform):
' original: {0}, translated: {1}')
.format(old_cite_ref_rawsources, new_cite_ref_rawsources),
location=node)
- for old in old_cite_refs:
- refname_ids_map.setdefault(old["refname"], []).append(old["ids"])
- for new in new_cite_refs:
- refname = new["refname"]
+ for oldc in old_cite_refs:
+ refname_ids_map.setdefault(oldc["refname"], []).append(oldc["ids"])
+ for newc in new_cite_refs:
+ refname = newc["refname"]
if refname_ids_map.get(refname):
- new["ids"] = refname_ids_map[refname].pop()
+ newc["ids"] = refname_ids_map[refname].pop()
# Original pending_xref['reftarget'] contain not-translated
# target name, new pending_xref must use original one.
# This code restricts to change ref-targets in the translation.
- old_refs = node.traverse(addnodes.pending_xref)
- new_refs = patch.traverse(addnodes.pending_xref)
+ old_xrefs = node.traverse(addnodes.pending_xref)
+ new_xrefs = patch.traverse(addnodes.pending_xref)
xref_reftarget_map = {}
- if 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]
+ if len(old_xrefs) != len(new_xrefs):
+ old_xref_rawsources = [xref.rawsource for xref in old_xrefs]
+ new_xref_rawsources = [xref.rawsource for xref in new_xrefs]
logger.warning(__('inconsistent term references in translated message.' +
' original: {0}, translated: {1}')
- .format(old_ref_rawsources, new_ref_rawsources),
+ .format(old_xref_rawsources, new_xref_rawsources),
location=node)
def get_ref_key(node):
- # type: (nodes.Node) -> Tuple[unicode, unicode, unicode]
+ # type: (addnodes.pending_xref) -> Tuple[str, str, str]
case = node["refdomain"], node["reftype"]
if case == ('std', 'term'):
return None
@@ -426,11 +415,11 @@ class Locale(SphinxTransform):
node["reftype"],
node['reftarget'],)
- for old in old_refs:
+ for old in old_xrefs:
key = get_ref_key(old)
if key:
xref_reftarget_map[key] = old.attributes
- for new in new_refs:
+ for new in new_xrefs:
key = get_ref_key(new)
# Copy attributes to keep original node behavior. Especially
# copying 'reftarget', 'py:module', 'py:class' are needed.
@@ -458,12 +447,12 @@ class Locale(SphinxTransform):
if 'index' in self.config.gettext_additional_targets:
# Extract and translate messages for index entries.
for node, entries in traverse_translatable_index(self.document):
- new_entries = [] # type: List[Tuple[unicode, unicode, unicode, unicode, unicode]] # NOQA
+ new_entries = [] # type: List[Tuple[str, str, str, str, str]]
for type, msg, tid, main, key_ in entries:
msg_parts = split_index_msg(type, msg)
msgstr_parts = []
for part in msg_parts:
- msgstr = catalog.gettext(part) # type: ignore
+ msgstr = catalog.gettext(part)
if not msgstr:
msgstr = part
msgstr_parts.append(msgstr)
@@ -474,11 +463,8 @@ class Locale(SphinxTransform):
node['entries'] = new_entries
# remove translated attribute that is used for avoiding double translation.
- def has_translatable(node):
- # type: (nodes.Node) -> bool
- return isinstance(node, nodes.Element) and 'translated' in node
- for node in self.document.traverse(has_translatable):
- node.delattr('translated')
+ for translated in self.document.traverse(NodeMatcher(translated=Any)): # type: nodes.Element # NOQA
+ translated.delattr('translated')
class RemoveTranslatableInline(SphinxTransform):
@@ -487,12 +473,13 @@ class RemoveTranslatableInline(SphinxTransform):
"""
default_priority = 999
- def apply(self):
- # type: () -> None
+ def apply(self, **kwargs):
+ # type: (Any) -> None
from sphinx.builders.gettext import MessageCatalogBuilder
if isinstance(self.app.builder, MessageCatalogBuilder):
return
- for inline in self.document.traverse(nodes.inline):
- if 'translatable' in inline:
- inline.parent.remove(inline)
- inline.parent += inline.children
+
+ matcher = NodeMatcher(nodes.inline, translatable=Any)
+ for inline in self.document.traverse(matcher): # type: nodes.inline
+ inline.parent.remove(inline)
+ inline.parent += inline.children