[FIX] *: adapt code to jsonb translations
Website: the context install_filename='dummy' is used to prevent arch_updated from becoming True while updating translations of ir_ui_view.arch_db (if arch_update becomes True, test_inherit_specific fails) Fuzzy search for jsonb translated fields has been adapted in the case of website. It may require some refactoring later.
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
@@ -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
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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': '<p>SpanishBody for <t t-out="object.name" /></p>',
|
||||
}]
|
||||
test_template.with_context(lang=lang_code).subject = 'SpanishSubject for {{ object.name }}'
|
||||
test_template.with_context(lang=lang_code).body_html = '<p>SpanishBody for <t t-out="object.name" /></p>'
|
||||
|
||||
# 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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -107,62 +107,29 @@ class TestMailTemplate(MailCommon):
|
||||
'body_html': '<p>foo</p>',
|
||||
})
|
||||
|
||||
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 = '<t t-esc="foo"/>'
|
||||
employee_template.with_context(lang='fr_FR').body_html = '<t t-esc="foo"/>'
|
||||
|
||||
employee_translations_of_body.unlink() # delete old translation, to test the creation now
|
||||
body_translation_vals['value'] = '<p t-esc="foo"/>'
|
||||
|
||||
# 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 = '<t t-esc="foo"/>'
|
||||
|
||||
# reset the body_html to static
|
||||
employee_template.body_html = False
|
||||
employee_template.body_html = '<p>foo</p>'
|
||||
|
||||
### 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) """
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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",
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 }}',
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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 = '<div>hello</div>'
|
||||
self.base_view.update_field_translations('arch_db', {'fr_BE': {'hello': 'bonjour'}})
|
||||
self.assertEqual(self.base_view.with_context(lang='fr_BE').arch, '<div>bonjour</div>')
|
||||
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, '<div>hello</div>',
|
||||
self.assertEqual(specific_view.with_context(lang='fr_BE').arch, '<div>bonjour</div>',
|
||||
"copy on write (COW) also copy existing translations")
|
||||
|
||||
translation.value = 'hi'
|
||||
self.assertEqual(specific_view.with_context(lang='en_US').arch, '<div>hello</div>',
|
||||
self.base_view.update_field_translations('arch_db', {'fr_BE': {'bonjour': 'salut'}})
|
||||
self.assertEqual(self.base_view.with_context(lang='fr_BE').arch, '<div>salut</div>')
|
||||
self.assertEqual(specific_view.with_context(lang='fr_BE').arch, '<div>bonjour</div>',
|
||||
"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, '<div>hi</div>',
|
||||
self.assertEqual(specific_view.with_context(lang='fr_BE').arch, '<div>salut</div>',
|
||||
"loading module translation copy translation from base to specific view")
|
||||
|
||||
def test_soc_complete_flow(self):
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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': {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record model="test.translation.import.model1" id="test_translation_import_model1_record1">
|
||||
<field name="name">Tableware</field>
|
||||
<field name="xml" type="xml"><form string="Fork"><div>Knife</div><div>Spoon</div></form></field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -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"
|
||||
|
||||
|
@@ -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 ""
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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 ""
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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')
|
||||
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
@@ -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,
|
||||
'<form string="Fourchette"><div>Couteau</div><div>Cuillère</div></form>'
|
||||
)
|
||||
|
||||
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,
|
||||
'<form string="Fourchette, Belgium"><div>Couteau, Belgium</div><div>Cuillère, Belgium</div></form>'
|
||||
)
|
||||
|
||||
# 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,
|
||||
'<form string="Fourchette"><div>Couteau, Canada</div><div>Cuillère</div></form>'
|
||||
)
|
||||
|
||||
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,
|
||||
'<form string="Fourchette"><div>Couteau</div><div>Cuillère</div></form>'
|
||||
)
|
||||
|
||||
# remove All translations
|
||||
record.name = False
|
||||
record.name = 'Tableware'
|
||||
record.xml = False
|
||||
record.xml = '<form string="Fork"><div>Knife</div><div>Spoon</div></form>'
|
||||
self.assertEqual(
|
||||
record.with_context(lang='fr_FR').name,
|
||||
'Tableware'
|
||||
)
|
||||
self.assertEqual(
|
||||
record.with_context(lang='fr_FR').xml,
|
||||
'<form string="Fork"><div>Knife</div><div>Spoon</div></form>'
|
||||
)
|
||||
|
||||
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,
|
||||
'<form string="Fourchette"><div>Couteau</div><div>Cuillère</div></form>'
|
||||
)
|
||||
|
||||
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 = []
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="action_test_translation_import" model="ir.actions.act_window">
|
||||
<field name="name">Test translation import</field>
|
||||
<record id="action_test_translation_import_model1" model="ir.actions.act_window">
|
||||
<field name="name">Test translation import model1</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">test.translation.import</field>
|
||||
<field name="res_model">test.translation.import.model1</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="target">current</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_test_translation" name="Test translation" sequence="650"/>
|
||||
<menuitem id="menu_test_translation_import" name="Test translation model1" sequence="650"/>
|
||||
|
||||
<menuitem id="menu_test_translation_import"
|
||||
name="Test translation import"
|
||||
action="action_test_translation_import"
|
||||
parent="menu_test_translation"/>
|
||||
<menuitem id="menu_test_translation_import_model1"
|
||||
name="Test translation import model1"
|
||||
action="action_test_translation_import_model1"
|
||||
parent="menu_test_translation_import"/>
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
Reference in New Issue
Block a user