[IMP] translations: wip, remove unnecessary code, support loading PO comments and...
authorOlivier Dony <odo@openerp.com>
Thu, 13 Sep 2012 14:29:20 +0000 (16:29 +0200)
committerOlivier Dony <odo@openerp.com>
Thu, 13 Sep 2012 14:29:20 +0000 (16:29 +0200)
bzr revid: odo@openerp.com-20120913142920-ggpeqth4s2wwqwd2

openerp/addons/base/__openerp__.py
openerp/addons/base/ir/ir.xml
openerp/addons/base/ir/ir_translation.py
openerp/addons/base/ir/ir_ui_view.py
openerp/addons/base/module/module.py
openerp/service/web_services.py
openerp/tests/common.py
openerp/tests/test_xmlrpc.py
openerp/tools/translate.py

index 937c9cb..082cf26 100644 (file)
@@ -44,6 +44,7 @@ The kernel of OpenERP, needed for all installation.
         'data/res.country.state.csv',
         'ir/wizard/wizard_menu_view.xml',
         'ir/ir.xml',
+        'ir/ir_translation_view.xml',
         'ir/ir_filters.xml',
         'ir/ir_config_parameter_view.xml',
         'ir/workflow/workflow_view.xml',
index 29bb681..515d964 100644 (file)
         <menuitem action="action_model_relation" id="ir_model_relation_menu" parent="base.next_id_9"
                   groups="base.group_no_one"/>
 
-        <!-- Translations -->
-
-        <record id="view_translation_search" model="ir.ui.view">
-            <field name="name">Translations</field>
-            <field name="model">ir.translation</field>
-            <field name="arch" type="xml">
-                <search string="Translations">
-                    <filter icon="terp-gdu-smart-failing"
-                        string="Untranslated"
-                        domain="['|',('value', '=', False),('value','=','')]"/>
-                    <field name="name" operator="="/>
-                    <field name="lang"/>
-                    <field name="src"/>
-                    <field name="value"/>
-                </search>
-            </field>
-        </record>
-
-        <record id="view_translation_form" model="ir.ui.view">
-            <field name="name">Translations</field>
-            <field name="model">ir.translation</field>
-            <field name="arch" type="xml">
-                <form string="Translations" version="7.0">
-                   <header>
-                        <field name="state" widget="statusbar" nolabel="1"/>
-                   </header>
-                   <sheet>
-                    <group>
-                        <group>
-                            <field name="name"/>
-                            <field name="lang"/>
-                        </group>
-                        <group>
-                            <field name="type"/>
-                            <field name="res_id"/>
-                        </group>
-                        <group string="Source Term">
-                           <field name="src" nolabel="1" height="400"/>
-                        </group>
-                        <group string="Translation">
-                           <field name="value" nolabel="1" height="400"/>
-                        </group>
-                    </group>
-                   </sheet>
-                </form>
-            </field>
-        </record>
-        <record id="view_translation_tree" model="ir.ui.view">
-            <field name="name">Translations</field>
-            <field name="model">ir.translation</field>
-            <field name="arch" type="xml">
-                <tree string="Translations" editable="bottom">
-                    <field name="src" readonly="True"/>
-                    <field name="value"/>
-                    <field name="name" readonly="True"/>
-                    <field name="lang" readonly="True"/>
-                    <field name="type" readonly="True"/>
-                </tree>
-            </field>
-        </record>
-
-        <record id="action_translation" model="ir.actions.act_window">
-            <field name="name">Translated Terms</field>
-            <field name="res_model">ir.translation</field>
-            <field name="view_type">form</field>
-            <field name="view_id" ref="view_translation_tree"/>
-        </record>
-        <menuitem action="action_translation" id="menu_action_translation" parent="base.menu_translation_app" />
-
         <!--
     =============================================================
     Menu Edition
index c8f9cf8..3ed61a7 100644 (file)
 #
 ##############################################################################
 
-from osv import fields, osv
 import tools
 import logging
+
 import openerp.modules
+from openerp.osv import fields, osv
 
 _logger = logging.getLogger(__name__)
 
@@ -58,7 +59,6 @@ class ir_translation_import_cursor(object):
         the data.
         @param parent an instance of ir.translation ORM model
         """
-
         self._cr = cr
         self._uid = uid
         self._context = context
@@ -77,8 +77,9 @@ class ir_translation_import_cursor(object):
         """Feed a translation, as a dictionary, into the cursor
         """
         params = dict(trans_dict, state="translated" if trans_dict['value'] else "to_translate")
-        self._cr.execute("""INSERT INTO %s (name, lang, res_id, src, type, imd_model, module, imd_name, value, state)
-                            VALUES (%%(name)s, %%(lang)s, %%(res_id)s, %%(src)s, %%(type)s, %%(imd_model)s, %%(module)s, %%(imd_name)s, %%(value)s, %%(state)s)""" % self._table_name,
+        self._cr.execute("""INSERT INTO %s (name, lang, res_id, src, type, imd_model, module, imd_name, value, state, comments)
+                            VALUES (%%(name)s, %%(lang)s, %%(res_id)s, %%(src)s, %%(type)s, %%(imd_model)s, %%(module)s,
+                                    %%(imd_name)s, %%(value)s, %%(state)s, %%(comments)s)""" % self._table_name,
                          params)
 
     def finish(self):
@@ -106,11 +107,10 @@ class ir_translation_import_cursor(object):
             for row in cr.fetchall():
                 _logger.debug("ir.translation.cursor: missing res_id for %s. %s/%s ", *row)
 
-        cr.execute("DELETE FROM %s WHERE res_id IS NULL AND module IS NOT NULL" % \
-            self._table_name)
-
         # Records w/o res_id must _not_ be inserted into our db, because they are
         # referencing non-existent data.
+        cr.execute("DELETE FROM %s WHERE res_id IS NULL AND module IS NOT NULL" % \
+            self._table_name)
 
         find_expr = "irt.lang = ti.lang AND irt.type = ti.type " \
                     " AND irt.name = ti.name AND irt.src = ti.src " \
@@ -120,14 +120,14 @@ class ir_translation_import_cursor(object):
         if self._overwrite:
             cr.execute("""UPDATE ONLY %s AS irt
                 SET value = ti.value,
-                state = 'translated' 
+                state = 'translated'
                 FROM %s AS ti
                 WHERE %s AND ti.value IS NOT NULL AND ti.value != ''
                 """ % (self._parent_table, self._table_name, find_expr))
 
         # Step 3: insert new translations
-        cr.execute("""INSERT INTO %s(name, lang, res_id, src, type, value, module, state)
-            SELECT name, lang, res_id, src, type, value, module, state
+        cr.execute("""INSERT INTO %s(name, lang, res_id, src, type, value, module, state, comments)
+            SELECT name, lang, res_id, src, type, value, module, state, comments
               FROM %s AS ti
               WHERE NOT EXISTS(SELECT 1 FROM ONLY %s AS irt WHERE %s);
               """ % (self._parent_table, self._table_name, self._parent_table, find_expr))
@@ -155,29 +155,37 @@ class ir_translation(osv.osv):
         return [(d['code'], d['name']) for d in lang_data]
 
     _columns = {
-        'name': fields.char('Field Name', size=128, required=True),
-        'res_id': fields.integer('Resource ID', select=True),
-        'lang': fields.selection(_get_language, string='Language', size=16),
-        'type': fields.selection(TRANSLATION_TYPE, string='Type', size=16, select=True),
+        'name': fields.char('Translated field', required=True),
+        'res_id': fields.integer('Record ID', select=True),
+        'lang': fields.selection(_get_language, string='Language'),
+        'type': fields.selection(TRANSLATION_TYPE, string='Type', select=True),
         'src': fields.text('Source'),
         'value': fields.text('Translation Value'),
-        'module': fields.char('Module Name', size=128),
-        'state': fields.selection([('to_translate','To Translate'),
-                                   ('inprogress','Translation in Progress'),
-                                   ('translated','Translated')])
+        'module': fields.char('Module', help="Module this term belongs to", select=True),
+
+        'state': fields.selection(
+            [('to_translate','To Translate'),
+             ('inprogress','Translation in Progress'),
+             ('translated','Translated')],
+            string="State",
+            help="Automatically set to let administators find new terms that might need to be translated"),
+
+        # aka gettext extracted-comments - we use them to flag openerp-web translation
+        # cfr: http://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_node/PO-Files.html
+        'comments': fields.text('Translation comments', select=True),
     }
-    
+
     _defaults = {
-        'state':'to_translate',
+        'state': 'to_translate',
     }
-    
-    _sql_constraints = [ ('lang_fkey_res_lang', 'FOREIGN KEY(lang) REFERENCES res_lang(code)', 
+
+    _sql_constraints = [ ('lang_fkey_res_lang', 'FOREIGN KEY(lang) REFERENCES res_lang(code)',
         'Language code of translation item must be among known languages' ), ]
 
     def _auto_init(self, cr, context=None):
         super(ir_translation, self)._auto_init(cr, context)
 
-        # FIXME: there is a size limit on btree indexed values so we can't index src column with normal btree. 
+        # FIXME: there is a size limit on btree indexed values so we can't index src column with normal btree.
         cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('ir_translation_ltns',))
         if cr.fetchone():
             #temporarily removed: cr.execute('CREATE INDEX ir_translation_ltns ON ir_translation (name, lang, type, src)')
@@ -203,7 +211,7 @@ class ir_translation(osv.osv):
         if field == 'lang':
             return
         return super(ir_translation, self)._check_selection_field_value(cr, uid, field, value, context=context)
-    
+
     @tools.ormcache_multi(skiparg=3, multi=6)
     def _get_ids(self, cr, uid, name, tt, lang, ids):
         translations = dict.fromkeys(ids, False)
@@ -263,14 +271,14 @@ class ir_translation(osv.osv):
         # FIXME: should assert that `source` is unicode and fix all callers to always pass unicode
         # so we can remove the string encoding/decoding.
         if not lang:
-            return u''
+            return tools.ustr(source or '')
         if isinstance(types, basestring):
             types = (types,)
         if source:
-            query = """SELECT value 
-                       FROM ir_translation 
-                       WHERE lang=%s 
-                        AND type in %s 
+            query = """SELECT value
+                       FROM ir_translation
+                       WHERE lang=%s
+                        AND type in %s
                         AND src=%s"""
             params = (lang or '', types, tools.ustr(source))
             if name:
@@ -304,9 +312,9 @@ class ir_translation(osv.osv):
         if isinstance(ids, (int, long)):
             ids = [ids]
         if vals.get('src') or ('value' in vals and not(vals.get('value'))):
-            result = vals.update({'state':'to_translate'})
+            vals.update({'state':'to_translate'})
         if vals.get('value'):
-            result = vals.update({'state':'translated'})
+            vals.update({'state':'translated'})
         result = super(ir_translation, self).write(cursor, user, ids, vals, context=context)
         for trans_obj in self.read(cursor, user, ids, ['name','type','res_id','src','lang'], context=context):
             self._get_source.clear_cache(self, user, trans_obj['name'], trans_obj['type'], trans_obj['lang'], trans_obj['src'])
@@ -374,40 +382,35 @@ class ir_translation(osv.osv):
         """ Return a cursor-like object for fast inserting translations
         """
         return ir_translation_import_cursor(cr, uid, self, context=context)
-    
-    def load(self, cr, modules, langs, flag=None, context=None):
-        translated_data = {}
+
+    def load(self, cr, modules, langs, context=None):
+        context = dict(context or {}) # local copy
         for module_name in modules:
-            translated_data[module_name] = {'messages':[]}
             modpath = openerp.modules.get_module_path(module_name)
             if not modpath:
-                # unable to find the module. we skip
                 continue
             for lang in langs:
-                iso_lang = tools.get_iso_codes(lang)
-                f = openerp.modules.get_module_resource(module_name, 'i18n', iso_lang + '.po')
-                context2 = context and context.copy() or {}
-                if f and '_' in iso_lang:
-                    iso_lang2 = iso_lang.split('_')[0]
-                    f2 = openerp.modules.get_module_resource(module_name, 'i18n', iso_lang2 + '.po')
-                    if f2:
-                        _logger.info('module %s: loading base translation file %s for language %s', module_name, iso_lang2, lang)
-                        translated_data[module_name]['messages'].extend(tools.trans_load(cr, f2, lang, verbose=False, flag=flag, module_name=module_name, context=context))
-                        context2['overwrite'] = True
-                # Implementation notice: we must first search for the full name of
-                # the language derivative, like "en_UK", and then the generic,
-                # like "en".
-                if (not f) and '_' in iso_lang:
-                    iso_lang = iso_lang.split('_')[0]
-                    f = openerp.modules.get_module_resource(module_name, 'i18n', iso_lang + '.po')
-                if f:
-                    _logger.info('module %s: loading translation file (%s) for language %s', module_name, iso_lang, lang)
-                    translated_data[module_name]['messages'].extend(tools.trans_load(cr, f, lang, verbose=False, flag=flag, module_name=module_name, context=context2))
-                elif iso_lang != 'en':
-                    _logger.warning('module %s: no translation for language %s', module_name, iso_lang)
-        return translated_data
-
-ir_translation()
+                lang_code = tools.get_iso_codes(lang)
+                base_lang_code = None
+                if '_' in lang_code:
+                    base_lang_code = lang_code.split('_')[0]
+
+                # Step 1: for sub-languages, load base language first (e.g. es_CL.po is loaded over es.po)
+                if base_lang_code:
+                    base_trans_file = openerp.modules.get_module_resource(module_name, 'i18n', base_lang_code + '.po')
+                    if base_trans_file:
+                        _logger.info('module %s: loading base translation file %s for language %s', module_name, base_lang_code, lang)
+                        tools.trans_load(cr, base_trans_file, lang, verbose=False, module_name=module_name, context=context)
+                        context['overwrite'] = True # make sure the requested translation will override the base terms later
+
+                # Step 2: then load the main translation file, possibly overriding the terms coming from the base language
+                trans_file = openerp.modules.get_module_resource(module_name, 'i18n', lang_code + '.po')
+                if trans_file:
+                    _logger.info('module %s: loading translation file (%s) for language %s', module_name, lang_code, lang)
+                    tools.trans_load(cr, trans_file, lang, verbose=False, module_name=module_name, context=context)
+                elif lang_code != 'en':
+                    _logger.warning('module %s: no translation for language %s', module_name, lang_code)
+        return True
 
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
 
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
index bad2c3d..db1e134 100644 (file)
@@ -192,15 +192,12 @@ class view(osv.osv):
     def write(self, cr, uid, ids, vals, context=None):
         if not isinstance(ids, (list, tuple)):
             ids = [ids]
-        result = super(view, self).write(cr, uid, ids, vals, context)
-
         # drop the corresponding view customizations (used for dashboards for example), otherwise
         # not all users would see the updated views
         custom_view_ids = self.pool.get('ir.ui.view.custom').search(cr, uid, [('ref_id','in',ids)])
         if custom_view_ids:
             self.pool.get('ir.ui.view.custom').unlink(cr, uid, custom_view_ids)
-
-        return result
+        return super(view, self).write(cr, uid, ids, vals, context)
 
     def graph_get(self, cr, uid, id, model, node_obj, conn_obj, src_node, des_node, label, scale, context=None):
         nodes=[]
index 04b1fed..541bf86 100644 (file)
@@ -632,20 +632,14 @@ class module(osv.osv):
             self.write(cr, uid, [mod_browse.id], {'category_id': p_id})
 
     def update_translations(self, cr, uid, ids, filter_lang=None, context=None):
-        pool = pooler.get_pool(cr.dbname)
-        if context is None:
-            context = {}
         if not filter_lang:
-            lang_obj = pool.get('res.lang')
-            lang_ids = lang_obj.search(cr, uid, [('translatable', '=', True)])
-            filter_lang = [lang.code for lang in lang_obj.browse(cr, uid, lang_ids)]
+            res_lang = self.pool.get('res.lang')
+            lang_ids = res_lang.search(cr, uid, [('translatable', '=', True)])
+            filter_lang = [lang.code for lang in res_lang.browse(cr, uid, lang_ids)]
         elif not isinstance(filter_lang, (list, tuple)):
             filter_lang = [filter_lang]
-
-        for mod in self.browse(cr, uid, ids):
-            if mod.state != 'installed':
-                continue
-            pool.get('ir.translation').load(cr, [mod.name], filter_lang, context=context)
+        modules = [m.name for m in self.browse(cr, uid, ids) if m.state == 'installed']
+        self.pool.get('ir.translation').load(cr, modules, filter_lang, context=context)
 
     def check(self, cr, uid, ids, context=None):
         for mod in self.browse(cr, uid, ids, context=context):
index 6e1f463..0a2e270 100644 (file)
@@ -777,36 +777,12 @@ class report_spool(netsvc.ExportService):
         else:
             raise Exception, 'ReportNotFound'
 
-class translation(netsvc.ExportService):
-
-    def __init__(self, name="translation"):
-        netsvc.ExportService.__init__(self, name)
-    
-    def exp_load(self, db, modules, langs, flag=None, context=None):
-        cr = pooler.get_db(db).cursor()
-        translated_data = pooler.get_pool(db).get('ir.translation').load(cr, modules, langs, flag, context=context)
-        cr.commit()
-        cr.close()
-        return translated_data
-    
-    def dispatch(self, method, params):
-        if method in ['load']:
-            # No security check for these methods
-            pass
-        else:
-            raise KeyError("Method not found: %s" % method)
-        fn = getattr(self, 'exp_'+method)
-        return fn(*params)
-
-
 def start_web_services():
     db()
     common()
     objects_proxy()
     wizard()
     report_spool()
-    translation()
-
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
 
index 80e1971..53c67c6 100644 (file)
@@ -83,7 +83,6 @@ class RpcCase(unittest2.TestCase):
         self.proxy.common_61 = xmlrpclib.ServerProxy(url_61 + 'common')
         self.proxy.db_61 = xmlrpclib.ServerProxy(url_61 + 'db')
         self.proxy.model_61 = xmlrpclib.ServerProxy(url_61 + 'model/' + DB)
-        self.proxy.translation_61 = xmlrpclib.ServerProxy(url_61 + 'translation')
 
     @classmethod
     def generate_database_name(cls):
index 70ac6b1..37655e7 100644 (file)
@@ -71,11 +71,6 @@ class test_xmlrpc(common.RpcCase):
         """
         assert self.proxy.db_60.drop(ADMIN_PASSWORD, DB) is True
 
-    def test_xmlrpc_61_translation_load(self):
-        """ Try a load translation service like web. """
-        messages = self.proxy.translation_61.load(DB, ['base', 'web'], ['en_US'], 'web')
-        assert messages
-
 if __name__ == '__main__':
     unittest2.main()
 
index 93034b9..0ead129 100644 (file)
@@ -262,7 +262,7 @@ class TinyPoFile(object):
         self.lines_count = len(self.lines);
 
         self.first = True
-        self.tnrs= []
+        self.extra_lines= []
         return self
 
     def _get_lines(self):
@@ -278,14 +278,14 @@ class TinyPoFile(object):
         return (self.lines_count - len(self.lines))
 
     def next(self):
-        type = name = res_id = source = trad = None
-
-        if self.tnrs:
-            type, name, res_id, source, trad = self.tnrs.pop(0)
+        trans_type = name = res_id = source = trad = None
+        if self.extra_lines:
+            trans_type, name, res_id, source, trad, comments = self.extra_lines.pop(0)
             if not res_id:
                 res_id = '0'
         else:
-            tmp_tnrs = []
+            comments = []
+            targets = []
             line = None
             fuzzy = False
             while (not line):
@@ -295,15 +295,20 @@ class TinyPoFile(object):
             while line.startswith('#'):
                 if line.startswith('#~ '):
                     break
-                if line.startswith('#:'):
+                if line.startswith('#.'):
+                    line = line[2:].strip()
+                    if not line.startswith('module:'):
+                        comments.append(line)
+                elif line.startswith('#:'):
                     for lpart in line[2:].strip().split(' '):
                         trans_info = lpart.strip().split(':',2)
                         if trans_info and len(trans_info) == 2:
-                            # looks like the translation type is missing, which is not
+                            # looks like the translation trans_type is missing, which is not
                             # unexpected because it is not a GetText standard. Default: 'code'
                             trans_info[:0] = ['code']
                         if trans_info and len(trans_info) == 3:
-                            tmp_tnrs.append(trans_info)
+                            # this is a ref line holding the destination info (model, field, record)
+                            targets.append(trans_info)
                 elif line.startswith('#,') and (line[2:].strip() == 'fuzzy'):
                     fuzzy = True
                 line = self.lines.pop(0).strip()
@@ -326,7 +331,7 @@ class TinyPoFile(object):
                 # if the source is "" and it's the first msgid, it's the special
                 # msgstr with the informations about the traduction and the
                 # traductor; we skip it
-                self.tnrs = []
+                self.extra_lines = []
                 while line:
                     line = self.lines.pop(0).strip()
                 return self.next()
@@ -343,10 +348,10 @@ class TinyPoFile(object):
                 trad += unquote(line)
                 line = self.lines.pop(0).strip()
 
-            if tmp_tnrs and not fuzzy:
-                type, name, res_id = tmp_tnrs.pop(0)
-                for t, n, r in tmp_tnrs:
-                    self.tnrs.append((t, n, r, source, trad))
+            if targets and not fuzzy:
+                trans_type, name, res_id = targets.pop(0)
+                for t, n, r in targets:
+                    self.extra_lines.append((t, n, r, source, trad, comments))
 
         self.first = False
 
@@ -355,7 +360,7 @@ class TinyPoFile(object):
                 self.warn('Missing "#:" formated comment at line %d for the following source:\n\t%s',
                         self.cur_line(), source[:30])
             return self.next()
-        return type, name, res_id, source, trad
+        return trans_type, name, res_id, source, trad, '\n'.join(comments)
 
     def write_infos(self, modules):
         import openerp.release as release
@@ -843,21 +848,14 @@ def trans_generate(lang, modules, cr):
 
     return out
 
-def trans_load(cr, filename, lang, verbose=True, flag=None, module_name=None, context=None):
+def trans_load(cr, filename, lang, verbose=True, module_name=None, context=None):
     try:
         fileobj = misc.file_open(filename)
-        traslation_obj = pooler.get_pool(cr.dbname).get('ir.translation')
         _logger.info("loading %s", filename)
-        transl = []
-        if flag == 'web':
-            cr.execute("select DISTINCT src,value from ir_translation where module='%s' AND lang='%s' AND value != ''"% (module_name,lang))
-            for src, value in cr.fetchall():
-                transl.append({'id': src, 'string': value})
-        else:
-            fileformat = os.path.splitext(filename)[-1][1:].lower()
-            trans_load_data(cr, fileobj, fileformat, lang, verbose=verbose, module_name=module_name, context=context)
+        fileformat = os.path.splitext(filename)[-1][1:].lower()
+        result = trans_load_data(cr, fileobj, fileformat, lang, verbose=verbose, module_name=module_name, context=context)
         fileobj.close()
-        return transl
+        return result
     except IOError:
         if verbose:
             _logger.error("couldn't read translation file %s", filename)
@@ -875,8 +873,7 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True,
     trans_obj = pool.get('ir.translation')
     iso_lang = misc.get_iso_codes(lang)
     try:
-        uid = 1
-        ids = lang_obj.search(cr, uid, [('code','=', lang)])
+        ids = lang_obj.search(cr, SUPERUSER_ID, [('code','=', lang)])
 
         if not ids:
             # lets create the language with locale information
@@ -893,14 +890,14 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True,
                 break
         elif fileformat == 'po':
             reader = TinyPoFile(fileobj)
-            f = ['type', 'name', 'res_id', 'src', 'value', 'module']
+            f = ['type', 'name', 'res_id', 'src', 'value', 'comments']
         else:
             _logger.error('Bad file format: %s', fileformat)
             raise Exception(_('Bad file format'))
 
         # read the rest of the file
         line = 1
-        irt_cursor = trans_obj._get_import_cursor(cr, uid, context=context)
+        irt_cursor = trans_obj._get_import_cursor(cr, SUPERUSER_ID, context=context)
 
         for row in reader:
             line += 1
@@ -911,11 +908,9 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True,
             # dictionary which holds values for this line of the csv file
             # {'lang': ..., 'type': ..., 'name': ..., 'res_id': ...,
             #  'src': ..., 'value': ..., 'module':...}
-            dic = dict.fromkeys(('name', 'res_id', 'src', 'type', 'imd_model', 'imd_name', 'module', 'value'))
+            dic = dict.fromkeys(('name', 'res_id', 'src', 'type', 'imd_model', 'imd_name', 'module', 'value', 'comments'))
             dic['lang'] = lang
             for i, field in enumerate(f):
-                if field == 'module':
-                    continue
                 dic[field] = row[i]
 
             # This would skip terms that fail to specify a res_id