diff --git a/sao/CHANGELOG b/sao/CHANGELOG index 82a0ce6d51..3661b0dbf2 100644 --- a/sao/CHANGELOG +++ b/sao/CHANGELOG @@ -1,3 +1,4 @@ +* Support language suffix in CSV export * Close tab with mouse middle click * Support language suffix in CSV import diff --git a/sao/src/window.js b/sao/src/window.js index 67cff3270b..d7b33e6603 100644 --- a/sao/src/window.js +++ b/sao/src/window.js @@ -1196,6 +1196,15 @@ Sao.Window.CSV = Sao.class_(Object, { init: function(title) { + this.languages = Sao.rpc({ + 'method': 'model.ir.lang.search_read', + 'params': [ + [['translatable', '=', true]], + 0, null, null, ['code', 'name'], + {}, + ], + }, this.session, false); + this.dialog = new Sao.Dialog(title, 'csv', 'lg'); this.el = this.dialog.modal; @@ -1398,14 +1407,6 @@ this.session = Sao.Session.current_session; this.fields_data = {}; // Ask before Removing this. this.fields_invert = {}; - this.languages = Sao.rpc({ - 'method': 'model.ir.lang.search_read', - 'params': [ - [['translatable', '=', true]], - 0, null, null, ['code', 'name'], - {}, - ], - }, this.session, false); Sao.Window.Import._super.init.call(this, Sao.i18n.gettext('CSV Import: %1', name)); @@ -1983,8 +1984,25 @@ this.fields[path] = node; // Insert relation only to real field - if (item.name.indexOf('.') == -1 && item.field.relation) { - node.children = {}; + if (item.name.indexOf('.') == -1) { + if (item.field.relation) { + node.children = {}; + } else if (item.field.translate) { + node.children = {}; + for (const language of this.languages) { + const l_path = `${path}:lang=${language.code}`; + const l_string = ( + `${long_string} (${language.name})`); + const l_node = { + path: l_path, + string: language.name, + long_string: l_string, + relation: null, + }; + node.children[language.code] = l_node; + this.fields[l_path] = l_node; + } + } } } } diff --git a/tryton/CHANGELOG b/tryton/CHANGELOG index c60858f0a6..3c381d384d 100644 --- a/tryton/CHANGELOG +++ b/tryton/CHANGELOG @@ -1,3 +1,4 @@ +* Support language suffix in CSV export * Close tab with mouse middle click * Support language suffix in CSV import diff --git a/tryton/tryton/gui/window/win_csv.py b/tryton/tryton/gui/window/win_csv.py index 83e1279e19..3ef53ad985 100644 --- a/tryton/tryton/gui/window/win_csv.py +++ b/tryton/tryton/gui/window/win_csv.py @@ -7,7 +7,7 @@ import sys from gi.repository import Gdk, GObject, Gtk -from tryton.common import IconFactory +from tryton.common import IconFactory, RPCExecute from tryton.common.underline import set_underline from tryton.config import TRYTON_ICON from tryton.gui import Main @@ -36,6 +36,11 @@ class WinCSV(NoModal): def __init__(self, *args, **kwargs): super(WinCSV, self).__init__(*args, **kwargs) + self.languages = RPCExecute( + 'model', 'ir.lang', 'search_read', [ + ('translatable', '=', True), + ], 0, None, [('name', 'DESC')], ['code', 'name']) + self.dialog = Gtk.Dialog( transient_for=self.parent, destroy_with_parent=True) Main().add_window(self.dialog) diff --git a/tryton/tryton/gui/window/win_export.py b/tryton/tryton/gui/window/win_export.py index 518ec1470d..df891aeb50 100644 --- a/tryton/tryton/gui/window/win_export.py +++ b/tryton/tryton/gui/window/win_export.py @@ -176,6 +176,13 @@ class WinExport(WinCSV): if '.' not in name: if field.get('relation'): self.model1.insert(node, 0, [None, '']) + elif field.get('translate', False): + for language in self.languages: + l_path = f"{path}:lang={language['code']}" + l_string = f"{string_} ({language['name']})" + self.model1.insert( + node, 0, [language['name'], l_path]) + self.fields[l_path] = (l_string, None) def _get_fields(self, model): try: diff --git a/tryton/tryton/gui/window/win_import.py b/tryton/tryton/gui/window/win_import.py index c976683274..f1a0041486 100644 --- a/tryton/tryton/gui/window/win_import.py +++ b/tryton/tryton/gui/window/win_import.py @@ -26,10 +26,6 @@ class WinImport(WinCSV): self.fields_data = {} self.fields = {} self.fields_invert = {} - self.languages = RPCExecute( - 'model', 'ir.lang', 'search_read', [ - ('translatable', '=', True), - ], 0, None, None, ['code', 'name']) super(WinImport, self).__init__() self.dialog.set_title(_('CSV Import: %s') % name) diff --git a/trytond/CHANGELOG b/trytond/CHANGELOG index afb84c6895..2bbe5920ea 100644 --- a/trytond/CHANGELOG +++ b/trytond/CHANGELOG @@ -1,3 +1,4 @@ +* Add language support per field in export data * Add translated descriptor for Char and Text fields * Include row and column in error message when importing data * Remove support for ``:id`` suffix in import data diff --git a/trytond/trytond/model/modelstorage.py b/trytond/trytond/model/modelstorage.py index b85cacb9b9..9d5f4ce7c0 100644 --- a/trytond/trytond/model/modelstorage.py +++ b/trytond/trytond/model/modelstorage.py @@ -742,8 +742,11 @@ class ModelStorage(Model): break field_name = fields_tree[i] descriptor = None + language = None if '.' in field_name: field_name, descriptor = field_name.split('.') + elif ':lang=' in field_name: + field_name, language = field_name.split(':lang=') eModel = pool.get(value.__name__) field = eModel._fields[field_name] if field.states and 'invisible' in field.states: @@ -754,6 +757,8 @@ class ModelStorage(Model): break if descriptor: value = getattr(field, descriptor)().__get__(value, eModel) + elif language: + value = field.translated().__get__(value, eModel)(language) else: value = getattr(value, field_name) if isinstance(value, (list, tuple)): @@ -790,6 +795,7 @@ class ModelStorage(Model): def _convert_field_names(cls, fields_names): pool = Pool() ModelField = pool.get('ir.model.field') + Lang = pool.get('ir.lang') result = [] for names in fields_names: descriptions = [] @@ -798,6 +804,9 @@ class ModelStorage(Model): translated = name.endswith('.translated') if translated: name = name[:-len('.translated')] + language = None + if ':lang=' in name: + name, language = name.split(':lang=') field = class_._fields[name] field_name = ModelField.get_name(class_.__name__, name) if translated: @@ -807,6 +816,9 @@ class ModelStorage(Model): elif isinstance(field, fields.Reference): field_name = gettext( 'ir.msg_field_model_name', field=field_name) + if language: + lang = Lang.get(language) + field_name += f' ({lang.name})' descriptions.append(field_name) if hasattr(field, 'get_target'): class_ = field.get_target() diff --git a/trytond/trytond/tests/export_data.py b/trytond/trytond/tests/export_data.py index b62f6b2aba..5736a28dbe 100644 --- a/trytond/trytond/tests/export_data.py +++ b/trytond/trytond/tests/export_data.py @@ -20,6 +20,7 @@ class ExportData(ModelSQL): float = fields.Float('Float') numeric = fields.Numeric('Numeric') char = fields.Char('Char') + char_translated = fields.Char("Char", translate=True) text = fields.Text('Text') date = fields.Date('Date') datetime = fields.DateTime('DateTime') diff --git a/trytond/trytond/tests/test_exportdata.py b/trytond/trytond/tests/test_exportdata.py index 75a525f4b0..e78c767159 100644 --- a/trytond/trytond/tests/test_exportdata.py +++ b/trytond/trytond/tests/test_exportdata.py @@ -7,6 +7,7 @@ from decimal import Decimal from trytond.pool import Pool from trytond.tests.test_tryton import activate_module, with_transaction +from trytond.transaction import Transaction class ExportDataTestCase(unittest.TestCase): @@ -130,6 +131,32 @@ class ExportDataTestCase(unittest.TestCase): ExportData.export_data([export1, export2], ['char']), [['test'], ['']]) + @with_transaction() + def test_char_translated(self): + "Test export_data char translated" + pool = Pool() + ExportData = pool.get('test.export_data') + Language = pool.get('ir.lang') + + english, = Language.search([('code', '=', 'en')]) + english.translatable = True + english.save() + + french, = Language.search([('code', '=', 'fr')]) + french.translatable = True + french.save() + + with Transaction().set_context(language='en'): + record, = ExportData.create([{'char_translated': 'foo'}]) + with Transaction().set_context(language='fr'): + ExportData.write([record], {'char_translated': 'bar'}) + + self.assertEqual( + ExportData.export_data( + [record], + ['char_translated:lang=en', 'char_translated:lang=fr']), + [['foo', 'bar']]) + @with_transaction() def test_text(self): 'Test export_data text' @@ -433,11 +460,14 @@ class ExportDataTestCase(unittest.TestCase): export1, = ExportData.create([{ 'char': "Test", + "char_translated": "Test 2", 'integer': 2, }]) self.assertEqual( - ExportData.export_data([export1], ['char', 'integer'], True), - [["Char", "Integer"], ["Test", 2]]) + ExportData.export_data( + [export1], + ['char', 'char_translated:lang=en', 'integer'], True), + [["Char", "Char (English)", "Integer"], ["Test", "Test 2", 2]]) @with_transaction() def test_nested_header(self):