mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Fix: Multiple references in the same line return the same link with i18n. Closes #1193
This commit is contained in:
2
CHANGES
2
CHANGES
@@ -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)
|
||||
|
||||
@@ -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'.
|
||||
|
||||
@@ -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>`_."
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'])
|
||||
|
||||
Reference in New Issue
Block a user