diff --git a/CHANGES b/CHANGES index d07bd361b84..9c8d3a1ec44 100644 --- a/CHANGES +++ b/CHANGES @@ -127,6 +127,7 @@ Bugs fixed * #2874: gettext builder could not extract all text under the ``only`` directives * #2485: autosummary crashes with multiple source_suffix values +* #1734: Could not translate the caption of toctree directive Documentation ------------- diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py index 284bc1c7528..c5ce4e2965d 100644 --- a/sphinx/addnodes.py +++ b/sphinx/addnodes.py @@ -14,9 +14,53 @@ from docutils import nodes -class toctree(nodes.General, nodes.Element): +class translatable: + """Node which supports translation. + + The translation goes forward with following steps: + + 1. Preserve original translatable messages + 2. Apply translated messages from message catalog + 3. Extract preserved messages (for gettext builder) + + The translatable nodes MUST preserve original messages. + And these messages should not be overridden at applying step. + Because they are used at final step; extraction. + """ + + def preserve_original_messages(self): + """Preserve original translatable messages.""" + raise NotImplementedError + + def apply_translated_message(self, original_message, translated_message): + """Apply translated message.""" + raise NotImplementedError + + def extract_original_messages(self): + """Extract translation messages. + + :returns: list of extracted messages or messages generator + """ + raise NotImplementedError + + +class toctree(nodes.General, nodes.Element, translatable): """Node for inserting a "TOC tree".""" + def preserve_original_messages(self): + if 'caption' in self: + self['rawcaption'] = self['caption'] + + def apply_translated_message(self, original_message, translated_message): + if self.get('rawcaption') == original_message: + self['caption'] = translated_message + + def extract_original_messages(self): + if 'rawcaption' in self: + return [self['rawcaption']] + else: + return [] + # domain-specific object descriptions (class, function etc.) diff --git a/sphinx/environment.py b/sphinx/environment.py index e3c29c29873..95ca3904b1a 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -1466,7 +1466,15 @@ def _entries_from_toctree(toctreenode, parents, newnode = addnodes.compact_paragraph('', '') caption = toctree.attributes.get('caption') if caption: - newnode += nodes.caption(caption, '', *[nodes.Text(caption)]) + caption_node = nodes.caption(caption, '', *[nodes.Text(caption)]) + caption_node.line = toctree.line + caption_node.source = toctree.source + caption_node.rawsource = toctree['rawcaption'] + if hasattr(toctree, 'uid'): + # move uid to caption_node to translate it + caption_node.uid = toctree.uid + del toctree.uid + newnode += caption_node newnode.extend(tocentries) newnode['toctree'] = True diff --git a/sphinx/io.py b/sphinx/io.py index 36ac7bf98cb..bc14d59aae5 100644 --- a/sphinx/io.py +++ b/sphinx/io.py @@ -13,9 +13,11 @@ from docutils.writers import UnfilteredWriter from six import string_types, text_type -from sphinx.transforms import ApplySourceWorkaround, ExtraTranslatableNodes, Locale, \ - CitationReferences, DefaultSubstitutions, MoveModuleTargets, HandleCodeBlocks, \ +from sphinx.transforms import ( + ApplySourceWorkaround, ExtraTranslatableNodes, PreserveTranslatableMessages, Locale, + CitationReferences, DefaultSubstitutions, MoveModuleTargets, HandleCodeBlocks, AutoNumbering, AutoIndexUpgrader, SortIds, RemoveTranslatableInline +) from sphinx.util import import_object, split_docinfo @@ -57,9 +59,10 @@ class SphinxStandaloneReader(SphinxBaseReader): """ Add our own transforms. """ - transforms = [ApplySourceWorkaround, ExtraTranslatableNodes, Locale, CitationReferences, - DefaultSubstitutions, MoveModuleTargets, HandleCodeBlocks, - AutoNumbering, AutoIndexUpgrader, SortIds, RemoveTranslatableInline] + transforms = [ApplySourceWorkaround, ExtraTranslatableNodes, PreserveTranslatableMessages, + Locale, CitationReferences, DefaultSubstitutions, MoveModuleTargets, + HandleCodeBlocks, AutoNumbering, AutoIndexUpgrader, SortIds, + RemoveTranslatableInline, PreserveTranslatableMessages] class SphinxI18nReader(SphinxBaseReader): diff --git a/sphinx/transforms.py b/sphinx/transforms.py index 7b09285a4e0..8792d8f1121 100644 --- a/sphinx/transforms.py +++ b/sphinx/transforms.py @@ -238,6 +238,17 @@ def publish_msgstr(app, source, source_path, source_line, config, settings): return doc +class PreserveTranslatableMessages(Transform): + """ + Preserve original translatable messages befor translation + """ + default_priority = 10 # this MUST be invoked before Locale transform + + def apply(self): + for node in self.document.traverse(addnodes.translatable): + node.preserve_original_messages() + + class Locale(Transform): """ Replace translatable nodes with their translated doctree. @@ -384,6 +395,11 @@ def is_named_target(node): if not msgstr or msgstr == msg: # as-of-yet untranslated continue + # update translatable nodes + if isinstance(node, addnodes.translatable): + node.apply_translated_message(msg, msgstr) + continue + # Avoid "Literal block expected; none found." warnings. # If msgstr ends with '::' then it cause warning message at # parser.parse() processing. diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index 98f84f2bfaa..e0a6e0195f7 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -86,6 +86,9 @@ def apply_source_workaround(node): def is_translatable(node): + if isinstance(node, addnodes.translatable): + return True + if isinstance(node, nodes.TextElement): if not node.source: return False # built-in message @@ -119,6 +122,10 @@ def is_translatable(node): def extract_messages(doctree): """Extract translatable messages from a document tree.""" for node in doctree.traverse(is_translatable): + if isinstance(node, addnodes.translatable): + for msg in node.extract_original_messages(): + yield node, msg + continue if isinstance(node, LITERAL_TYPE_NODES): msg = node.rawsource if not msg: diff --git a/tests/roots/test-intl/contents.po b/tests/roots/test-intl/contents.po new file mode 100644 index 00000000000..e7f71eafac4 --- /dev/null +++ b/tests/roots/test-intl/contents.po @@ -0,0 +1,20 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) 2010, Georg Brandl & Team +# This file is distributed under the same license as the Sphinx package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: Sphinx 0.6\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-12-16 14:11+0000\n" +"PO-Revision-Date: 2012-12-18 06:14+0900\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 "Table of Contents" +msgstr "TABLE OF CONTENTS" diff --git a/tests/roots/test-intl/contents.txt b/tests/roots/test-intl/contents.txt index 8882137f395..20542dc81be 100644 --- a/tests/roots/test-intl/contents.txt +++ b/tests/roots/test-intl/contents.txt @@ -4,6 +4,7 @@ CONTENTS .. toctree:: :maxdepth: 2 :numbered: + :caption: Table of Contents subdir/contents bom diff --git a/tests/test_intl.py b/tests/test_intl.py index de1d56e3342..1f7242dfa96 100644 --- a/tests/test_intl.py +++ b/tests/test_intl.py @@ -118,6 +118,11 @@ def assert_count(expected_expr, result, count): def test_text_builder(app, status, warning): app.builder.build_all() + # --- toctree + + result = (app.outdir / 'contents.txt').text(encoding='utf-8') + yield assert_startswith, result, u"CONTENTS\n********\n\nTABLE OF CONTENTS\n" + # --- warnings in translation warnings = getwarning(warning) @@ -318,6 +323,12 @@ def test_text_builder(app, status, warning): def test_gettext_builder(app, status, warning): app.builder.build_all() + # --- toctree + expect = read_po(app.srcdir / 'contents.po') + actual = read_po(app.outdir / 'contents.pot') + for expect_msg in [m for m in expect if m.id]: + yield assert_in, expect_msg.id, [m.id for m in actual if m.id] + # --- definition terms: regression test for #2198, #2205 expect = read_po(app.srcdir / 'definition_terms.po') actual = read_po(app.outdir / 'definition_terms.pot')