[FIX] translate: avoid body/div wrappers when translating text inside HTML (#17160)

This commit is contained in:
Raphael Collet
2017-05-23 16:45:19 +02:00
committed by GitHub
parent 8bb9869812
commit f69c659b1a
2 changed files with 42 additions and 21 deletions
+8 -2
View File
@@ -205,10 +205,16 @@ class TranslationToolsTestCase(unittest.TestCase):
self.assertEquals(result, expect)
def test_translate_html(self):
""" Test html_translate(). """
source = """<blockquote>A <h2>B</h2> C</blockquote>"""
result = html_translate(lambda term: term, source)
self.assertEquals(result, source)
def test_translate_html_i(self):
""" Test xml_translate() and html_translate() with <i> elements. """
source = """<i class="fa-check"></i>"""
source = """<p>A <i class="fa-check"></i> B</p>"""
result = xml_translate(lambda term: term, source)
self.assertEquals(result, """<i class="fa-check"/>""")
self.assertEquals(result, """<p>A <i class="fa-check"/> B</p>""")
result = html_translate(lambda term: term, source)
self.assertEquals(result, source)
+34 -19
View File
@@ -17,7 +17,7 @@ from datetime import datetime
from os.path import join
from babel.messages import extract
from lxml import etree
from lxml import etree, html
import odoo
from odoo.tools import config
@@ -153,8 +153,13 @@ avoid_pattern = re.compile(r"\s*<!DOCTYPE", re.IGNORECASE | re.MULTILINE | re.UN
node_pattern = re.compile(r"<[^>]*>(.*)</[^<]*>", re.DOTALL | re.MULTILINE | re.UNICODE)
def translate_xml_node(node, callback, method, parser=None):
""" Return the translation of the given XML/HTML node. """
def translate_xml_node(node, callback, parse, serialize):
""" Return the translation of the given XML/HTML node.
:param callback: callback(text) returns translated text or None
:param parse: parse(text) returns a node (text is unicode)
:param serialize: serialize(node) returns unicode text
"""
def nonspace(text):
return bool(text) and not text.isspace()
@@ -182,7 +187,7 @@ def translate_xml_node(node, callback, method, parser=None):
def translate_content(node):
""" Return ``node`` with its content translated inline. """
# serialize the node that contains the stuff to translate
text = etree.tostring(node, method=method, encoding='utf8').decode('utf8')
text = serialize(node)
# retrieve the node's content and translate it
match = node_pattern.match(text)
trans = translate_text(match.group(1))
@@ -190,7 +195,7 @@ def translate_xml_node(node, callback, method, parser=None):
# replace the content, and convert it back to an XML node
text = text[:match.start(1)] + trans + text[match.end(1):]
try:
node = etree.fromstring(encode(text), parser=parser)
node = parse(text)
except etree.ParseError:
# fallback: escape the translation as text
node = etree.Element(node.tag, node.attrib, node.nsmap)
@@ -275,6 +280,21 @@ def translate_xml_node(node, callback, method, parser=None):
return node
def parse_xml(text):
return etree.fromstring(encode(text))
def serialize_xml(node):
return etree.tostring(node, method='xml', encoding='utf8').decode('utf8')
_HTML_PARSER = etree.HTMLParser(encoding='utf8')
def parse_html(text):
return html.fragment_fromstring(encode(text), parser=_HTML_PARSER)
def serialize_html(node):
return etree.tostring(node, method='html', encoding='utf8').decode('utf8')
def xml_translate(callback, value):
""" Translate an XML value (string), using `callback` for translating text
appearing in `value`.
@@ -283,17 +303,15 @@ def xml_translate(callback, value):
return value
try:
root = etree.fromstring(encode(value))
result = translate_xml_node(root, callback, 'xml')
return etree.tostring(result, method='xml', encoding='utf8').decode('utf8')
root = parse_xml(value)
result = translate_xml_node(root, callback, parse_xml, serialize_xml)
return serialize_xml(result)
except etree.ParseError:
# fallback for translated terms: use an HTML parser and wrap the term
wrapped = "<div>%s</div>" % encode(value)
root = etree.fromstring(wrapped, etree.HTMLParser(encoding='utf-8'))
# root is html > body > div; translate the div only
result = translate_xml_node(root[0][0], callback, 'xml')
root = parse_html("<div>%s</div>" % value)
result = translate_xml_node(root, callback, parse_xml, serialize_xml)
# remove tags <div> and </div> from result
return etree.tostring(result, method='xml', encoding='utf8').decode('utf8')[5:-6]
return serialize_xml(result)[5:-6]
def html_translate(callback, value):
""" Translate an HTML value (string), using `callback` for translating text
@@ -303,14 +321,11 @@ def html_translate(callback, value):
return value
try:
parser = etree.HTMLParser(encoding='utf-8')
# value may be some HTML fragment, wrap it into a div
wrapped = "<div>%s</div>" % encode(value)
root = etree.fromstring(wrapped, parser)
# root is html > body > div; translate the div only
result = translate_xml_node(root[0][0], callback, 'html', parser)
root = parse_html("<div>%s</div>" % value)
result = translate_xml_node(root, callback, parse_html, serialize_html)
# remove tags <div> and </div> from result
value = etree.tostring(result, method='html', encoding='utf8').decode('utf8')[5:-6]
value = serialize_html(result)[5:-6]
except ValueError:
_logger.exception("Cannot translate malformed HTML, using source value instead")