diff --git a/addons/l10n_fr_fec/wizard/account_fr_fec.py b/addons/l10n_fr_fec/wizard/account_fr_fec.py
index bdc7e3979b7..50bd4700272 100644
--- a/addons/l10n_fr_fec/wizard/account_fr_fec.py
+++ b/addons/l10n_fr_fec/wizard/account_fr_fec.py
@@ -153,14 +153,19 @@ class AccountFrFec(models.TransientModel):
unaffected_earnings_results = self._do_query_unaffected_earnings()
unaffected_earnings_line = False
- sql_query = '''
+ if self.pool['account.account'].name.translate:
+ lang = self.env.user.lang or get_lang(self.env).code
+ aa_name = f"COALESCE(aa.name->>'{lang}', aa.name->>'en_US')"
+ else:
+ aa_name = "aa.name"
+ sql_query = f'''
SELECT
'OUV' AS JournalCode,
'Balance initiale' AS JournalLib,
'OUVERTURE/' || %s AS EcritureNum,
%s AS EcritureDate,
MIN(aa.code) AS CompteNum,
- replace(replace(MIN(aa.name), '|', '/'), '\t', '') AS CompteLib,
+ replace(replace(MIN({aa_name}), '|', '/'), '\t', '') AS CompteLib,
'' AS CompAuxNum,
'' AS CompAuxLib,
'-' AS PieceRef,
@@ -236,14 +241,14 @@ class AccountFrFec(models.TransientModel):
rows_to_write.append(unaffected_earnings_results)
# INITIAL BALANCE - receivable/payable
- sql_query = '''
+ sql_query = f'''
SELECT
'OUV' AS JournalCode,
'Balance initiale' AS JournalLib,
'OUVERTURE/' || %s AS EcritureNum,
%s AS EcritureDate,
MIN(aa.code) AS CompteNum,
- replace(MIN(aa.name), '|', '/') AS CompteLib,
+ replace(MIN({aa_name}), '|', '/') AS CompteLib,
CASE WHEN MIN(aa.account_type) IN ('asset_receivable', 'liability_payable')
THEN
CASE WHEN rp.ref IS null OR rp.ref = ''
@@ -298,14 +303,19 @@ class AccountFrFec(models.TransientModel):
rows_to_write.append(listrow)
# LINES
- sql_query = '''
+ if self.pool['account.journal'].name.translate:
+ lang = self.env.user.lang or get_lang(self.env).code
+ aj_name = f"COALESCE(aj.name->>'{lang}', aj.name->>'en_US')"
+ else:
+ aj_name = "aj.name"
+ sql_query = f'''
SELECT
REGEXP_REPLACE(replace(aj.code, '|', '/'), '[\\t\\r\\n]', ' ', 'g') AS JournalCode,
- REGEXP_REPLACE(replace(COALESCE(aj__name.value, aj.name), '|', '/'), '[\\t\\r\\n]', ' ', 'g') AS JournalLib,
+ REGEXP_REPLACE(replace({aj_name}, '|', '/'), '[\\t\\r\\n]', ' ', 'g') AS JournalLib,
REGEXP_REPLACE(replace(am.name, '|', '/'), '[\\t\\r\\n]', ' ', 'g') AS EcritureNum,
TO_CHAR(am.date, 'YYYYMMDD') AS EcritureDate,
aa.code AS CompteNum,
- REGEXP_REPLACE(replace(aa.name, '|', '/'), '[\\t\\r\\n]', ' ', 'g') AS CompteLib,
+ REGEXP_REPLACE(replace({aa_name}, '|', '/'), '[\\t\\r\\n]', ' ', 'g') AS CompteLib,
CASE WHEN aa.account_type IN ('asset_receivable', 'liability_payable')
THEN
CASE WHEN rp.ref IS null OR rp.ref = ''
@@ -343,11 +353,6 @@ class AccountFrFec(models.TransientModel):
LEFT JOIN account_move am ON am.id=aml.move_id
LEFT JOIN res_partner rp ON rp.id=aml.partner_id
JOIN account_journal aj ON aj.id = am.journal_id
- LEFT JOIN ir_translation aj__name ON aj__name.res_id = aj.id
- AND aj__name.type = 'model'
- AND aj__name.name = 'account.journal,name'
- AND aj__name.lang = %s
- AND aj__name.value != ''
JOIN account_account aa ON aa.id = aml.account_id
LEFT JOIN res_currency rc ON rc.id = aml.currency_id
LEFT JOIN account_full_reconcile rec ON rec.id = aml.full_reconcile_id
@@ -369,9 +374,8 @@ class AccountFrFec(models.TransientModel):
am.name,
aml.id
'''
- lang = self.env.user.lang or get_lang(self.env).code
self._cr.execute(
- sql_query, (lang, self.date_from, self.date_to, company.id))
+ sql_query, (self.date_from, self.date_to, company.id))
for row in self._cr.fetchall():
rows_to_write.append(list(row))
diff --git a/addons/mail/models/__init__.py b/addons/mail/models/__init__.py
index a7eaa787c3b..03bcd6eb3c1 100644
--- a/addons/mail/models/__init__.py
+++ b/addons/mail/models/__init__.py
@@ -52,7 +52,6 @@ from . import ir_http
from . import ir_mail_server
from . import ir_model
from . import ir_model_fields
-from . import ir_translation
from . import ir_ui_view
from . import ir_qweb
from . import ir_websocket
diff --git a/addons/mail/models/ir_translation.py b/addons/mail/models/ir_translation.py
deleted file mode 100644
index a572447c11a..00000000000
--- a/addons/mail/models/ir_translation.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from odoo import models, _
-from odoo.exceptions import AccessError
-
-
-class IrTranslation(models.Model):
- _inherit = 'ir.translation'
-
- def create(self, vals_list):
- translations = super().create(vals_list)
- translations._check_is_dynamic()
- return translations
-
- def write(self, vals):
- res = super().write(vals)
- self._check_is_dynamic()
- return res
-
- def _check_is_dynamic(self):
- # if we don't modify translation of at least a model that inherits from mail.render.mixin, we ignore it
- # translation.name can be a path, and so not in the pool, so type(None) will exclude these translations.
- translations_for_mail_render_mixin = self.filtered(
- lambda translation: issubclass(type(self.env.get(translation.name.split(',')[0])), self.pool['mail.render.mixin'])
- )
- if not translations_for_mail_render_mixin:
- return
-
- # if we are admin, or that we can update mail.template we ignore
- if self.env.is_admin() or self.env.user.has_group('mail.group_mail_template_editor'):
- return
-
- # Check that we don't add qweb code in translation when you don't have the rights
-
- # prefill cache
- ids_by_model_by_lang = {}
- tuple_lang_model_id = translations_for_mail_render_mixin.mapped(
- lambda translation: (translation.lang, translation.name.split(',')[0], translation.res_id)
- )
- for lang, model, _id in tuple_lang_model_id:
- ids_by_model_by_lang.setdefault(lang, {}).setdefault(model, set()).add(_id)
- for lang in ids_by_model_by_lang:
- for res_model, res_ids in ids_by_model_by_lang[lang].items():
- self.env[res_model].with_context(lang=lang).browse(res_ids)
-
- for trans in translations_for_mail_render_mixin:
- res_model, res_id = trans.name.split(',')[0], trans.res_id
- rec = self.env[res_model].with_context(lang=trans.lang).browse(res_id)
-
- if rec._is_dynamic():
- group = self.env.ref('mail.group_mail_template_editor')
- more_info = len(self) > 1 and ' [%s]' % rec or ''
- raise AccessError(
- _('Only users belonging to the "%(group)s" group can modify translation related to dynamic templates.%(xtra)s',
- group=group.name,
- xtra=more_info
- )
- )
diff --git a/addons/mail/models/mail_render_mixin.py b/addons/mail/models/mail_render_mixin.py
index 9e8d2cff87d..39874b7b25c 100644
--- a/addons/mail/models/mail_render_mixin.py
+++ b/addons/mail/models/mail_render_mixin.py
@@ -104,6 +104,16 @@ class MailRenderMixin(models.AbstractModel):
self._check_access_right_dynamic_template()
return True
+ def _update_field_translations(self, fname, translations, digest=None):
+ res = super()._update_field_translations(fname, translations, digest)
+ # TBD the below check is only for model_term translations.
+ # Because for model translations, super().update_field_translations will call write to check
+ if self._unrestricted_rendering:
+ # If the rendering is unrestricted (e.g. mail.template),
+ # check the user is part of the mail editor group to modify a template if the template is dynamic
+ self._check_access_right_dynamic_template()
+ return res
+
# ------------------------------------------------------------
# TOOLS
# ------------------------------------------------------------
diff --git a/addons/mail/models/template_reset_mixin.py b/addons/mail/models/template_reset_mixin.py
index b03aa5362d5..9b7b59fce3d 100644
--- a/addons/mail/models/template_reset_mixin.py
+++ b/addons/mail/models/template_reset_mixin.py
@@ -11,7 +11,7 @@ from odoo.modules import get_module_resource
from odoo.modules.module import get_resource_from_path, get_resource_path
from odoo.tools.convert import xml_import
from odoo.tools.misc import file_open
-from odoo.tools.translate import TranslationFileReader
+from odoo.tools.translate import TranslationFileReader, _trans_load_data
class TemplateResetMixin(models.AbstractModel):
@@ -62,10 +62,8 @@ class TemplateResetMixin(models.AbstractModel):
def _override_translation_term(self, module_name, xml_ids):
processed_base_langs = []
- trans_to_remove = []
for code, _ in self.env['res.lang'].get_installed():
lang_code = tools.get_iso_codes(code)
- translation_processed = False
# In case of sub languages (e.g fr_BE), load the base language first, (e.g fr.po) and
# then load the main translation file (e.g fr_BE.po)
@@ -76,28 +74,17 @@ class TemplateResetMixin(models.AbstractModel):
if base_trans_file:
if base_lang_code not in processed_base_langs:
processed_base_langs.append(base_lang_code)
- translation_processed = self._process_translation_data(base_trans_file, code, xml_ids)
+ self._process_translation_data(base_trans_file, code, xml_ids)
# Step 2: reset translation file with main language file (can possibly override the
# terms coming from the base language)
trans_file = get_module_resource(module_name, 'i18n', lang_code + '.po')
if trans_file:
- translation_processed = self._process_translation_data(trans_file, code, xml_ids)
+ self._process_translation_data(trans_file, code, xml_ids)
- # If no translation data available to update, unlink custom translated terms linked to template
- if not translation_processed:
- trans_to_remove.append(code)
-
- if trans_to_remove:
- self.env['ir.translation'].search([
- ('name', 'like', f'{self._name},%'),
- ('res_id', '=', self.id),
- ('lang', 'in', trans_to_remove),
- ('module', 'in', [module_name, False]),
- ]).unlink()
def _process_translation_data(self, trans_file, lang, xml_ids):
- """Populates the ir_translation table.
+ """Load translations.
:param trans_file: path to a translation file to open, e.g. {addon_path}/mail/i18n/es.po
:param lang: language code of the translations contained in `trans_file`
@@ -106,17 +93,9 @@ class TemplateResetMixin(models.AbstractModel):
:return Boolean: True if translation data is available and processed, False otherwise
"""
with file_open(trans_file, mode='rb', filter_ext=(".po",)) as fileobj:
- translation_processed = False
reader = TranslationFileReader(fileobj)
# Process a single PO entry
- for row in reader:
- if row.get('imd_name') in xml_ids:
- translation_processed = True
- # copy Translation from Source to Destination object
- self.env['ir.translation']._set_ids(
- row['name'], 'model', lang, self._ids, row.get('value', ''), row['src'],
- )
- return translation_processed
+ _trans_load_data(self.env.cr, reader, lang, overwrite=True, force_overwrite=True, xml_ids=xml_ids)
def reset_template(self):
"""Resets the Template with values given in source file. We ignore the case of
diff --git a/addons/mail/tests/common.py b/addons/mail/tests/common.py
index 26fbd22c4d3..d8061f99946 100644
--- a/addons/mail/tests/common.py
+++ b/addons/mail/tests/common.py
@@ -1117,84 +1117,11 @@ class MailCommon(common.TransactionCase, MailCase):
# Make sure Spanish translations have not been altered
if test_record:
- description_translations = cls.env['ir.translation'].search([
- ('module', '=', 'test_mail'),
- ('src', '=', test_record._description),
- ('lang', '=', lang_code)
- ])
- if description_translations:
- description_translations.update({'value': 'Spanish description'})
- else:
- description_translations.create({
- 'lang': lang_code,
- 'module': 'test_mail',
- 'name': 'ir.model,name',
- 'res_id': cls.env['ir.model']._get_id(test_record._name),
- 'src': test_record._description,
- 'state': 'translated',
- 'type': 'model',
- 'value': 'Spanish description',
- })
-
- translations_tocreate = []
- # Have a TestStuff always available
- test_stuff_translations = cls.env['ir.translation'].search([
- ('module', '=', 'test_mail'),
- ('src', '=', 'TestStuff'),
- ('lang', '=', lang_code)
- ])
- if test_stuff_translations:
- test_stuff_translations.update({'value': 'TestSpanishStuff'})
- else:
- translations_tocreate.append({
- 'lang': lang_code,
- 'name': 'idontknow',
- 'module': 'test_mail',
- 'res_id': False,
- 'src': 'TestStuff',
- 'state': 'translated',
- 'type': 'code',
- 'value': 'TestSpanishStuff',
- })
-
- view_translations = cls.env['ir.translation'].search([
- ('module', '=', 'mail'),
- ('src', '=', 'View %s'),
- ('lang', '=', lang_code)
- ])
- if view_translations:
- view_translations.update({'value': 'SpanishView'})
- else:
- translations_tocreate.append({
- 'lang': lang_code,
- 'name': 'idontknow',
- 'module': 'mail',
- 'res_id': False,
- 'src': 'View %s',
- 'state': 'translated',
- 'type': 'code',
- 'value': 'SpanishView %s',
- })
+ cls.env['ir.model']._get(test_record._name).with_context(lang=lang_code).name = 'Spanish description'
# Prepare some translated value for template if given
- if test_template:
- translations_tocreate += [{
- 'lang': lang_code,
- 'module': 'mail',
- 'name': 'mail.template,subject',
- 'res_id': test_template.id,
- 'state': 'translated',
- 'type': 'model',
- 'value': 'SpanishSubject for {{ object.name }}',
- }, {
- 'lang': lang_code,
- 'module': 'mail',
- 'name': 'mail.template,body_html',
- 'res_id': test_template.id,
- 'state': 'translated',
- 'type': 'model',
- 'value': '
SpanishBody for
',
- }]
+ test_template.with_context(lang=lang_code).subject = 'SpanishSubject for {{ object.name }}'
+ test_template.with_context(lang=lang_code).body_html = 'SpanishBody for
'
# create a custom layout for email notification
if not layout_arch_db:
@@ -1233,17 +1160,11 @@ class MailCommon(common.TransactionCase, MailCase):
'name': 'test_layout',
'res_id': view.id
})
- translations_tocreate.append({
- 'lang': lang_code,
- 'module': 'mail',
- 'name': 'ir.ui.view,arch_db',
- 'res_id': view.id,
- 'src': 'English Layout for',
- 'state': 'translated',
- 'type': 'model_terms',
- 'value': 'Spanish Layout para',
+ view.update_field_translations('arch_db', {
+ lang_code: {
+ 'English Layout for': 'Spanish Layout para'
+ }
})
- cls.env['ir.translation'].create(translations_tocreate)
def _generate_attachments_data(self, count, res_model=None, res_id=None, attach_values=None):
# attachment visibility depends on what they are attached to
diff --git a/addons/mail/tests/test_mail_render.py b/addons/mail/tests/test_mail_render.py
index 1c3c8202653..7a601f87a35 100644
--- a/addons/mail/tests/test_mail_render.py
+++ b/addons/mail/tests/test_mail_render.py
@@ -112,22 +112,9 @@ class TestMailRender(common.MailCommon):
})
# some translations
- cls.env['ir.translation'].create({
- 'type': 'model',
- 'name': 'mail.template,subject',
- 'lang': 'fr_FR',
- 'res_id': cls.test_template.id,
- 'src': cls.test_template.subject,
- 'value': cls.base_qweb_bits_fr[0],
- })
- cls.env['ir.translation'].create({
- 'type': 'model',
- 'name': 'mail.template,body_html',
- 'lang': 'fr_FR',
- 'res_id': cls.test_template.id,
- 'src': cls.test_template.body_html,
- 'value': cls.base_qweb_bits_fr[1],
- })
+ cls.test_template.with_context(lang='fr_FR').subject = cls.base_qweb_bits_fr[0]
+ cls.test_template.with_context(lang='fr_FR').body_html = cls.base_qweb_bits_fr[1]
+
cls.env['ir.model.data'].create({
'name': 'test_template_xmlid',
'module': 'mail',
diff --git a/addons/mail/tests/test_mail_template.py b/addons/mail/tests/test_mail_template.py
index 6d7d50df4fe..75ab522fad7 100644
--- a/addons/mail/tests/test_mail_template.py
+++ b/addons/mail/tests/test_mail_template.py
@@ -107,62 +107,29 @@ class TestMailTemplate(MailCommon):
'body_html': 'foo
',
})
- Translation = self.env['ir.translation']
-
### check qweb dynamic
- Translation.insert_missing(employee_template._fields['body_html'], employee_template)
- employee_translations_of_body = Translation.with_user(self.user_employee).search(
- [('res_id', '=', employee_template.id), ('name', '=', 'mail.template,body_html'), ('lang', '=', 'fr_FR')],
- limit=1
- )
- # keep a copy to create new translation later
- body_translation_vals = employee_translations_of_body.read([])[0]
-
# write on translation for template without dynamic code is allowed
- employee_translations_of_body.value = 'non-qweb'
+ employee_template.with_context(lang='fr_FR').body_html = 'non-qweb'
# cannot write dynamic code on mail_template translation for employee without the group mail_template_editor.
with self.assertRaises(AccessError):
- employee_translations_of_body.value = ''
+ employee_template.with_context(lang='fr_FR').body_html = ''
- employee_translations_of_body.unlink() # delete old translation, to test the creation now
- body_translation_vals['value'] = ''
-
- # admin can create
- new = Translation.create(body_translation_vals)
- new.unlink()
-
- # Employee without mail_template_editor group cannot create dynamic translation for mail.render.mixin
- with self.assertRaises(AccessError):
- Translation.with_user(self.user_employee).create(body_translation_vals)
+ employee_template.with_context(lang='fr_FR').sudo().body_html = ''
+ # reset the body_html to static
+ employee_template.body_html = False
+ employee_template.body_html = 'foo
'
### check qweb inline dynamic
- Translation.insert_missing(employee_template._fields['subject'], employee_template)
- employee_translations_of_subject = Translation.with_user(self.user_employee).search(
- [('res_id', '=', employee_template.id), ('name', '=', 'mail.template,subject'), ('lang', '=', 'fr_FR')],
- limit=1
- )
- # keep a copy to create new translation later
- subject_translation_vals = employee_translations_of_subject.read([])[0]
-
# write on translation for template without dynamic code is allowed
- employee_translations_of_subject.value = 'non-qweb'
+ employee_template.with_context(lang='fr_FR').subject = 'non-qweb'
# cannot write dynamic code on mail_template translation for employee without the group mail_template_editor.
with self.assertRaises(AccessError):
- employee_translations_of_subject.value = '{{ object.foo }}'
+ employee_template.with_context(lang='fr_FR').subject = '{{ object.foo }}'
- employee_translations_of_subject.unlink() # delete old translation, to test the creation now
- subject_translation_vals['value'] = '{{ object.foo }}'
-
- # admin can create
- new = Translation.create(subject_translation_vals)
- new.unlink()
-
- # Employee without mail_template_editor group cannot create dynamic translation for mail.render.mixin
- with self.assertRaises(AccessError):
- Translation.with_user(self.user_employee).create(subject_translation_vals)
+ employee_template.with_context(lang='fr_FR').sudo().subject = '{{ object.foo }}'
def test_server_archived_usage_protection(self):
""" Test the protection against using archived server (servers used cannot be archived) """
diff --git a/addons/privacy_lookup/wizard/privacy_lookup_wizard.py b/addons/privacy_lookup/wizard/privacy_lookup_wizard.py
index 1be671a4b25..4d362285ee5 100644
--- a/addons/privacy_lookup/wizard/privacy_lookup_wizard.py
+++ b/addons/privacy_lookup/wizard/privacy_lookup_wizard.py
@@ -131,6 +131,8 @@ class PrivacyLookupWizard(models.TransientModel):
rec_name = model._rec_name or 'name'
is_normalized = field_name == 'email_normalized' or (model_name == 'mailing.trace' and field_name == 'email')
if rec_name in model and model._fields[model._rec_name].type == 'char':
+ if model._fields[model._rec_name].translate:
+ rec_name = f"jsonb_path_query_array({rec_name}, '$.*')::text"
additional_query += """
{field_name} {search_type} %s OR {rec_name} ilike %s
""".format(
diff --git a/addons/purchase_stock/tests/test_reordering_rule.py b/addons/purchase_stock/tests/test_reordering_rule.py
index 9e934fb1c3b..028a0ca1fec 100644
--- a/addons/purchase_stock/tests/test_reordering_rule.py
+++ b/addons/purchase_stock/tests/test_reordering_rule.py
@@ -576,8 +576,8 @@ class TestReorderingRule(TransactionCase):
])],
})
self.env['res.lang']._activate_lang('fr_FR')
- self.env['ir.translation']._set_ids('product.template,name', 'model', 'fr_FR', product.product_tmpl_id.ids, 'produit en français')
- self.env['ir.translation']._set_ids('product.product,name', 'model', 'fr_FR', product.ids, 'produit en français')
+ product.product_tmpl_id.with_context(lang='fr_FR').name = 'produit en français'
+ product.with_context(lang='fr_FR').name = 'produit en français'
default_vendor = self.env["res.partner"].create({
"name": "Supplier A",
})
diff --git a/addons/sale_management/models/sale_order_template.py b/addons/sale_management/models/sale_order_template.py
index 34369d1271a..648880d8d16 100644
--- a/addons/sale_management/models/sale_order_template.py
+++ b/addons/sale_management/models/sale_order_template.py
@@ -94,33 +94,7 @@ class SaleOrderTemplate(models.Model):
for lang in languages:
for line in self.sale_order_template_line_ids:
if line.name == line.product_id.get_product_multiline_description_sale():
- self.create_or_update_translations(
- model_name='sale.order.template.line,name', lang_code=lang.code,
- res_id=line.id, src=line.name,
- value=line.product_id.with_context(lang=lang.code).get_product_multiline_description_sale())
+ line.with_context(lang=lang.code).name = line.product_id.with_context(lang=lang.code).get_product_multiline_description_sale()
for option in self.sale_order_template_option_ids:
if option.name == option.product_id.get_product_multiline_description_sale():
- self.create_or_update_translations(
- model_name='sale.order.template.option,name', lang_code=lang.code,
- res_id=option.id, src=option.name,
- value=option.product_id.with_context(lang=lang.code).get_product_multiline_description_sale())
-
- def create_or_update_translations(self, model_name, lang_code, res_id, src, value):
- data = {
- 'type': 'model',
- 'name': model_name,
- 'lang': lang_code,
- 'res_id': res_id,
- 'src': src,
- 'value': value,
- 'state': 'inprogress',
- }
- existing_trans = self.env['ir.translation'].search([
- ('name', '=', model_name),
- ('res_id', '=', res_id),
- ('lang', '=', lang_code)
- ])
- if not existing_trans:
- self.env['ir.translation'].create(data)
- else:
- existing_trans.write(data)
+ option.with_context(lang=lang.code).name = option.product_id.with_context(lang=lang.code).get_product_multiline_description_sale()
diff --git a/addons/test_mail/tests/test_message_post.py b/addons/test_mail/tests/test_message_post.py
index 7341c9fd44a..56ddcbf4a2b 100644
--- a/addons/test_mail/tests/test_message_post.py
+++ b/addons/test_mail/tests/test_message_post.py
@@ -19,6 +19,7 @@ from odoo.exceptions import AccessError
from odoo.tests import tagged
from odoo.tools import mute_logger, formataddr
from odoo.tests.common import users
+from odoo.tools.translate import code_translations
class TestMessagePostCommon(TestMailCommon, TestRecipients):
@@ -1240,6 +1241,12 @@ class TestMessagePostLang(TestMailCommon, TestRecipients):
@users('employee')
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_layout_email_lang_context(self):
+ # hack into code translations:
+ code_translations.python_translations[('test_mail', 'es_ES')] = {'TestStuff': 'TestSpanishStuff'}
+ self.addCleanup(code_translations.python_translations.pop, ('test_mail', 'es_ES'))
+ code_translations.python_translations[('mail', 'es_ES')] = {'View %s': 'SpanishView %s'}
+ self.addCleanup(code_translations.python_translations.pop, ('mail', 'es_ES'))
+
test_records = self.test_records.with_user(self.env.user).with_context(lang='es_ES')
test_records[1].message_subscribe(self.partner_2.ids)
diff --git a/addons/test_mail_sms/tests/test_sms_composer.py b/addons/test_mail_sms/tests/test_sms_composer.py
index 3c54a009c32..03ed57fccaf 100644
--- a/addons/test_mail_sms/tests/test_sms_composer.py
+++ b/addons/test_mail_sms/tests/test_sms_composer.py
@@ -415,14 +415,7 @@ class TestSMSComposerMass(TestSMSCommon):
def test_composer_mass_active_ids_w_template_and_lang(self):
self.env['res.lang']._activate_lang('fr_FR')
- self.env['ir.translation'].create({
- 'type': 'model',
- 'name': 'sms.template,body',
- 'lang': 'fr_FR',
- 'res_id': self.sms_template.id,
- 'src': self.sms_template.body,
- 'value': 'Cher·e· {{ object.display_name }} ceci est un SMS.',
- })
+ self.sms_template.with_context(lang='fr_FR').body = 'Cher·e· {{ object.display_name }} ceci est un SMS.'
# set template to try to use customer lang
self.sms_template.write({
'lang': '{{ object.customer_id.lang }}',
@@ -481,14 +474,7 @@ class TestSMSComposerMass(TestSMSCommon):
and the usage with the sms composer """
# Create the lang info
self.env['res.lang']._activate_lang('fr_FR')
- self.env['ir.translation'].create({
- 'type': 'model',
- 'name': 'sms.template,body',
- 'lang': 'fr_FR',
- 'res_id': self.sms_template.id,
- 'src': self.sms_template.body,
- 'value': "Hello {{ object.display_name }} ceci est en français.",
- })
+ self.sms_template.with_context(lang='fr_FR').body = 'Hello {{ object.display_name }} ceci est en français.'
# set template to try to use customer lang
self.sms_template.write({
'lang': '{{ object.customer_id.lang }}',
diff --git a/addons/test_mail_sms/tests/test_sms_template.py b/addons/test_mail_sms/tests/test_sms_template.py
index beebc78124a..225a95fdda3 100644
--- a/addons/test_mail_sms/tests/test_sms_template.py
+++ b/addons/test_mail_sms/tests/test_sms_template.py
@@ -29,13 +29,8 @@ class TestSmsTemplate(TestSMSCommon, TestSMSRecipients):
def test_sms_template_lang(self):
self.env['res.lang']._activate_lang('fr_FR')
self.user_admin.write({'lang': 'en_US'})
- self.env['ir.translation'].create({
- 'type': 'model',
- 'name': 'sms.template,body',
- 'lang': 'fr_FR',
- 'res_id': self.sms_template.id,
- 'src': self.sms_template.body,
- 'value': self.body_fr,
+ self.sms_template.update_field_translations('body', {
+ 'fr_FR': self.body_fr
})
# set template to try to use customer lang
self.sms_template.write({
diff --git a/addons/test_website/tests/test_is_multilang.py b/addons/test_website/tests/test_is_multilang.py
index 167256c15bf..22167234d01 100644
--- a/addons/test_website/tests/test_is_multilang.py
+++ b/addons/test_website/tests/test_is_multilang.py
@@ -38,21 +38,10 @@ class TestIsMultiLang(odoo.tests.HttpCase):
website.domain = self.base_url() # for _is_canonical_url
website.default_lang_id = en
website.language_ids = en + it + be
- params = {
- 'src': country1.name,
- 'value': country1.name + ' Italia',
- 'type': 'model',
- 'name': 'res.country,name',
- 'res_id': country1.id,
- 'lang': it.code,
- 'state': 'translated',
- }
- self.env['ir.translation'].create(params)
- params.update({
- 'value': country1.name + ' Belgium',
- 'lang': be.code,
+ country1.update_field_translations('name', {
+ it.code: country1.name + ' Italia',
+ be.code: country1.name + ' Belgium'
})
- self.env['ir.translation'].create(params)
r = self.url_open(f'/test_lang_url/{country1.id}')
self.assertEqual(r.status_code, 200)
diff --git a/addons/website/models/ir_module_module.py b/addons/website/models/ir_module_module.py
index 24ed8667643..69988752211 100644
--- a/addons/website/models/ir_module_module.py
+++ b/addons/website/models/ir_module_module.py
@@ -3,7 +3,7 @@
import logging
import os
-from collections import OrderedDict
+from collections import defaultdict, OrderedDict
from odoo import api, fields, models
from odoo.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG
@@ -188,14 +188,31 @@ class IrModuleModule(models.Model):
def _post_copy(self, old_rec, new_rec):
self.ensure_one()
translated_fields = self._theme_translated_fields.get(old_rec._name, [])
+ cur_lang = self.env.lang or 'en_US'
+ old_rec.flush_recordset()
for (src_field, dst_field) in translated_fields:
- self._cr.execute("""INSERT INTO ir_translation (lang, src, name, res_id, state, value, type, module)
- SELECT t.lang, t.src, %s, %s, t.state, t.value, t.type, t.module
- FROM ir_translation t
- WHERE name = %s
- AND res_id = %s
- ON CONFLICT DO NOTHING""",
- (dst_field, new_rec.id, src_field, old_rec.id))
+ __, src_fname = src_field.split(',')
+ dst_mname, dst_fname = dst_field.split(',')
+ if dst_mname != new_rec._name:
+ continue
+ old_field = old_rec._fields[src_fname]
+ old_translations = old_field._get_stored_translations(old_rec)
+ if not old_translations:
+ continue
+ if not callable(old_field.translate):
+ if old_rec[src_fname] == new_rec[dst_fname]:
+ new_rec.update_field_translations(dst_fname, old_translations)
+ else:
+ old_translation_lang = old_translations.get(cur_lang) or old_translations.get('en_US')
+ # {from_lang_term: {lang: to_lang_term}
+ translation_dictionary = old_field.get_translation_dictionary(old_translation_lang, {
+ lang: value for lang, value in old_translations.items() if lang != cur_lang})
+ # {lang: {old_term: new_term}
+ translations = defaultdict(dict)
+ for from_lang_term, to_lang_terms in translation_dictionary.items():
+ for lang, to_lang_term in to_lang_terms.items():
+ translations[lang][from_lang_term] = to_lang_term
+ new_rec.with_context(install_filename='dummy').update_field_translations(dst_fname, translations)
def _theme_load(self, website):
"""
diff --git a/addons/website/models/website.py b/addons/website/models/website.py
index 3114f59fe12..61ef236875a 100644
--- a/addons/website/models/website.py
+++ b/addons/website/models/website.py
@@ -1379,12 +1379,13 @@ class Website(models.Model):
def _get_cached(self, field):
return self._get_cached_values()[field]
- def _get_html_fields(self):
- html_fields = [('ir_ui_view', 'arch_db')]
+ def _get_html_fields_attributes(self):
+ html_fields = [('ir_ui_view', 'arch_db', True)]
cr = self.env.cr
cr.execute(r"""
SELECT f.model,
- f.name
+ f.name,
+ f.translate
FROM ir_model_fields f
JOIN ir_model m
ON m.id = f.model_id
@@ -1394,10 +1395,10 @@ class Website(models.Model):
AND f.model NOT LIKE 'ir.actions%'
AND f.model != 'mail.message'
""")
- for model, name in cr.fetchall():
+ for model, name, translate in cr.fetchall():
table = self.env[model]._table
if tools.table_exists(cr, table) and tools.column_exists(cr, table, name):
- html_fields.append((table, name))
+ html_fields.append((table, name, translate))
return html_fields
def _get_snippets_assets(self):
@@ -1428,7 +1429,7 @@ class Website(models.Model):
""")
return self.env.cr.fetchall()
- def _is_snippet_used(self, snippet_module, snippet_id, asset_version, asset_type, html_fields):
+ def _is_snippet_used(self, snippet_module, snippet_id, asset_version, asset_type, html_fields_attributes):
snippet_occurences = []
# Check snippet template definition to avoid disabling its related assets.
# This special case is needed because snippet template definitions do not
@@ -1440,11 +1441,12 @@ class Website(models.Model):
# As well as every snippet dropped in html fields
self.env.cr.execute(sql.SQL(" UNION ").join(
- sql.SQL("SELECT regexp_matches({}, {}, 'g') FROM {}").format(
+ sql.SQL("SELECT regexp_matches({}{}, {}, 'g') FROM {}").format(
sql.Identifier(column),
+ sql.SQL("->>'en_US'" if translate else ''),
sql.Placeholder('snippet_regex'),
sql.Identifier(table)
- ) for table, column in html_fields
+ ) for table, column, translate in html_fields_attributes
), {'snippet_regex': f'<([^>]*data-snippet="{snippet_id}"[^>]*)>'})
results = self.env.cr.fetchall()
for r in results:
@@ -1461,10 +1463,10 @@ class Website(models.Model):
def _disable_unused_snippets_assets(self):
snippets_assets = self._get_snippets_assets()
- html_fields = self._get_html_fields()
+ html_fields_attributes = self._get_html_fields_attributes()
for snippet_module, snippet_id, asset_version, asset_type, _asset_id in snippets_assets:
- is_snippet_used = self._is_snippet_used(snippet_module, snippet_id, asset_version, asset_type, html_fields)
+ is_snippet_used = self._is_snippet_used(snippet_module, snippet_id, asset_version, asset_type, html_fields_attributes)
# The regex catches XXX.scss, XXX.js and XXX_variables.scss
assets_regex = f'{snippet_id}/{asset_version}.+{asset_type}'
@@ -1652,6 +1654,7 @@ class Website(models.Model):
"""
match_pattern = r'[\w-]{%s,}' % min(4, len(search) - 3)
similarity_threshold = 0.3
+ lang = self.env.lang or 'en_US'
for search_detail in search_details:
model_name, fields = search_detail['model'], search_detail['search_fields']
model = self.env[model_name]
@@ -1668,6 +1671,11 @@ class Website(models.Model):
field=unaccent(sql.SQL("{table}.{field}").format(
table=sql.Identifier((self.env['ir.ui.view'] if field == 'arch_db' or (field == 'name' and 'arch_db' in fields) else model)._table),
field=sql.Identifier(field)
+ )) if not (self.env['ir.ui.view'] if field == 'arch_db' or (field == 'name' and 'arch_db' in fields) else model)._fields[field].translate else
+ unaccent(sql.SQL("COALESCE({table}.{field}->>{lang}, {table}.{field}->>'en_US')").format(
+ table=sql.Identifier((self.env['ir.ui.view'] if field == 'arch_db' or (field == 'name' and 'arch_db' in fields) else model)._table),
+ field=sql.Identifier(field),
+ lang=sql.Literal(lang)
))
) for field in fields]
best_similarity = sql.SQL('GREATEST({similarities})').format(
@@ -1698,55 +1706,6 @@ class Website(models.Model):
)
self.env.cr.execute(query, {'search': search})
ids = {row[0] for row in self.env.cr.fetchall() if row[1] and row[1] >= similarity_threshold}
- if self.env.lang:
- # Specific handling for website.page that inherits its arch_db and name fields
- # TODO make more generic
- if 'arch_db' in fields:
- # Look for partial translations
- similarity = sql.SQL("word_similarity({search}, {field})").format(
- search=unaccent(sql.Placeholder('search')),
- field=unaccent(sql.SQL('t.value'))
- )
- names = ['%s,%s' % (self.env['ir.ui.view']._name, field) for field in fields]
- query = sql.SQL("""
- SELECT {table}.id, {similarity} AS _similarity
- FROM {table}
- LEFT JOIN ir_ui_view v ON {table}.view_id = v.id
- LEFT JOIN ir_translation t ON v.id = t.res_id
- WHERE t.lang = {lang}
- AND t.name = ANY({names})
- AND t.type = 'model_terms'
- AND t.value IS NOT NULL
- ORDER BY _similarity desc
- LIMIT 1000
- """).format(
- table=sql.Identifier(model._table),
- similarity=similarity,
- lang=sql.Placeholder('lang'),
- names=sql.Placeholder('names'),
- )
- else:
- similarity = sql.SQL("word_similarity({search}, {field})").format(
- search=unaccent(sql.Placeholder('search')),
- field=unaccent(sql.SQL('value'))
- )
- names = ['%s,%s' % (model._name, field) for field in fields]
- query = sql.SQL("""
- SELECT res_id, {similarity} AS _similarity
- FROM ir_translation
- WHERE lang = {lang}
- AND name = ANY({names})
- AND type = 'model'
- AND value IS NOT NULL
- ORDER BY _similarity desc
- LIMIT 1000
- """).format(
- similarity=similarity,
- lang=sql.Placeholder('lang'),
- names=sql.Placeholder('names'),
- )
- self.env.cr.execute(query, {'lang': self.env.lang, 'names': names, 'search': search})
- ids.update(row[0] for row in self.env.cr.fetchall() if row[1] and row[1] >= similarity_threshold)
domain.append([('id', 'in', list(ids))])
domain = AND(domain)
records = model.search_read(domain, fields, limit=limit)
diff --git a/addons/website/models/website_page.py b/addons/website/models/website_page.py
index 2a0945456d4..eeb9139dc9d 100644
--- a/addons/website/models/website_page.py
+++ b/addons/website/models/website_page.py
@@ -231,28 +231,18 @@ class Page(models.Model):
# Perform search in translations
# TODO Remove when domains will support xml_translate fields
query = sql.SQL("""
- SELECT {table}.{id}
+ SELECT {table}.id
FROM {table}
- LEFT JOIN ir_ui_view v ON {table}.{view_id} = v.{id}
- LEFT JOIN ir_translation t ON v.{id} = t.{res_id}
- WHERE t.lang = {lang}
- AND t.name = ANY({names})
- AND t.type = 'model_terms'
- AND t.value ilike {search}
+ LEFT JOIN ir_ui_view v ON {table}.view_id = v.id
+ WHERE v.name ILIKE {search}
+ OR jsonb_path_query_array(v.arch_db,'$.*')::text ILIKE {search}
LIMIT {limit}
""").format(
table=sql.Identifier(self._table),
- id=sql.Identifier('id'),
- view_id=sql.Identifier('view_id'),
- res_id=sql.Identifier('res_id'),
- lang=sql.Placeholder('lang'),
- names=sql.Placeholder('names'),
search=sql.Placeholder('search'),
limit=sql.Placeholder('limit'),
)
self.env.cr.execute(query, {
- 'lang': self.env.lang,
- 'names': ['ir.ui.view,arch_db', 'ir.ui.view,name'],
'search': '%%%s%%' % escape_psql(search),
'limit': limit,
})
diff --git a/addons/website/tests/test_configurator.py b/addons/website/tests/test_configurator.py
index 641c7406b0d..5cf817e609a 100644
--- a/addons/website/tests/test_configurator.py
+++ b/addons/website/tests/test_configurator.py
@@ -56,11 +56,10 @@ class TestConfiguratorCommon(odoo.tests.HttpCase):
class TestConfiguratorTranslation(TestConfiguratorCommon):
def test_01_configurator_translation(self):
- with mute_logger('odoo.addons.base.models.ir_translation'):
- self.env["base.language.install"].create({
- 'overwrite': True,
- 'lang_ids': [(6, 0, [self.env.ref('base.lang_fr').id])],
- }).lang_install()
+ self.env["base.language.install"].create({
+ 'overwrite': True,
+ 'lang_ids': [(6, 0, [self.env.ref('base.lang_fr').id])],
+ }).lang_install()
feature = self.env['website.configurator.feature'].search([('name', '=', 'Privacy Policy')])
feature.with_context(lang='fr_FR').write({'name': 'Politique de confidentialité'})
self.env.ref('base.user_admin').write({'lang': self.env.ref('base.lang_fr').code})
diff --git a/addons/website/tests/test_controllers.py b/addons/website/tests/test_controllers.py
index 2b9acdf929a..ae7a843c291 100644
--- a/addons/website/tests/test_controllers.py
+++ b/addons/website/tests/test_controllers.py
@@ -20,6 +20,7 @@ class TestControllers(tests.HttpCase):
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
suggested_links_url = base_url + '/website/get_suggested_links'
+ old_pages = Page
for i in range(0, 10):
new_page = Page.create({
'name': 'Generic',
@@ -32,11 +33,14 @@ class TestControllers(tests.HttpCase):
'is_published': True,
})
if i % 2 == 0:
- # mark as old
- new_page._write({'write_date': '2020-01-01'})
+ old_pages += new_page
else:
last_5_url_edited.append(new_page.url)
+ self.opener.post(url=suggested_links_url, json={'params': {'needle': '/'}})
+ # mark as old
+ old_pages._write({'write_date': '2020-01-01'})
+
res = self.opener.post(url=suggested_links_url, json={'params': {'needle': '/'}})
resp = json.loads(res.content)
assert 'result' in resp
diff --git a/addons/website/tests/test_disable_unused_snippets_assets.py b/addons/website/tests/test_disable_unused_snippets_assets.py
index 4be813cb03d..87a9d0f2f1f 100644
--- a/addons/website/tests/test_disable_unused_snippets_assets.py
+++ b/addons/website/tests/test_disable_unused_snippets_assets.py
@@ -63,9 +63,11 @@ class TestDisableSnippetsAssets(TransactionCase):
self.homepage.write({
'arch_db': HOMEPAGE_UP_TO_DATE,
})
+ self.homepage.flush_recordset()
self.mega_menu.write({
'mega_menu_content': MEGA_MENU_OUTDATED,
})
+ self.mega_menu.flush_recordset()
self.Website._disable_unused_snippets_assets()
s_website_form_000_scss = self._get_snippet_asset('s_website_form', '000', 'scss')
diff --git a/addons/website/tests/test_menu.py b/addons/website/tests/test_menu.py
index eb0e721292b..b391304be45 100644
--- a/addons/website/tests/test_menu.py
+++ b/addons/website/tests/test_menu.py
@@ -69,7 +69,7 @@ class TestMenu(common.TransactionCase):
self.assertEqual(total_menus + 4, Menu.search_count([]), "New website's bootstraping should have duplicate default menu tree (Top/Home/Contactus/Sub Default Menu)")
def test_04_specific_menu_translation(self):
- Translation = self.env['ir.translation']
+ IrModuleModule = self.env['ir.module.module']
Menu = self.env['website.menu']
existing_menus = Menu.search([])
@@ -85,9 +85,6 @@ class TestMenu(common.TransactionCase):
# create fr_FR translation for template menu
self.env.ref('base.lang_fr').active = True
template_menu.with_context(lang='fr_FR').name = 'Menu en français'
- Translation.search([
- ('name', '=', 'website.menu,name'), ('res_id', '=', template_menu.id),
- ]).module = 'website'
self.assertEqual(specific1.name, 'Menu in english',
'Translating template menu does not translate specific menu')
@@ -95,7 +92,7 @@ class TestMenu(common.TransactionCase):
specific1.name = 'Menu in french'
# loading translation add missing specific translation
- Translation._load_module_terms(['website'], ['fr_FR'])
+ IrModuleModule._load_module_terms(['website'], ['fr_FR'])
Menu.invalidate_model(['name'])
self.assertEqual(specific1.name, 'Menu in french',
'Load translation without overwriting keep existing translation')
@@ -103,7 +100,7 @@ class TestMenu(common.TransactionCase):
'Load translation add missing translation from template menu')
# loading translation with overwrite sync all translations from menu template
- Translation._load_module_terms(['website'], ['fr_FR'], overwrite=True)
+ IrModuleModule._load_module_terms(['website'], ['fr_FR'], overwrite=True)
Menu.invalidate_model(['name'])
self.assertEqual(specific1.name, 'Menu en français',
'Load translation with overwriting update existing menu from template')
diff --git a/addons/website/tests/test_views.py b/addons/website/tests/test_views.py
index 1dcdeb807f2..a039f42b219 100644
--- a/addons/website/tests/test_views.py
+++ b/addons/website/tests/test_views.py
@@ -995,29 +995,25 @@ class TestCowViewSaving(TestViewSavingCommon):
View.pool._init = original_pool_init
def test_specific_view_translation(self):
- Translation = self.env['ir.translation']
-
- Translation.insert_missing(self.base_view._fields['arch_db'], self.base_view)
- translation = Translation.search([
- ('res_id', '=', self.base_view.id), ('name', '=', 'ir.ui.view,arch_db')
- ])
- translation.value = 'hello'
- translation.module = 'website'
-
+ self.env['res.lang']._activate_lang('fr_BE')
+ self.base_view.with_context(lang='en_US').arch_db = 'hello
'
+ self.base_view.update_field_translations('arch_db', {'fr_BE': {'hello': 'bonjour'}})
+ self.assertEqual(self.base_view.with_context(lang='fr_BE').arch, 'bonjour
')
self.base_view.with_context(website_id=1).write({'active': True})
specific_view = self.base_view._get_specific_views() - self.base_view
- self.assertEqual(specific_view.with_context(lang='en_US').arch, 'hello
',
+ self.assertEqual(specific_view.with_context(lang='fr_BE').arch, 'bonjour
',
"copy on write (COW) also copy existing translations")
- translation.value = 'hi'
- self.assertEqual(specific_view.with_context(lang='en_US').arch, 'hello
',
+ self.base_view.update_field_translations('arch_db', {'fr_BE': {'bonjour': 'salut'}})
+ self.assertEqual(self.base_view.with_context(lang='fr_BE').arch, 'salut
')
+ self.assertEqual(specific_view.with_context(lang='fr_BE').arch, 'bonjour
',
"updating translation of base view doesn't update specific view")
- Translation._load_module_terms(['website'], ['en_US'], overwrite=True)
+ self.env['ir.module.module']._load_module_terms(['website'], ['en_US', 'fr_BE'], overwrite=True)
specific_view.invalidate_model(['arch_db', 'arch'])
- self.assertEqual(specific_view.with_context(lang='en_US').arch, 'hi
',
+ self.assertEqual(specific_view.with_context(lang='fr_BE').arch, 'salut
',
"loading module translation copy translation from base to specific view")
def test_soc_complete_flow(self):
diff --git a/addons/website_blog/tests/test_performance.py b/addons/website_blog/tests/test_performance.py
index 164656e44c2..b05c8b8a4f1 100644
--- a/addons/website_blog/tests/test_performance.py
+++ b/addons/website_blog/tests/test_performance.py
@@ -14,7 +14,7 @@ class TestBlogPerformance(UtilPerf):
def test_10_perf_sql_blog_standard_data(self):
self.assertEqual(self._get_url_hot_query('/blog'), 11)
- self.assertEqual(self._get_url_hot_query('/blog', cache=False), 27)
+ self.assertEqual(self._get_url_hot_query('/blog', cache=False), 24)
def test_20_perf_sql_blog_bigger_data_scaling(self):
BlogPost = self.env['blog.post']
@@ -27,9 +27,9 @@ class TestBlogPerformance(UtilPerf):
blog_post.tag_ids += blog_tags
blog_tags = blog_tags[:-1]
self.assertEqual(self._get_url_hot_query('/blog'), 11)
- self.assertEqual(self._get_url_hot_query('/blog', cache=False), 34)
- self.assertEqual(self._get_url_hot_query(blog_post[0].website_url), 19)
- self.assertEqual(self._get_url_hot_query(blog_post[0].website_url, cache=False), 22)
+ self.assertEqual(self._get_url_hot_query('/blog', cache=False), 31)
+ self.assertEqual(self._get_url_hot_query(blog_post[0].website_url), 16)
+ self.assertEqual(self._get_url_hot_query(blog_post[0].website_url, cache=False), 18)
def test_30_perf_sql_blog_bigger_data_scaling(self):
BlogPost = self.env['blog.post']
diff --git a/odoo/addons/test_translation_import/__manifest__.py b/odoo/addons/test_translation_import/__manifest__.py
index 5a80ba01f7f..85aeefdbb7c 100644
--- a/odoo/addons/test_translation_import/__manifest__.py
+++ b/odoo/addons/test_translation_import/__manifest__.py
@@ -7,7 +7,8 @@
'depends': ['base'],
'data': [
'security/ir.model.access.csv',
- 'view.xml'
+ 'view.xml',
+ 'data/test_translation_import_data.xml'
],
'installable': True,
'assets': {
diff --git a/odoo/addons/test_translation_import/data/test_translation_import_data.xml b/odoo/addons/test_translation_import/data/test_translation_import_data.xml
new file mode 100644
index 00000000000..4e8678b376f
--- /dev/null
+++ b/odoo/addons/test_translation_import/data/test_translation_import_data.xml
@@ -0,0 +1,7 @@
+
+
+
+ Tableware
+
+
+
diff --git a/odoo/addons/test_translation_import/i18n/dot.csv b/odoo/addons/test_translation_import/i18n/dot.csv
index 5c4fc6ae34d..f39f2e152e8 100644
--- a/odoo/addons/test_translation_import/i18n/dot.csv
+++ b/odoo/addons/test_translation_import/i18n/dot.csv
@@ -1,2 +1,5 @@
module,type,name,res_id,src,value,comments
-test_translation_import,code,addons/test_translation_import/models.py,20,Accounting,samva,
+test_translation_import,model,"test.translation.import.model1,name",test_translation_import.test_translation_import_model1_record1,Tableware,"Tableware, Dot"
+test_translation_import,model_term,"test.translation.import.model1,html",test_translation_import.test_translation_import_model1_record1,Fork,"Fork, Dot"
+test_translation_import,model_term,"test.translation.import.model1,html",test_translation_import.test_translation_import_model1_record1,Spoon,"Spoon, Dot"
+test_translation_import,model_term,"test.translation.import.model1,html",test_translation_import.test_translation_import_model1_record1,Knife,"Kinfe, Dot"
diff --git a/odoo/addons/test_translation_import/i18n/fr.po b/odoo/addons/test_translation_import/i18n/fr.po
index 7d888109b76..9b099908c6d 100644
--- a/odoo/addons/test_translation_import/i18n/fr.po
+++ b/odoo/addons/test_translation_import/i18n/fr.po
@@ -1,80 +1,169 @@
-# This is a test PO file, not a true french translation.
-# See the POT file for further information.
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * test_translation_import
+#
msgid ""
msgstr ""
-"Project-Id-Version: OpenERP Server 6.1\n"
+"Project-Id-Version: Odoo Server 15.5alpha1+e\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-10-17 12:36+0000\n"
-"PO-Revision-Date: 2012-10-17 12:36+0000\n"
-"Last-Translator: <>\n"
+"POT-Creation-Date: 2022-07-01 12:46+0000\n"
+"PO-Revision-Date: 2022-07-01 12:46+0000\n"
+"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
-# Note: there is normally an additional line:
-# #: code:addons/test_translation_import/models.py:15
-# This line is present in the POT and removed here to test the translation
-# import behavior.
#. module: test_translation_import
-#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import__name
+#: code:addons/test_translation_import/models/models.py:0
#, python-format
-msgid "1XBUO5PUYH2RYZSA1FTLRYS8SPCNU1UYXMEYMM25ASV7JC2KTJZQESZYRV9L8CGB"
-msgstr "1XBUO5PUYH2RYZSA1FTLRYS8SPCNU1UYXMEYMM25ASV7JC2KTJZQESZYRV9L8CGB in french"
+msgid "Code Lazy, English"
+msgstr ""
#. module: test_translation_import
-#: model:ir.model.fields.selection,name:test_translation_import.selection__test_translation_import__import_type__bar
-msgid "Bar Import Type"
-msgstr "Bar in french"
-
-# Note: the line in pot is 21 and is 14 here
-#. module: test_translation_import
-#: code:addons/test_translation_import/models.py:14
+#: code:addons/test_translation_import/models/models.py:0
#, python-format
-msgid "Ijkl"
-msgstr "Ijkl in french"
+msgid "Code, %(num)s, %(symbol)s, English"
+msgstr ""
#. module: test_translation_import
-#: model:ir.model.fields.selection,name:test_translation_import.selection__test_translation_import__import_type__foo
-msgid "Foo Import Type"
-msgstr "Foo in french"
+#: code:addons/test_translation_import/models/models.py:0
+#, python-format
+msgid "Code, %s, English"
+msgstr ""
#. module: test_translation_import
-#: model:ir.model.fields,help:test_translation_import.field_test_translation_import__name
-msgid "Efgh"
-msgstr "Efgh in french"
+#: code:addons/test_translation_import/models/models.py:0
+#, python-format
+msgid "Code, English"
+msgstr "Code, Français"
#. module: test_translation_import
-#: model:ir.model,name:test_translation_import.model_test_translation_import
-msgid "Test: Translation Import"
-msgstr "Test: Import de traductions"
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__create_uid
+msgid "Created by"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__create_date
+msgid "Created on"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__display_name
+msgid "Display Name"
+msgstr ""
+
+#. module: test_translation_import
+#: model_terms:test.translation.import.model1,xml:test_translation_import.test_translation_import_model1_record1
+msgid "Fork"
+msgstr "Fourchette"
+
+#. module: test_translation_import
+#: model:ir.model.fields,help:test_translation_import.field_test_translation_import_model1__name
+msgid "Help, English"
+msgstr "Aider, Français"
+
+#. module: test_translation_import
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__id
+msgid "ID"
+msgstr ""
+
+#. module: test_translation_import
+#: model_terms:test.translation.import.model1,xml:test_translation_import.test_translation_import_model1_record1
+msgid "Knife"
+msgstr "Couteau"
+
+#. module: test_translation_import
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1____last_update
+msgid "Last Modified on"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__write_uid
+msgid "Last Updated by"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__write_date
+msgid "Last Updated on"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__name
+msgid "Name"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__selection
+msgid "Selection"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.model.fields.selection,name:test_translation_import.selection__test_translation_import_model1__selection__bar
+msgid "Selection Bar"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.model.fields.selection,name:test_translation_import.selection__test_translation_import_model1__selection__foo
+msgid "Selection Foo"
+msgstr ""
+
+#. module: test_translation_import
+#: model_terms:test.translation.import.model1,xml:test_translation_import.test_translation_import_model1_record1
+msgid "Spoon"
+msgstr "Cuillère"
+
+#. module: test_translation_import
+#: model:test.translation.import.model1,name:test_translation_import.test_translation_import_model1_record1
+msgid "Tableware"
+msgstr "Vaisselle"
+
+#. module: test_translation_import
+#: model:ir.actions.act_window,name:test_translation_import.action_test_translation_import_model1
+#: model:ir.ui.menu,name:test_translation_import.menu_test_translation_import_model1
+msgid "Test translation import model1"
+msgstr ""
#. module: test_translation_import
-#: model:ir.actions.act_window,name:test_translation_import.action_test_translation_import
#: model:ir.ui.menu,name:test_translation_import.menu_test_translation_import
-msgid "Test translation import"
+msgid "Test translation model1"
msgstr "Test translation import in french"
#. module: test_translation_import
-#: model:ir.ui.menu,name:test_translation_import.menu_test_translation
-msgid "Test translation"
-msgstr "Test translation in french"
+#: model:ir.model,name:test_translation_import.model_test_translation_import_model1
+msgid "Translation Test 1"
+msgstr ""
#. module: test_translation_import
-#: code:addons/base/models/arthur.py:42
-#: code:addons/base/models/arthur.py:43
-msgid "Test translation with two code lines"
-msgstr "Test traduction avec deux lignes de code"
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__xml
+msgid "XML"
+msgstr ""
#. module: test_translation_import
-#: code:addons/base/models/ford.py:43
-msgid "Test translation with a code type but different line number in pot"
-msgstr "Test traduction avec un type code mais différent numéro de ligne dans le pot"
+#. openerp-web
+#: code:addons/test_translation_import/static/src/xml/js_templates.xml:0
+#, python-format
+msgid "do export"
+msgstr ""
#. module: test_translation_import
-#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import__other_name
-#: code:addons/base/models/marvin.py:42
-#: code:addons/base/models/trillian.py:42
-msgid "Test translation with two code type and model"
-msgstr "Test traduction avec deux type code et un model"
+#. openerp-web
+#: code:addons/test_translation_import/static/src/xml/js_templates.xml:0
+#, python-format
+msgid "slot"
+msgstr ""
+
+#. module: test_translation_import
+#. openerp-web
+#: code:addons/test_translation_import/static/src/xml/js_templates.xml:0
+#, python-format
+msgid "slot 2"
+msgstr ""
+
+#. module: test_translation_import
+#. openerp-web
+#: code:addons/test_translation_import/static/src/xml/js_templates.xml:0
+#, python-format
+msgid "text node"
+msgstr ""
diff --git a/odoo/addons/test_translation_import/i18n/fr_BE.po b/odoo/addons/test_translation_import/i18n/fr_BE.po
index 371d1f00606..20b4021ae52 100644
--- a/odoo/addons/test_translation_import/i18n/fr_BE.po
+++ b/odoo/addons/test_translation_import/i18n/fr_BE.po
@@ -1,12 +1,13 @@
-# This is a test PO file, not a true french translation.
-# See the POT file for further information.
+# This is a test PO file, not a complete one. It is manually maintained
+# to test the import translation behavior of Odoo.
+
msgid ""
msgstr ""
-"Project-Id-Version: OpenERP Server 6.1\n"
+"Project-Id-Version: Odoo Server 15.5alpha1+e\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-10-17 12:36+0000\n"
-"PO-Revision-Date: 2012-10-17 12:36+0000\n"
-"Last-Translator: <>\n"
+"POT-Creation-Date: 2022-07-01 12:46+0000\n"
+"PO-Revision-Date: 2022-07-01 12:46+0000\n"
+"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -14,22 +15,154 @@ msgstr ""
"Plural-Forms: \n"
#. module: test_translation_import
-#: code:addons/test_translation_import/models.py:14
+#: code:addons/test_translation_import/models/models.py:0
#, python-format
-msgid "Ijkl"
-msgstr "Ijkl in belgian french"
-
-#. module: test_translation_import
-#: model:ir.model,name:test_translation_import.model_test_translation_import
-msgid "Test: Translation Import"
-msgstr "Test: Import de traductions in belgian french"
-
-#. module: test_translation_import
-#: model:ir.model.fields,help:test_translation_import.field_test_translation_import__name
-msgid "Efgh"
+msgid "Code Lazy, English"
msgstr ""
#. module: test_translation_import
-#: code:addons/base/models/ford.py:43
-msgid "Test translation with a code type but different line number in pot"
+#: code:addons/test_translation_import/models/models.py:0
+#, python-format
+msgid "Code, %(num)s, %(symbol)s, English"
+msgstr "Code, %(num)s, %(symbol)s, Français, Belgium"
+
+#. module: test_translation_import
+#: code:addons/test_translation_import/models/models.py:0
+#, python-format
+msgid "Code, %s, English"
+msgstr "Code, %s, Français, Belgium"
+
+#. module: test_translation_import
+#: code:addons/test_translation_import/models/models.py:0
+#, python-format
+msgid "Code, English"
+msgstr "Code, Français, Belgium"
+
+#. module: test_translation_import
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__create_uid
+msgid "Created by"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__create_date
+msgid "Created on"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__display_name
+msgid "Display Name"
+msgstr ""
+
+#. module: test_translation_import
+#: model_terms:test.translation.import.model1,xml:test_translation_import.test_translation_import_model1_record1
+msgid "Fork"
+msgstr "Fourchette, Belgium"
+
+#. module: test_translation_import
+#: model:ir.model.fields,help:test_translation_import.field_test_translation_import_model1__name
+msgid "Help, English"
+msgstr "Aider, Français, Belgium"
+
+#. module: test_translation_import
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__id
+msgid "ID"
+msgstr ""
+
+#. module: test_translation_import
+#: model_terms:test.translation.import.model1,xml:test_translation_import.test_translation_import_model1_record1
+msgid "Knife"
+msgstr "Couteau, Belgium"
+
+#. module: test_translation_import
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1____last_update
+msgid "Last Modified on"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__write_uid
+msgid "Last Updated by"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__write_date
+msgid "Last Updated on"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__name
+msgid "Name"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__selection
+msgid "Selection"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.model.fields.selection,name:test_translation_import.selection__test_translation_import_model1__selection__bar
+msgid "Selection Bar"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.model.fields.selection,name:test_translation_import.selection__test_translation_import_model1__selection__foo
+msgid "Selection Foo"
+msgstr ""
+
+#. module: test_translation_import
+#: model_terms:test.translation.import.model1,xml:test_translation_import.test_translation_import_model1_record1
+msgid "Spoon"
+msgstr "Cuillère, Belgium"
+
+#. module: test_translation_import
+#: model:test.translation.import.model1,name:test_translation_import.test_translation_import_model1_record1
+msgid "Tableware"
+msgstr "Vaisselle, Belgium"
+
+#. module: test_translation_import
+#: model:ir.actions.act_window,name:test_translation_import.action_test_translation_import_model1
+#: model:ir.ui.menu,name:test_translation_import.menu_test_translation_import_model1
+msgid "Test translation import model1"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.ui.menu,name:test_translation_import.menu_test_translation_import
+msgid "Test translation model1"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.model,name:test_translation_import.model_test_translation_import_model1
+msgid "Translation Test 1"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__xml
+msgid "XML"
+msgstr ""
+
+#. module: test_translation_import
+#. openerp-web
+#: code:addons/test_translation_import/static/src/xml/js_templates.xml:0
+#, python-format
+msgid "do export"
+msgstr ""
+
+#. module: test_translation_import
+#. openerp-web
+#: code:addons/test_translation_import/static/src/xml/js_templates.xml:0
+#, python-format
+msgid "slot"
+msgstr ""
+
+#. module: test_translation_import
+#. openerp-web
+#: code:addons/test_translation_import/static/src/xml/js_templates.xml:0
+#, python-format
+msgid "slot 2"
+msgstr ""
+
+#. module: test_translation_import
+#. openerp-web
+#: code:addons/test_translation_import/static/src/xml/js_templates.xml:0
+#, python-format
+msgid "text node"
msgstr ""
diff --git a/odoo/addons/test_translation_import/i18n/fr_CA.po b/odoo/addons/test_translation_import/i18n/fr_CA.po
new file mode 100644
index 00000000000..04ac1beab61
--- /dev/null
+++ b/odoo/addons/test_translation_import/i18n/fr_CA.po
@@ -0,0 +1,50 @@
+# This is a test PO file, not a complete one. It is manually maintained
+# to test the import translation behavior of Odoo.
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 15.5alpha1+e\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2022-07-01 12:46+0000\n"
+"PO-Revision-Date: 2022-07-01 12:46+0000\n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+
+#. module: test_translation_import
+#: model:test.translation.import.model1,name:test_translation_import.test_translation_import_model1_record1
+msgid "Tableware"
+msgstr ""
+
+#. module: test_translation_import
+#: model_term:test.translation.import.model1,xml:test_translation_import.test_translation_import_model1_record1
+msgid "Fork"
+msgstr ""
+
+#. module: test_translation_import
+#: model_term:test.translation.import.model1,xml:test_translation_import.test_translation_import_model1_record1
+msgid "Knife"
+msgstr "Couteau, Canada"
+
+#. module: test_translation_import
+#: model_term:test.translation.import.model1,xml:test_translation_import.test_translation_import_model1_record1
+msgid "Spoon"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.model.fields,help:test_translation_import.field_test_translation_import_model1__name
+msgid "Help, English"
+msgstr ""
+
+#. module: test_translation_import
+#: code:addons/test_translation_import/models/model.py:0
+msgid "Code, English"
+msgstr ""
+
+#. module: test_translation_import
+#: code:addons/test_translation_import/models/model.py:0
+msgid "Code Lazy, English"
+msgstr ""
diff --git a/odoo/addons/test_translation_import/i18n/test_translation_import.pot b/odoo/addons/test_translation_import/i18n/test_translation_import.pot
index 2685bd6f2b5..ec34ab91d10 100644
--- a/odoo/addons/test_translation_import/i18n/test_translation_import.pot
+++ b/odoo/addons/test_translation_import/i18n/test_translation_import.pot
@@ -1,21 +1,13 @@
# This is a test POT file, not a true template. It is manually maintained
# to test the import translation behavior of OpenERP.
#
-# In particular, the
-# `1XBUO5PUYH2RYZSA1FTLRYS8SPCNU1UYXMEYMM25ASV7JC2KTJZQESZYRV9L8CGB` source is
-# given with two targets (the #: comments): `code` and `field`. The code one is
-# removed in the fr.po file. Still, the import should generate a database entry
-# for the `code` one. I.e. the targets defined in the POT must be added to the
-# targets defined in the PO file. This was done to fix a bug, as reported by
-# lp:933496.
-#
msgid ""
msgstr ""
-"Project-Id-Version: OpenERP Server 6.1\n"
+"Project-Id-Version: Odoo Server 15.5alpha1+e\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-10-17 12:36+0000\n"
-"PO-Revision-Date: 2012-10-17 12:36+0000\n"
-"Last-Translator: <>\n"
+"POT-Creation-Date: 2022-07-01 12:46+0000\n"
+"PO-Revision-Date: 2022-07-01 12:46+0000\n"
+"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -23,113 +15,154 @@ msgstr ""
"Plural-Forms: \n"
#. module: test_translation_import
-#: code:addons/test_translation_import/models.py:15
-#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import__name
+#: code:addons/test_translation_import/models/models.py:0
#, python-format
-msgid "1XBUO5PUYH2RYZSA1FTLRYS8SPCNU1UYXMEYMM25ASV7JC2KTJZQESZYRV9L8CGB"
+msgid "Code Lazy, English"
msgstr ""
#. module: test_translation_import
-#: code:addons/test_translation_import/models.py:21
+#: code:addons/test_translation_import/models/models.py:0
#, python-format
-msgid "Accounting"
+msgid "Code, %(num)s, %(symbol)s, English"
msgstr ""
#. module: test_translation_import
-#: model:ir.model.fields.selection,name:test_translation_import.selection__test_translation_import__import_type__bar
-msgid "Bar Import Type"
+#: code:addons/test_translation_import/models/models.py:0
+#, python-format
+msgid "Code, %s, English"
msgstr ""
#. module: test_translation_import
-#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import__create_uid
+#: code:addons/test_translation_import/models/models.py:0
+#, python-format
+msgid "Code, English"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__create_uid
msgid "Created by"
msgstr ""
#. module: test_translation_import
-#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import__create_date
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__create_date
msgid "Created on"
msgstr ""
#. module: test_translation_import
-#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import__display_name
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__display_name
msgid "Display Name"
msgstr ""
#. module: test_translation_import
-#: model:ir.model.fields,help:test_translation_import.field_test_translation_import__name
-msgid "Efgh"
+#: model_terms:test.translation.import.model1,xml:test_translation_import.test_translation_import_model1_record1
+msgid "Fork"
msgstr ""
#. module: test_translation_import
-#: model:ir.model.fields.selection,name:test_translation_import.selection__test_translation_import__import_type__foo
-msgid "Foo Import Type"
+#: model:ir.model.fields,help:test_translation_import.field_test_translation_import_model1__name
+msgid "Help, English"
msgstr ""
#. module: test_translation_import
-#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import__id
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__id
msgid "ID"
msgstr ""
#. module: test_translation_import
-#: code:addons/test_translation_import/models.py:21
-#, python-format
-msgid "Ijkl"
+#: model_terms:test.translation.import.model1,xml:test_translation_import.test_translation_import_model1_record1
+msgid "Knife"
msgstr ""
#. module: test_translation_import
-#: code:addons/test_translation_import/models.py:19
-#, python-format
-msgid "Klingon"
-msgstr ""
-
-#. module: test_translation_import
-#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import____last_update
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1____last_update
msgid "Last Modified on"
msgstr ""
#. module: test_translation_import
-#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import__write_uid
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__write_uid
msgid "Last Updated by"
msgstr ""
#. module: test_translation_import
-#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import__write_date
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__write_date
msgid "Last Updated on"
msgstr ""
#. module: test_translation_import
-#: model:ir.ui.menu,name:test_translation_import.menu_test_translation
-msgid "Test translation"
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__name
+msgid "Name"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__selection
+msgid "Selection"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.model.fields.selection,name:test_translation_import.selection__test_translation_import_model1__selection__bar
+msgid "Selection Bar"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.model.fields.selection,name:test_translation_import.selection__test_translation_import_model1__selection__foo
+msgid "Selection Foo"
+msgstr ""
+
+#. module: test_translation_import
+#: model_terms:test.translation.import.model1,xml:test_translation_import.test_translation_import_model1_record1
+msgid "Spoon"
+msgstr ""
+
+#. module: test_translation_import
+#: model:test.translation.import.model1,name:test_translation_import.test_translation_import_model1_record1
+msgid "Tableware"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.actions.act_window,name:test_translation_import.action_test_translation_import_model1
+#: model:ir.ui.menu,name:test_translation_import.menu_test_translation_import_model1
+msgid "Test translation import model1"
msgstr ""
#. module: test_translation_import
-#: model:ir.actions.act_window,name:test_translation_import.action_test_translation_import
#: model:ir.ui.menu,name:test_translation_import.menu_test_translation_import
-msgid "Test translation import"
+msgid "Test translation model1"
msgstr ""
#. module: test_translation_import
-#: code:addons/base/models/arthur.py:42
-#: code:addons/base/models/arthur.py:43
+#: model:ir.model,name:test_translation_import.model_test_translation_import_model1
+msgid "Translation Test 1"
+msgstr ""
+
+#. module: test_translation_import
+#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import_model1__xml
+msgid "XML"
+msgstr ""
+
+#. module: test_translation_import
+#. openerp-web
+#: code:addons/test_translation_import/static/src/xml/js_templates.xml:0
#, python-format
-msgid "Test translation with two code lines"
+msgid "do export"
msgstr ""
#. module: test_translation_import
-#: code:addons/base/models/ford.py:42
+#. openerp-web
+#: code:addons/test_translation_import/static/src/xml/js_templates.xml:0
#, python-format
-msgid "Test translation with a code type but different line number in pot"
+msgid "slot"
msgstr ""
#. module: test_translation_import
-#: model:ir.model.fields,field_description:test_translation_import.field_test_translation_import__other_name
-#: code:addons/base/models/marvin.py:42
-#: code:addons/base/models/trillian.py:42
+#. openerp-web
+#: code:addons/test_translation_import/static/src/xml/js_templates.xml:0
#, python-format
-msgid "Test translation with two code type and model"
+msgid "slot 2"
msgstr ""
#. module: test_translation_import
-#: model:ir.model,name:test_translation_import.model_test_translation_import
-msgid "Test: Translation Import"
+#. openerp-web
+#: code:addons/test_translation_import/static/src/xml/js_templates.xml:0
+#, python-format
+msgid "text node"
msgstr ""
diff --git a/odoo/addons/test_translation_import/i18n/tlh.po b/odoo/addons/test_translation_import/i18n/tlh.po
index 758a27ffa08..0906bc6ad7b 100644
--- a/odoo/addons/test_translation_import/i18n/tlh.po
+++ b/odoo/addons/test_translation_import/i18n/tlh.po
@@ -2,11 +2,11 @@
# to test the import translation behavior of Odoo.
msgid ""
msgstr ""
-"Project-Id-Version: Odoo Server saas~11.4\n"
+"Project-Id-Version: Odoo Server 15.5alpha1+e\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-09-18 13:20+0000\n"
-"PO-Revision-Date: 2018-09-18 13:20+0000\n"
-"Last-Translator: <>\n"
+"POT-Creation-Date: 2022-07-01 12:46+0000\n"
+"PO-Revision-Date: 2022-07-01 12:46+0000\n"
+"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -14,7 +14,16 @@ msgstr ""
"Plural-Forms: \n"
#. module: test_translation_import
-#: code:addons/test_translation_import/models.py:18
-#, python-format
-msgid "Klingon"
-msgstr "tlhIngan"
+#: model:test.translation.import.model1,name:test_translation_import.test_translation_import_model1_record1
+msgid "Tableware"
+msgstr "Tableware, Klingon"
+
+#. module: test_translation_import
+#: code:addons/test_translation_import/models/model.py:0
+msgid "Code, English"
+msgstr "Code, Klingon"
+
+#. module: test_translation_import
+#: code:addons/test_translation_import/models/model.py:0
+msgid "Code Lazy, English"
+msgstr "Code Lazy, Klingon"
diff --git a/odoo/addons/test_translation_import/models.py b/odoo/addons/test_translation_import/models.py
deleted file mode 100644
index 9f5bd31b547..00000000000
--- a/odoo/addons/test_translation_import/models.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from odoo import models, fields, _
-
-class m(models.TransientModel):
- """ A model to provide source strings. """
- _name = 'test.translation.import'
- _description = 'Test: Translation Import'
-
- name = fields.Char('1XBUO5PUYH2RYZSA1FTLRYS8SPCNU1UYXMEYMM25ASV7JC2KTJZQESZYRV9L8CGB',
- size=32, help='Efgh')
- other_name = fields.Char('Test translation with two code type and model')
- import_type = fields.Selection([
- ('foo', 'Foo Import Type'),
- ('bar', 'Bar Import Type'),
- ])
-
- _('Ijkl')
-
- # With the name label above, this source string should be generated twice.
- _('1XBUO5PUYH2RYZSA1FTLRYS8SPCNU1UYXMEYMM25ASV7JC2KTJZQESZYRV9L8CGB')
-
- _('Klingon')
-
- _('Accounting')
diff --git a/odoo/addons/test_translation_import/models/__init__.py b/odoo/addons/test_translation_import/models/__init__.py
new file mode 100644
index 00000000000..0650744f6bc
--- /dev/null
+++ b/odoo/addons/test_translation_import/models/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/odoo/addons/test_translation_import/models/models.py b/odoo/addons/test_translation_import/models/models.py
new file mode 100644
index 00000000000..87f41253965
--- /dev/null
+++ b/odoo/addons/test_translation_import/models/models.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+from odoo import fields, models, _, _lt
+from odoo.tools.translate import xml_translate
+
+class TestTranslationImportModel1(models.Model):
+ _name = 'test.translation.import.model1'
+ _description = 'Translation Test 1'
+
+ name = fields.Char('Name', translate=True, help='Help, English')
+ selection = fields.Selection([
+ ('foo', 'Selection Foo'),
+ ('bar', 'Selection Bar'),
+ ])
+ xml = fields.Text('XML', translate=xml_translate)
+
+ def get_code_translation(self):
+ return _('Code, English')
+
+ def get_code_lazy_translation(self):
+ return _lt('Code Lazy, English')
+
+ def get_code_placeholder_translation(self, *args, **kwargs):
+ return _('Code, %s, English', *args, **kwargs)
+
+ def get_code_named_placeholder_translation(self, *args, **kwargs):
+ return _('Code, %(num)s, %(symbol)s, English', *args, **kwargs)
diff --git a/odoo/addons/test_translation_import/security/ir.model.access.csv b/odoo/addons/test_translation_import/security/ir.model.access.csv
index ba076d4035f..228691f2d88 100644
--- a/odoo/addons/test_translation_import/security/ir.model.access.csv
+++ b/odoo/addons/test_translation_import/security/ir.model.access.csv
@@ -1,2 +1,2 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
-"access_test_translation_import","access.test.translation.import","model_test_translation_import",,0,0,0,0
+"access_test_translation_import_model1","access.test.translation.import.model1","model_test_translation_import_model1",,0,0,0,0
\ No newline at end of file
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 1ae05ac3228..a0f0b9e7a21 100644
--- a/odoo/addons/test_translation_import/tests/test_term_count.py
+++ b/odoo/addons/test_translation_import/tests/test_term_count.py
@@ -9,153 +9,96 @@ from odoo.tests import common, tagged
from odoo.tools.misc import file_open, mute_logger
from odoo.tools.translate import _, _lt, TranslationFileReader, TranslationModuleReader
from odoo import Command
+from odoo.addons.base.models.ir_fields import BOOLEAN_TRANSLATIONS
-TRANSLATED_TERM = _lt("Klingon")
+class TestImport(common.TransactionCase):
-class TestTermCount(common.TransactionCase):
+ def test_import_code_translation(self):
+ self.env['res.lang']._activate_lang('fr_FR')
- def test_count_term(self):
- """
- Just make sure we have as many translation entries as we wanted.
- """
+ # Tip: code translations don't need to be imported explicitly
+ # odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr.po', 'fr_FR', verbose=False)
+
+ model = self.env['test.translation.import.model1']
+ self.assertEqual(
+ model.with_context(lang='fr_FR').get_code_translation(),
+ 'Code, Français'
+ )
+
+ def test_import_model_translation(self):
self.env['res.lang']._activate_lang('fr_FR')
odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr.po', 'fr_FR', verbose=False)
- translations = self.env['ir.translation'].search([
- ('lang', '=', 'fr_FR'),
- ('src', '=', '1XBUO5PUYH2RYZSA1FTLRYS8SPCNU1UYXMEYMM25ASV7JC2KTJZQESZYRV9L8CGB'),
- ], order='type')
- self.assertEqual(len(translations), 2)
- self.assertEqual(translations[0].type, 'code')
- self.assertEqual(translations[0].module, 'test_translation_import')
- self.assertEqual(translations[0].name, 'addons/test_translation_import/models.py')
- self.assertEqual(translations[0].comments, '')
- self.assertEqual(translations[0].res_id, 15)
- self.assertEqual(translations[1].type, 'model')
- self.assertEqual(translations[1].module, 'test_translation_import')
- self.assertEqual(translations[1].name, 'ir.model.fields,field_description')
- self.assertEqual(translations[1].comments, '')
- field = self.env['ir.model.fields'].search([('model', '=', 'test.translation.import'), ('name', '=', 'name')])
- self.assertEqual(translations[1].res_id, field.id)
- def test_count_term_module(self):
- """
- Just make sure we have as many translation entries as we wanted and module deducted from file content
- """
+ record = self.env.ref('test_translation_import.test_translation_import_model1_record1')
+ self.assertEqual(
+ record.with_context(lang='fr_FR').name,
+ 'Vaisselle'
+ )
+
+ def test_import_model_term_translation(self):
self.env['res.lang']._activate_lang('fr_FR')
odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr.po', 'fr_FR', verbose=False)
- translations = self.env['ir.translation'].search([
- ('lang', '=', 'fr_FR'),
- ('src', '=', 'Ijkl'),
- ('module', '=', 'test_translation_import'),
- ])
- self.assertEqual(len(translations), 1)
- self.assertEqual(translations.res_id, 21)
+
+ record = self.env.ref('test_translation_import.test_translation_import_model1_record1')
+ self.assertEqual(
+ record.with_context(lang='fr_FR').xml,
+ ''
+ )
def test_noupdate(self):
"""
Make sure no update do not overwrite translations
"""
menu = self.env.ref('test_translation_import.menu_test_translation_import')
- menu.name = "New Name"
+ self.assertEqual(menu.name, 'Test translation model1')
# install french and change translation content
self.env['res.lang']._activate_lang('fr_FR')
odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr.po', 'fr_FR', verbose=False)
+ self.assertEqual(menu.with_context(lang='fr_FR').name, "Test translation import in french")
menu.with_context(lang='fr_FR').name = "Nouveau nom"
# reload with overwrite
odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr.po', 'fr_FR', verbose=False, overwrite=True)
- # trans_load invalidates ormcache but not record cache
- self.env.invalidate_all()
- self.assertEqual(menu.name, "New Name")
+ self.assertEqual(menu.name, "Test translation model1")
self.assertEqual(menu.with_context(lang='fr_FR').name, "Nouveau nom")
def test_lang_with_base(self):
self.env['res.lang']._activate_lang('fr_BE')
+ self.env['res.lang']._activate_lang('fr_CA')
odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr.po', 'fr_BE', verbose=False)
odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr_BE.po', 'fr_BE', verbose=False, overwrite=True)
+ odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr.po', 'fr_CA', verbose=False)
+ odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr_CA.po', 'fr_CA', verbose=False, overwrite=True)
# language override base language
- translations = self.env['ir.translation'].search([
- ('lang', '=', 'fr_BE'),
- ('value', '=like', '% belgian french'),
- ])
- self.assertEqual(len(translations), 2)
+ record = self.env.ref('test_translation_import.test_translation_import_model1_record1')
+ self.assertEqual(
+ record.with_context(lang='fr_BE').get_code_translation(),
+ 'Code, Français, Belgium'
+ )
+ self.assertEqual(
+ record.with_context(lang='fr_BE').name,
+ 'Vaisselle, Belgium'
+ )
+ self.assertEqual(
+ record.with_context(lang='fr_BE').xml,
+ ''
+ )
# not specified localized language fallback on base language
- translations = self.env['ir.translation'].search([
- ('lang', '=', 'fr_BE'),
- ('src', '=', 'Efgh'),
- ('value', '=', 'Efgh in french'),
- ])
- self.assertEqual(len(translations), 1)
- translations = self.env['ir.translation'].search([
- ('lang', '=', 'fr_BE'),
- ('src', '=', 'Test translation with a code type but different line number in pot'),
- ('value', '=', 'Test traduction avec un type code mais différent numéro de ligne dans le pot'),
- ])
- self.assertEqual(len(translations), 1)
-
- def test_no_duplicate(self):
- """
- Just make sure we do not create duplicated translation with 'code' type
- """
- self.env['res.lang']._activate_lang('fr_FR')
- odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr.po', 'fr_FR', verbose=False)
- ids = self.env['ir.translation'].search([
- ('lang', '=', 'fr_FR'),
- ('src', '=', 'Test translation with two code lines'),
- ])
- self.assertEqual(len(ids), 1)
-
- ids = self.env['ir.translation'].search([
- ('lang', '=', 'fr_FR'),
- ('src', '=', 'Test translation with a code type but different line number in pot'),
- ])
- self.assertEqual(len(ids), 1)
-
- ids = self.env['ir.translation'].search([
- ('lang', '=', 'fr_FR'),
- ('src', '=', 'Test translation with two code type and model'),
- ])
- self.assertEqual(len(ids), 2)
- self.assertEqual(len(ids.filtered(lambda t: t.type == 'code')), 1)
-
- def test_export_empty_string(self):
- """When the string and the translation is equal the translation is empty"""
- # Export the translations
- def update_translations(create_empty_translation=False):
- self.env['res.lang']._activate_lang('fr_FR')
- with closing(io.BytesIO()) as bufferobj:
- odoo.tools.trans_export('fr_FR', ['test_translation_import'], bufferobj, 'po', self.cr)
- bufferobj.name = 'test_translation_import/i18n/fr.po'
- odoo.tools.trans_load_data(self.cr, bufferobj, 'po', 'fr_FR',
- verbose=False,
- create_empty_translation=create_empty_translation,
- overwrite=True)
-
- # Check that the not translated key is not created
- update_translations()
- translation = self.env['ir.translation'].search_count([('src', '=', 'Efgh'), ('value', '=', '')])
- self.assertFalse(translation, 'An empty translation is not imported')
-
- # Check that "Generate Missing Terms" create empty string for not translated key
- update_translations(create_empty_translation=True)
- translation = self.env['ir.translation'].search_count([('src', '=', 'Efgh'), ('value', '=', '')])
- self.assertTrue(translation, 'The translation of "Efgh" should be empty')
-
- # Modify the value translated for the equal value of the key
- menu = self.env.ref('test_translation_import.menu_test_translation_import')
- menu.name = "New Name"
- menu.with_context(lang='fr_FR').name = "New Name"
- update_translations()
- self.assertEqual(menu.with_context(lang='fr_FR').name, "New Name", 'The translation of "New Name" should be "New Name"')
-
- # Modify the value translated for another different value
- menu.name = "New Name"
- menu.with_context(lang='fr_FR').name = "Nouveau nom"
- update_translations()
- self.assertEqual(menu.with_context(lang='fr_FR').name, "Nouveau nom", 'The translation of "New Name" should be "Nouveau nom"')
+ self.assertEqual(
+ record.with_context(lang='fr_CA').get_code_translation(),
+ 'Code, Français'
+ )
+ self.assertEqual(
+ record.with_context(lang='fr_CA').name,
+ 'Vaisselle'
+ )
+ self.assertEqual(
+ record.with_context(lang='fr_CA').xml,
+ ''
+ )
def test_import_from_po_file(self):
"""Test the import from a single po file works"""
@@ -174,11 +117,15 @@ class TestTermCount(common.TransactionCase):
tlh_lang = self.env['res.lang']._lang_get('tlh')
self.assertTrue(tlh_lang, "The imported language was not creates")
- trans_count = self.env['ir.translation'].search_count([('lang', '=', 'tlh')])
- self.assertEqual(trans_count, 1, "The imported translations were not created")
-
- self.env = self.env(context=dict(self.env.context, lang="tlh"))
- self.assertEqual(_("Klingon"), "tlhIngan", "The code translation was not applied")
+ record = self.env.ref('test_translation_import.test_translation_import_model1_record1')
+ self.assertEqual(
+ record.with_context(lang='tlh').get_code_translation(),
+ 'Code, Klingon'
+ )
+ self.assertEqual(
+ record.with_context(lang='tlh').name,
+ 'Tableware, Klingon'
+ )
def test_lazy_translation(self):
"""Test the import from a single po file works"""
@@ -194,21 +141,34 @@ class TestTermCount(common.TransactionCase):
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")
+ model = self.env['test.translation.import.model1']
+ TRANSLATED_TERM = model.get_code_lazy_translation()
+
+ self.assertEqual(
+ model.with_context(lang='tlh').get_code_translation(),
+ "Code, Klingon",
+ "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")
+ TRANSLATED_TERM == "Code, English"
+ self.assertEqual(str(TRANSLATED_TERM), "Code Lazy, English", "The translation should not be applied yet")
context = {'lang': "tlh"}
- self.assertEqual(str(TRANSLATED_TERM), "tlhIngan", "The lazy code translation was not applied")
+ self.assertEqual(str(TRANSLATED_TERM), "Code Lazy, Klingon", "The lazy code translation was not applied")
- self.assertEqual("Do you speak " + TRANSLATED_TERM, "Do you speak tlhIngan", "str + _lt concatenation failed")
- self.assertEqual(TRANSLATED_TERM + ", I speak it", "tlhIngan, I speak it", "_lt + str concatenation failed")
- self.assertEqual(TRANSLATED_TERM + TRANSLATED_TERM, "tlhIngantlhIngan", "_lt + _lt concatenation failed")
+ self.assertEqual("Do you speak " + TRANSLATED_TERM, "Do you speak Code Lazy, Klingon", "str + _lt concatenation failed")
+ self.assertEqual(TRANSLATED_TERM + ", I speak it", "Code Lazy, Klingon, I speak it", "_lt + str concatenation failed")
+ self.assertEqual(TRANSLATED_TERM + TRANSLATED_TERM, "Code Lazy, KlingonCode Lazy, Klingon", "_lt + _lt concatenation failed")
+
+ # test lazy translation in another module
+ self.env['res.lang']._activate_lang('fr_FR')
+ context = {'lang': 'en_US'}
+ self.assertEqual(str(BOOLEAN_TRANSLATIONS[0]), 'yes')
+ context = {'lang': 'fr_FR'}
+ self.assertEqual(str(BOOLEAN_TRANSLATIONS[0]), 'oui')
def test_import_from_csv_file(self):
"""Test the import from a single CSV file works"""
@@ -227,118 +187,44 @@ class TestTermCount(common.TransactionCase):
dot_lang = self.env['res.lang']._lang_get('dot')
self.assertTrue(dot_lang, "The imported language was not creates")
- trans_count = self.env['ir.translation'].search_count([('lang', '=', 'dot')])
- self.assertEqual(trans_count, 1, "The imported translations were not created")
-
- self.env = self.env(context=dict(self.env.context, lang="dot"))
- self.assertEqual(_("Accounting"), "samva", "The code translation was not applied")
-
- def test_export_pollution(self):
- """ Test that exporting the translation only exports the translations of the module """
- with file_open('test_translation_import/i18n/dot.csv', 'rb') as f:
- csv_file = base64.b64encode(f.read())
-
- # dot.csv only contains one term
- import_tlh = self.env["base.language.import"].create({
- 'name': 'Dothraki',
- 'code': 'dot',
- 'data': csv_file,
- 'filename': 'dot.csv',
- })
- with mute_logger('odoo.addons.base.models.res_lang'):
- import_tlh.import_lang()
-
- # create a translation that has the same src as an existing field but no module
- # information and a different res_id that the real field
- # this translation should not be included in the export
- self.env['ir.translation'].create({
- 'src': '1XBUO5PUYH2RYZSA1FTLRYS8SPCNU1UYXMEYMM25ASV7JC2KTJZQESZYRV9L8CGB',
- 'value': '1XBUO5PUYH2RYZSA1FTLRYS8SPCNU1UYXMEYMM25ASV7JC2KTJZQESZYRV9L8CGB in Dothraki',
- 'type': 'model',
- 'name': 'ir.model.fields,field_description',
- 'res_id': -1,
- 'lang': 'dot',
- })
- module = self.env.ref('base.module_test_translation_import')
- export = self.env["base.language.export"].create({
- 'lang': 'dot',
- 'format': 'po',
- 'modules': [Command.set([module.id])]
- })
- export.act_getfile()
- po_file = export.data
- reader = TranslationFileReader(base64.b64decode(po_file).decode(), fileformat='po')
- for row in reader:
- if row['value']:
- # should contains only one row from the csv, not the manual one
- self.assertEqual(row['src'], "Accounting")
- self.assertEqual(row['value'], "samva")
+ # code translation cannot be changed or imported, it only depends on the po file in the module directory
+ record = self.env.ref('test_translation_import.test_translation_import_model1_record1')
+ self.assertEqual(
+ record.with_context(lang='dot').get_code_translation(),
+ 'Code, English'
+ )
+ self.assertEqual(
+ record.with_context(lang='dot').name,
+ 'Tableware, Dot'
+ )
def test_translation_placeholder(self):
"""Verify placeholder use in _()"""
- context = {'lang': "fr_BE"}
- self.env.ref("base.lang_fr_BE").active = True
+ self.env['res.lang']._activate_lang('fr_BE')
- # translation with positional placeholders
- translation = self.env['ir.translation'].create({
- 'src': 'Text with %s placeholder',
- 'value': 'Text avec %s marqueur',
- 'type': 'code',
- 'name': 'addons/test_translation_import/tests/test_count_term.py',
- 'res_id': 0,
- 'lang': 'fr_BE',
- })
+ model_fr_BE = self.env['test.translation.import.model1'].with_context(lang='fr_BE')
# correctly translate
self.assertEqual(
- _("Text with %s placeholder", 1),
- "Text avec 1 marqueur",
+ model_fr_BE.get_code_placeholder_translation(1),
+ "Code, 1, Français, Belgium",
"Translation placeholders were not applied"
)
# source error: wrong arguments
- with self.assertRaises(TypeError), self.cr.savepoint():
- _("Text with %s placeholder", 1, "🧀")
-
- # translation error: log error and fallback on source
- translation.value = "Text avec s% marqueur"
- with self.assertLogs('odoo.tools.translate', 'ERROR'):
- self.assertEqual(
- _("Text with %s placeholder", 1),
- "Text with 1 placeholder",
- "Fallback to source was not used for bad translation"
- )
-
-
- # translation with named placeholders
- translation = self.env['ir.translation'].create({
- 'src': 'Text with %(num)s placeholders %(symbol)s',
- 'value': 'Text avec %(num)s marqueurs %(symbol)s',
- 'type': 'code',
- 'name': 'addons/test_translation_import/tests/test_count_term.py',
- 'res_id': 0,
- 'lang': 'fr_BE',
- })
+ with self.assertRaises(TypeError):
+ model_fr_BE.get_code_placeholder_translation(1, "🧀")
# correctly translate
self.assertEqual(
- _("Text with %(num)s placeholders %(symbol)s", num=2, symbol="🧀"),
- "Text avec 2 marqueurs 🧀",
+ model_fr_BE.get_code_named_placeholder_translation(num=2, symbol="🧀"),
+ "Code, 2, 🧀, Français, Belgium",
"Translation placeholders were not applied"
)
# source error: wrong arguments
- with self.assertRaises(KeyError), self.cr.savepoint():
- _("Text with %(num)s placeholders %(symbol)s", symbol="🧀")
-
- # translation error: log error and fallback on source
- translation.value = "Text avec %(num)s marqueurs %(symbole)s"
- with self.assertLogs('odoo.tools.translate', 'ERROR'):
- self.assertEqual(
- _("Text with %(num)s placeholders %(symbol)s", num=2, symbol="🧀"),
- "Text with 2 placeholders 🧀",
- "Fallback to source was not used for bad translation"
- )
+ with self.assertRaises(KeyError):
+ model_fr_BE.get_code_named_placeholder_translation(symbol="🧀"),
@tagged('post_install', '-at_install')
@@ -347,23 +233,10 @@ class TestTranslationFlow(common.TransactionCase):
def test_export_import(self):
""" Ensure export+import gives the same result as loading a language """
# load language and generate missing terms to create missing empty terms
- with mute_logger('odoo.addons.base.models.ir_translation'):
- self.env["base.language.install"].create({
- 'overwrite': True,
- 'lang_ids': [(6, 0, [self.env.ref('base.lang_fr').id])],
- }).lang_install()
-
- self.env["base.update.translations"].create({'lang': 'fr_FR'}).act_update()
-
- translations = self.env["ir.translation"].search([
- ('lang', '=', 'fr_FR'),
- ('module', '=', 'test_translation_import'),
- ('value', '!=', ''),
- ])
-
- # minus 3 as the original fr.po contains 3 fake code translations (cf
- # test_no_duplicate test) which are not found by babel_extract_terms
- init_translation_count = len(translations) - 3
+ self.env["base.language.install"].create({
+ 'overwrite': True,
+ 'lang_ids': [(6, 0, [self.env.ref('base.lang_fr').id])],
+ }).lang_install()
module = self.env.ref('base.module_test_translation_import')
export = self.env["base.language.export"].create({
@@ -375,7 +248,30 @@ class TestTranslationFlow(common.TransactionCase):
po_file = export.data
self.assertIsNotNone(po_file)
- translations.unlink()
+ record = self.env.ref('test_translation_import.test_translation_import_model1_record1')
+ record.invalidate_recordset()
+ self.assertEqual(
+ record.with_context(lang='fr_FR').name,
+ 'Vaisselle'
+ )
+ self.assertEqual(
+ record.with_context(lang='fr_FR').xml,
+ ''
+ )
+
+ # remove All translations
+ record.name = False
+ record.name = 'Tableware'
+ record.xml = False
+ record.xml = ''
+ self.assertEqual(
+ record.with_context(lang='fr_FR').name,
+ 'Tableware'
+ )
+ self.assertEqual(
+ record.with_context(lang='fr_FR').xml,
+ ''
+ )
import_fr = self.env["base.language.import"].create({
'name': 'French',
@@ -387,13 +283,14 @@ class TestTranslationFlow(common.TransactionCase):
with mute_logger('odoo.addons.base.models.res_lang'):
import_fr.import_lang()
- import_translation = self.env["ir.translation"].search([
- ('lang', '=', 'fr_FR'),
- ('module', '=', 'test_translation_import'),
- ('value', '!=', ''),
- ])
-
- self.assertEqual(init_translation_count, len(import_translation))
+ self.assertEqual(
+ record.with_context(lang='fr_FR').name,
+ 'Vaisselle'
+ )
+ self.assertEqual(
+ record.with_context(lang='fr_FR').xml,
+ ''
+ )
def test_export_import_csv(self):
""" Ensure can reimport exported csv """
@@ -409,11 +306,6 @@ class TestTranslationFlow(common.TransactionCase):
po_file = export.data
self.assertIsNotNone(po_file)
- self.env["ir.translation"].search([
- ('lang', '=', 'fr_FR'),
- ('module', '=', 'test_translation_import')
- ]).unlink()
-
import_fr = self.env["base.language.import"].create({
'name': 'French',
'code': 'fr_FR',
@@ -422,7 +314,7 @@ class TestTranslationFlow(common.TransactionCase):
'overwrite': False,
})
with mute_logger('odoo.addons.base.models.res_lang'):
- import_fr.with_context(create_empty_translation=True).import_lang()
+ import_fr.with_context().import_lang()
def test_export_static_templates(self):
trans_static = []
diff --git a/odoo/addons/test_translation_import/view.xml b/odoo/addons/test_translation_import/view.xml
index b77f4ef9210..1c60768166d 100644
--- a/odoo/addons/test_translation_import/view.xml
+++ b/odoo/addons/test_translation_import/view.xml
@@ -2,19 +2,19 @@
-
- Test translation import
+
+ Test translation import model1
ir.actions.act_window
- test.translation.import
+ test.translation.import.model1
tree,form
current
-
+
-
+