diff --git a/odoo/addons/base/tests/test_translate.py b/odoo/addons/base/tests/test_translate.py index 59fe7b8efbc..7c009a23dab 100644 --- a/odoo/addons/base/tests/test_translate.py +++ b/odoo/addons/base/tests/test_translate.py @@ -1105,6 +1105,58 @@ class TestXMLTranslation(TransactionCase): self.assertEqual(view.with_env(env_fr).arch_db, archf % (terms_fr[0], terms_en[1], terms_en[2])) self.assertEqual(view.with_env(env_nl).arch_db, archf % (terms_nl[0], terms_en[1], terms_en[2])) + def test_sync_xml_inline_modifiers(self): + """ Check translations of 'arch' after xml tags changes in source terms + when source term had updated modifiers attributes + """ + archf = '''
''' + terms_en = ('Bread and cheese', 'Knive and Fork', 'Knive and Fork') + terms_fr = ('Pain et fromage', 'Couteau et Fourchette', 'Couteau et Fourchette') + terms_nl = ('Brood and kaas', 'Mes en Vork', 'Mes en Vork') + view = self.create_view(archf, terms_en, en_US=terms_en, fr_FR=terms_fr, nl_NL=terms_nl) + + env_nolang = self.env(context={"install_mode": True}) + env_en = self.env(context={'lang': 'en_US', "install_mode": True}) + env_fr = self.env(context={'lang': 'fr_FR', "install_mode": True}) + env_nl = self.env(context={'lang': 'nl_NL', "install_mode": True}) + + self.assertEqual(view.with_env(env_nolang).arch_db, archf % terms_en) + self.assertEqual(view.with_env(env_en).arch_db, archf % terms_en) + self.assertEqual(view.with_env(env_fr).arch_db, archf % terms_fr) + self.assertEqual(view.with_env(env_nl).arch_db, archf % terms_nl) + + # modify attributes in source term + terms_en = ('Bread and cheese', 'Knife and Fork', 'Knife and Fork') + view.with_env(env_en).write({'arch_db': archf % terms_en}) + terms_fr = ('Pain et fromage', 'Couteau et Fourchette', 'Couteau et Fourchette') + + # check whether translations have been kept + self.assertEqual(view.with_env(env_nolang).arch_db, archf % terms_en) + self.assertEqual(view.with_env(env_en).arch_db, archf % terms_en) + self.assertEqual(view.with_env(env_fr).arch_db, archf % terms_fr) + self.assertEqual(view.with_env(env_nl).arch_db, archf % terms_nl) + + # modify attributes in source term + terms_en = ('Bread and cheese', 'Knife and Fork', 'Knife and Fork') + view.with_env(env_en).write({'arch_db': archf % terms_en}) + terms_fr = ('Pain et fromage', 'Couteau et Fourchette', 'Couteau et Fourchette') + + # check whether translations have been kept + self.assertEqual(view.with_env(env_nolang).arch_db, archf % terms_en) + self.assertEqual(view.with_env(env_en).arch_db, archf % terms_en) + self.assertEqual(view.with_env(env_fr).arch_db, archf % terms_fr) + self.assertEqual(view.with_env(env_nl).arch_db, archf % terms_nl) + def test_cache_consistency(self): view = self.env["ir.ui.view"].create({ "name": "test_translate_xml_cache_invalidation", diff --git a/odoo/fields.py b/odoo/fields.py index 470d74f088e..601cbaa7009 100644 --- a/odoo/fields.py +++ b/odoo/fields.py @@ -1854,6 +1854,7 @@ class _String(Field): text2terms[self.get_text_content(term)].append(term) is_text = self.translate.is_text if hasattr(self.translate, 'is_text') else lambda term: True + term_adapter = self.translate.term_adapter if hasattr(self.translate, 'term_adapter') else None for old_term in list(translation_dictionary.keys()): if old_term not in new_terms: old_term_text = self.get_text_content(old_term) @@ -1863,7 +1864,11 @@ class _String(Field): old_is_text = is_text(old_term) closest_is_text = is_text(closest_term) if old_is_text or not closest_is_text: - translation_dictionary[closest_term] = translation_dictionary.pop(old_term) + if not closest_is_text and records.env.context.get("install_mode") and lang == 'en_US' and term_adapter: + adapter = term_adapter(closest_term) + translation_dictionary[closest_term] = {k: adapter(v) for k, v in translation_dictionary.pop(old_term).items()} + else: + translation_dictionary[closest_term] = translation_dictionary.pop(old_term) # pylint: disable=not-callable new_translations = { l: self.translate(lambda term: translation_dictionary.get(term, {l: None})[l], cache_value) diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py index 311dfe3b996..50866ffb37e 100644 --- a/odoo/tools/translate.py +++ b/odoo/tools/translate.py @@ -292,6 +292,47 @@ def parse_xml(text): def serialize_xml(node): return etree.tostring(node, method='xml', encoding='unicode') + +MODIFIER_ATTRS = {"invisible", "readonly", "required", "column_invisible", "attrs", "states"} +def xml_term_adapter(term_en): + """ + Returns an `adapter(term)` function that will ensure the modifiers are copied + from the base `term_en` to the translated `term` when the XML structure of + both terms match. `term_en` and any input `term` to the adapter must be valid + XML terms. Using the adapter only makes sense if `term_en` contains some tags + from TRANSLATED_ELEMENTS. + """ + orig_node = parse_xml(f"