Fix: Multiple references in the same line return the same link with i18n. Closes #1193

This commit is contained in:
Takayuki Shimizukawa
2013-06-17 00:12:02 +09:00
parent bd4da7df55
commit afc6b6d1f8
5 changed files with 140 additions and 85 deletions

View File

@@ -34,6 +34,8 @@ Bugs fixed
when using multibyte characters to "Project name" on quickstart.
* #1090: Fix multiple cross references (term, ref, doc) in the same line
return the same link with i18n.
* #1193: Fix multiple link references in the same line return the same
link with i18n.
Release 1.2 (beta1 released Mar 31, 2013)

View File

@@ -199,6 +199,24 @@ class Locale(Transform):
if not isinstance(patch, nodes.paragraph):
continue # skip for now
# update title(section) target name-id mapping
if isinstance(node, nodes.title):
section_node = node.parent
new_name = nodes.fully_normalize_name(patch.astext())
old_name = nodes.fully_normalize_name(node.astext())
if old_name != new_name:
# if name would be changed, replace node names and
# document nameids mapping with new name.
names = section_node.setdefault('names', [])
names.append(new_name)
if old_name in names:
names.remove(old_name)
id = self.document.nameids.pop(old_name)
self.document.set_name_id_map(
section_node, id, section_node, explicit=None)
# auto-numbered foot note reference should use original 'ids'.
def is_autonumber_footnote_ref(node):
return isinstance(node, nodes.footnote_reference) and \
@@ -224,24 +242,10 @@ class Locale(Transform):
'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)
# refnamed footnote and citation should use original 'ids'.

View File

@@ -30,3 +30,18 @@ msgstr "INLINE LINK BY `SPHINX <http://sphinx-doc.org>`_."
msgid "Unnamed link__."
msgstr "UNNAMED LINK__."
msgid "link target swapped translation"
msgstr "LINK TARGET SWAPPED TRANSLATION"
msgid "link to external1_ and external2_."
msgstr "LINK TO external2_ AND external1_."
msgid "link to `Sphinx <http://sphinx-doc.org>`_ and `Python <http://python.org>`_."
msgstr "LINK TO `THE PYTHON <http://python.org>`_ AND `THE SPHINX <http://sphinx-doc.org>`_."
msgid "Multiple references in the same line"
msgstr "MULTIPLE REFERENCES IN THE SAME LINE"
msgid "Link to `Sphinx <http://sphinx-doc.org>`_, `Python <http://python.org>`_, Python_, Unnamed__ and `i18n with external links`_."
msgstr "LINK TO `EXTERNAL LINKS`_, Python_, `THE SPHINX <http://sphinx-doc.org>`_, UNNAMED__ AND `THE PYTHON <http://python.org>`_."

View File

@@ -4,10 +4,32 @@ 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__.
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
link target swapped translation
================================
link to external1_ and external2_.
link to `Sphinx <http://sphinx-doc.org>`_ and `Python <http://python.org>`_.
.. _external1: http://example.com/external1
.. _external2: http://example.com/external2
Multiple references in the same line
=====================================
Link to `Sphinx <http://sphinx-doc.org>`_, `Python <http://python.org>`_, Python_, Unnamed__ and `i18n with external links`_.
.. __: http://google.com

View File

@@ -72,6 +72,34 @@ def teardown_module():
(root / 'xx').rmtree(True)
def elem_gettexts(elem):
def itertext(self):
# this function copied from Python-2.7 'ElementTree.itertext'.
# for compatibility to Python-2.5, 2.6, 3.1
tag = self.tag
if not isinstance(tag, basestring) and tag is not None:
return
if self.text:
yield self.text
for e in self:
for s in itertext(e):
yield s
if e.tail:
yield e.tail
return filter(None, [s.strip() for s in itertext(elem)])
def elem_getref(elem):
return elem.attrib.get('refid') or elem.attrib.get('refuri')
def assert_elem_text_refs(elem, text, refs):
_text = elem_gettexts(elem)
assert _text == text
_refs = map(elem_getref, elem.findall('reference'))
assert _refs == refs
@with_intl_app(buildername='text')
def test_simple(app):
app.builder.build(['bom'])
@@ -195,48 +223,58 @@ def test_i18n_link_to_undefined_reference(app):
assert len(re.findall(expected_expr, result)) == 1
@with_intl_app(buildername='html', cleanenv=True)
@with_intl_app(buildername='xml', cleanenv=True)
def test_i18n_keep_external_links(app):
"""regression test for #1044"""
# regression test for #1044
app.builder.build(['external_links'])
result = (app.outdir / 'external_links.html').text(encoding='utf-8')
et = ElementTree.parse(app.outdir / 'external_links.xml')
secs = et.findall('section')
para0 = secs[0].findall('paragraph')
# external link check
expect_line = (u'<li>EXTERNAL LINK TO <a class="reference external" '
u'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
assert_elem_text_refs(
para0[0],
['EXTERNAL LINK TO', 'Python', '.'],
['http://python.org'])
# internal link check
expect_line = (u'<li><a class="reference internal" '
u'href="#i18n-with-external-links">EXTERNAL '
u'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
assert_elem_text_refs(
para0[1],
['EXTERNAL LINKS', 'IS INTERNAL LINK.'],
['i18n-with-external-links'])
# inline link check
expect_line = (u'<li>INLINE LINK BY <a class="reference external" '
u'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
assert_elem_text_refs(
para0[2],
['INLINE LINK BY', 'SPHINX', '.'],
['http://sphinx-doc.org'])
# unnamed link check
expect_line = (u'<li>UNNAMED <a class="reference external" '
u'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
assert_elem_text_refs(
para0[3],
['UNNAMED', 'LINK', '.'],
['http://google.com'])
# link target swapped translation
para1 = secs[1].findall('paragraph')
assert_elem_text_refs(
para1[0],
['LINK TO', 'external2', 'AND', 'external1', '.'],
['http://example.com/external2', 'http://example.com/external1'])
assert_elem_text_refs(
para1[1],
['LINK TO', 'THE PYTHON', 'AND', 'THE SPHINX', '.'],
['http://python.org', 'http://sphinx-doc.org'])
# multiple references in the same line
para2 = secs[2].findall('paragraph')
assert_elem_text_refs(
para2[0],
['LINK TO', 'EXTERNAL LINKS', ',', 'Python', ',', 'THE SPHINX',
',', 'UNNAMED', 'AND', 'THE PYTHON', '.'],
['i18n-with-external-links', 'http://python.org',
'http://sphinx-doc.org', 'http://google.com',
'http://python.org'])
@with_intl_app(buildername='text', warning=warnfile, cleanenv=True)
@@ -295,40 +333,14 @@ def test_i18n_glossary_terms(app):
@with_intl_app(buildername='xml', warning=warnfile)
def test_i18n_role_xref(app):
# regression test for #1090
def gettexts(elem):
def itertext(self):
# this function copied from Python-2.7 'ElementTree.itertext'.
# for compatibility to Python-2.5, 2.6, 3.1
tag = self.tag
if not isinstance(tag, basestring) and tag is not None:
return
if self.text:
yield self.text
for e in self:
for s in itertext(e):
yield s
if e.tail:
yield e.tail
return filter(None, [s.strip() for s in itertext(elem)])
def getref(elem):
return elem.attrib.get('refid') or elem.attrib.get('refuri')
def assert_text_refs(elem, text, refs):
_text = gettexts(elem)
assert _text == text
_refs = map(getref, elem.findall('reference'))
assert _refs == refs
# regression test for #1090, #1193
app.builddir.rmtree(True) #for warnings acceleration
app.builder.build(['role_xref'])
et = ElementTree.parse(app.outdir / 'role_xref.xml')
sec1, sec2 = et.findall('section')
para1, = sec1.findall('paragraph')
assert_text_refs(
assert_elem_text_refs(
para1,
['LINK TO', "I18N ROCK'N ROLE XREF", ',', 'CONTENTS', ',',
'SOME NEW TERM', '.'],
@@ -337,32 +349,32 @@ def test_i18n_role_xref(app):
'glossary_terms#term-some-new-term'])
para2 = sec2.findall('paragraph')
assert_text_refs(
assert_elem_text_refs(
para2[0],
['LINK TO', 'SOME OTHER NEW TERM', 'AND', 'SOME NEW TERM', '.'],
['glossary_terms#term-some-other-new-term',
'glossary_terms#term-some-new-term'])
assert_text_refs(
assert_elem_text_refs(
para2[1],
['LINK TO', 'SAME TYPE LINKS', 'AND', "I18N ROCK'N ROLE XREF", '.'],
['same-type-links', 'i18n-role-xref'])
assert_text_refs(
assert_elem_text_refs(
para2[2],
['LINK TO', 'I18N WITH GLOSSARY TERMS', 'AND', 'CONTENTS', '.'],
['glossary_terms', 'contents'])
assert_text_refs(
assert_elem_text_refs(
para2[3],
['LINK TO', '--module', 'AND', '-m', '.'],
['cmdoption--module', 'cmdoption-m'])
assert_text_refs(
assert_elem_text_refs(
para2[4],
['LINK TO', 'env2', 'AND', 'env1', '.'],
['envvar-env2', 'envvar-env1'])
assert_text_refs(
assert_elem_text_refs(
para2[5],
['LINK TO', 'token2', 'AND', 'token1', '.'],
[]) #TODO: how do I link token role to productionlist?
assert_text_refs(
assert_elem_text_refs(
para2[6],
['LINK TO', 'same-type-links', 'AND', "i18n-role-xref", '.'],
['same-type-links', 'i18n-role-xref'])