From 56ad4485262ddfc9919a7c5441f7d31614e92e77 Mon Sep 17 00:00:00 2001 From: Nozomu Kaneko Date: Tue, 18 Dec 2012 06:08:23 +0900 Subject: [PATCH 1/7] add `translatable` node, which does nothing but contain other nodes. --- sphinx/addnodes.py | 3 +++ sphinx/writers/html.py | 5 +++++ sphinx/writers/latex.py | 5 +++++ sphinx/writers/manpage.py | 5 +++++ sphinx/writers/texinfo.py | 5 +++++ sphinx/writers/text.py | 5 +++++ 6 files changed, 28 insertions(+) diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py index 94f1d615f..94f670f01 100644 --- a/sphinx/addnodes.py +++ b/sphinx/addnodes.py @@ -15,6 +15,9 @@ from docutils import nodes class toctree(nodes.General, nodes.Element): """Node for inserting a "TOC tree".""" +class translatable(nodes.General, nodes.TextElement): + """An element which contains translatable nodes.""" + # domain-specific object descriptions (class, function etc.) diff --git a/sphinx/writers/html.py b/sphinx/writers/html.py index d714ed4f3..508df3b53 100644 --- a/sphinx/writers/html.py +++ b/sphinx/writers/html.py @@ -548,6 +548,11 @@ class HTMLTranslator(BaseTranslator): node['classes'].append('field-odd') self.body.append(self.starttag(node, 'tr', '', CLASS='field')) + def visit_translatable(self, node): + pass + def depart_translatable(self, node): + pass + def unknown_visit(self, node): raise NotImplementedError('Unknown node: ' + node.__class__.__name__) diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index aadf44011..d3a78d678 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -1512,5 +1512,10 @@ class LaTeXTranslator(nodes.NodeVisitor): def depart_system_message(self, node): self.body.append('\n') + def visit_translatable(self, node): + pass + def depart_translatable(self, node): + pass + def unknown_visit(self, node): raise NotImplementedError('Unknown node: ' + node.__class__.__name__) diff --git a/sphinx/writers/manpage.py b/sphinx/writers/manpage.py index e074691e4..50237ef93 100644 --- a/sphinx/writers/manpage.py +++ b/sphinx/writers/manpage.py @@ -340,5 +340,10 @@ class ManualPageTranslator(BaseTranslator): self.body.append(node.astext()) raise nodes.SkipNode + def visit_translatable(self, node): + pass + def depart_translatable(self, node): + pass + def unknown_visit(self, node): raise NotImplementedError('Unknown node: ' + node.__class__.__name__) diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py index 198d1e061..d3435a482 100644 --- a/sphinx/writers/texinfo.py +++ b/sphinx/writers/texinfo.py @@ -1156,6 +1156,11 @@ class TexinfoTranslator(nodes.NodeVisitor): def depart_problematic(self, node): self.body.append('<') + def visit_translatable(self, node): + pass + def depart_translatable(self, node): + pass + def unimplemented_visit(self, node): self.builder.warn("unimplemented node type: %r" % node, (self.curfilestack[-1], node.line)) diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py index e62c05f47..9ca1cb48e 100644 --- a/sphinx/writers/text.py +++ b/sphinx/writers/text.py @@ -719,6 +719,11 @@ class TextTranslator(nodes.NodeVisitor): def depart_container(self, node): pass + def visit_translatable(self, node): + pass + def depart_translatable(self, node): + pass + def visit_problematic(self, node): self.add_text('>>') def depart_problematic(self, node): From a1e9f2d675e145f5038b874ef8dd1246af62a79b Mon Sep 17 00:00:00 2001 From: Nozomu Kaneko Date: Tue, 18 Dec 2012 06:08:25 +0900 Subject: [PATCH 2/7] make docfield translatable --- sphinx/util/docfields.py | 16 +++++++++--- tests/root/i18n/docfields.po | 39 +++++++++++++++++++++++++++++ tests/root/i18n/docfields.txt | 46 +++++++++++++++++++++++++++++++++++ tests/root/i18n/index.txt | 1 + tests/test_intl.py | 37 ++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 tests/root/i18n/docfields.po create mode 100644 tests/root/i18n/docfields.txt diff --git a/sphinx/util/docfields.py b/sphinx/util/docfields.py index 896319f56..793264777 100644 --- a/sphinx/util/docfields.py +++ b/sphinx/util/docfields.py @@ -67,7 +67,9 @@ class Field(object): fieldname += nodes.Text(' ') fieldname += self.make_xref(self.rolename, domain, fieldarg, nodes.Text) - fieldbody = nodes.field_body('', nodes.paragraph('', '', *content)) + par = nodes.paragraph() + par += content + fieldbody = nodes.field_body('', par) return nodes.field('', fieldname, fieldbody) @@ -255,6 +257,11 @@ class DocFieldTransformer(object): [nodes.Text(argtype)] fieldarg = argname + translatable_content = addnodes.translatable(fieldbody.rawsource) + translatable_content.source = fieldbody.parent.source + translatable_content.line = fieldbody.parent.line + translatable_content += content + # grouped entries need to be collected in one entry, while others # get one entry per field if typedesc.is_grouped: @@ -264,10 +271,11 @@ class DocFieldTransformer(object): groupindices[typename] = len(entries) group = [typedesc, []] entries.append(group) - group[1].append(typedesc.make_entry(fieldarg, content)) + entry = typedesc.make_entry(fieldarg, translatable_content) + group[1].append(entry) else: - entries.append([typedesc, - typedesc.make_entry(fieldarg, content)]) + entry = typedesc.make_entry(fieldarg, translatable_content) + entries.append([typedesc, entry]) # step 2: all entries are collected, construct the new field list new_list = nodes.field_list() diff --git a/tests/root/i18n/docfields.po b/tests/root/i18n/docfields.po new file mode 100644 index 000000000..f906ca19e --- /dev/null +++ b/tests/root/i18n/docfields.po @@ -0,0 +1,39 @@ +# 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\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 "i18n with docfields" +msgstr "I18N WITH DOCFIELDS" + +msgid "description of parameter param" +msgstr "DESCRIPTION OF PARAMETER param" + +msgid "description of parameter foo" +msgstr "DESCRIPTION OF PARAMETER foo" + +msgid "description of parameter bar" +msgstr "DESCRIPTION OF PARAMETER bar" + +msgid "if the values are not valid" +msgstr "IF THE VALUES ARE NOT VALID" + +msgid "if the values are out of range" +msgstr "IF THE VALUES ARE OUT OF RANGE" + +msgid "a new :class:`Cls3` instance" +msgstr "A NEW :class:`Cls3` INSTANCE" + diff --git a/tests/root/i18n/docfields.txt b/tests/root/i18n/docfields.txt new file mode 100644 index 000000000..e4dab8e55 --- /dev/null +++ b/tests/root/i18n/docfields.txt @@ -0,0 +1,46 @@ +:tocdepth: 2 + +i18n with docfields +=================== + +.. single TypedField + +.. class:: Cls1 + :noindex: + + :param param: description of parameter param + +.. grouped TypedFields + +.. class:: Cls2 + :noindex: + + :param foo: description of parameter foo + :param bar: description of parameter bar + + +.. single GroupedField + +.. class:: Cls3(values) + :noindex: + + :raises ValueError: if the values are out of range + +.. grouped GroupedFields + +.. class:: Cls4(values) + :noindex: + + :raises TypeError: if the values are not valid + :raises ValueError: if the values are out of range + + +.. single Field + +.. class:: Cls5 + :noindex: + + :returns: a new :class:`Cls3` instance + +.. Field is never grouped + diff --git a/tests/root/i18n/index.txt b/tests/root/i18n/index.txt index ee2d1c5f4..3f98cff6e 100644 --- a/tests/root/i18n/index.txt +++ b/tests/root/i18n/index.txt @@ -6,3 +6,4 @@ external_links refs_inconsistency literalblock + docfields diff --git a/tests/test_intl.py b/tests/test_intl.py index 67507ed11..1cb5333a1 100644 --- a/tests/test_intl.py +++ b/tests/test_intl.py @@ -206,3 +206,40 @@ def test_i18n_literalblock_warning(app): expected_warning_expr = u'.*/i18n/literalblock.txt:\\d+: ' \ u'WARNING: Literal block expected; none found.' assert re.search(expected_warning_expr, warnings) + + +@with_app(buildername='text', cleanenv=True, + confoverrides={'language': 'xx', 'locale_dirs': ['.'], + 'gettext_compact': False}) +def test_i18n_docfields(app): + app.builder.build(['i18n/docfields']) + result = (app.outdir / 'i18n' / 'docfields.txt').text(encoding='utf-8') + expect = (u"\nI18N WITH DOCFIELDS" + u"\n*******************\n" + u"\nclass class Cls1\n" + u"\n Parameters:" + u"\n **param** -- DESCRIPTION OF PARAMETER param\n" + u"\nclass class Cls2\n" + u"\n Parameters:" + u"\n * **foo** -- DESCRIPTION OF PARAMETER foo\n" + u"\n * **bar** -- DESCRIPTION OF PARAMETER bar\n" + u"\nclass class Cls3(values)\n" + u"\n Raises ValueError:" + u"\n IF THE VALUES ARE OUT OF RANGE\n" + u"\nclass class Cls4(values)\n" + u"\n Raises:" + u"\n * **TypeError** -- IF THE VALUES ARE NOT VALID\n" + u"\n * **ValueError** -- IF THE VALUES ARE OUT OF RANGE\n" + u"\nclass class Cls5\n" + u"\n Returns:" + u'\n A NEW "Cls3" INSTANCE\n') + assert result == expect + + +@with_app(buildername='html', cleanenv=True, + confoverrides={'language': 'xx', 'locale_dirs': ['.'], + 'gettext_compact': False}) +def test_i18n_docfields_html(app): + app.builder.build(['i18n/docfields']) + result = (app.outdir / 'i18n' / 'docfields.html').text(encoding='utf-8') + # expect no error by build From 9262d0550d94a3a510fed7835ee35b7e7f6e2019 Mon Sep 17 00:00:00 2001 From: Nozomu Kaneko Date: Mon, 31 Dec 2012 23:31:15 +0900 Subject: [PATCH 3/7] use `paragraph` nodes instead of `translatable` nodes. --- sphinx/environment.py | 19 ++++++++++++++++++- sphinx/util/docfields.py | 7 +++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/sphinx/environment.py b/sphinx/environment.py index 0b9e5bccc..1f578b339 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -284,12 +284,29 @@ class Locale(Transform): node.children = patch.children +class DocFields(Transform): + """ + Remove nested paragraphs generated by docfields. + """ + default_priority = 999 + + def apply(self): + for field_list in self.document.traverse(nodes.field_list): + for field_body in field_list.traverse(nodes.field_body): + for par in field_list.traverse(nodes.paragraph): + if not ('removable' in par and + isinstance(par.parent, nodes.paragraph)): + continue + par.parent.remove(par) + par.parent += par.children + + class SphinxStandaloneReader(standalone.Reader): """ Add our own transforms. """ transforms = [Locale, CitationReferences, DefaultSubstitutions, - MoveModuleTargets, HandleCodeBlocks, SortIds] + MoveModuleTargets, HandleCodeBlocks, SortIds, DocFields] def get_transforms(self): return standalone.Reader.get_transforms(self) + self.transforms diff --git a/sphinx/util/docfields.py b/sphinx/util/docfields.py index 793264777..1de6a6870 100644 --- a/sphinx/util/docfields.py +++ b/sphinx/util/docfields.py @@ -67,9 +67,7 @@ class Field(object): fieldname += nodes.Text(' ') fieldname += self.make_xref(self.rolename, domain, fieldarg, nodes.Text) - par = nodes.paragraph() - par += content - fieldbody = nodes.field_body('', par) + fieldbody = nodes.field_body('', nodes.paragraph('', '', content)) return nodes.field('', fieldname, fieldbody) @@ -257,7 +255,8 @@ class DocFieldTransformer(object): [nodes.Text(argtype)] fieldarg = argname - translatable_content = addnodes.translatable(fieldbody.rawsource) + translatable_content = nodes.paragraph(fieldbody.rawsource, + removable=True) translatable_content.source = fieldbody.parent.source translatable_content.line = fieldbody.parent.line translatable_content += content From b73bec10a2b3c8d1e44cff2345b32c4eb5f44cb0 Mon Sep 17 00:00:00 2001 From: Nozomu Kaneko Date: Mon, 31 Dec 2012 23:45:11 +0900 Subject: [PATCH 4/7] Backed out changeset fabfac71d3e2 --- sphinx/addnodes.py | 3 --- sphinx/writers/html.py | 5 ----- sphinx/writers/latex.py | 5 ----- sphinx/writers/manpage.py | 5 ----- sphinx/writers/texinfo.py | 5 ----- sphinx/writers/text.py | 5 ----- 6 files changed, 28 deletions(-) diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py index 94f670f01..94f1d615f 100644 --- a/sphinx/addnodes.py +++ b/sphinx/addnodes.py @@ -15,9 +15,6 @@ from docutils import nodes class toctree(nodes.General, nodes.Element): """Node for inserting a "TOC tree".""" -class translatable(nodes.General, nodes.TextElement): - """An element which contains translatable nodes.""" - # domain-specific object descriptions (class, function etc.) diff --git a/sphinx/writers/html.py b/sphinx/writers/html.py index 508df3b53..d714ed4f3 100644 --- a/sphinx/writers/html.py +++ b/sphinx/writers/html.py @@ -548,11 +548,6 @@ class HTMLTranslator(BaseTranslator): node['classes'].append('field-odd') self.body.append(self.starttag(node, 'tr', '', CLASS='field')) - def visit_translatable(self, node): - pass - def depart_translatable(self, node): - pass - def unknown_visit(self, node): raise NotImplementedError('Unknown node: ' + node.__class__.__name__) diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index d3a78d678..aadf44011 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -1512,10 +1512,5 @@ class LaTeXTranslator(nodes.NodeVisitor): def depart_system_message(self, node): self.body.append('\n') - def visit_translatable(self, node): - pass - def depart_translatable(self, node): - pass - def unknown_visit(self, node): raise NotImplementedError('Unknown node: ' + node.__class__.__name__) diff --git a/sphinx/writers/manpage.py b/sphinx/writers/manpage.py index 50237ef93..e074691e4 100644 --- a/sphinx/writers/manpage.py +++ b/sphinx/writers/manpage.py @@ -340,10 +340,5 @@ class ManualPageTranslator(BaseTranslator): self.body.append(node.astext()) raise nodes.SkipNode - def visit_translatable(self, node): - pass - def depart_translatable(self, node): - pass - def unknown_visit(self, node): raise NotImplementedError('Unknown node: ' + node.__class__.__name__) diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py index d3435a482..198d1e061 100644 --- a/sphinx/writers/texinfo.py +++ b/sphinx/writers/texinfo.py @@ -1156,11 +1156,6 @@ class TexinfoTranslator(nodes.NodeVisitor): def depart_problematic(self, node): self.body.append('<') - def visit_translatable(self, node): - pass - def depart_translatable(self, node): - pass - def unimplemented_visit(self, node): self.builder.warn("unimplemented node type: %r" % node, (self.curfilestack[-1], node.line)) diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py index 9ca1cb48e..e62c05f47 100644 --- a/sphinx/writers/text.py +++ b/sphinx/writers/text.py @@ -719,11 +719,6 @@ class TextTranslator(nodes.NodeVisitor): def depart_container(self, node): pass - def visit_translatable(self, node): - pass - def depart_translatable(self, node): - pass - def visit_problematic(self, node): self.add_text('>>') def depart_problematic(self, node): From 68dd735b6015173e0fcea6592786375d3bbf31f1 Mon Sep 17 00:00:00 2001 From: Nozomu Kaneko Date: Tue, 1 Jan 2013 03:04:10 +0900 Subject: [PATCH 5/7] skip DocFields transform for gettext in order to extract messages --- sphinx/environment.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sphinx/environment.py b/sphinx/environment.py index 1f578b339..55ae61a72 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -291,6 +291,11 @@ class DocFields(Transform): default_priority = 999 def apply(self): + from sphinx.builders.gettext import MessageCatalogBuilder + env = self.document.settings.env + builder = env.app.builder + if isinstance(builder, MessageCatalogBuilder): + return for field_list in self.document.traverse(nodes.field_list): for field_body in field_list.traverse(nodes.field_body): for par in field_list.traverse(nodes.paragraph): From d2a087e88d838b7c7f62643e06f666961d4b33e5 Mon Sep 17 00:00:00 2001 From: Nozomu Kaneko Date: Sun, 6 Jan 2013 20:33:43 +0900 Subject: [PATCH 6/7] fix tests correspond to 6c70ca26a841 (Divide test_intl target ...) --- tests/test_intl.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/tests/test_intl.py b/tests/test_intl.py index d3c1a4b3f..5e454683d 100644 --- a/tests/test_intl.py +++ b/tests/test_intl.py @@ -288,12 +288,10 @@ def test_i18n_index_entries(app): assert re.search(expr, result, re.M) -@with_app(buildername='text', cleanenv=True, - confoverrides={'language': 'xx', 'locale_dirs': ['.'], - 'gettext_compact': False}) +@with_intl_app(buildername='text', cleanenv=True) def test_i18n_docfields(app): - app.builder.build(['i18n/docfields']) - result = (app.outdir / 'i18n' / 'docfields.txt').text(encoding='utf-8') + app.builder.build(['docfields']) + result = (app.outdir / 'docfields.txt').text(encoding='utf-8') expect = (u"\nI18N WITH DOCFIELDS" u"\n*******************\n" u"\nclass class Cls1\n" @@ -316,10 +314,8 @@ def test_i18n_docfields(app): assert result == expect -@with_app(buildername='html', cleanenv=True, - confoverrides={'language': 'xx', 'locale_dirs': ['.'], - 'gettext_compact': False}) +@with_intl_app(buildername='html', cleanenv=True) def test_i18n_docfields_html(app): - app.builder.build(['i18n/docfields']) - result = (app.outdir / 'i18n' / 'docfields.html').text(encoding='utf-8') + app.builder.build(['docfields']) + result = (app.outdir / 'docfields.html').text(encoding='utf-8') # expect no error by build From 58c0c79c17fd46ba6674c9971896306dc48cbe8d Mon Sep 17 00:00:00 2001 From: Nozomu Kaneko Date: Sun, 13 Jan 2013 02:14:00 +0900 Subject: [PATCH 7/7] use ``nodes.inline`` with a "translatable" attr instead --- sphinx/environment.py | 19 ++++++++----------- sphinx/util/docfields.py | 4 ++-- sphinx/util/nodes.py | 2 +- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/sphinx/environment.py b/sphinx/environment.py index 0745df3bf..1b4e6723d 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -321,9 +321,9 @@ class Locale(Transform): node['entries'] = new_entries -class DocFields(Transform): +class RemoveTranslatableInline(Transform): """ - Remove nested paragraphs generated by docfields. + Remove inline nodes used for translation as placeholders. """ default_priority = 999 @@ -333,14 +333,10 @@ class DocFields(Transform): builder = env.app.builder if isinstance(builder, MessageCatalogBuilder): return - for field_list in self.document.traverse(nodes.field_list): - for field_body in field_list.traverse(nodes.field_body): - for par in field_list.traverse(nodes.paragraph): - if not ('removable' in par and - isinstance(par.parent, nodes.paragraph)): - continue - par.parent.remove(par) - par.parent += par.children + for inline in self.document.traverse(nodes.inline): + if 'translatable' in inline: + inline.parent.remove(inline) + inline.parent += inline.children class SphinxStandaloneReader(standalone.Reader): @@ -348,7 +344,8 @@ class SphinxStandaloneReader(standalone.Reader): Add our own transforms. """ transforms = [Locale, CitationReferences, DefaultSubstitutions, - MoveModuleTargets, HandleCodeBlocks, SortIds, DocFields] + MoveModuleTargets, HandleCodeBlocks, SortIds, + RemoveTranslatableInline] def get_transforms(self): return standalone.Reader.get_transforms(self) + self.transforms diff --git a/sphinx/util/docfields.py b/sphinx/util/docfields.py index e3b12f4ca..b59d3f317 100644 --- a/sphinx/util/docfields.py +++ b/sphinx/util/docfields.py @@ -255,8 +255,8 @@ class DocFieldTransformer(object): [nodes.Text(argtype)] fieldarg = argname - translatable_content = nodes.paragraph(fieldbody.rawsource, - removable=True) + translatable_content = nodes.inline(fieldbody.rawsource, + translatable=True) translatable_content.source = fieldbody.parent.source translatable_content.line = fieldbody.parent.line translatable_content += content diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index 65accbf20..97047784a 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -61,7 +61,7 @@ def extract_messages(doctree): if not node.source: continue # built-in message - if isinstance(node, IGNORED_NODES): + if isinstance(node, IGNORED_NODES) and 'translatable' not in node: continue # orphan # XXX ignore all metadata (== docinfo)