From c53fa4b369919875e5da423ef0a93c097398bc76 Mon Sep 17 00:00:00 2001 From: shimizukawa Date: Sun, 22 Feb 2015 13:51:02 +0900 Subject: [PATCH] refs #1235: i18n: 'literal-block' node can be translated if 'literal-block' is set to `gettext_additional_targets`. --- CHANGES | 2 + doc/config.rst | 1 + sphinx/environment.py | 8 ++-- sphinx/transforms.py | 41 ++++++++++++++++-- sphinx/util/nodes.py | 9 +++- sphinx/util/pycompat.py | 13 ++++++ tests/roots/test-intl/literalblock.po | 37 ++++++++++++++++ tests/roots/test-intl/literalblock.txt | 28 +++++++++++++ tests/test_intl.py | 58 +++++++++++++++++++++++++- 9 files changed, 187 insertions(+), 10 deletions(-) diff --git a/CHANGES b/CHANGES index 22869c6e5..2facbe0be 100644 --- a/CHANGES +++ b/CHANGES @@ -23,6 +23,8 @@ Features added nav-item-0, 1, 2...). * New option `sphinx-quickstart --use-make-mode` for generating Makefile that use sphinx-build make-mode. +* #1235: i18n: 'literal-block' node can be translated if 'literal-block' + is set to `gettext_additional_targets`. Bugs fixed ---------- diff --git a/doc/config.rst b/doc/config.rst index 1f13dd5d1..ace0f96e4 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -494,6 +494,7 @@ documentation on :ref:`intl` for details. i18n additionally. You can specify below names: :index: index terms + :literal-block: literal blocks: ``::`` and ``code-block``. The default is ``[]``. diff --git a/sphinx/environment.py b/sphinx/environment.py index 676899670..177017d22 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -50,7 +50,7 @@ from sphinx.locale import _ from sphinx.versioning import add_uids, merge_doctrees from sphinx.transforms import DefaultSubstitutions, MoveModuleTargets, \ HandleCodeBlocks, AutoNumbering, SortIds, CitationReferences, Locale, \ - RemoveTranslatableInline, SphinxContentsFilter + RemoveTranslatableInline, SphinxContentsFilter, ExtraTranslatableNodes orig_role_function = roles.role @@ -98,9 +98,9 @@ class SphinxStandaloneReader(standalone.Reader): """ Add our own transforms. """ - transforms = [Locale, CitationReferences, DefaultSubstitutions, - MoveModuleTargets, HandleCodeBlocks, AutoNumbering, SortIds, - RemoveTranslatableInline] + transforms = [ExtraTranslatableNodes, Locale, CitationReferences, + DefaultSubstitutions, MoveModuleTargets, HandleCodeBlocks, + AutoNumbering, SortIds, RemoveTranslatableInline] def get_transforms(self): return standalone.Reader.get_transforms(self) + self.transforms diff --git a/sphinx/transforms.py b/sphinx/transforms.py index c2e30022a..598427d4e 100644 --- a/sphinx/transforms.py +++ b/sphinx/transforms.py @@ -20,8 +20,11 @@ from docutils.transforms.parts import ContentsFilter from sphinx import addnodes from sphinx.locale import _, init as init_locale from sphinx.util import split_index_msg -from sphinx.util.nodes import traverse_translatable_index, extract_messages +from sphinx.util.nodes import ( + traverse_translatable_index, extract_messages, LITERAL_TYPE_NODES, +) from sphinx.util.osutil import ustrftime, find_catalog +from sphinx.util.pycompat import indent from sphinx.domains.std import ( make_term_from_paragraph_node, make_termnodes_from_paragraph_node, @@ -156,6 +159,28 @@ class CitationReferences(Transform): citnode.parent.replace(citnode, refnode) +TRANSLATABLE_NODES = { + 'literal-block': nodes.literal_block, +} +class ExtraTranslatableNodes(Transform): + """ + make nodes translatable + """ + default_priority = 10 + + def apply(self): + targets = self.document.settings.env.config.gettext_additional_targets + target_nodes = [v for k, v in TRANSLATABLE_NODES.items() if k in targets] + if not target_nodes: + return + + def is_translatable_node(node): + return isinstance(node, tuple(target_nodes)) + + for node in self.document.traverse(is_translatable_node): + node['translatable'] = True + + class CustomLocaleReporter(object): """ Replacer for document.reporter.get_source_and_line method. @@ -177,7 +202,7 @@ class Locale(Transform): """ Replace translatable nodes with their translated doctree. """ - default_priority = 0 + default_priority = 20 def apply(self): env = self.document.settings.env @@ -331,6 +356,11 @@ class Locale(Transform): msgstr += '\n\n dummy literal' # dummy literal node will discard by 'patch = patch[0]' + # literalblock need literal block notation to avoid it become + # paragraph. + if isinstance(node, LITERAL_TYPE_NODES): + msgstr = '::\n\n' + indent(msgstr, ' '*3) + patch = new_document(source, settings) CustomLocaleReporter(node.source, node.line).set_reporter(patch) parser.parse(msgstr, patch) @@ -339,7 +369,7 @@ class Locale(Transform): except IndexError: # empty node pass # XXX doctest and other block markup - if not isinstance(patch, nodes.paragraph): + if not isinstance(patch, (nodes.paragraph,) + LITERAL_TYPE_NODES): continue # skip for now # auto-numbered foot note reference should use original 'ids'. @@ -466,6 +496,11 @@ class Locale(Transform): for child in patch.children: child.parent = node node.children = patch.children + + # for highlighting that expects .rawsource and .astext() are same. + if isinstance(node, LITERAL_TYPE_NODES): + node.rawsource = node.astext() + node['translated'] = True if 'index' in env.config.gettext_additional_targets: diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index 0fb2e14d1..76778fdcc 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -73,6 +73,9 @@ IGNORED_NODES = ( nodes.doctest_block, #XXX there are probably more ) +LITERAL_TYPE_NODES = ( + nodes.literal_block, +) def extract_messages(doctree): """Extract translatable messages from a document tree.""" for node in doctree.traverse(nodes.TextElement): @@ -87,7 +90,11 @@ def extract_messages(doctree): if isinstance(node, nodes.field_name) and node.children[0] == 'orphan': continue - msg = node.rawsource.replace('\n', ' ').strip() + if isinstance(node, LITERAL_TYPE_NODES): + msg = node.rawsource + else: + msg = node.rawsource.replace('\n', ' ').strip() + # XXX nodes rendering empty are likely a bug in sphinx.addnodes if msg: yield node, msg diff --git a/sphinx/util/pycompat.py b/sphinx/util/pycompat.py index 28ab1b75c..62b77a655 100644 --- a/sphinx/util/pycompat.py +++ b/sphinx/util/pycompat.py @@ -52,6 +52,8 @@ if PY3: def __str__(self): return self.__unicode__() + from textwrap import indent + else: # Python 2 u = 'u' @@ -75,6 +77,17 @@ else: def __str__(self): return self.__unicode__().encode('utf8') + # backport from python3 + def indent(text, prefix, predicate=None): + if predicate is None: + def predicate(line): + return line.strip() + + def prefixed_lines(): + for line in text.splitlines(True): + yield (prefix + line if predicate(line) else line) + return ''.join(prefixed_lines()) + def execfile_(filepath, _globals): from sphinx.util.osutil import fs_encoding diff --git a/tests/roots/test-intl/literalblock.po b/tests/roots/test-intl/literalblock.po index 5b5f71e07..16712dc37 100644 --- a/tests/roots/test-intl/literalblock.po +++ b/tests/roots/test-intl/literalblock.po @@ -28,3 +28,40 @@ msgstr "MISSING LITERAL BLOCK::" msgid "That's all." msgstr "THAT'S ALL." +msgid "code blocks" +msgstr "CODE-BLOCKS" + +msgid "" +"def main\n" +" 'result'\n" +"end" +msgstr "" +"def main\n" +" 'RESULT'\n" +"end" + +msgid "" +"#include \n" +"int main(int argc, char** argv)\n" +"{\n" +" return 0;\n" +"}" +msgstr "" +"#include \n" +"int main(int ARGC, char** ARGV)\n" +"{\n" +" return 0;\n" +"}" + +msgid "" +"#include \n" +"int main(int argc, char** argv)\n" +"{\n" +" return 0;\n" +"}" +msgstr "" +"#include \n" +"int main(int ARGC, char** ARGV)\n" +"{\n" +" return 0;\n" +"}" diff --git a/tests/roots/test-intl/literalblock.txt b/tests/roots/test-intl/literalblock.txt index c9c710997..42f75cd68 100644 --- a/tests/roots/test-intl/literalblock.txt +++ b/tests/roots/test-intl/literalblock.txt @@ -11,3 +11,31 @@ Correct literal block:: Missing literal block:: That's all. + +code blocks +============== + +.. highlight:: ruby + +:: + + def main + 'result' + end + +:: + + #include + int main(int argc, char** argv) + { + return 0; + } + +.. code-block:: c + + #include + int main(int argc, char** argv) + { + return 0; + } + diff --git a/tests/test_intl.py b/tests/test_intl.py index 140cff062..732047ff0 100644 --- a/tests/test_intl.py +++ b/tests/test_intl.py @@ -27,7 +27,7 @@ from util import tempdir, rootdir, path, gen_with_app, SkipTest, \ root = tempdir / 'test-intl' -def gen_with_intl_app(*args, **kw): +def gen_with_intl_app(builder, confoverrides={}, *args, **kw): default_kw = { 'testroot': 'intl', 'confoverrides': { @@ -36,7 +36,8 @@ def gen_with_intl_app(*args, **kw): }, } default_kw.update(kw) - return gen_with_app(*args, **default_kw) + default_kw['confoverrides'].update(confoverrides) + return gen_with_app(builder, *args, **default_kw) def setup_module(): @@ -621,3 +622,56 @@ def test_xml_builder(app, status, warning): ['label-bridged-target-section', 'section-and-label', 'section-and-label']) + + +@gen_with_intl_app('html', freshenv=True) +def test_additional_targets_should_not_be_translated(app, status, warning): + app.builder.build_all() + + result = (app.outdir / 'literalblock.html').text(encoding='utf-8') + + # title should be translated + expected_expr = 'CODE-BLOCKS' + yield assert_equal, len(re.findall(expected_expr, result)), 2, (expected_expr, result) + + # ruby code block should not be translated but be highlighted + expected_expr = """'result'""" + yield assert_equal, len(re.findall(expected_expr, result)), 1, (expected_expr, result) + + # C code block without lang should not be translated and *ruby* highlighted + expected_expr = """#include <stdlib.h>""" + yield assert_equal, len(re.findall(expected_expr, result)), 1, (expected_expr, result) + + # C code block with lang should not be translated but be *C* highlighted + expected_expr = """#include <stdio.h>""" + yield assert_equal, len(re.findall(expected_expr, result)), 1, (expected_expr, result) + + +@gen_with_intl_app('html', freshenv=True, + confoverrides={ + 'gettext_additional_targets': [ + 'index', + 'literal-block', + ], + }) +def test_additional_targets_should_be_translated(app, status, warning): + app.builder.build_all() + + result = (app.outdir / 'literalblock.html').text(encoding='utf-8') + + # title should be translated + expected_expr = 'CODE-BLOCKS' + yield assert_equal, len(re.findall(expected_expr, result)), 2, (expected_expr, result) + + # ruby code block should be translated and be highlighted + expected_expr = """'RESULT'""" + yield assert_equal, len(re.findall(expected_expr, result)), 1, (expected_expr, result) + + # C code block without lang should be translated and *ruby* highlighted + expected_expr = """#include <STDLIB.H>""" + yield assert_equal, len(re.findall(expected_expr, result)), 1, (expected_expr, result) + + # C code block with lang should be translated and be *C* highlighted + expected_expr = """#include <STDIO.H>""" + yield assert_equal, len(re.findall(expected_expr, result)), 1, (expected_expr, result) +