[FIX] translate: avoid body/div wrappers when translating text inside HTML (#17160)
This commit is contained in:
@@ -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
@@ -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")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user