test_term_count.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. # -*- coding: utf-8 -*-
  2. import base64
  3. import io
  4. from odoo.tests import common, tagged
  5. from odoo.tools.misc import file_open, mute_logger
  6. from odoo.tools.translate import TranslationModuleReader, code_translations, CodeTranslations, PYTHON_TRANSLATION_COMMENT, JAVASCRIPT_TRANSLATION_COMMENT, WEB_TRANSLATION_COMMENT
  7. from odoo import Command
  8. from odoo.addons.base.models.ir_fields import BOOLEAN_TRANSLATIONS
  9. class TestImport(common.TransactionCase):
  10. def test_import_code_translation(self):
  11. self.env['res.lang']._activate_lang('fr_FR')
  12. # Tip: code translations don't need to be imported explicitly
  13. model = self.env['test.translation.import.model1']
  14. self.assertEqual(
  15. model.with_context(lang='fr_FR').get_code_translation(),
  16. 'Code, Français'
  17. )
  18. def test_import_model_translation(self):
  19. self.env['res.lang']._activate_lang('fr_FR')
  20. self.env['ir.module.module']._load_module_terms(['test_translation_import'], ['fr_FR'])
  21. record = self.env.ref('test_translation_import.test_translation_import_model1_record1')
  22. self.assertEqual(
  23. record.with_context(lang='fr_FR').name,
  24. 'Vaisselle'
  25. )
  26. def test_import_model_term_translation(self):
  27. self.env['res.lang']._activate_lang('fr_FR')
  28. self.env['ir.module.module']._load_module_terms(['test_translation_import'], ['fr_FR'])
  29. record = self.env.ref('test_translation_import.test_translation_import_model1_record1')
  30. self.assertEqual(
  31. record.with_context(lang='fr_FR').xml,
  32. '<form string="Fourchette"><div>Couteau</div><div>Cuillère</div></form>'
  33. )
  34. def test_noupdate(self):
  35. """
  36. Make sure no update do not overwrite translations
  37. """
  38. menu = self.env.ref('test_translation_import.menu_test_translation_import')
  39. self.assertEqual(menu.name, 'Test translation model1')
  40. # install french and change translation content
  41. self.env['res.lang']._activate_lang('fr_FR')
  42. self.env['ir.module.module']._load_module_terms(['test_translation_import'], ['fr_FR'])
  43. self.assertEqual(menu.with_context(lang='fr_FR').name, "Test translation import in french")
  44. menu.with_context(lang='fr_FR').name = "Nouveau nom"
  45. # reload with overwrite
  46. self.env['ir.module.module']._load_module_terms(['test_translation_import'], ['fr_FR'], overwrite=True)
  47. self.assertEqual(menu.name, "Test translation model1")
  48. self.assertEqual(menu.with_context(lang='fr_FR').name, "Nouveau nom")
  49. def test_lang_with_base(self):
  50. self.env['res.lang']._activate_lang('fr_BE')
  51. self.env['res.lang']._activate_lang('fr_CA')
  52. self.env['ir.module.module']._load_module_terms(['test_translation_import'], ['fr_BE', 'fr_CA'], overwrite=True)
  53. # language override base language
  54. record = self.env.ref('test_translation_import.test_translation_import_model1_record1')
  55. self.assertEqual(
  56. record.with_context(lang='fr_BE').get_code_translation(),
  57. 'Code, Français, Belgium'
  58. )
  59. self.assertEqual(
  60. record.with_context(lang='fr_BE').name,
  61. 'Vaisselle, Belgium'
  62. )
  63. self.assertEqual(
  64. record.with_context(lang='fr_BE').xml,
  65. '<form string="Fourchette, Belgium"><div>Couteau, Belgium</div><div>Cuillère, Belgium</div></form>'
  66. )
  67. # not specified localized language fallback on base language
  68. self.assertEqual(
  69. record.with_context(lang='fr_CA').get_code_translation(),
  70. 'Code, Français'
  71. )
  72. self.assertEqual(
  73. record.with_context(lang='fr_CA').name,
  74. 'Vaisselle'
  75. )
  76. self.assertEqual(
  77. record.with_context(lang='fr_CA').xml,
  78. '<form string="Fourchette"><div>Couteau, Canada</div><div>Cuillère</div></form>'
  79. )
  80. def test_import_from_po_file(self):
  81. """Test the import from a single po file works"""
  82. with file_open('test_translation_import/i18n/tlh.po', 'rb') as f:
  83. po_file = base64.encodebytes(f.read())
  84. import_tlh = self.env["base.language.import"].create({
  85. 'name': 'Klingon',
  86. 'code': 'tlh',
  87. 'data': po_file,
  88. 'filename': 'tlh.po',
  89. })
  90. with mute_logger('odoo.addons.base.models.res_lang'):
  91. import_tlh.import_lang()
  92. tlh_lang = self.env['res.lang']._lang_get('tlh')
  93. self.assertTrue(tlh_lang, "The imported language was not creates")
  94. record = self.env.ref('test_translation_import.test_translation_import_model1_record1')
  95. self.assertEqual(
  96. record.with_context(lang='tlh').get_code_translation(),
  97. 'Code, Klingon'
  98. )
  99. self.assertEqual(
  100. record.with_context(lang='tlh').name,
  101. 'Tableware, Klingon'
  102. )
  103. def test_lazy_translation(self):
  104. """Test the import from a single po file works"""
  105. with file_open('test_translation_import/i18n/tlh.po', 'rb') as f:
  106. po_file = base64.encodebytes(f.read())
  107. import_tlh = self.env["base.language.import"].create({
  108. 'name': 'Klingon',
  109. 'code': 'tlh',
  110. 'data': po_file,
  111. 'filename': 'tlh.po',
  112. })
  113. with mute_logger('odoo.addons.base.models.res_lang'):
  114. import_tlh.import_lang()
  115. model = self.env['test.translation.import.model1']
  116. TRANSLATED_TERM = model.get_code_lazy_translation()
  117. self.assertEqual(
  118. model.with_context(lang='tlh').get_code_translation(),
  119. "Code, Klingon",
  120. "The direct code translation was not applied"
  121. )
  122. context = None
  123. # Comparison of lazy strings must be explicitely casted to string
  124. with self.assertRaises(NotImplementedError):
  125. TRANSLATED_TERM == "Code, English"
  126. self.assertEqual(str(TRANSLATED_TERM), "Code Lazy, English", "The translation should not be applied yet")
  127. context = {'lang': "tlh"}
  128. self.assertEqual(str(TRANSLATED_TERM), "Code Lazy, Klingon", "The lazy code translation was not applied")
  129. self.assertEqual("Do you speak " + TRANSLATED_TERM, "Do you speak Code Lazy, Klingon", "str + _lt concatenation failed")
  130. self.assertEqual(TRANSLATED_TERM + ", I speak it", "Code Lazy, Klingon, I speak it", "_lt + str concatenation failed")
  131. self.assertEqual(TRANSLATED_TERM + TRANSLATED_TERM, "Code Lazy, KlingonCode Lazy, Klingon", "_lt + _lt concatenation failed")
  132. # test lazy translation in another module
  133. self.env['res.lang']._activate_lang('fr_FR')
  134. context = {'lang': 'en_US'}
  135. self.assertEqual(str(BOOLEAN_TRANSLATIONS[0]), 'yes')
  136. context = {'lang': 'fr_FR'}
  137. self.assertEqual(str(BOOLEAN_TRANSLATIONS[0]), 'oui')
  138. def test_import_from_csv_file(self):
  139. """Test the import from a single CSV file works"""
  140. with file_open('test_translation_import/i18n/dot.csv', 'rb') as f:
  141. po_file = base64.encodebytes(f.read())
  142. import_tlh = self.env["base.language.import"].create({
  143. 'name': 'Dothraki',
  144. 'code': 'dot',
  145. 'data': po_file,
  146. 'filename': 'dot.csv',
  147. })
  148. with mute_logger('odoo.addons.base.models.res_lang'):
  149. import_tlh.import_lang()
  150. dot_lang = self.env['res.lang']._lang_get('dot')
  151. self.assertTrue(dot_lang, "The imported language was not creates")
  152. # code translation cannot be changed or imported, it only depends on the po file in the module directory
  153. record = self.env.ref('test_translation_import.test_translation_import_model1_record1')
  154. self.assertEqual(
  155. record.with_context(lang='dot').get_code_translation(),
  156. 'Code, English'
  157. )
  158. self.assertEqual(
  159. record.with_context(lang='dot').name,
  160. 'Tableware, Dot'
  161. )
  162. def test_translation_placeholder(self):
  163. """Verify placeholder use in _()"""
  164. self.env['res.lang']._activate_lang('fr_BE')
  165. model_fr_BE = self.env['test.translation.import.model1'].with_context(lang='fr_BE')
  166. # correctly translate
  167. self.assertEqual(
  168. model_fr_BE.get_code_placeholder_translation(1),
  169. "Code, 1, Français, Belgium",
  170. "Translation placeholders were not applied"
  171. )
  172. # source error: wrong arguments
  173. with self.assertRaises(TypeError):
  174. model_fr_BE.get_code_placeholder_translation(1, "🧀")
  175. # correctly translate
  176. self.assertEqual(
  177. model_fr_BE.get_code_named_placeholder_translation(num=2, symbol="🧀"),
  178. "Code, 2, 🧀, Français, Belgium",
  179. "Translation placeholders were not applied"
  180. )
  181. # source error: wrong arguments
  182. with self.assertRaises(KeyError):
  183. model_fr_BE.get_code_named_placeholder_translation(symbol="🧀"),
  184. @tagged('post_install', '-at_install')
  185. class TestTranslationFlow(common.TransactionCase):
  186. def test_export_import(self):
  187. """ Ensure export+import gives the same result as loading a language """
  188. self.env["base.language.install"].create({
  189. 'overwrite': True,
  190. 'lang_ids': [(6, 0, [self.env.ref('base.lang_fr').id])],
  191. }).lang_install()
  192. module = self.env.ref('base.module_test_translation_import')
  193. export = self.env["base.language.export"].create({
  194. 'lang': 'fr_FR',
  195. 'format': 'po',
  196. 'modules': [Command.set([module.id])]
  197. })
  198. export.act_getfile()
  199. po_file_data = export.data
  200. self.assertIsNotNone(po_file_data)
  201. # test code translations
  202. new_code_translations = CodeTranslations()
  203. # a hack to load code translations for new_code_translations
  204. with io.BytesIO(base64.b64decode(po_file_data)) as po_file:
  205. po_file.name = 'fr_FR.po'
  206. def filter_func_for_python(row):
  207. return row.get('value') and (
  208. PYTHON_TRANSLATION_COMMENT in row['comments']
  209. or JAVASCRIPT_TRANSLATION_COMMENT not in row['comments'])
  210. new_code_translations.python_translations[('test_translation_import', 'fr_FR')] = \
  211. CodeTranslations._read_code_translations_file(po_file, filter_func_for_python)
  212. def filter_func_for_javascript(row):
  213. return row.get('value') and (
  214. JAVASCRIPT_TRANSLATION_COMMENT in row['comments']
  215. or WEB_TRANSLATION_COMMENT in row['comments'])
  216. new_code_translations.web_translations[('test_translation_import', 'fr_FR')] = {
  217. "messages": [
  218. {"id": src, "string": value}
  219. for src, value in CodeTranslations._read_code_translations_file(
  220. po_file, filter_func_for_javascript).items()
  221. ]
  222. }
  223. old_python = code_translations.get_python_translations('test_translation_import', 'fr_FR')
  224. new_python = new_code_translations.get_python_translations('test_translation_import', 'fr_FR')
  225. self.assertEqual(old_python, new_python, 'python code translations are not exported/imported correctly')
  226. old_web = code_translations.get_web_translations('test_translation_import', 'fr_FR')
  227. new_web = new_code_translations.get_web_translations('test_translation_import', 'fr_FR')
  228. self.assertEqual(old_web, new_web, 'web client code translations are not exported/imported correctly')
  229. self.assertNotIn('text node', new_python, 'web client only translations should not be stored as python translations')
  230. self.assertFalse(
  231. any(
  232. tran['id'] == 'Code Lazy, English'
  233. for tran in new_web['messages']
  234. ), 'Python only translations should not be stored as webclient translations'
  235. )
  236. # test model and model terms translations
  237. record = self.env.ref('test_translation_import.test_translation_import_model1_record1')
  238. record.invalidate_recordset()
  239. self.assertEqual(
  240. record.with_context(lang='fr_FR').name,
  241. 'Vaisselle'
  242. )
  243. self.assertEqual(
  244. record.with_context(lang='fr_FR').xml,
  245. '<form string="Fourchette"><div>Couteau</div><div>Cuillère</div></form>'
  246. )
  247. # remove All translations
  248. record.name = False
  249. record.name = 'Tableware'
  250. record.xml = False
  251. record.xml = '<form string="Fork"><div>Knife</div><div>Spoon</div></form>'
  252. self.assertEqual(
  253. record.with_context(lang='fr_FR').name,
  254. 'Tableware'
  255. )
  256. self.assertEqual(
  257. record.with_context(lang='fr_FR').xml,
  258. '<form string="Fork"><div>Knife</div><div>Spoon</div></form>'
  259. )
  260. import_fr = self.env["base.language.import"].create({
  261. 'name': 'French',
  262. 'code': 'fr_FR',
  263. 'data': export.data,
  264. 'filename': export.name,
  265. 'overwrite': False,
  266. })
  267. with mute_logger('odoo.addons.base.models.res_lang'):
  268. import_fr.import_lang()
  269. self.assertEqual(
  270. record.with_context(lang='fr_FR').name,
  271. 'Vaisselle'
  272. )
  273. self.assertEqual(
  274. record.with_context(lang='fr_FR').xml,
  275. '<form string="Fourchette"><div>Couteau</div><div>Cuillère</div></form>'
  276. )
  277. def test_export_import_csv(self):
  278. """ Ensure can reimport exported csv """
  279. self.env.ref("base.lang_fr").active = True
  280. module = self.env.ref('base.module_test_translation_import')
  281. export = self.env["base.language.export"].create({
  282. 'lang': 'fr_FR',
  283. 'format': 'csv',
  284. 'modules': [Command.set([module.id])]
  285. })
  286. export.act_getfile()
  287. po_file = export.data
  288. self.assertIsNotNone(po_file)
  289. import_fr = self.env["base.language.import"].create({
  290. 'name': 'French',
  291. 'code': 'fr_FR',
  292. 'data': export.data,
  293. 'filename': export.name,
  294. 'overwrite': False,
  295. })
  296. with mute_logger('odoo.addons.base.models.res_lang'):
  297. import_fr.with_context().import_lang()
  298. def test_export_static_templates(self):
  299. trans_static = []
  300. po_reader = TranslationModuleReader(self.env.cr, ['test_translation_import'])
  301. for line in po_reader:
  302. module, ttype, name, res_id, source, value, comments = line
  303. if name == "addons/test_translation_import/static/src/xml/js_templates.xml":
  304. trans_static.append(source)
  305. self.assertNotIn('no export', trans_static)
  306. self.assertIn('do export', trans_static)
  307. self.assertIn('text node', trans_static)
  308. self.assertIn('slot', trans_static)
  309. self.assertIn('slot 2', trans_static)
  310. def test_export_spreadsheet(self):
  311. terms = []
  312. po_reader = TranslationModuleReader(self.env.cr, ['test_translation_import'])
  313. for line in po_reader:
  314. _module, _ttype, name, _res_id, source, _value, _comments = line
  315. if name == "addons/test_translation_import/data/files/test_spreadsheet_dashboard.json":
  316. terms.append(source)
  317. self.assertEqual(set(terms), {
  318. 'exported 1',
  319. 'exported 2',
  320. 'exported 3',
  321. 'Bar chart title',
  322. 'Scorecard description',
  323. 'Scorecard chart',
  324. 'Opportunities',
  325. 'Pipeline',
  326. 'Pipeline Analysis',
  327. 'link label',
  328. 'aa (\\"inside\\") bb',
  329. 'with spaces',
  330. 'hello \\"world\\"',
  331. })