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'):