From f4319580e576e033b95db1627dd11f126c187323 Mon Sep 17 00:00:00 2001 From: Martin Trigaux <mat@odoo.com> Date: Mon, 18 Feb 2019 15:24:00 +0000 Subject: [PATCH] [ADD] tools: lazy translation method Introduces the method _lt The translation method is now evaluated lazily. It allows to declare global variables with translatable content e.g. this code will now work: LABEL = _lt("User") def _compute_label(self): context = {'lang': self.partner_id.lang} self.user_label = LABEL --- odoo/__init__.py | 2 +- .../tests/test_term_count.py | 30 +++++++++++- odoo/tools/translate.py | 49 ++++++++++++++++++- 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/odoo/__init__.py b/odoo/__init__.py index 6bfe00b84996..bf5ec3e94b4e 100644 --- a/odoo/__init__.py +++ b/odoo/__init__.py @@ -100,7 +100,7 @@ from . import tools from . import models from . import fields from . import api -from odoo.tools.translate import _ +from odoo.tools.translate import _, _lt #---------------------------------------------------------- # Other imports, which may require stuff from above diff --git a/odoo/addons/test_translation_import/tests/test_term_count.py b/odoo/addons/test_translation_import/tests/test_term_count.py index 90bef9417ae1..01ce7ad4d349 100644 --- a/odoo/addons/test_translation_import/tests/test_term_count.py +++ b/odoo/addons/test_translation_import/tests/test_term_count.py @@ -7,9 +7,11 @@ import io import odoo from odoo.tests import common, tagged from odoo.tools.misc import file_open, mute_logger -from odoo.tools.translate import _ +from odoo.tools.translate import _, _lt +TRANSLATED_TERM = _lt("Klingon") + class TestTermCount(common.TransactionCase): def test_count_term(self): @@ -179,6 +181,32 @@ class TestTermCount(common.TransactionCase): self.env.context = dict(self.env.context, lang="tlh") self.assertEqual(_("Klingon"), "tlhIngan", "The code translation was not applied") + def test_lazy_translation(self): + """Test the import from a single po file works""" + with file_open('test_translation_import/i18n/tlh.po', 'rb') as f: + po_file = base64.encodestring(f.read()) + + import_tlh = self.env["base.language.import"].create({ + 'name': 'Klingon', + 'code': 'tlh', + 'data': po_file, + 'filename': 'tlh.po', + }) + with mute_logger('odoo.addons.base.models.res_lang'): + import_tlh.import_lang() + + context = {'lang': "tlh"} + self.assertEqual(_("Klingon"), "tlhIngan", "The direct code translation was not applied") + context = None + + # Comparison of lazy strings must be explicitely casted to string + with self.assertRaises(NotImplementedError): + TRANSLATED_TERM == "Klingon" + self.assertEqual(str(TRANSLATED_TERM), "Klingon", "The translation should not be applied yet") + + context = {'lang': "tlh"} + self.assertEqual(str(TRANSLATED_TERM), "tlhIngan", "The lazy code translation was not applied") + def test_import_from_csv_file(self): """Test the import from a single CSV file works""" with file_open('test_translation_import/i18n/dot.csv', 'rb') as f: diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py index 56936cc6241d..36a350ddaafb 100644 --- a/odoo/tools/translate.py +++ b/odoo/tools/translate.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import codecs import fnmatch +import functools import inspect import io import locale @@ -444,6 +445,9 @@ class GettextAlias(object): return lang def __call__(self, source): + return self._get_translation(source) + + def _get_translation(self, source): res = source cr = None is_new_cr = False @@ -452,6 +456,9 @@ class GettextAlias(object): if frame is None: return source frame = frame.f_back + if not frame: + return source + frame = frame.f_back if not frame: return source lang = self._get_lang(frame) @@ -473,6 +480,45 @@ class GettextAlias(object): cr.close() return res + +@functools.total_ordering +class _lt: + """ Lazy code translation + + Similar to GettextAlias but the translation lookup will be done only at + __str__ execution. + + A code using translated global variables such as: + + LABEL = _lt("User") + + def _compute_label(self): + context = {'lang': self.partner_id.lang} + self.user_label = LABEL + + works as expected (unlike the classic GettextAlias implementation). + """ + + __slots__ = ['_source'] + def __init__(self, source): + self._source = source + + def __str__(self): + # Call _._get_translation() like _() does, so that we have the same number + # of stack frames calling _get_translation() + return _._get_translation(self._source) + + def __eq__(self, other): + """ Prevent using equal operators + + Prevent direct comparisons with ``self``. + One should compare the translation of ``self._source`` as ``str(self) == X``. + """ + raise NotImplementedError() + + def __lt__(self, other): + raise NotImplementedError() + _ = GettextAlias() @@ -974,7 +1020,8 @@ def trans_generate(lang, modules, cr): _logger.debug("Scanning files of modules at %s", path) for root, dummy, files in walksymlinks(path): for fname in fnmatch.filter(files, '*.py'): - babel_extract_terms(fname, path, root) + babel_extract_terms(fname, path, root, + extract_keywords={'_': None, '_lt': None}) # Javascript source files in the static/src/js directory, rest is ignored (libs) if fnmatch.fnmatch(root, '*/static/src/js*'): for fname in fnmatch.filter(files, '*.js'): -- GitLab