diff --git a/odoo/addons/base/models/ir_translation.py b/odoo/addons/base/models/ir_translation.py index 8c5906a19a4..f460050a7e9 100644 --- a/odoo/addons/base/models/ir_translation.py +++ b/odoo/addons/base/models/ir_translation.py @@ -649,6 +649,30 @@ class IrTranslation(models.Model): """.format(", ".join(["%s"] * len(rows_by_type['model_terms']))) self.env.cr.execute(query, rows_by_type['model_terms']) + def _update_translations(self, vals_list): + """ Update translations of type 'model' or 'model_terms'. + + This method is used for update of translations where the given + ``vals_list`` is trusted to be the right values + No new translation will be created + """ + grouped_rows = {} + for vals in vals_list: + key = (vals['lang'], vals['type'], vals['name']) + grouped_rows.setdefault(key, [vals['value'], vals['src'], vals['state'], []]) + grouped_rows[key][3].append(vals['res_id']) + + for where, values in grouped_rows.items(): + self._cr.execute( + """ UPDATE ir_translation + SET value=%s, + src=%s, + state=%s + WHERE lang=%s AND type=%s AND name=%s AND res_id in %s + """, + (values[0], values[1], values[2], where[0], where[1], where[2], tuple(values[3])) + ) + @api.model def translate_fields(self, model, id, field=None): """ Open a view for translating the field(s) of the record (model, id). """ diff --git a/odoo/addons/base/tests/test_translate.py b/odoo/addons/base/tests/test_translate.py index f4a3cb29c94..f04b1035b08 100644 --- a/odoo/addons/base/tests/test_translate.py +++ b/odoo/addons/base/tests/test_translate.py @@ -393,6 +393,92 @@ class TestTranslation(TransactionCase): ]) self.assertEqual(translation_fr.src, 'Customers', "Did not set English version as source") +class TestTranslationWrite(TransactionCase): + + def setUp(self): + super().setUp() + self.category = self.env['res.partner.category'].create({'name': 'Reblochon'}) + + def test_01_en(self): + langs = self.env['res.lang'].get_installed() + self.assertEqual([('en_US', 'English (US)')], langs, "Test did not started with expected languages") + + self.category.with_context(lang='en_US').write({'name': 'English Name'}) + name = self.category.with_context(lang=None).read(['name']) + self.assertEqual(name[0]['name'], "English Name", "Reference field not updated") + translations = self.env['ir.translation'].search([ + ('name', '=', 'res.partner.category,name'), + ('res_id', '=', self.category.id), + ('lang', '=', 'en_US'), + ]) + self.assertEqual(len(translations), 0, "No English translation should be created when writing in English") + + def test_02_en_translated(self): + langs = self.env['res.lang'].get_installed() + self.assertEqual([('en_US', 'English (US)')], langs, "Test did not started with expected languages") + translation = self.env['ir.translation'].create({ + 'type': 'model', + 'name': 'res.partner.category,name', + 'lang': 'en_US', + 'res_id': self.category.id, + 'src': 'Reblochon', + 'value': 'Translated Name', + 'state': 'translated', + }) + + self.category.with_context(lang='en_US').write({'name': 'English Name'}) + translation_value = translation.read(['value']) + self.assertEqual(translation_value[0]['value'], "English Name", "Existing translation was not updated") + + source_name = self.category.with_context(lang=None).read(['name']) + self.assertEqual(source_name[0]['name'], "English Name", "Reference field not updated") + + def test_03_fr_single(self): + self.env['res.lang'].load_lang('fr_FR') + self.env['res.users'].with_context(active_test=False).search([]).write({'lang': 'fr_FR'}) + self.env.ref('base.lang_en').active = False + + langs = self.env['res.lang'].get_installed() + self.assertEqual([('fr_FR', 'French / Français')], langs, "Test did not started with expected languages") + + self.category.with_context(lang='fr_FR').write({'name': 'French Name'}) + source_name = self.category.with_context(lang=None).read(['name']) + self.assertEqual(source_name[0]['name'], "French Name", "Reference field not updated") + translations = self.env['ir.translation'].search([ + ('name', '=', 'res.partner.category,name'), + ('res_id', '=', self.category.id), + ('lang', '=', 'fr_FR'), + ]) + self.assertEqual(len(translations), 0, "No French translation should be created when writing in French") + + def test_04_fr_multi(self): + self.env['res.lang'].load_lang('fr_FR') + + langs = self.env['res.lang'].get_installed() + self.assertEqual([('en_US', 'English (US)'), ('fr_FR', 'French / Français')], langs, + "Test did not started with expected languages") + self.env['ir.translation'].create({ + 'type': 'model', + 'name': 'res.partner.category,name', + 'lang': 'en_US', + 'res_id': self.category.id, + 'src': 'Reblochon', + 'value': 'Translated Name', + 'state': 'translated', + }) + + self.category.with_context(lang='fr_FR').write({'name': 'French Name'}) + self.category.with_context(lang='en_US').write({'name': 'English Name'}) + translations = self.env['ir.translation'].search([ + ('name', '=', 'res.partner.category,name'), + ('res_id', '=', self.category.id), + ], order='lang') + self.assertRecordValues(translations, [ + {'src': 'English Name', 'value': 'English Name', 'lang': 'en_US'}, + {'src': 'English Name', 'value': 'French Name', 'lang': 'fr_FR'} + ]) + + class TestXMLTranslation(TransactionCase): def setUp(self): diff --git a/odoo/addons/test_new_api/tests/test_new_fields.py b/odoo/addons/test_new_api/tests/test_new_fields.py index b8f254e512f..d372f6f23ae 100644 --- a/odoo/addons/test_new_api/tests/test_new_fields.py +++ b/odoo/addons/test_new_api/tests/test_new_fields.py @@ -1230,8 +1230,7 @@ class TestFields(common.TransactionCase): email = self.env.ref('test_new_api.emailmessage_0_0') self.assertEqual(email.message, message) - french = self.env['res.lang']._lang_get('fr_FR') - french.active = True + self.env['res.lang'].load_lang('fr_FR') def count(msg): # return the number of translations of msg.label diff --git a/odoo/models.py b/odoo/models.py index 01c728a65d2..5ffde4cd104 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -3397,6 +3397,12 @@ Fields: other_fields = [] # list of non-column fields single_lang = len(self.env['res.lang'].get_installed()) <= 1 has_translation = self.env.lang and self.env.lang != 'en_US' + # when there is only one language, update existing translations but + # do not create new ones + if single_lang: + process_translations = self.env['ir.translation']._update_translations + else: + process_translations = self.env['ir.translation']._upsert_translations for name, val in vals.items(): field = self._fields[name] @@ -3411,7 +3417,7 @@ Fields: val = field.convert_to_column(val, self, vals) columns.append((name, field.column_format, val)) tname = "%s,%s" % (self._name, name) - if field.translate is True and not single_lang: + if field.translate is True and self.env.lang: self.env['ir.translation']._set_source(tname, self.ids, val) updated.append(name) else: @@ -3451,7 +3457,7 @@ Fields: # synchronize translated terms when possible. self.env['ir.translation']._sync_terms_translations(field, self) - elif has_translation and field.translate: + elif self.env.lang and field.translate: # The translated value of a field has been modified. src_trans = self.with_context(lang=None).read([name])[0][name] if not src_trans: @@ -3469,7 +3475,8 @@ Fields: state='translated', res_id=res_id) for res_id in self.ids] - self.env['ir.translation']._upsert_translations(translation_values) + if translation_values: + process_translations(translation_values) # mark fields to recompute; do this before setting other fields, because # the latter can require the value of computed fields, e.g., a one2many