odoo_17.0.1/odoo/addons/base/tests/test_translate.py

1435 lines
67 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from hashlib import sha256
from unittest.mock import patch
import logging
import time
from psycopg2 import IntegrityError
from psycopg2.extras import Json
import io
from odoo.exceptions import UserError
from odoo.tools import sql
from odoo.tools.translate import quote, unquote, xml_translate, html_translate, TranslationImporter, TranslationModuleReader
from odoo.tests.common import TransactionCase, BaseCase, new_test_user, tagged
_stats_logger = logging.getLogger('odoo.tests.stats')
# a string with various unicode characters
SPECIAL_CHARACTERS = " ¥®°²Æçéðπ⁉€∇⓵▲☑♂♥✓➔『にㄅ㊀中한︸🌈🌍👌😀"
class TranslationToolsTestCase(BaseCase):
def assertItemsEqual(self, a, b, msg=None):
self.assertEqual(sorted(a), sorted(b), msg)
def test_quote_unquote(self):
def test_string(str):
quoted = quote(str)
#print "\n1:", repr(str)
#print "2:", repr(quoted)
unquoted = unquote("".join(quoted.split('"\n"')))
#print "3:", repr(unquoted)
self.assertEqual(str, unquoted)
test_string("""test \nall kinds\n \n o\r
\\\\ nope\n\n"
""")
# The ones with 1+ backslashes directly followed by
# a newline or literal N can fail... we would need a
# state-machine parser to handle these, but this would
# be much slower so it's better to avoid them at the moment
self.assertRaises(AssertionError, quote, """test \nall kinds\n\no\r
\\\\nope\n\n"
""")
def test_translate_xml_base(self):
""" Test xml_translate() without formatting elements. """
terms = []
source = """<form string="Form stuff">
<h1>Blah blah blah</h1>
Put some more text here
<field name="foo"/>
</form>"""
result = xml_translate(terms.append, source)
self.assertEqual(result, source)
self.assertItemsEqual(terms,
['Form stuff', 'Blah blah blah', 'Put some more text here'])
def test_translate_xml_text(self):
""" Test xml_translate() on plain text. """
terms = []
source = "Blah blah blah"
result = xml_translate(terms.append, source)
self.assertEqual(result, source)
self.assertItemsEqual(terms, [source])
def test_translate_xml_unicode(self):
""" Test xml_translate() on plain text with unicode characters. """
terms = []
source = u"Un heureux évènement"
result = xml_translate(terms.append, source)
self.assertEqual(result, source)
self.assertItemsEqual(terms, [source])
def test_translate_xml_text_entity(self):
""" Test xml_translate() on plain text with HTML escaped entities. """
terms = []
source = "Blah&amp;nbsp;blah&amp;nbsp;blah"
result = xml_translate(terms.append, source)
self.assertEqual(result, source)
self.assertItemsEqual(terms, [source])
def test_translate_xml_inline1(self):
""" Test xml_translate() with formatting elements. """
terms = []
source = """<form string="Form stuff">
<h1>Blah <i>blah</i> blah</h1>
Put some <b>more text</b> here
<field name="foo"/>
</form>"""
result = xml_translate(terms.append, source)
self.assertEqual(result, source)
self.assertItemsEqual(terms,
['Form stuff', 'Blah <i>blah</i> blah', 'Put some <b>more text</b> here'])
def test_translate_xml_inline2(self):
""" Test xml_translate() with formatting elements embedding other elements. """
terms = []
source = """<form string="Form stuff">
<b><h1>Blah <i>blah</i> blah</h1></b>
Put <em>some <b>more text</b></em> here
<field name="foo"/>
</form>"""
result = xml_translate(terms.append, source)
self.assertEqual(result, source)
self.assertItemsEqual(terms,
['Form stuff', 'Blah <i>blah</i> blah', 'Put <em>some <b>more text</b></em> here'])
def test_translate_xml_inline3(self):
""" Test xml_translate() with formatting elements without actual text. """
terms = []
source = """<form string="Form stuff">
<div>
<span class="before"/>
<h1>Blah blah blah</h1>
<span class="after">
<i class="hack"/>
</span>
</div>
</form>"""
result = xml_translate(terms.append, source)
self.assertEqual(result, source)
self.assertItemsEqual(terms,
['Form stuff', 'Blah blah blah'])
def test_translate_xml_inline4(self):
""" Test xml_translate() with inline elements with translated attrs only. """
terms = []
source = """<form string="Form stuff">
<div>
<label for="stuff"/>
<span class="fa fa-globe" title="Title stuff"/>
</div>
</form>"""
result = xml_translate(terms.append, source)
self.assertEqual(result, source)
self.assertItemsEqual(terms,
['Form stuff', '<span class="fa fa-globe" title="Title stuff"/>'])
def test_translate_xml_inline5(self):
""" Test xml_translate() with inline elements with empty translated attrs only. """
terms = []
source = """<form string="Form stuff">
<div>
<label for="stuff"/>
<span class="fa fa-globe" title=""/>
</div>
</form>"""
result = xml_translate(terms.append, source)
self.assertEqual(result, source)
self.assertItemsEqual(terms, ['Form stuff'])
def test_translate_xml_t(self):
""" Test xml_translate() with t-* attributes. """
terms = []
source = """<t t-name="stuff">
stuff before
<span t-field="o.name"/>
stuff after
</t>"""
result = xml_translate(terms.append, source)
self.assertEqual(result, source)
self.assertItemsEqual(terms,
['stuff before', 'stuff after'])
def test_translate_xml_off(self):
""" Test xml_translate() with attribute translate="off". """
terms = []
source = """<div>
stuff before
<div t-translation="off">Do not translate this</div>
stuff after
</div>"""
result = xml_translate(terms.append, source)
self.assertEqual(result, source)
self.assertItemsEqual(terms,
['stuff before', 'stuff after'])
def test_translate_xml_attribute(self):
""" Test xml_translate() with <attribute> elements. """
terms = []
source = """<field name="foo" position="attributes">
<attribute name="string">Translate this</attribute>
<attribute name="option">Do not translate this</attribute>
</field>"""
result = xml_translate(terms.append, source)
self.assertEqual(result, source)
self.assertItemsEqual(terms,
['Translate this'])
def test_translate_xml_a(self):
""" Test xml_translate() with <a> elements. """
terms = []
source = """<t t-name="stuff">
<ul class="nav navbar-nav">
<li class="nav-item">
<a class="nav-link oe_menu_leaf" href="/web#menu_id=42&amp;action=54">
<span class="oe_menu_text">Blah</span>
</a>
</li>
</ul>
</t>"""
result = xml_translate(terms.append, source)
self.assertEqual(result, source)
self.assertItemsEqual(terms,
['<span class="oe_menu_text">Blah</span>'])
def test_translate_xml_with_namespace(self):
""" Test xml_translate() on elements with namespaces. """
terms = []
# do not slit the long line below, otherwise the result will not match
source = """<Invoice xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2" xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<cbc:UBLVersionID t-esc="version_id"/>
<t t-foreach="[1, 2, 3, 4]" t-as="value">
Oasis <cac:Test t-esc="value"/>
</t>
</Invoice>"""
result = xml_translate(terms.append, source)
self.assertEqual(result, source)
self.assertItemsEqual(terms, ['Oasis'])
result = xml_translate(lambda term: term, source)
self.assertEqual(result, source)
def test_translate_xml_invalid_translations(self):
""" Test xml_translate() with invalid translations. """
source = """<form string="Form stuff">
<h1>Blah <i>blah</i> blah</h1>
Put some <b>more text</b> here
<field name="foo"/>
</form>"""
translations = {
"Put some <b>more text</b> here": "Mettre <b>plus de texte</i> ici",
}
expect = """<form string="Form stuff">
<h1>Blah <i>blah</i> blah</h1>
Mettre <b>plus de texte ici
</b><field name="foo"/>
</form>"""
result = xml_translate(translations.get, source)
self.assertEqual(result, expect)
def test_translate_xml_illegal_translations(self):
# attributes
make_xml = '<form string="{}">test</form>'.format
attr = 'Damien Roberts" <d.roberts@example.com>'
escaped_attr = 'Damien Roberts&quot; &lt;d.roberts@example.com&gt;'
# {legal: legal(not escaped attr)}
self.assertEqual(
xml_translate({'X': attr}.get, make_xml('X')),
make_xml(escaped_attr),
'attr should be translated and escaped',
)
# {legal(not escaped attr): legal}
self.assertEqual(
xml_translate({attr: 'X'}.get, make_xml(escaped_attr)),
make_xml('X'),
'attrs should be translated by using unescaped old terms',
)
# {illegal(escaped attr): legal}
self.assertEqual(
xml_translate({escaped_attr: 'X'}.get, make_xml(escaped_attr)),
make_xml(escaped_attr),
'attrs cannot be translated by using escaped old terms',
)
# text and elements
make_xml = '<form string="X">{}</form>'.format
term = '<i class="fa fa-circle" role="img" aria-label="Invalid" title="Invalid"/>'
# {legal: legal}
valid = '<i class="fa fa-circle" role="img" aria-label="Non-valide" title="Non-valide"/>X'
self.assertEqual(
xml_translate({term: valid}.get, make_xml(term)),
make_xml(valid),
'content in inline-block should be treated as one term and translated',
)
# {legal: illegal(has no text)}
invalid = '<i class="fa fa-circle" role="img"/>'
self.assertEqual(
xml_translate({term: invalid}.get, make_xml(term)),
make_xml(term),
f'translation {invalid!r} has no text and should be dropped as a translation',
)
invalid = ' '
self.assertEqual(
xml_translate({term: invalid}.get, make_xml(term)),
make_xml(term),
f'translation {invalid!r} has no text and should be dropped as a translation',
)
invalid = '<i> </i>'
self.assertEqual(
xml_translate({term: invalid}.get, make_xml(term)),
make_xml(term),
f'translation {invalid!r} has no text and should be dropped as a translation',
)
# {legal: illegal(has non-translatable elements)}
invalid = '<div>X</div>'
self.assertEqual(
xml_translate({term: invalid}.get, make_xml(term)),
make_xml(term),
f'translation {invalid!r} has non-translatable elements(elements not in TRANSLATED_ELEMENTS)',
)
def test_translate_html(self):
""" Test html_translate(). """
source = """<blockquote>A <h2>B</h2> C</blockquote>"""
result = html_translate(lambda term: term, source)
self.assertEqual(result, source)
def test_translate_html_i(self):
""" Test xml_translate() and html_translate() with <i> elements. """
source = """<p>A <i class="fa-check"></i> B</p>"""
result = xml_translate(lambda term: term, source)
self.assertEqual(result, """<p>A <i class="fa-check"/> B</p>""")
result = html_translate(lambda term: term, source)
self.assertEqual(result, source)
class TestLanguageInstall(TransactionCase):
def test_language_install(self):
fr = self.env['res.lang'].with_context(active_test=False).search([('code', '=', 'fr_FR')])
self.assertTrue(fr)
wizard = self.env['base.language.install'].create({'lang_ids': fr.ids})
self.env.flush_all()
# running the wizard calls _load_module_terms() to load PO files
loaded = []
def _load_module_terms(self, modules, langs, overwrite=False):
loaded.append((modules, langs, overwrite))
with patch('odoo.addons.base.models.ir_module.Module._load_module_terms', _load_module_terms):
wizard.lang_install()
# _load_module_terms is called once with lang='fr_FR' and overwrite=True
self.assertEqual(len(loaded), 1)
self.assertEqual(loaded[0][1], ['fr_FR'])
self.assertEqual(loaded[0][2], True)
@tagged('post_install', '-at_install')
class TestTranslationExport(TransactionCase):
def test_export_translatable_resources(self):
"""Read files of installed modules and export translatable terms"""
with self.assertNoLogs('odoo.tools.translate', "ERROR"):
TranslationModuleReader(self.env.cr)
class TestTranslation(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env['res.lang']._activate_lang('fr_FR')
cls.env.ref('base.module_base')._update_translations(['fr_FR'])
cls.customers = cls.env['res.partner.category'].create({'name': 'Customers'})
cls.customers_xml_id = cls.customers.export_data(['id']).get('datas')[0][0]
po_string = '''
#. module: __export__
#: model:res.partner.category,name:%s
msgid "Customers"
msgstr "Clients"
''' % cls.customers_xml_id
with io.BytesIO(bytes(po_string, encoding='utf-8')) as f:
f.name = 'dummy'
translation_importer = TranslationImporter(cls.env.cr, verbose=True)
translation_importer.load(f, 'po', 'fr_FR')
translation_importer.save(overwrite=True)
def test_101_create_translated_record(self):
category = self.customers.with_context({})
self.assertEqual(category.name, 'Customers', "Error in basic name")
category_fr = category.with_context({'lang': 'fr_FR'})
self.assertEqual(category_fr.name, 'Clients', "Translation not found")
def test_102_duplicate_record(self):
category = self.customers.with_context({'lang': 'fr_FR'}).copy()
category_no = category.with_context({})
self.assertEqual(category_no.name, 'Customers', "Duplication should copy all translations")
category_fr = category.with_context({'lang': 'fr_FR'})
self.assertEqual(category_fr.name, 'Clients', "Did not found translation for initial value")
def test_103_duplicate_record_fr(self):
category = self.customers.with_context({'lang': 'fr_FR'}).copy({'name': 'Clients (copie)'})
category_no = category.with_context({})
self.assertEqual(category_no.name, 'Clients (copie)', "Duplication should set untranslated value")
category_fr = category.with_context({'lang': 'fr_FR'})
self.assertEqual(category_fr.name, 'Clients (copie)', "Did not used default value for translated value")
def test_104_orderby_translated_field(self):
""" Test search ordered by a translated field. """
# create a category with a French translation
padawans = self.env['res.partner.category'].create({'name': 'Padawans'})
padawans_fr = padawans.with_context(lang='fr_FR')
padawans_fr.write({'name': 'Apprentis'})
# search for categories, and sort them by (translated) name
categories = padawans_fr.search([('id', 'in', [self.customers.id, padawans.id])], order='name')
self.assertEqual(categories.ids, [padawans.id, self.customers.id],
"Search ordered by translated name should return Padawans (Apprentis) before Customers (Clients)")
def test_105_duplicate_record_multi_no_en(self):
self.env['res.partner'].with_context(active_test=False).search([]).write({'lang': 'fr_FR'})
self.env['res.lang']._activate_lang('nl_NL')
self.customers.with_context(lang='nl_NL').name = 'Klanten'
self.env['res.lang']._activate_lang('zh_CN')
self.customers.with_context(lang='zh_CN').name = '客户'
self.env.ref('base.lang_en').active = False
self.env.ref('base.lang_zh_CN').active = False
category = self.customers
translations = category._fields['name']._get_stored_translations(category)
self.assertDictEqual(
translations,
{
'en_US': 'Customers',
'fr_FR': 'Clients',
'nl_NL': 'Klanten',
'zh_CN': '客户',
}
)
category_copy = self.customers.with_context(lang='fr_FR').copy()
translations = category_copy._fields['name']._get_stored_translations(category_copy)
self.assertDictEqual(
translations,
{
'en_US': 'Customers',
'fr_FR': 'Clients',
'nl_NL': 'Klanten',
},
'English, French and Dutch translation should be copied, Chinese translation should be dropped'
)
def test_107_duplicate_record_en(self):
category = self.customers.with_context({'lang': 'en_US'}).copy()
category_no = category.with_context({})
self.assertEqual(category_no.name, 'Customers', "Duplication did not set untranslated value")
category_fr = category.with_context({'lang': 'fr_FR'})
self.assertEqual(category_fr.name, 'Clients', "Did not found translation for initial value")
def test_108_search_en(self):
CategoryEn = self.env['res.partner.category'].with_context(lang='en_US')
category_equal = CategoryEn.search([('name', '=', 'Customers')])
self.assertEqual(category_equal.id, self.customers.id, "Search with '=' doesn't work for English")
category_ilike = CategoryEn.search([('name', 'ilike', 'stoMer')])
self.assertIn(self.customers, category_ilike, "Search with 'ilike' doesn't work for English")
category_eq_ilike = CategoryEn.search([('name', '=ilike', 'CustoMers')])
self.assertIn(self.customers, category_eq_ilike, "Search with '=ilike' doesn't work for English")
category_in = CategoryEn.search([('name', 'in', ['Customers'])])
self.assertIn(self.customers, category_in, "Search with 'in' doesn't work for English")
def test_109_search_fr(self):
CategoryFr = self.env['res.partner.category'].with_context(lang='fr_FR')
category_equal = CategoryFr.search([('name', '=', 'Clients')])
self.assertEqual(category_equal.id, self.customers.id, "Search with '=' doesn't work for non English")
category_ilike = CategoryFr.search([('name', 'ilike', 'lIen')])
self.assertIn(self.customers, category_ilike, "Search with 'ilike' doesn't work for non English")
category_eq_ilike = CategoryFr.search([('name', '=ilike', 'clieNts')])
self.assertIn(self.customers, category_eq_ilike, "Search with '=ilike' doesn't work for non English")
category_in = CategoryFr.search([('name', 'in', ['Clients'])])
self.assertIn(self.customers, category_in, "Search with 'in' doesn't work for non English")
def test_110_search_es(self):
self.env['res.lang']._activate_lang('es_ES')
langs = self.env['res.lang'].get_installed()
self.assertEqual([('en_US', 'English (US)'), ('fr_FR', 'French / Français'), ('es_ES', 'Spanish / Español')],
langs, "Test did not start with the expected languages")
CategoryEs = self.env['res.partner.category'].with_context(lang='es_ES')
category_equal = CategoryEs.search([('name', '=', 'Customers')])
self.assertEqual(category_equal.id, self.customers.id, "Search with '=' should use the English name if the current language translation is not available")
category_ilike = CategoryEs.search([('name', 'ilike', 'usTom')])
self.assertIn(self.customers, category_ilike, "Search with 'ilike' should use the English name if the current language translation is not available")
category_eq_ilike = CategoryEs.search([('name', '=ilike', 'CustoMers')])
self.assertIn(self.customers, category_eq_ilike, "Search with '=ilike' should use the English name if the current language translation is not available")
category_in = CategoryEs.search([('name', 'in', ['Customers'])])
self.assertIn(self.customers, category_in, "Search with 'in' should use the English name if the current language translation is not available")
def test_111_prefetch_langs(self):
category_en = self.customers.with_context(lang='en_US')
self.env.ref('base.lang_nl').active = True
category_nl = category_en.with_context(lang='nl_NL')
category_nl.name = 'Klanten'
self.assertTrue(self.env.ref('base.lang_fr').active)
category_fr = category_en.with_context(lang='fr_FR')
self.assertFalse(self.env.ref('base.lang_zh_CN').active)
category_zh = category_en.with_context(lang='zh_CN')
self.env['res.partner'].with_context(active_test=False).search([]).write({'lang': 'fr_FR'})
self.env.ref('base.lang_en').active = False
category_fr.with_context(prefetch_langs=True).name
category_nl.name
category_en.name
category_zh.name
category_fr.invalidate_recordset()
with self.assertQueryCount(1):
self.assertEqual(category_fr.with_context(prefetch_langs=True).name, 'Clients')
with self.assertQueryCount(0):
self.assertEqual(category_nl.name, 'Klanten')
self.assertEqual(category_en.name, 'Customers')
self.assertEqual(category_zh.name, 'Customers')
# TODO Currently, the unique constraint doesn't work for translatable field
# def test_111_unique_en(self):
# Country = self.env['res.country']
# country_1 = Country.create({'name': 'Odoo'})
# country_1.with_context(lang='fr_FR').name = 'Odoo_Fr'
# country_1.flush_recordset()
#
# country_2 = Country.create({'name': 'Odoo2'})
# with self.assertRaises(IntegrityError), mute_logger('odoo.sql_db'):
# country_2.name = 'Odoo'
# country_2.flush_recordset()
#
# with self.assertRaises(IntegrityError), mute_logger('odoo.sql_db'):
# country_3 = Country.create({'name': 'Odoo'})
class TestTranslationWrite(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.category = cls.env['res.partner.category'].create({'name': 'Reblochon'})
cls.category_xml_id = cls.category.export_data(['id']).get('datas')[0][0]
def test_00(self):
self.env['res.lang']._activate_lang('fr_FR')
langs = self.env['res.lang'].get_installed()
self.assertEqual([('en_US', 'English (US)'), ('fr_FR', 'French / Français')], langs,
"Test did not started with expected languages")
category = self.env['res.partner.category'].with_context(lang='en_US').create({'name': 'English'})
self.assertEqual(category.with_context(lang='en_US').name, 'English')
self.assertEqual(category.with_context(lang='fr_FR').name, 'English')
category.with_context(lang='en_US').name = 'English 2'
self.assertEqual(category.with_context(lang='fr_FR').name, 'English 2')
category2 = self.env['res.partner.category'].with_context(lang='fr_FR').create({'name': 'French'})
self.assertEqual(category2.with_context(lang='en_US').name, 'French')
self.assertEqual(category2.with_context(lang='fr_FR').name, 'French')
category2.with_context(lang='en_US').name = 'English'
self.assertEqual(category2.with_context(lang='fr_FR').name, 'French')
category3 = self.env['res.partner.category'].with_context(lang='en_US').create({'name': 'English'})
self.assertEqual(category3.with_context(lang='en_US').name, 'English')
self.assertEqual(category3.with_context(lang='fr_FR').name, 'English')
category3.with_context(lang='fr_FR').name = 'French 2'
category3.with_context(lang='en_US').name = 'English 2'
self.assertEqual(category3.with_context(lang='fr_FR').name, 'French 2')
def test_03_fr_single(self):
self.env['res.lang']._activate_lang('fr_FR')
self.env['res.partner'].with_context(active_test=False).search([]).write({'lang': 'fr_FR'})
self.env.ref('base.lang_en').active = False
langs = self.env['res.lang'].get_installed()
self.assertEqual([('fr_FR', 'French / Français')], langs, "Test did not started with expected languages")
self.category.with_context(lang='fr_FR').write({'name': 'French Name'})
fr_name = self.category.with_context(lang='fr_FR').read(['name'])
self.assertEqual(fr_name[0]['name'], "French Name", "Reference field not updated")
# read from the cache
self.assertEqual(self.category.with_context(lang='fr_FR').name, "French Name")
# read from database
self.category.invalidate_recordset()
self.assertEqual(self.category.with_context(lang='fr_FR').name, "French Name")
def test_04_fr_multi(self):
self.env['res.lang']._activate_lang('fr_FR')
langs = self.env['res.lang'].get_installed()
self.assertEqual([('en_US', 'English (US)'), ('fr_FR', 'French / Français')], langs,
"Test did not started with expected languages")
po_string = '''
#. module: __export__
#: model:res.partner.category,name:%s
msgid "Reblochon"
msgstr "Translated Name"
''' % self.category_xml_id
with io.BytesIO(bytes(po_string, encoding='utf-8')) as f:
f.name = 'dummy'
translation_importer = TranslationImporter(self.env.cr, verbose=True)
translation_importer.load(f, 'po', 'fr_FR')
translation_importer.save(overwrite=True)
self.category.with_context(lang='fr_FR').write({'name': 'French Name'})
self.category.with_context(lang='en_US').write({'name': 'English Name'})
# read from the cache first
self.assertEqual(self.category.with_context(lang=None).name, "English Name")
self.assertEqual(self.category.with_context(lang='fr_FR').name, "French Name")
self.assertEqual(self.category.with_context(lang='en_US').name, "English Name")
# force save to database and clear the cache: force a clean state
self.category.invalidate_recordset()
# read from database
self.assertEqual(self.category.with_context(lang=None).name, "English Name")
self.assertEqual(self.category.with_context(lang='fr_FR').name, "French Name")
self.assertEqual(self.category.with_context(lang='en_US').name, "English Name")
def test_04_fr_multi_no_en(self):
self.env['res.lang']._activate_lang('fr_FR')
self.env['res.lang']._activate_lang('es_ES')
self.env['res.partner'].with_context(active_test=False).search([]).write({'lang': 'fr_FR'})
self.env.ref('base.lang_en').active = False
langs = self.env['res.lang'].get_installed()
self.assertEqual([('fr_FR', 'French / Français'), ('es_ES', 'Spanish / Español')], langs,
"Test did not start with the expected languages")
self.category.with_context(lang='fr_FR').write({'name': 'French Name'})
self.category.with_context(lang='es_ES').write({'name': 'Spanish Name'})
self.category.with_context(lang=None).write({'name': 'None Name'})
# read from the cache first
self.assertEqual(self.category.with_context(lang='fr_FR').name, "French Name")
self.assertEqual(self.category.with_context(lang='es_ES').name, "Spanish Name")
self.assertEqual(self.category.with_context(lang=None).name, "None Name")
# force save to database and clear the cache: force a clean state
self.category.invalidate_recordset()
# read from database
self.assertEqual(self.category.with_context(lang='fr_FR').name, "French Name")
self.assertEqual(self.category.with_context(lang='es_ES').name, "Spanish Name")
self.assertEqual(self.category.with_context(lang=None).name, "None Name")
def test_05_remove_multi_false(self):
self._test_05_remove_multi(False)
def _test_05_remove_multi(self, empty_value):
self.env['res.lang']._activate_lang('fr_FR')
langs = self.env['res.lang'].get_installed()
self.assertEqual([('en_US', 'English (US)'), ('fr_FR', 'French / Français')], langs,
"Test did not started with expected languages")
belgium = self.env.ref('base.be')
# vat_label is translatable and not required
belgium.with_context(lang='en_US').write({'vat_label': 'VAT'})
belgium.with_context(lang='fr_FR').write({'vat_label': 'TVA'})
# remove the value
belgium.with_context(lang='fr_FR').write({'vat_label': empty_value})
# should recover the initial value from db
self.assertEqual(
empty_value,
belgium.with_context(lang='fr_FR').vat_label,
"Value should be the empty_value"
)
self.assertEqual(
empty_value,
belgium.with_context(lang='en_US').vat_label,
"Value should be the empty_value"
)
self.assertEqual(
empty_value,
belgium.with_context(lang=None).vat_label,
"Value should be the empty_value"
)
belgium.with_context(lang='en_US').write({'vat_label': 'VAT'})
belgium.with_context(lang='fr_FR').write({'vat_label': 'TVA'})
# remove the value
belgium.with_context(lang='en_US').write({'vat_label': empty_value})
self.assertEqual(
empty_value,
belgium.with_context(lang='fr_FR').vat_label,
"Value should be the empty_value"
)
self.assertEqual(
empty_value,
belgium.with_context(lang='en_US').vat_label,
"Value should be the empty_value"
)
self.assertEqual(
empty_value,
belgium.with_context(lang=None).vat_label,
"Value should be the empty_value"
)
def test_write_empty_and_value(self):
self.env['res.lang']._activate_lang('fr_FR')
self.env['res.lang']._activate_lang('nl_NL')
langs = self.env['res.lang'].get_installed()
self.assertEqual([('nl_NL', 'Dutch / Nederlands'), ('en_US', 'English (US)'), ('fr_FR', 'French / Français')], langs,
"Test did not started with expected languages")
belgium = self.env.ref('base.be')
# vat_label is translatable and not required
belgium.with_context(lang='en_US').write({'vat_label': 'VAT_US'})
belgium.with_context(lang='fr_FR').write({'vat_label': 'VAT_FR'})
belgium.with_context(lang='nl_NL').write({'vat_label': 'VAT_NL'})
belgium.invalidate_recordset()
belgium.with_context(lang='en_US').write({'vat_label': False})
belgium.with_context(lang='fr_FR').write({'vat_label': 'TVA_FR2'})
self.assertEqual(belgium.with_context(lang='en_US').vat_label, 'TVA_FR2')
self.assertEqual(belgium.with_context(lang='nl_NL').vat_label, 'TVA_FR2')
belgium.with_context(lang='fr_FR').write({'vat_label': 'TVA_FR3'})
belgium.with_context(lang='en_US').write({'vat_label': ''})
self.assertEqual(belgium.with_context(lang='en_US').vat_label, '')
self.assertEqual(belgium.with_context(lang='nl_NL').vat_label, '')
def test_create_empty_false(self):
self._test_create_empty(False)
# feature removed
# def test_cresate_emtpy_empty_string(self):
# self._test_create_empty('')
def _test_create_empty(self, empty_value):
self.env['res.lang']._activate_lang('fr_FR')
langs = self.env['res.lang'].get_installed()
self.assertEqual([('en_US', 'English (US)'), ('fr_FR', 'French / Français')], langs,
"Test did not started with expected languages")
group = self.env['res.groups'].create({'name': 'test_group', 'comment': empty_value})
self.assertEqual(group.with_context(lang='en_US').comment, empty_value)
self.assertEqual(group.with_context(lang='fr_FR').comment, empty_value)
group.with_context(lang='fr_FR').comment = 'French comment'
self.assertEqual(group.with_context(lang='fr_FR').comment, 'French comment')
self.assertEqual(group.with_context(lang='en_US').comment, 'French comment')
group.with_context(lang='fr_FR').comment = 'French comment 2'
self.assertEqual(group.with_context(lang='fr_FR').comment, 'French comment 2')
self.assertEqual(group.with_context(lang='en_US').comment, 'French comment')
def test_update_field_translations(self):
self.env['res.lang']._activate_lang('fr_FR')
categoryEN = self.category.with_context(lang='en_US')
categoryFR = self.category.with_context(lang='fr_FR')
self.category.update_field_translations('name', {'en_US': 'English Name', 'fr_FR': 'French Name'})
self.assertEqual(categoryEN.name, 'English Name')
self.assertEqual(categoryFR.name, 'French Name')
# void fr_FR translation and fallback to en_US
self.category.update_field_translations('name', {'fr_FR': False})
self.assertEqual(categoryEN.name, 'English Name')
self.assertEqual(categoryFR.name, 'English Name')
categoryEN.name = 'English Name 2'
self.assertEqual(categoryEN.name, 'English Name 2')
self.assertEqual(categoryFR.name, 'English Name 2')
# cannot void en_US
self.category.update_field_translations('name', {'en_US': 'English Name', 'fr_FR': 'French Name'})
self.category.update_field_translations('name', {'en_US': False})
self.assertEqual(categoryEN.name, 'English Name')
self.assertEqual(categoryFR.name, 'French Name')
# empty str is a valid translation
self.category.update_field_translations('name', {'fr_FR': ''})
self.assertEqual(categoryEN.name, 'English Name')
self.assertEqual(categoryFR.name, '')
self.category.update_field_translations('name', {'en_US': '', 'fr_FR': 'French Name'})
self.assertEqual(categoryEN.name, '')
self.assertEqual(categoryFR.name, 'French Name')
# raise error when the translations are in the form for model_terms translated fields
with self.assertRaises(UserError):
self.category.update_field_translations('name', {'fr_FR': {'English Name': 'French Name'}})
def test_update_field_translations_for_empty(self):
self.env['res.lang']._activate_lang('nl_NL')
self.env['res.lang']._activate_lang('fr_FR')
group = self.env['res.groups'].create({'name': 'test_group', 'comment': False})
groupEN = group.with_context(lang='en_US')
groupFR = group.with_context(lang='fr_FR')
groupNL = group.with_context(lang='nl_NL')
self.assertEqual(groupEN.comment, False)
groupFR.update_field_translations('comment', {'nl_NL': 'Dutch Name', 'fr_FR': 'French Name'})
self.assertEqual(groupEN.comment, 'French Name', 'fr_FR value as the current env.lang is chosen as the default en_US value')
self.assertEqual(groupFR.comment, 'French Name')
self.assertEqual(groupNL.comment, 'Dutch Name')
group.comment = False
groupFR.update_field_translations('comment', {'nl_NL': False, 'fr_FR': False})
groupFR.flush_recordset()
self.cr.execute("SELECT comment FROM res_groups WHERE id = %s", (group.id,))
(comment,) = self.cr.fetchone()
self.assertEqual(comment, None)
def test_field_selection(self):
""" Test translations of field selections. """
self.env['res.lang']._activate_lang('fr_FR')
field = self.env['ir.model']._fields['state']
self.assertEqual([key for key, _ in field.selection], ['manual', 'base'])
ir_field = self.env['ir.model.fields']._get('ir.model', 'state')
ir_field = ir_field.with_context(lang='fr_FR')
ir_field.selection_ids[0].name = 'Custo'
ir_field.selection_ids[1].name = 'Pas touche!'
fg = self.env['ir.model'].fields_get(['state'])
self.assertEqual(fg['state']['selection'], field.selection)
fg = self.env['ir.model'].with_context(lang='fr_FR').fields_get(['state'])
self.assertEqual(fg['state']['selection'],
[('manual', 'Custo'), ('base', 'Pas touche!')])
def test_load_views(self):
""" Test translations of field descriptions in get_view(). """
self.env['res.lang']._activate_lang('fr_FR')
# add translation for the string of field ir.model.name
ir_model_field = self.env['ir.model.fields']._get('ir.model', 'name')
LABEL = "Description du Modèle"
ir_model_field_xml_id = ir_model_field.export_data(['id']).get('datas')[0][0]
po_string = '''
#. module: __export__
#: model:ir.model.fields,field_description:%s
msgid "Model Description"
msgstr "%s"
''' % (ir_model_field_xml_id, LABEL)
with io.BytesIO(bytes(po_string, encoding='utf-8')) as f:
f.name = 'dummy'
translation_importer = TranslationImporter(self.env.cr, verbose=True)
translation_importer.load(f, 'po', 'fr_FR')
translation_importer.save(overwrite=True)
# check that fields_get() returns the expected label
model = self.env['ir.model'].with_context(lang='fr_FR')
info = model.fields_get(['name'])
self.assertEqual(info['name']['string'], LABEL)
# check that get_views() also returns the expected label
info = model.get_views([(False, 'form')])
self.assertEqual(info['models'][model._name]['name']['string'], LABEL)
class TestXMLTranslation(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env['res.lang']._activate_lang('fr_FR')
cls.env['res.lang']._activate_lang('nl_NL')
cls.env.ref('base.module_base')._update_translations(['fr_FR', 'nl_NL'])
def create_view(self, archf, terms, **kwargs):
view = self.env['ir.ui.view'].create({
'name': 'test',
'model': 'res.partner',
'arch': archf % terms,
})
view.invalidate_recordset()
val = {'en_US': archf % terms}
for lang, trans_terms in kwargs.items():
val[lang] = archf % trans_terms
query = """UPDATE ir_ui_view
SET arch_db = %s
WHERE id = %s"""
self.env.cr.execute(query, (Json(val), view.id))
return view
def test_copy(self):
""" Create a simple view, fill in translations, and copy it. """
archf = '<form string="%s"><div>%s</div><div>%s</div></form>'
terms_en = ('Knife', 'Fork', 'Spoon')
terms_fr = ('Couteau', 'Fourchette', 'Cuiller')
view0 = self.create_view(archf, terms_en, fr_FR=terms_fr)
env_en = self.env(context={'lang': 'en_US'})
env_fr = self.env(context={'lang': 'fr_FR'})
# check translated field
self.assertEqual(view0.with_env(env_en).arch_db, archf % terms_en)
self.assertEqual(view0.with_env(env_fr).arch_db, archf % terms_fr)
# copy without lang
view1 = view0.with_env(env_en).copy({})
self.assertEqual(view1.with_env(env_en).arch_db, archf % terms_en)
self.assertEqual(view1.with_env(env_fr).arch_db, archf % terms_fr)
# copy with lang='fr_FR'
view2 = view0.with_env(env_fr).copy({})
self.assertEqual(view2.with_env(env_en).arch_db, archf % terms_en)
self.assertEqual(view2.with_env(env_fr).arch_db, archf % terms_fr)
# copy with lang='fr_FR' and translate=html_translate
self.patch(type(self.env['ir.ui.view']).arch_db, 'translate', html_translate)
view3 = view0.with_env(env_fr).copy({})
self.assertEqual(view3.with_env(env_en).arch_db, archf % terms_en)
self.assertEqual(view3.with_env(env_fr).arch_db, archf % terms_fr)
def test_spaces(self):
""" Create translations where value has surrounding spaces. """
archf = '<form string="%s"><div>%s</div><div>%s</div></form>'
terms_en = ('Knife', 'Fork', 'Spoon')
terms_fr = (' Couteau', 'Fourchette ', ' Cuiller ')
self.create_view(archf, terms_en, fr_FR=terms_fr)
def test_sync(self):
""" Check translations after minor change in source terms. """
archf = '<form string="X">%s</form>'
terms_en = ('Bread and cheeze',)
terms_fr = ('Pain et fromage',)
terms_nl = ('Brood and kaas',)
view = self.create_view(archf, terms_en, en_US=terms_en, fr_FR=terms_fr, nl_NL=terms_nl)
env_nolang = self.env(context={})
env_en = self.env(context={'lang': 'en_US'})
env_fr = self.env(context={'lang': 'fr_FR'})
env_nl = self.env(context={'lang': 'nl_NL'})
self.assertEqual(view.with_env(env_nolang).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_en).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_fr).arch_db, archf % terms_fr)
self.assertEqual(view.with_env(env_nl).arch_db, archf % terms_nl)
# modify source term in view (fixed type in 'cheeze')
terms_en = ('Bread and cheese',)
view.with_env(env_en).write({'arch_db': archf % terms_en})
# check whether translations have been synchronized
self.assertEqual(view.with_env(env_nolang).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_en).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_fr).arch_db, archf % terms_fr)
self.assertEqual(view.with_env(env_nl).arch_db, archf % terms_nl)
view = self.create_view(archf, terms_fr, en_US=terms_en, fr_FR=terms_fr, nl_NL=terms_nl)
# modify source term in view in another language with close term
new_terms_fr = ('Pains et fromage',)
view.with_env(env_fr).write({'arch_db': archf % new_terms_fr})
# check whether translations have been synchronized
self.assertEqual(view.with_env(env_en).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_fr).arch_db, archf % new_terms_fr)
self.assertEqual(view.with_env(env_nl).arch_db, archf % terms_nl)
def test_sync_xml(self):
""" Check translations of 'arch' after xml tags changes in source terms. """
archf = '<form string="X">%s<div>%s</div></form>'
terms_en = ('Bread and cheese', 'Fork')
terms_fr = ('Pain et fromage', 'Fourchette')
terms_nl = ('Brood and kaas', 'Vork')
view = self.create_view(archf, terms_en, en_US=terms_en, fr_FR=terms_fr, nl_NL=terms_nl)
env_nolang = self.env(context={})
env_en = self.env(context={'lang': 'en_US'})
env_fr = self.env(context={'lang': 'fr_FR'})
env_nl = self.env(context={'lang': 'nl_NL'})
self.assertEqual(view.with_env(env_nolang).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_en).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_fr).arch_db, archf % terms_fr)
self.assertEqual(view.with_env(env_nl).arch_db, archf % terms_nl)
# modify source term in view (add css style)
terms_en = ('Bread <span style="font-weight:bold">and</span> cheese', 'Fork')
view.with_env(env_en).write({'arch_db': archf % terms_en})
# check whether translations have been kept
self.assertEqual(view.with_env(env_nolang).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_en).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_fr).arch_db, archf % terms_fr)
self.assertEqual(view.with_env(env_nl).arch_db, archf % terms_nl)
# modify source term in view (actual text change)
terms_en = ('Bread <span style="font-weight:bold">and</span> butter', 'Fork')
view.with_env(env_en).write({'arch_db': archf % terms_en})
# check whether translations have been reset
self.assertEqual(view.with_env(env_nolang).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_en).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_fr).arch_db, archf % (terms_en[0], terms_fr[1]))
self.assertEqual(view.with_env(env_nl).arch_db, archf % (terms_en[0], terms_nl[1]))
def test_sync_xml_no_en(self):
self.env['res.lang']._activate_lang('fr_FR')
self.env['res.lang']._activate_lang('nl_NL')
archf = '<form string="X">%s<div>%s</div></form>'
terms_en = ('Bread and cheese', 'Fork')
terms_fr = ('Pain et fromage', 'Fourchetta')
terms_nl = ('Brood and kaas', 'Vork')
view = self.create_view(archf, terms_en, en_US=terms_en, fr_FR=terms_fr, nl_NL=terms_nl)
self.env['res.partner'].with_context(active_test=False).search([]).write({'lang': 'fr_FR'})
self.env.ref('base.lang_en').active = False
env_nolang = self.env(context={})
env_fr = self.env(context={'lang': 'fr_FR'})
env_nl = self.env(context={'lang': 'nl_NL'})
# style change and typo change
terms_fr = ('Pain <span style="font-weight:bold">et</span> fromage', 'Fourchette')
view.with_env(env_fr).write({'arch_db': archf % terms_fr})
self.assertEqual(view.with_env(env_nolang).arch_db, archf % terms_fr)
self.assertEqual(view.with_env(env_fr).arch_db, archf % terms_fr)
self.assertEqual(view.with_env(env_nl).arch_db, archf % terms_nl)
def test_sync_text_to_xml(self):
""" Check translations of 'arch' after xml tags changes in source terms. """
archf = '<form string="X">%s</form>'
terms_en = ('<span>Hi</span>',)
terms_fr = ('<span>Salut</span>',)
terms_nl = ('<span>Hallo</span>',)
view = self.create_view(archf, terms_en, en_US=terms_en, fr_FR=terms_fr, nl_NL=terms_nl)
env_nolang = self.env(context={})
env_en = self.env(context={'lang': 'en_US'})
env_fr = self.env(context={'lang': 'fr_FR'})
# modify the arch view, keep the same text content: 'Hi'
terms_en = 'Hi'
archf = '<form string="X"><setting string="%s"><span color="red"/></setting></form>'
view.with_env(env_en).write({'arch_db': archf % terms_en})
self.assertEqual(view.with_env(env_nolang).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_en).arch_db, archf % terms_en)
# check that we didn't set string="&lt;span&gt;Salut&lt;/span&gt;"
self.assertEqual(view.with_env(env_fr).arch_db, archf % terms_en)
def test_sync_xml_collision(self):
""" Check translations of 'arch' after xml tags changes in source terms
when the same term appears in different elements with different
styles.
"""
archf = '''<form class="row">
%s
<div class="s_table_of_content_vertical_navbar" data-name="Navbar" contenteditable="false">
<div class="s_table_of_content_navbar" style="top: 76px;"><a href="#table_of_content_heading_1672668075678_4" class="table_of_content_link">%s</a></div>
</div>
<div class="s_table_of_content_main" data-name="Content">
<section class="pb16">
<h1 data-anchor="true" class="o_default_snippet_text" id="table_of_content_heading_1672668075678_4">%s</h1>
</section>
</div>
</form>'''
terms_en = ('Bread and cheese', 'Knive and Fork', 'Knive <span style="font-weight:bold">and</span> Fork')
terms_fr = ('Pain et fromage', 'Couteau et Fourchette', 'Couteau et Fourchette')
terms_nl = ('Brood and kaas', 'Mes en Vork', 'Mes en Vork')
view = self.create_view(archf, terms_en, en_US=terms_en, fr_FR=terms_fr, nl_NL=terms_nl)
env_nolang = self.env(context={})
env_en = self.env(context={'lang': 'en_US'})
env_fr = self.env(context={'lang': 'fr_FR'})
env_nl = self.env(context={'lang': 'nl_NL'})
self.assertEqual(view.with_env(env_nolang).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_en).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_fr).arch_db, archf % terms_fr)
self.assertEqual(view.with_env(env_nl).arch_db, archf % terms_nl)
# modify source term in view (small change)
terms_en = ('Bread and cheese', 'Knife and Fork', 'Knife <span style="font-weight:bold">and</span> Fork')
view.with_env(env_en).write({'arch_db': archf % terms_en})
# check whether translations have been kept
self.assertEqual(view.with_env(env_nolang).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_en).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_fr).arch_db, archf % terms_fr)
self.assertEqual(view.with_env(env_nl).arch_db, archf % terms_nl)
# modify source term in view (actual text change)
terms_en = ('Bread and cheese', 'Fork and Knife', 'Fork <span style="font-weight:bold">and</span> Knife')
view.with_env(env_en).write({'arch_db': archf % terms_en})
# check whether translations have been reset
self.assertEqual(view.with_env(env_nolang).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_en).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_fr).arch_db, archf % (terms_fr[0], terms_en[1], terms_en[2]))
self.assertEqual(view.with_env(env_nl).arch_db, archf % (terms_nl[0], terms_en[1], terms_en[2]))
def test_sync_xml_inline_modifiers(self):
""" Check translations of 'arch' after xml tags changes in source terms
when source term had updated modifiers attributes
"""
archf = '''<form class="row">
%s
<div class="s_table_of_content_vertical_navbar" data-name="Navbar" contenteditable="false">
<div class="s_table_of_content_navbar" style="top: 76px;"><a href="#table_of_content_heading_1672668075678_4" class="table_of_content_link">%s</a></div>
</div>
<div class="s_table_of_content_main" data-name="Content">
<section class="pb16">
<h1 data-anchor="true" class="o_default_snippet_text" id="table_of_content_heading_1672668075678_4">%s</h1>
</section>
</div>
</form>'''
terms_en = ('Bread and cheese', 'Knive and Fork', 'Knive <span style="font-weight:bold">and</span> Fork')
terms_fr = ('Pain et fromage', 'Couteau et Fourchette', 'Couteau <span style="font-weight:bold">et</span> Fourchette')
terms_nl = ('Brood and kaas', 'Mes en Vork', 'Mes en Vork')
view = self.create_view(archf, terms_en, en_US=terms_en, fr_FR=terms_fr, nl_NL=terms_nl)
env_nolang = self.env(context={"install_mode": True})
env_en = self.env(context={'lang': 'en_US', "install_mode": True})
env_fr = self.env(context={'lang': 'fr_FR', "install_mode": True})
env_nl = self.env(context={'lang': 'nl_NL', "install_mode": True})
self.assertEqual(view.with_env(env_nolang).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_en).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_fr).arch_db, archf % terms_fr)
self.assertEqual(view.with_env(env_nl).arch_db, archf % terms_nl)
# modify attributes in source term
terms_en = ('Bread and cheese', 'Knife and Fork', 'Knife <span invisible="1">and</span> Fork')
view.with_env(env_en).write({'arch_db': archf % terms_en})
terms_fr = ('Pain et fromage', 'Couteau et Fourchette', 'Couteau <span style="font-weight:bold" invisible="1">et</span> Fourchette')
# check whether translations have been kept
self.assertEqual(view.with_env(env_nolang).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_en).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_fr).arch_db, archf % terms_fr)
self.assertEqual(view.with_env(env_nl).arch_db, archf % terms_nl)
# modify attributes in source term
terms_en = ('Bread and cheese', 'Knife and Fork', 'Knife <span readonly="1">and</span> Fork')
view.with_env(env_en).write({'arch_db': archf % terms_en})
terms_fr = ('Pain et fromage', 'Couteau et Fourchette', 'Couteau <span style="font-weight:bold" readonly="1">et</span> Fourchette')
# check whether translations have been kept
self.assertEqual(view.with_env(env_nolang).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_en).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_fr).arch_db, archf % terms_fr)
self.assertEqual(view.with_env(env_nl).arch_db, archf % terms_nl)
def test_sync_xml_close_terms(self):
""" Check translations of 'arch' after xml tags changes in source terms. """
archf = '<form string="X">%s<div>%s</div>%s</form>'
terms_en = ('RandomRandom1', 'RandomRandom2', 'RandomRandom3')
terms_fr = ('RandomRandom1', 'AléatoireAléatoire2', 'AléatoireAléatoire3')
view = self.create_view(archf, terms_en, en_US=terms_en, fr_FR=terms_fr)
env_nolang = self.env(context={})
env_en = self.env(context={'lang': 'en_US'})
env_fr = self.env(context={'lang': 'fr_FR'})
self.assertEqual(view.with_env(env_nolang).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_en).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_fr).arch_db, archf % terms_fr)
# modify source term in view
terms_en = ('RandomRandom1', 'SomethingElse', 'RandomRandom3')
view.with_env(env_en).write({'arch_db': archf % terms_en})
# check whether close terms have correct translations
self.assertEqual(view.with_env(env_nolang).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_en).arch_db, archf % terms_en)
self.assertEqual(view.with_env(env_fr).arch_db, archf % ('RandomRandom1', 'SomethingElse', 'AléatoireAléatoire3'))
def test_cache_consistency(self):
view = self.env["ir.ui.view"].create({
"name": "test_translate_xml_cache_invalidation",
"model": "res.partner",
"arch": "<form><b>content</b></form>",
})
view_fr = view.with_context({"lang": "fr_FR"})
self.assertIn("<b>", view.arch_db)
self.assertIn("<b>", view_fr.arch_db)
# write with no lang, and check consistency in other languages
view.write({"arch_db": "<form><i>content</i></form>"})
self.assertIn("<i>", view.arch_db)
self.assertIn("<i>", view_fr.arch_db)
def test_update_field_translations(self):
archf = '<form string="X">%s<div>%s</div></form>'
terms_en = ('Bread and cheese', 'Fork')
terms_fr = ('Pain et fromage', 'Fourchette')
terms_nl = ('Brood and kaas', 'Vork')
view = self.create_view(archf, terms_en, fr_FR=terms_fr, nl_NL=terms_nl)
# cache arch_db
view.arch_db
view.with_context(lang='en_US').arch_db
view.with_context(lang='fr_FR').arch_db
view_nl = view.with_context(lang='nl_NL').arch_db
view.update_field_translations('arch_db', {
'en_US': {'Fork': 'Fork2'},
'fr_FR': {'Fourchette': 'Fourchette2'}
})
self.assertEqual(view.arch_db, '<form string="X">Bread and cheese<div>Fork2</div></form>')
self.assertEqual(view.with_context(lang='en_US').arch_db, '<form string="X">Bread and cheese<div>Fork2</div></form>')
self.assertEqual(view.with_context(lang='fr_FR').arch_db, '<form string="X">Pain et fromage<div>Fourchette2</div></form>')
self.assertEqual(view.with_context(lang='nl_NL').arch_db, view_nl)
view.invalidate_recordset()
self.assertEqual(view.arch_db, '<form string="X">Bread and cheese<div>Fork2</div></form>')
self.assertEqual(view.with_context(lang='en_US').arch_db, '<form string="X">Bread and cheese<div>Fork2</div></form>')
self.assertEqual(view.with_context(lang='fr_FR').arch_db, '<form string="X">Pain et fromage<div>Fourchette2</div></form>')
self.assertEqual(view.with_context(lang='nl_NL').arch_db, view_nl)
# update translations for fallback values and en_US
self.env['res.lang']._activate_lang('es_ES')
self.assertEqual(view.with_context(lang='es_ES').arch_db, '<form string="X">Bread and cheese<div>Fork2</div></form>')
view.update_field_translations('arch_db', {
'en_US': {'Fork2': 'Fork3'},
'es_ES': {'Fork2': 'Tenedor3'}
})
self.assertEqual(view.with_context(lang='en_US').arch_db, '<form string="X">Bread and cheese<div>Fork3</div></form>')
self.assertEqual(view.with_context(lang='es_ES').arch_db, '<form string="X">Bread and cheese<div>Tenedor3</div></form>')
def test_delay_translations(self):
archf = '<form string="%s"><div>%s</div><div>%s</div></form>'
terms_en = ('Knife', 'Fork', 'Spoon')
terms_fr = ('Couteau', 'Fourchette', 'Cuiller')
view0 = self.create_view(archf, terms_en, fr_FR=terms_fr)
archf2 = '<form string="%s"><p>%s</p><div>%s</div></form>'
terms_en2 = ('new Knife', 'Fork', 'Spoon')
# write en_US with delay_translations
view0.with_context(lang='en_US', delay_translations=True).arch_db = archf2 % terms_en2
view0.invalidate_recordset()
self.assertEqual(
view0.with_context(lang='en_US').arch_db,
archf2 % terms_en2,
'en_US value should be the latest one since it is updated directly'
)
self.assertEqual(view0.with_context(lang='en_US', check_translations=True).arch_db, archf2 % terms_en2)
self.assertEqual(
view0.with_context(lang='fr_FR').arch_db,
archf % terms_fr,
"fr_FR value should keep the same since its translations hasn't been confirmed"
)
self.assertEqual(
view0.with_context(lang='fr_FR', edit_translations=True).arch_db,
'<form string="'
'&lt;span '
'class=&quot;o_delay_translation&quot; '
'data-oe-model=&quot;ir.ui.view&quot; '
f'data-oe-id=&quot;{view0.id}&quot; '
'data-oe-field=&quot;arch_db&quot; '
'data-oe-translation-state=&quot;to_translate&quot; '
f'data-oe-translation-initial-sha=&quot;{sha256(terms_en2[0].encode()).hexdigest()}&quot;'
'&gt;'
f'{terms_en2[0]}'
'&lt;/span&gt;"'
'>'
'<p>'
'<span '
'class="o_delay_translation" '
'data-oe-model="ir.ui.view" '
f'data-oe-id="{view0.id}" '
'data-oe-field="arch_db" '
'data-oe-translation-state="translated" '
f'data-oe-translation-initial-sha="{sha256(terms_fr[1].encode()).hexdigest()}"'
'>'
f'{terms_fr[1]}'
'</span>'
'</p>'
'<div>'
'<span '
'class="o_delay_translation" '
'data-oe-model="ir.ui.view" '
f'data-oe-id="{view0.id}" '
'data-oe-field="arch_db" '
'data-oe-translation-state="translated" '
f'data-oe-translation-initial-sha="{sha256(terms_fr[2].encode()).hexdigest()}"'
'>'
f'{terms_fr[2]}'
'</span>'
'</div>'
'</form>'
)
self.assertEqual(
view0.with_context(lang='fr_FR', check_translations=True).arch_db,
archf2 % (terms_en2[0], terms_fr[1], terms_fr[2])
)
self.assertEqual(
view0.with_context(lang='nl_NL').arch_db,
archf2 % terms_en2,
"nl_NL value should fallback to en_US value"
)
self.assertEqual(
view0.with_context(lang='nl_NL', check_translations=True).arch_db,
archf2 % terms_en2
)
# update and confirm translations
view0.update_field_translations('arch_db', {'fr_FR': {}})
self.assertEqual(
view0.with_context(lang='fr_FR').arch_db,
archf2 % (terms_en2[0], terms_fr[1], terms_fr[2])
)
self.assertEqual(
view0.with_context(lang='fr_FR', check_translations=True).arch_db,
archf2 % (terms_en2[0], terms_fr[1], terms_fr[2])
)
def test_delay_translations_no_term(self):
archf = '<form string="%s"><div>%s</div><div>%s</div></form>'
terms_en = ('Knife', 'Fork', 'Spoon')
terms_fr = ('Couteau', 'Fourchette', 'Cuiller')
view0 = self.create_view(archf, terms_en, fr_FR=terms_fr)
archf2 = '<form/>'
# delay_translations only works when the written value has at least one translatable term
view0.with_context(lang='en_US', delay_translations=True).arch_db = archf2
for lang in ('en_US', 'fr_FR', 'nl_NL'):
self.assertEqual(
view0.with_context(lang=lang).arch_db,
archf2,
f'arch_db for {lang} should be {archf2}'
)
self.assertEqual(
view0.with_context(lang=lang, check_translations=True).arch_db,
archf2,
f'arch_db for {lang} should be {archf2} when check_translations'
)
class TestHTMLTranslation(TransactionCase):
def test_write_non_existing(self):
html = '''
<h1>My First Heading</h1>
<p>My first paragraph.</p>
'''
company = self.env['res.company'].browse(9999)
company.report_footer = html
self.assertHTMLEqual(company.report_footer, html)
# flushing on non-existing records does not break for scalar fields; the
# same behavior is expected for translated fields
company.flush_recordset()
def test_delay_translations_no_term(self):
self.env['res.lang']._activate_lang('fr_FR')
self.env['res.lang']._activate_lang('nl_NL')
Company = self.env['res.company']
company0 = Company.create({'name': 'company_1', 'report_footer': '<h1>Knife</h1>'})
company0.update_field_translations('report_footer', {'fr_FR': {'Knife': 'Couteau'}})
for html in ('<h1></h1>', '', False):
# delay_translations only works when the written value has at least one translatable term
company0.with_context(lang='en_US', delay_translations=True).report_footer = html
for lang in ('en_US', 'fr_FR', 'nl_NL'):
self.assertEqual(
company0.with_context(lang=lang).report_footer,
html,
f'report_footer for {lang} should be {html}'
)
self.assertEqual(
company0.with_context(lang=lang, check_translations=True).report_footer,
html,
f'report_footer for {lang} should be {html} when check_translations'
)
@tagged('post_install', '-at_install')
class TestLanguageInstallPerformance(TransactionCase):
def test_language_install(self):
""" Install a language on a complete database. """
fr_BE = self.env.ref('base.lang_fr_BE')
self.assertFalse(fr_BE.active)
t0 = time.time()
fr_BE.toggle_active()
t1 = time.time()
_stats_logger.info("installed language fr_BE in %.3fs", t1 - t0)
class TestTranslationTrigramIndexPatterns(BaseCase):
def test_value_conversion(self):
sc = SPECIAL_CHARACTERS
cases = [
# pylint: disable=bad-whitespace
( 'abc', '%abc%', 'simple text is not escaped correctly'),
( 'a"bc', r'%a\\"bc%', '" is not escaped correctly'),
(r'a\bc', r'%a\\\\bc%', r'\ is not escaped correctly'),
( 'a\nbc', r'%a\\nbc%', r'\n is not escaped correctly'),
( 'a_bc', r'%a\_bc%', '_ is not escaped correctly'),
( 'a%bc', r'%a\%bc%', '% is not escaped correctly'),
( 'a_', '%', 'values with less than 3 characters should be dropped'),
( sc, f'%{sc}%', 'special characters should not be escaped'),
]
for value, expected, message in cases:
self.assertEqual(sql.value_to_translated_trigram_pattern(value), expected, message)
def test_pattern_conversion(self):
sc = SPECIAL_CHARACTERS
cases = [
# pylint: disable=bad-whitespace
( 'abc', '%abc%', 'simple pattern is not escaped correctly'),
( 'a"bc', r'%a\\"bc%', '" is not escaped correctly'),
(r'a\\bc', r'%a\\\\bc%', r'\ is not escaped correctly'),
( 'a\nbc', r'%a\\nbc%', r'\n is not escaped correctly'),
(r'a\_bc', r'%a\_bc%', r"\_ shouldn't be escaped"),
(r'a\%bc', r'%a\%bc%', r"\% shouldn't be escaped"),
( 'abc_def', '%abc%def%', 'wildcard character _ should be changed to %'),
( 'abc%def', '%abc%def%', "wildcard character % shouldn't be escaped"),
(r'a\bc', '%abc%', r'redundant \ for pattern should be removed'),
( 'abc_de', '%abc%', 'sub patterns less than 3 characters should be dropped'),
( 'ab', '%', 'patterns without trigram should be simplified'),
( sc, f'%{sc}%', 'special characters should not be escaped'),
]
for original_pattern, escaped_pattern, message in cases:
self.assertEqual(sql.pattern_to_translated_trigram_pattern(original_pattern), escaped_pattern, message)