summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sphinx/environment.py54
-rw-r--r--tests/root/contents.txt1
-rw-r--r--tests/root/i18n/external_links.po32
-rw-r--r--tests/root/i18n/external_links.txt13
-rw-r--r--tests/root/i18n/footnote.po33
-rw-r--r--tests/root/i18n/footnote.txt11
-rw-r--r--tests/root/i18n/index.txt7
-rw-r--r--tests/root/i18n/refs_inconsistency.po39
-rw-r--r--tests/root/i18n/refs_inconsistency.txt13
-rw-r--r--tests/test_intl.py162
10 files changed, 339 insertions, 26 deletions
diff --git a/sphinx/environment.py b/sphinx/environment.py
index bfad00f43..1adc68093 100644
--- a/sphinx/environment.py
+++ b/sphinx/environment.py
@@ -194,6 +194,7 @@ class Locale(Transform):
Replace translatable nodes with their translated doctree.
"""
default_priority = 0
+
def apply(self):
env = self.document.settings.env
settings, source = self.document.settings, self.document['source']
@@ -226,11 +227,53 @@ class Locale(Transform):
if not isinstance(patch, nodes.paragraph):
continue # skip for now
- # copy text children
- for i, child in enumerate(patch.children):
- if isinstance(child, nodes.Text):
- child.parent = node
- node.children[i] = child
+ # auto-numbered foot note reference should use original 'ids'.
+ is_autonumber_footnote_ref = lambda node: \
+ isinstance(node, nodes.footnote_reference) \
+ and node.get('auto') == 1
+ old_foot_refs = node.traverse(is_autonumber_footnote_ref)
+ new_foot_refs = patch.traverse(is_autonumber_footnote_ref)
+ if len(old_foot_refs) != len(new_foot_refs):
+ env.warn_node('inconsistent footnote references in '
+ 'translated message', node)
+ for old, new in zip(old_foot_refs, new_foot_refs):
+ new['ids'] = old['ids']
+ self.document.autofootnote_refs.remove(old)
+ self.document.note_autofootnote_ref(new)
+
+ # reference should use original 'refname'.
+ # * reference target ".. _Python: ..." is not translatable.
+ # * section refname is not translatable.
+ # * inline reference "`Python <...>`_" has no 'refname'.
+ is_refnamed_ref = lambda node: \
+ isinstance(node, nodes.reference) \
+ and 'refname' in node
+ old_refs = node.traverse(is_refnamed_ref)
+ new_refs = patch.traverse(is_refnamed_ref)
+ applied_refname_map = {}
+ if len(old_refs) != len(new_refs):
+ env.warn_node('inconsistent references in '
+ 'translated message', node)
+ for new in new_refs:
+ if new['refname'] in applied_refname_map:
+ # 2nd appearance of the reference
+ new['refname'] = applied_refname_map[new['refname']]
+ elif old_refs:
+ # 1st appearance of the reference in old_refs
+ old = old_refs.pop(0)
+ refname = old['refname']
+ new['refname'] = refname
+ applied_refname_map[new['refname']] = refname
+ else:
+ # the reference is not found in old_refs
+ applied_refname_map[new['refname']] = new['refname']
+
+ self.document.note_refname(new)
+
+ # update leaves
+ for child in patch.children:
+ child.parent = node
+ node.children = patch.children
class SphinxStandaloneReader(standalone.Reader):
@@ -1768,4 +1811,3 @@ class BuildEnvironment:
if 'orphan' in self.metadata[docname]:
continue
self.warn(docname, 'document isn\'t included in any toctree')
-
diff --git a/tests/root/contents.txt b/tests/root/contents.txt
index ad246cb77..0a8ca00e2 100644
--- a/tests/root/contents.txt
+++ b/tests/root/contents.txt
@@ -28,6 +28,7 @@ Contents:
extensions
versioning/index
only
+ i18n/index
Python <http://python.org/>
diff --git a/tests/root/i18n/external_links.po b/tests/root/i18n/external_links.po
new file mode 100644
index 000000000..4cd19ddab
--- /dev/null
+++ b/tests/root/i18n/external_links.po
@@ -0,0 +1,32 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2012, foof
+# This file is distributed under the same license as the foo package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: sphinx 1.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-11-22 08:28\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\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 "i18n with external links"
+msgstr "EXTERNAL LINKS"
+
+msgid "External link to Python_."
+msgstr "EXTERNAL LINK TO Python_."
+
+msgid "Internal link to `i18n with external links`_."
+msgstr "`EXTERNAL LINKS`_ IS INTERNAL LINK."
+
+msgid "Inline link by `Sphinx <http://sphinx-doc.org>`_."
+msgstr "INLINE LINK BY `SPHINX <http://sphinx-doc.org>`_."
+
+msgid "Unnamed link__."
+msgstr "UNNAMED LINK__."
diff --git a/tests/root/i18n/external_links.txt b/tests/root/i18n/external_links.txt
new file mode 100644
index 000000000..7ac1db02e
--- /dev/null
+++ b/tests/root/i18n/external_links.txt
@@ -0,0 +1,13 @@
+:tocdepth: 2
+
+i18n with external links
+========================
+.. #1044 external-links-dont-work-in-localized-html
+
+* External link to Python_.
+* Internal link to `i18n with external links`_.
+* Inline link by `Sphinx <http://sphinx-doc.org>`_.
+* Unnamed link__.
+
+.. _Python: http://python.org
+.. __: http://google.com
diff --git a/tests/root/i18n/footnote.po b/tests/root/i18n/footnote.po
new file mode 100644
index 000000000..47f8d3db4
--- /dev/null
+++ b/tests/root/i18n/footnote.po
@@ -0,0 +1,33 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2012, foof
+# This file is distributed under the same license as the foo package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: sphinx 1.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-11-22 08:28\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\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 "i18n with Footnote"
+msgstr "I18N WITH FOOTNOTE"
+
+msgid "[100]_ Contents [#]_ for `i18n with Footnote`_ [ref]_"
+msgstr "`I18N WITH FOOTNOTE`_ INCLUDE THIS CONTENTS [ref]_ [#]_ [100]_"
+
+msgid "This is a auto numbered footnote."
+msgstr "THIS IS A AUTO NUMBERED FOOTNOTE."
+
+msgid "This is a named footnote."
+msgstr "THIS IS A NAMED FOOTNOTE."
+
+msgid "This is a numbered footnote."
+msgstr "THIS IS A NUMBERED FOOTNOTE."
+
diff --git a/tests/root/i18n/footnote.txt b/tests/root/i18n/footnote.txt
new file mode 100644
index 000000000..3ef76bbaa
--- /dev/null
+++ b/tests/root/i18n/footnote.txt
@@ -0,0 +1,11 @@
+:tocdepth: 2
+
+i18n with Footnote
+==================
+.. #955 cant-build-html-with-footnotes-when-using
+
+[100]_ Contents [#]_ for `i18n with Footnote`_ [ref]_
+
+.. [#] This is a auto numbered footnote.
+.. [ref] This is a named footnote.
+.. [100] This is a numbered footnote.
diff --git a/tests/root/i18n/index.txt b/tests/root/i18n/index.txt
new file mode 100644
index 000000000..f35e27663
--- /dev/null
+++ b/tests/root/i18n/index.txt
@@ -0,0 +1,7 @@
+.. toctree::
+ :maxdepth: 2
+ :numbered:
+
+ footnote
+ external_links
+ refs_inconsistency
diff --git a/tests/root/i18n/refs_inconsistency.po b/tests/root/i18n/refs_inconsistency.po
new file mode 100644
index 000000000..9cab687fd
--- /dev/null
+++ b/tests/root/i18n/refs_inconsistency.po
@@ -0,0 +1,39 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2012, foof
+# This file is distributed under the same license as the foo package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: sphinx 1.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-12-05 08:28\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\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 "i18n with refs inconsistency"
+msgstr "I18N WITH REFS INCONSISTENCY"
+
+msgid "[100]_ for [#]_ footnote [ref2]_."
+msgstr "FOR FOOTNOTE [ref2]_."
+
+msgid "for reference_."
+msgstr "reference_ FOR reference_."
+
+msgid "normal text."
+msgstr "ORPHAN REFERENCE: `I18N WITH REFS INCONSISTENCY`_."
+
+msgid "This is a auto numbered footnote."
+msgstr "THIS IS A AUTO NUMBERED FOOTNOTE."
+
+msgid "This is a named footnote."
+msgstr "THIS IS A NAMED FOOTNOTE."
+
+msgid "This is a numbered footnote."
+msgstr "THIS IS A NUMBERED FOOTNOTE."
+
diff --git a/tests/root/i18n/refs_inconsistency.txt b/tests/root/i18n/refs_inconsistency.txt
new file mode 100644
index 000000000..c65c5b458
--- /dev/null
+++ b/tests/root/i18n/refs_inconsistency.txt
@@ -0,0 +1,13 @@
+:tocdepth: 2
+
+i18n with refs inconsistency
+=============================
+
+* [100]_ for [#]_ footnote [ref2]_.
+* for reference_.
+* normal text.
+
+.. [#] This is a auto numbered footnote.
+.. [ref2] This is a named footnote.
+.. [100] This is a numbered footnote.
+.. _reference: http://www.example.com
diff --git a/tests/test_intl.py b/tests/test_intl.py
index d1e28002f..94b21e8d2 100644
--- a/tests/test_intl.py
+++ b/tests/test_intl.py
@@ -11,34 +11,45 @@
"""
from subprocess import Popen, PIPE
+import re
+import os
+from StringIO import StringIO
from util import *
from util import SkipTest
+warnfile = StringIO()
+
+
def setup_module():
(test_root / 'xx' / 'LC_MESSAGES').makedirs()
# Compile all required catalogs into binary format (*.mo).
- for catalog in 'bom', 'subdir':
- try:
- p = Popen(['msgfmt', test_root / '%s.po' % catalog, '-o',
- test_root / 'xx' / 'LC_MESSAGES' / '%s.mo' % catalog],
- stdout=PIPE, stderr=PIPE)
- except OSError:
- # The test will fail the second time it's run if we don't
- # tear down here. Not sure if there's a more idiomatic way
- # of ensuring that teardown gets run in the event of an
- # exception from the setup function.
- teardown_module()
- raise SkipTest # most likely msgfmt was not found
- else:
- stdout, stderr = p.communicate()
- if p.returncode != 0:
- print stdout
- print stderr
- assert False, 'msgfmt exited with return code %s' % p.returncode
- assert (test_root / 'xx' / 'LC_MESSAGES' / ('%s.mo' % catalog)
- ).isfile(), 'msgfmt failed'
+ for dirpath, dirs, files in os.walk(test_root):
+ dirpath = path(dirpath)
+ for f in [f for f in files if f.endswith('.po')]:
+ po = dirpath / f
+ mo = test_root / 'xx' / 'LC_MESSAGES' / (
+ os.path.relpath(po[:-3], test_root) + '.mo')
+ if not mo.parent.exists():
+ mo.parent.makedirs()
+ try:
+ p = Popen(['msgfmt', po, '-o', mo],
+ stdout=PIPE, stderr=PIPE)
+ except OSError:
+ # The test will fail the second time it's run if we don't
+ # tear down here. Not sure if there's a more idiomatic way
+ # of ensuring that teardown gets run in the event of an
+ # exception from the setup function.
+ teardown_module()
+ raise SkipTest # most likely msgfmt was not found
+ else:
+ stdout, stderr = p.communicate()
+ if p.returncode != 0:
+ print stdout
+ print stderr
+ assert False, 'msgfmt exited with return code %s' % p.returncode
+ assert mo.isfile(), 'msgfmt failed'
def teardown_module():
@@ -63,3 +74,114 @@ def test_subdir(app):
app.builder.build(['subdir/includes'])
result = (app.outdir / 'subdir' / 'includes.txt').text(encoding='utf-8')
assert result.startswith(u"\ntranslation\n***********\n\n")
+
+
+@with_app(buildername='html', cleanenv=True,
+ confoverrides={'language': 'xx', 'locale_dirs': ['.'],
+ 'gettext_compact': False})
+def test_i18n_footnote_break_refid(app):
+ """test for #955 cant-build-html-with-footnotes-when-using"""
+ app.builder.build(['i18n/footnote'])
+ result = (app.outdir / 'i18n' / 'footnote.html').text(encoding='utf-8')
+ # expect no error by build
+
+
+@with_app(buildername='text', cleanenv=True,
+ confoverrides={'language': 'xx', 'locale_dirs': ['.'],
+ 'gettext_compact': False})
+def test_i18n_footnote_regression(app):
+ """regression test for fix #955"""
+ app.builder.build(['i18n/footnote'])
+ result = (app.outdir / 'i18n' / 'footnote.txt').text(encoding='utf-8')
+ expect = (u"\nI18N WITH FOOTNOTE"
+ u"\n******************\n" # underline matches new translation
+ u"\nI18N WITH FOOTNOTE INCLUDE THIS CONTENTS [ref] [1] [100]\n"
+ u"\n[1] THIS IS A AUTO NUMBERED FOOTNOTE.\n"
+ u"\n[ref] THIS IS A NAMED FOOTNOTE.\n"
+ u"\n[100] THIS IS A NUMBERED FOOTNOTE.\n")
+ assert result == expect
+
+
+@with_app(buildername='text', warning=warnfile, cleanenv=True,
+ confoverrides={'language': 'xx', 'locale_dirs': ['.'],
+ 'gettext_compact': False})
+def test_i18n_warn_for_number_of_references_inconsistency(app):
+ app.builddir.rmtree(True)
+ app.builder.build(['i18n/refs_inconsistency'])
+ result = (app.outdir / 'i18n' / 'refs_inconsistency.txt').text(encoding='utf-8')
+ expect = (u"\nI18N WITH REFS INCONSISTENCY"
+ u"\n****************************\n"
+ u"\n* FOR FOOTNOTE [ref2].\n"
+ u"\n* reference FOR reference.\n"
+ u"\n* ORPHAN REFERENCE: I18N WITH REFS INCONSISTENCY.\n"
+ u"\n[1] THIS IS A AUTO NUMBERED FOOTNOTE.\n"
+ u"\n[ref2] THIS IS A NAMED FOOTNOTE.\n"
+ u"\n[100] THIS IS A NUMBERED FOOTNOTE.\n")
+ assert result == expect
+
+ warnings = warnfile.getvalue().replace(os.sep, '/')
+ warning_fmt = u'.*/i18n/refs_inconsistency.txt:\\d+: ' \
+ u'WARNING: inconsistent %s in translated message\n'
+ expected_warning_expr = (
+ warning_fmt % 'footnote references' +
+ warning_fmt % 'references' +
+ warning_fmt % 'references')
+ assert re.search(expected_warning_expr, warnings)
+
+
+@with_app(buildername='html', cleanenv=True,
+ confoverrides={'language': 'xx', 'locale_dirs': ['.'],
+ 'gettext_compact': False})
+def test_i18n_link_to_undefined_reference(app):
+ app.builder.build(['i18n/refs_inconsistency'])
+ result = (app.outdir / 'i18n' / 'refs_inconsistency.html').text(encoding='utf-8')
+
+ expected_expr = """<a class="reference external" href="http://www.example.com">reference</a>"""
+ assert len(re.findall(expected_expr, result)) == 2
+
+ expected_expr = """<a class="reference internal" href="#reference">reference</a>"""
+ assert len(re.findall(expected_expr, result)) == 0
+
+ expected_expr = """<a class="reference internal" href="#i18n-with-refs-inconsistency">I18N WITH REFS INCONSISTENCY</a>"""
+ assert len(re.findall(expected_expr, result)) == 1
+
+
+@with_app(buildername='html', cleanenv=True,
+ confoverrides={'language': 'xx', 'locale_dirs': ['.'],
+ 'gettext_compact': False})
+def test_i18n_keep_external_links(app):
+ """regression test for #1044"""
+ app.builder.build(['i18n/external_links'])
+ result = (app.outdir / 'i18n' / 'external_links.html').text(encoding='utf-8')
+
+ # external link check
+ expect_line = u"""<li>EXTERNAL LINK TO <a class="reference external" href="http://python.org">Python</a>.</li>"""
+ matched = re.search('^<li>EXTERNAL LINK TO .*$', result, re.M)
+ matched_line = ''
+ if matched:
+ matched_line = matched.group()
+ assert expect_line == matched_line
+
+ # internal link check
+ expect_line = u"""<li><a class="reference internal" href="#i18n-with-external-links">EXTERNAL LINKS</a> IS INTERNAL LINK.</li>"""
+ matched = re.search('^<li><a .* IS INTERNAL LINK.</li>$', result, re.M)
+ matched_line = ''
+ if matched:
+ matched_line = matched.group()
+ assert expect_line == matched_line
+
+ # inline link check
+ expect_line = u"""<li>INLINE LINK BY <a class="reference external" href="http://sphinx-doc.org">SPHINX</a>.</li>"""
+ matched = re.search('^<li>INLINE LINK BY .*$', result, re.M)
+ matched_line = ''
+ if matched:
+ matched_line = matched.group()
+ assert expect_line == matched_line
+
+ # unnamed link check
+ expect_line = u"""<li>UNNAMED <a class="reference external" href="http://google.com">LINK</a>.</li>"""
+ matched = re.search('^<li>UNNAMED .*$', result, re.M)
+ matched_line = ''
+ if matched:
+ matched_line = matched.group()
+ assert expect_line == matched_line