123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- # -*- coding: utf-8 -*-
- import base64
- import io
- from odoo.tests import common, tagged
- from odoo.tools.misc import file_open, mute_logger
- from odoo.tools.translate import TranslationModuleReader, code_translations, CodeTranslations, PYTHON_TRANSLATION_COMMENT, JAVASCRIPT_TRANSLATION_COMMENT, WEB_TRANSLATION_COMMENT
- from odoo import Command
- from odoo.addons.base.models.ir_fields import BOOLEAN_TRANSLATIONS
- class TestImport(common.TransactionCase):
- def test_import_code_translation(self):
- self.env['res.lang']._activate_lang('fr_FR')
- # Tip: code translations don't need to be imported explicitly
- 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')
- self.env['ir.module.module']._load_module_terms(['test_translation_import'], ['fr_FR'])
- 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')
- self.env['ir.module.module']._load_module_terms(['test_translation_import'], ['fr_FR'])
- 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')
- self.assertEqual(menu.name, 'Test translation model1')
- # install french and change translation content
- self.env['res.lang']._activate_lang('fr_FR')
- self.env['ir.module.module']._load_module_terms(['test_translation_import'], ['fr_FR'])
- 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
- self.env['ir.module.module']._load_module_terms(['test_translation_import'], ['fr_FR'], overwrite=True)
- 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')
- self.env['ir.module.module']._load_module_terms(['test_translation_import'], ['fr_BE', 'fr_CA'], overwrite=True)
- # language override base language
- 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
- 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"""
- with file_open('test_translation_import/i18n/tlh.po', 'rb') as f:
- po_file = base64.encodebytes(f.read())
- import_tlh = self.env["base.language.import"].create({
- 'name': 'Klingon',
- 'code': 'tlh',
- 'data': po_file,
- 'filename': 'tlh.po',
- })
- with mute_logger('odoo.addons.base.models.res_lang'):
- import_tlh.import_lang()
- tlh_lang = self.env['res.lang']._lang_get('tlh')
- self.assertTrue(tlh_lang, "The imported language was not creates")
- 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"""
- with file_open('test_translation_import/i18n/tlh.po', 'rb') as f:
- po_file = base64.encodebytes(f.read())
- import_tlh = self.env["base.language.import"].create({
- 'name': 'Klingon',
- 'code': 'tlh',
- 'data': po_file,
- 'filename': 'tlh.po',
- })
- with mute_logger('odoo.addons.base.models.res_lang'):
- import_tlh.import_lang()
- 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 == "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), "Code Lazy, Klingon", "The lazy code translation was not applied")
- 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"""
- with file_open('test_translation_import/i18n/dot.csv', 'rb') as f:
- po_file = base64.encodebytes(f.read())
- import_tlh = self.env["base.language.import"].create({
- 'name': 'Dothraki',
- 'code': 'dot',
- 'data': po_file,
- 'filename': 'dot.csv',
- })
- with mute_logger('odoo.addons.base.models.res_lang'):
- import_tlh.import_lang()
- dot_lang = self.env['res.lang']._lang_get('dot')
- self.assertTrue(dot_lang, "The imported language was not creates")
- # 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 _()"""
- self.env['res.lang']._activate_lang('fr_BE')
- model_fr_BE = self.env['test.translation.import.model1'].with_context(lang='fr_BE')
- # correctly translate
- self.assertEqual(
- 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):
- model_fr_BE.get_code_placeholder_translation(1, "🧀")
- # correctly translate
- self.assertEqual(
- 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):
- model_fr_BE.get_code_named_placeholder_translation(symbol="🧀"),
- @tagged('post_install', '-at_install')
- class TestTranslationFlow(common.TransactionCase):
- def test_export_import(self):
- """ Ensure export+import gives the same result as loading a language """
- 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({
- 'lang': 'fr_FR',
- 'format': 'po',
- 'modules': [Command.set([module.id])]
- })
- export.act_getfile()
- po_file_data = export.data
- self.assertIsNotNone(po_file_data)
- # test code translations
- new_code_translations = CodeTranslations()
- # a hack to load code translations for new_code_translations
- with io.BytesIO(base64.b64decode(po_file_data)) as po_file:
- po_file.name = 'fr_FR.po'
- def filter_func_for_python(row):
- return row.get('value') and (
- PYTHON_TRANSLATION_COMMENT in row['comments']
- or JAVASCRIPT_TRANSLATION_COMMENT not in row['comments'])
- new_code_translations.python_translations[('test_translation_import', 'fr_FR')] = \
- CodeTranslations._read_code_translations_file(po_file, filter_func_for_python)
- def filter_func_for_javascript(row):
- return row.get('value') and (
- JAVASCRIPT_TRANSLATION_COMMENT in row['comments']
- or WEB_TRANSLATION_COMMENT in row['comments'])
- new_code_translations.web_translations[('test_translation_import', 'fr_FR')] = {
- "messages": [
- {"id": src, "string": value}
- for src, value in CodeTranslations._read_code_translations_file(
- po_file, filter_func_for_javascript).items()
- ]
- }
- old_python = code_translations.get_python_translations('test_translation_import', 'fr_FR')
- new_python = new_code_translations.get_python_translations('test_translation_import', 'fr_FR')
- self.assertEqual(old_python, new_python, 'python code translations are not exported/imported correctly')
- old_web = code_translations.get_web_translations('test_translation_import', 'fr_FR')
- new_web = new_code_translations.get_web_translations('test_translation_import', 'fr_FR')
- self.assertEqual(old_web, new_web, 'web client code translations are not exported/imported correctly')
- self.assertNotIn('text node', new_python, 'web client only translations should not be stored as python translations')
- self.assertFalse(
- any(
- tran['id'] == 'Code Lazy, English'
- for tran in new_web['messages']
- ), 'Python only translations should not be stored as webclient translations'
- )
- # test model and model terms translations
- 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',
- 'code': 'fr_FR',
- 'data': export.data,
- 'filename': export.name,
- 'overwrite': False,
- })
- with mute_logger('odoo.addons.base.models.res_lang'):
- import_fr.import_lang()
- 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 """
- self.env.ref("base.lang_fr").active = True
- module = self.env.ref('base.module_test_translation_import')
- export = self.env["base.language.export"].create({
- 'lang': 'fr_FR',
- 'format': 'csv',
- 'modules': [Command.set([module.id])]
- })
- export.act_getfile()
- po_file = export.data
- self.assertIsNotNone(po_file)
- import_fr = self.env["base.language.import"].create({
- 'name': 'French',
- 'code': 'fr_FR',
- 'data': export.data,
- 'filename': export.name,
- 'overwrite': False,
- })
- with mute_logger('odoo.addons.base.models.res_lang'):
- import_fr.with_context().import_lang()
- def test_export_static_templates(self):
- trans_static = []
- po_reader = TranslationModuleReader(self.env.cr, ['test_translation_import'])
- for line in po_reader:
- module, ttype, name, res_id, source, value, comments = line
- if name == "addons/test_translation_import/static/src/xml/js_templates.xml":
- trans_static.append(source)
- self.assertNotIn('no export', trans_static)
- self.assertIn('do export', trans_static)
- self.assertIn('text node', trans_static)
- self.assertIn('slot', trans_static)
- self.assertIn('slot 2', trans_static)
- def test_export_spreadsheet(self):
- terms = []
- po_reader = TranslationModuleReader(self.env.cr, ['test_translation_import'])
- for line in po_reader:
- _module, _ttype, name, _res_id, source, _value, _comments = line
- if name == "addons/test_translation_import/data/files/test_spreadsheet_dashboard.json":
- terms.append(source)
- self.assertEqual(set(terms), {
- 'exported 1',
- 'exported 2',
- 'exported 3',
- 'Bar chart title',
- 'Scorecard description',
- 'Scorecard chart',
- 'Opportunities',
- 'Pipeline',
- 'Pipeline Analysis',
- 'link label',
- 'aa (\\"inside\\") bb',
- 'with spaces',
- 'hello \\"world\\"',
- })
|