From 98602e93cc3f8586db5d22932aecdd5192584a55 Mon Sep 17 00:00:00 2001 From: xmo-odoo Date: Wed, 28 Jun 2017 15:35:53 +0200 Subject: [PATCH] [FIX] PO translations export Within the scope of P3 compatibility, a StringIO was replaced by BytesIO in b4429c2a91889bf60b56e0c7909c844d357363f7, however I missed that BytesIO is much stricter about types than StringIO *even in Python 2*. Some bits of text would sneak up in PoFile.write or PoFile.write_info and blow up the export. The BytesIO makes sense for output (tgz is definitely not a text format and base64 is a bytes -> bytes encoding), but ensuring the entirety of PoFile worked on bytes seemed distasteful (and lots of changes), it seemed more sensible at all levels that PoFile would make sure to properly work on *text*, and to add a text adapter wrapper around the BytesIO buffer. io.TextIOWrapper was almost perfect (it closes the underlying stream on close but that can be worked around) *however* it turns out TextIOWrapper does not work on Python 2's `file` objects... so use the older codecs.StreamReaderWriter which should be able to do the job properly. fixes #17834 --- odoo/tools/translate.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py index 9cede781be0..4de28f70530 100644 --- a/odoo/tools/translate.py +++ b/odoo/tools/translate.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. - import codecs import csv import fnmatch import inspect +import io import locale import logging import os @@ -474,7 +474,13 @@ def unquote(str): # class to handle po files class PoFile(object): def __init__(self, buffer): - self.buffer = buffer + # TextIOWrapper closes its underlying buffer on close *and* can't + # handle actual file objects (on python 2) + self.buffer = codecs.StreamReaderWriter( + stream=buffer, + Reader=codecs.getreader('utf-8'), + Writer=codecs.getwriter('utf-8') + ) def __iter__(self): self.buffer.seek(0) @@ -489,7 +495,7 @@ class PoFile(object): lines = self.buffer.readlines() # remove the BOM (Byte Order Mark): if len(lines): - lines[0] = unicode(lines[0], 'utf8').lstrip(unicode( codecs.BOM_UTF8, "utf8")) + lines[0] = lines[0].lstrip(u"\ufeff") lines.append('') # ensure that the file ends with at least an empty line return lines @@ -594,7 +600,7 @@ class PoFile(object): def write_infos(self, modules): import odoo.release as release - self.buffer.write("# Translation of %(project)s.\n" \ + self.buffer.write(u"# Translation of %(project)s.\n" \ "# This file contains the translation of the following modules:\n" \ "%(modules)s" \ "#\n" \ @@ -622,30 +628,30 @@ class PoFile(object): def write(self, modules, tnrs, source, trad, comments=None): plurial = len(modules) > 1 and 's' or '' - self.buffer.write("#. module%s: %s\n" % (plurial, ', '.join(modules))) + self.buffer.write(u"#. module%s: %s\n" % (plurial, ', '.join(modules))) if comments: - self.buffer.write(''.join(('#. %s\n' % c for c in comments))) + self.buffer.write(u''.join(('#. %s\n' % c for c in comments))) code = False for typy, name, res_id in tnrs: - self.buffer.write("#: %s:%s:%s\n" % (typy, name, res_id)) + self.buffer.write(u"#: %s:%s:%s\n" % (typy, name, res_id)) if typy == 'code': code = True if code: # only strings in python code are python formated - self.buffer.write("#, python-format\n") + self.buffer.write(u"#, python-format\n") if not isinstance(trad, unicode): trad = unicode(trad, 'utf8') if not isinstance(source, unicode): source = unicode(source, 'utf8') - msg = "msgid %s\n" \ - "msgstr %s\n\n" \ + msg = u"msgid %s\n" \ + u"msgstr %s\n\n" \ % (quote(source), quote(trad)) - self.buffer.write(msg.encode('utf8')) + self.buffer.write(msg) # Methods to export the translation file