diff --git a/odoo/__init__.py b/odoo/__init__.py index 6bfe00b84996247cc6339a69d05e0a906027f2d6..bf5ec3e94b4e2ee04e28a2c26bc7daae24b9c348 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 90bef9417ae1e5a75cab9319397dce48f213d214..01ce7ad4d34966c8772202d4c1ba28537c2f26fa 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 56936cc6241d74a26352d477f3c3cd3008b4f4f8..36a350ddaafbcf7aa166b27745a18175f93e4cca 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'):