diff --git a/odoo/addons/base/tests/test_translate.py b/odoo/addons/base/tests/test_translate.py index 1cd833c9df6..de607798a82 100644 --- a/odoo/addons/base/tests/test_translate.py +++ b/odoo/addons/base/tests/test_translate.py @@ -205,10 +205,16 @@ class TranslationToolsTestCase(unittest.TestCase): self.assertEquals(result, expect) def test_translate_html(self): + """ Test html_translate(). """ + source = """
A""" + result = html_translate(lambda term: term, source) + self.assertEquals(result, source) + + def test_translate_html_i(self): """ Test xml_translate() and html_translate() with elements. """ - source = """""" + source = """B
C
A B
""" result = xml_translate(lambda term: term, source) - self.assertEquals(result, """""") + self.assertEquals(result, """A B
""") result = html_translate(lambda term: term, source) self.assertEquals(result, source) diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py index c947ae5e75d..fbef3443929 100644 --- a/odoo/tools/translate.py +++ b/odoo/tools/translate.py @@ -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*]*>(.*)[^<]*>", 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 = "