Skip to content

Commit

Permalink
Fix #1734: Could not translate the caption of toctree directive
Browse files Browse the repository at this point in the history
  • Loading branch information
tk0miya committed Sep 1, 2016
1 parent c6e1029 commit 9b00c31
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGES
Expand Up @@ -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
-------------
Expand Down
46 changes: 45 additions & 1 deletion sphinx/addnodes.py
Expand Up @@ -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.)

Expand Down
10 changes: 9 additions & 1 deletion sphinx/environment.py
Expand Up @@ -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

Expand Down
13 changes: 8 additions & 5 deletions sphinx/io.py
Expand Up @@ -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


Expand Down Expand Up @@ -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):
Expand Down
16 changes: 16 additions & 0 deletions sphinx/transforms.py
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions sphinx/util/nodes.py
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
20 changes: 20 additions & 0 deletions 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 <Tests> package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: Sphinx <Tests> 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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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"
1 change: 1 addition & 0 deletions tests/roots/test-intl/contents.txt
Expand Up @@ -4,6 +4,7 @@ CONTENTS
.. toctree::
:maxdepth: 2
:numbered:
:caption: Table of Contents

subdir/contents
bom
Expand Down
11 changes: 11 additions & 0 deletions tests/test_intl.py
Expand Up @@ -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)
Expand Down Expand Up @@ -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')
Expand Down

0 comments on commit 9b00c31

Please sign in to comment.