From eab46cdca678a87b64e24ee3765eee9274b0df7b Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Wed, 27 Mar 2019 15:29:02 +0000 Subject: [PATCH] [IMP] tools: remove custom pofile writer And create a generic class to handles it, using polib for po files --- odoo/tools/translate.py | 222 +++++++++++++++++++++------------------- 1 file changed, 115 insertions(+), 107 deletions(-) diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py index a8e13162cb3..555b14fea9a 100644 --- a/odoo/tools/translate.py +++ b/odoo/tools/translate.py @@ -607,137 +607,145 @@ class PoFileReader: _logger.error("malformed po file: unknown occurrence: %s", occurrence) +def TranslationFileWriter(target, fileformat='po', lang=None, modules=None): + """ Iterate over translation file to return Odoo translation entries """ + if fileformat == 'csv': + return CSVFileWriter(target) -# class to handle po files -class PoFile(object): + if fileformat == 'po': + return PoFileWriter(target, modules=modules, lang=lang) - def __init__(self, buffer): - # TextIOWrapper closes its underlying buffer on close *and* can't - self.buffer = codecs.StreamReaderWriter( - stream=buffer, - Reader=codecs.getreader('utf-8'), - Writer=codecs.getwriter('utf-8') - ) + if fileformat == 'tgz': + return TarFileWriter(target, lang=lang) - def write_infos(self, modules): + raise Exception(_('Unrecognized extension: must be one of ' + '.csv, .po, or .tgz (received .%s).') % fileformat) + + +class CSVFileWriter: + def __init__(self, target): + self.writer = pycompat.csv_writer(target, dialect='UNIX') + # write header first + self.writer.writerow(("module","type","name","res_id","src","value","comments")) + + + def write_rows(self, rows): + for module, type, name, res_id, src, trad, comments in rows: + comments = '\n'.join(comments) + self.writer.writerow((module, type, name, res_id, src, trad, comments)) + + +class PoFileWriter: + """ Iterate over po file to return Odoo translation entries """ + def __init__(self, target, modules, lang): import odoo.release as release - self.buffer.write(u"# Translation of %(project)s.\n" \ - "# This file contains the translation of the following modules:\n" \ - "%(modules)s" \ - "#\n" \ - "msgid \"\"\n" \ - "msgstr \"\"\n" \ - '''"Project-Id-Version: %(project)s %(version)s\\n"\n''' \ - '''"Report-Msgid-Bugs-To: \\n"\n''' \ - '''"POT-Creation-Date: %(now)s\\n"\n''' \ - '''"PO-Revision-Date: %(now)s\\n"\n''' \ - '''"Last-Translator: <>\\n"\n''' \ - '''"Language-Team: \\n"\n''' \ - '''"MIME-Version: 1.0\\n"\n''' \ - '''"Content-Type: text/plain; charset=UTF-8\\n"\n''' \ - '''"Content-Transfer-Encoding: \\n"\n''' \ - '''"Plural-Forms: \\n"\n''' \ - "\n" - % { 'project': release.description, - 'version': release.version, - 'modules': ''.join("#\t* %s\n" % m for m in modules), - 'now': datetime.utcnow().strftime('%Y-%m-%d %H:%M')+"+0000", - } - ) + self.buffer = target + self.lang = lang + self.po = polib.POFile() - def write(self, modules, tnrs, source, trad, comments=None): + self.po.header = "Translation of %s.\n" \ + "This file contains the translation of the following modules:\n" \ + "%s" % (release.description, ''.join("\t* %s\n" % m for m in modules)) + now = datetime.utcnow().strftime('%Y-%m-%d %H:%M+0000') + self.po.metadata = { + 'Project-Id-Version': "%s %s" % (release.description, release.version), + 'Report-Msgid-Bugs-To': '', + 'POT-Creation-Date': now, + 'PO-Revision-Date': now, + 'Last-Translator': '', + 'Language-Team': '', + 'MIME-Version': '1.0', + 'Content-Type': 'text/plain; charset=UTF-8', + 'Content-Transfer-Encoding': '', + 'Plural-Forms': '', + } - plurial = len(modules) > 1 and 's' or '' - self.buffer.write(u"#. module%s: %s\n" % (plurial, ', '.join(modules))) + def write_rows(self, rows): + # we now group the translations by source. That means one translation per source. + grouped_rows = {} + for module, type, name, res_id, src, trad, comments in rows: + row = grouped_rows.setdefault(src, {}) + row.setdefault('modules', set()).add(module) + if not row.get('translation') and trad != src: + row['translation'] = trad + row.setdefault('tnrs', []).append((type, name, res_id)) + row.setdefault('comments', set()).update(comments) + for src, row in sorted(grouped_rows.items()): + if not self.lang: + # translation template, so no translation value + row['translation'] = '' + elif not row.get('translation'): + row['translation'] = '' + self.add_entry(row['modules'], row['tnrs'], src, row['translation'], row['comments']) + + # buffer expects bytes + self.buffer.write(str(self.po).encode()) + + def add_entry(self, modules, tnrs, source, trad, comments=None): + entry = polib.POEntry( + msgid=source, + msgstr=trad, + ) + plural = len(modules) > 1 and 's' or '' + entry.comment = "module%s: %s" % (plural, ', '.join(modules)) if comments: - self.buffer.write(u''.join(('#. %s\n' % c for c in comments))) + entry.comment += "\n" + "\n".join(comments) code = False for typy, name, res_id in tnrs: - self.buffer.write(u"#: %s:%s:%s\n" % (typy, name, res_id)) if typy == 'code': code = True - + if isinstance(res_id, int): + # second term of occurrence must be a digit + entry.occurrences.append((u"%s:%s" % (typy, name), res_id)) + else: + entry.occurrences.append((u"%s:%s:%s" % (typy, name, res_id), '')) if code: - # only strings in python code are python formatted - self.buffer.write(u"#, python-format\n") + entry.flags.append("python-format") + self.po.append(entry) - msg = ( - u"msgid %s\n" - u"msgstr %s\n\n" - ) % ( - quote(str(source)), - quote(str(trad)) - ) - self.buffer.write(msg) +class TarFileWriter: + + def __init__(self, target, lang): + self.tar = tarfile.open(fileobj=target, mode='w|gz') + self.lang = lang + + def write_rows(self, rows): + rows_by_module = defaultdict(list) + for row in rows: + module = row[0] + rows_by_module[module].append(row) + + for mod, modrows in rows_by_module.items(): + with io.BytesIO() as buf: + po = PoFileWriter(buf, modules=[mod], lang=self.lang) + po.write_rows(modrows) + buf.seek(0) + + info = tarfile.TarInfo( + join(mod, 'i18n', '{basename}.{ext}'.format( + basename=self.lang or mod, + ext='po' if self.lang else 'pot', + ))) + # addfile will read bytes from the buffer so + # size *must* be set first + info.size = len(buf.getvalue()) + + self.tar.addfile(info, fileobj=buf) + + self.tar.close() # Methods to export the translation file def trans_export(lang, modules, buffer, format, cr): - def _process(format, modules, rows, buffer, lang): - if format == 'csv': - writer = pycompat.csv_writer(buffer, dialect='UNIX') - # write header first - writer.writerow(("module","type","name","res_id","src","value","comments")) - for module, type, name, res_id, src, trad, comments in rows: - comments = '\n'.join(comments) - writer.writerow((module, type, name, res_id, src, trad, comments)) - - elif format == 'po': - writer = PoFile(buffer) - writer.write_infos(modules) - - # we now group the translations by source. That means one translation per source. - grouped_rows = {} - for module, type, name, res_id, src, trad, comments in rows: - row = grouped_rows.setdefault(src, {}) - row.setdefault('modules', set()).add(module) - if not row.get('translation') and trad != src: - row['translation'] = trad - row.setdefault('tnrs', []).append((type, name, res_id)) - row.setdefault('comments', set()).update(comments) - - for src, row in sorted(grouped_rows.items()): - if not lang: - # translation template, so no translation value - row['translation'] = '' - elif not row.get('translation'): - row['translation'] = '' - writer.write(row['modules'], row['tnrs'], src, row['translation'], row['comments']) - - elif format == 'tgz': - rows_by_module = defaultdict(list) - for row in rows: - module = row[0] - rows_by_module[module].append(row) - - with tarfile.open(fileobj=buffer, mode='w|gz') as tar: - for mod, modrows in rows_by_module.items(): - with io.BytesIO() as buf: - _process('po', [mod], modrows, buf, lang) - buf.seek(0) - - info = tarfile.TarInfo( - join(mod, 'i18n', '{basename}.{ext}'.format( - basename=lang or mod, - ext='po' if lang else 'pot', - ))) - # addfile will read bytes from the buffer so - # size *must* be set first - info.size = len(buf.getvalue()) - - tar.addfile(info, fileobj=buf) - else: - raise Exception(_('Unrecognized extension: must be one of ' - '.csv, .po, or .tgz (received .%s).') % format) - translations = trans_generate(lang, modules, cr) modules = set(t[0] for t in translations) - _process(format, modules, translations, buffer, lang) + writer = TranslationFileWriter(buffer, fileformat=format, lang=lang, modules=modules) + writer.write_rows(translations) del translations