From 6346f52acf5831bbb39736ac5e8b22f5e4a0d75e Mon Sep 17 00:00:00 2001 From: "Chong Wang (cwg)" Date: Mon, 17 Oct 2022 07:53:13 +0000 Subject: [PATCH] [ADD] transifex: add transifex module to help translation model/model_terms translations: A transifex link if available is displayed after the language in the translation dialog to help translators to contribute their translations. The tooltip message for mouse hovering is 'Contribute' code translations: Setting -> Translations -> Application Terms -> Transifex Code Translations In order to reuse the list view and search bar, model transifex.code.translation is created to store all code translations. This model is 1. readonly 2. reloaded on demand/by cron(7 days) to avoid increasing the Odoo restart time 3. updated for new installed modules/languages while opening the list view 4. shared to all users without duplicated translations closes odoo/odoo#111685 X-original-commit: 7b30badb52e2a948c41f4340a01f6118818c6711 Signed-off-by: Raphael Collet Signed-off-by: Wang Chong (cwg) --- addons/transifex/__init__.py | 4 + addons/transifex/__manifest__.py | 37 +++++ addons/transifex/data/transifex_data.xml | 20 +++ addons/transifex/i18n/transifex.pot | 134 ++++++++++++++++++ addons/transifex/models/__init__.py | 6 + addons/transifex/models/models.py | 31 ++++ .../models/transifex_code_translation.py | 71 ++++++++++ .../transifex/models/transifex_translation.py | 79 +++++++++++ addons/transifex/security/ir.model.access.csv | 2 + addons/transifex/static/description/icon.png | Bin 0 -> 7344 bytes addons/transifex/static/description/icon.svg | 1 + .../src/views/fields/translation_dialog.xml | 10 ++ .../views/reload_code_translations_views.js | 25 ++++ .../views/reload_code_translations_views.xml | 10 ++ .../views/code_translation_views.xml | 47 ++++++ .../src/views/fields/translation_dialog.js | 7 +- odoo/models.py | 11 +- odoo/tools/translate.py | 4 +- 18 files changed, 484 insertions(+), 15 deletions(-) create mode 100644 addons/transifex/__init__.py create mode 100644 addons/transifex/__manifest__.py create mode 100644 addons/transifex/data/transifex_data.xml create mode 100644 addons/transifex/i18n/transifex.pot create mode 100644 addons/transifex/models/__init__.py create mode 100644 addons/transifex/models/models.py create mode 100644 addons/transifex/models/transifex_code_translation.py create mode 100644 addons/transifex/models/transifex_translation.py create mode 100644 addons/transifex/security/ir.model.access.csv create mode 100644 addons/transifex/static/description/icon.png create mode 100644 addons/transifex/static/description/icon.svg create mode 100644 addons/transifex/static/src/views/fields/translation_dialog.xml create mode 100644 addons/transifex/static/src/views/reload_code_translations_views.js create mode 100644 addons/transifex/static/src/views/reload_code_translations_views.xml create mode 100644 addons/transifex/views/code_translation_views.xml diff --git a/addons/transifex/__init__.py b/addons/transifex/__init__.py new file mode 100644 index 00000000000..dc5e6b693d1 --- /dev/null +++ b/addons/transifex/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models diff --git a/addons/transifex/__manifest__.py b/addons/transifex/__manifest__.py new file mode 100644 index 00000000000..2d56593f9a9 --- /dev/null +++ b/addons/transifex/__manifest__.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'Transifex integration', + 'version': '1.0', + 'summary': 'Add a link to edit a translation in Transifex', + 'category': 'Hidden/Tools', + 'description': + """ +Transifex integration +===================== +This module will add a link to the Transifex project in the translation view. +The purpose of this module is to speed up translations of the main modules. + +To work, Odoo uses Transifex configuration files `.tx/config` to detect the +project source. Custom modules will not be translated (as not published on +the main Transifex project). + +The language the user tries to translate must be activated on the Transifex +project. + """, + 'data': [ + 'data/transifex_data.xml', + 'views/code_translation_views.xml', + 'security/ir.model.access.csv' + ], + 'assets': { + 'web.assets_backend': [ + 'transifex/static/src/views/fields/translation_dialog.xml', + 'transifex/static/src/views/*.js', + 'transifex/static/src/views/*.xml', + ], + }, + 'depends': ['base', 'web'], + 'license': 'LGPL-3', +} diff --git a/addons/transifex/data/transifex_data.xml b/addons/transifex/data/transifex_data.xml new file mode 100644 index 00000000000..4ec36668c1a --- /dev/null +++ b/addons/transifex/data/transifex_data.xml @@ -0,0 +1,20 @@ + + + + + transifex.project_url + https://www.transifex.com/odoo + + + + Transifex: Reload code translations + + code + model.reload() + 7 + days + -1 + + + + diff --git a/addons/transifex/i18n/transifex.pot b/addons/transifex/i18n/transifex.pot new file mode 100644 index 00000000000..5c47cb71971 --- /dev/null +++ b/addons/transifex/i18n/transifex.pot @@ -0,0 +1,134 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * transifex +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-01-23 14:34+0000\n" +"PO-Revision-Date: 2023-01-23 14:34+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: transifex +#: model:ir.model,name:transifex.model_base +msgid "Base" +msgstr "" + +#. module: transifex +#: model:ir.model.fields,field_description:transifex.field_transifex_code_translation__source +msgid "Code" +msgstr "" + +#. module: transifex +#: model:ir.model,name:transifex.model_transifex_code_translation +msgid "Code Translation" +msgstr "" + +#. module: transifex +#. odoo-javascript +#: code:addons/transifex/static/src/views/fields/translation_dialog.xml:0 +#: model_terms:ir.ui.view,arch_db:transifex.transifex_code_translation_tree_view +#, python-format +msgid "Contribute" +msgstr "" + +#. module: transifex +#: model:ir.model.fields,field_description:transifex.field_transifex_code_translation__display_name +msgid "Display Name" +msgstr "" + +#. module: transifex +#: model_terms:ir.ui.view,arch_db:transifex.transifex_code_translation_view_search +msgid "Group By" +msgstr "" + +#. module: transifex +#: model:ir.model.fields,field_description:transifex.field_transifex_code_translation__id +msgid "ID" +msgstr "" + +#. module: transifex +#: model:ir.model.fields,field_description:transifex.field_transifex_code_translation__lang +msgid "Language" +msgstr "" + +#. module: transifex +#: model:ir.model.fields,field_description:transifex.field_transifex_code_translation____last_update +msgid "Last Modified on" +msgstr "" + +#. module: transifex +#: model:ir.model.fields,field_description:transifex.field_transifex_code_translation__module +#: model_terms:ir.ui.view,arch_db:transifex.transifex_code_translation_view_search +msgid "Module" +msgstr "" + +#. module: transifex +#: model:ir.model.fields,help:transifex.field_transifex_code_translation__module +msgid "Module this term belongs to" +msgstr "" + +#. module: transifex +#: model_terms:ir.ui.view,arch_db:transifex.transifex_code_translation_view_search +msgid "Not Translated" +msgstr "" + +#. module: transifex +#: model:ir.model.fields,help:transifex.field_transifex_code_translation__transifex_url +msgid "Propose a modification in the official version of Odoo" +msgstr "" + +#. module: transifex +#. odoo-javascript +#: code:addons/transifex/static/src/views/reload_code_translations_views.xml:0 +#, python-format +msgid "Reload" +msgstr "" + +#. module: transifex +#: model_terms:ir.ui.view,arch_db:transifex.transifex_code_translation_view_search +msgid "Search Code Translations" +msgstr "" + +#. module: transifex +#: model_terms:ir.ui.view,arch_db:transifex.transifex_code_translation_tree_view +msgid "Transifex" +msgstr "" + +#. module: transifex +#: model_terms:ir.ui.view,arch_db:transifex.transifex_code_translation_tree_view +msgid "Transifex Code Translation" +msgstr "" + +#. module: transifex +#: model:ir.actions.server,name:transifex.action_code_translations +#: model:ir.ui.menu,name:transifex.menu_transifex_code_translations +msgid "Transifex Code Translations" +msgstr "" + +#. module: transifex +#: model:ir.model,name:transifex.model_transifex_translation +msgid "Transifex Translation" +msgstr "" + +#. module: transifex +#: model:ir.model.fields,field_description:transifex.field_transifex_code_translation__transifex_url +msgid "Transifex URL" +msgstr "" + +#. module: transifex +#: model:ir.actions.server,name:transifex.transifex_code_translation_reload_ir_actions_server +#: model:ir.cron,cron_name:transifex.transifex_code_translation_reload +msgid "Transifex: Reload code translations" +msgstr "" + +#. module: transifex +#: model:ir.model.fields,field_description:transifex.field_transifex_code_translation__value +msgid "Translation Value" +msgstr "" diff --git a/addons/transifex/models/__init__.py b/addons/transifex/models/__init__.py new file mode 100644 index 00000000000..4a641a2a50c --- /dev/null +++ b/addons/transifex/models/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models +from . import transifex_code_translation +from . import transifex_translation diff --git a/addons/transifex/models/models.py b/addons/transifex/models/models.py new file mode 100644 index 00000000000..f110a314d61 --- /dev/null +++ b/addons/transifex/models/models.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models + +class BaseModel(models.AbstractModel): + _inherit = 'base' + + def get_field_translations(self, field_name, langs=None): + """ get model/model_term translations for records with transifex url + :param str field_name: field name + :param list langs: languages + + :return: (translations, context) where + translations: list of dicts like [{"lang": lang, "source": source_term, "value": value_term, + "module": module, "transifexURL": transifex_url}] + context: {"translation_type": "text"/"char", "translation_show_source": True/False} + """ + translations, context = super().get_field_translations(field_name, langs=langs) + external_id = self.get_external_id().get(self.id) + if not external_id: + return translations, context + + module = external_id.split('.')[0] + if module not in self.pool._init_modules: + return translations, context + + for translation in translations: + translation['module'] = module + self.env['transifex.translation']._update_transifex_url(translations) + return translations, context diff --git a/addons/transifex/models/transifex_code_translation.py b/addons/transifex/models/transifex_code_translation.py new file mode 100644 index 00000000000..37042f9d2ae --- /dev/null +++ b/addons/transifex/models/transifex_code_translation.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import psycopg2 + +from odoo import api, models, fields +from odoo.tools.translate import CodeTranslations + + +class TransifexCodeTranslation(models.Model): + _name = "transifex.code.translation" + _description = "Code Translation" + _log_access = False + + source = fields.Text(string='Code') + value = fields.Text(string='Translation Value') + module = fields.Char(help="Module this term belongs to") + lang = fields.Selection(selection='_get_languages', string='Language', validate=False) + transifex_url = fields.Char("Transifex URL", compute='_compute_transifex_url', + help="Propose a modification in the official version of Odoo") + + def _get_languages(self): + return self.env['res.lang'].get_installed() + + def _compute_transifex_url(self): + self.transifex_url = False + self.env['transifex.translation']._update_transifex_url(self) + + def _load_code_translations(self, module_names=None, langs=None): + try: + # the table lock promises translations for a (module, language) will only be created once + self.env.cr.execute(f'LOCK TABLE {self._table} IN EXCLUSIVE MODE NOWAIT') + + if module_names is None: + module_names = self.env['ir.module.module'].search([('state', '=', 'installed')]).mapped('name') + if langs is None: + langs = [lang for lang, _ in self._get_languages() if lang != 'en_US'] + self.env.cr.execute(f'SELECT DISTINCT module, lang FROM {self._table}') + loaded_code_translations = set(self.env.cr.fetchall()) + create_value_list = [ + { + 'source': src, + 'value': value, + 'module': module_name, + 'lang': lang, + } + for module_name in module_names + for lang in langs + if (module_name, lang) not in loaded_code_translations + for src, value in CodeTranslations._get_code_translations(module_name, lang, lambda x: True).items() + ] + self.sudo().create(create_value_list) + + except psycopg2.errors.LockNotAvailable: + return False + + return True + + def _open_code_translations(self): + self._load_code_translations() + return { + 'name': 'Code Translations', + 'type': 'ir.actions.act_window', + 'res_model': 'transifex.code.translation', + 'view_mode': 'list', + } + + @api.model + def reload(self): + self.env.cr.execute(f'DELETE FROM {self._table}') + return self._load_code_translations() diff --git a/addons/transifex/models/transifex_translation.py b/addons/transifex/models/transifex_translation.py new file mode 100644 index 00000000000..810ad0a70ac --- /dev/null +++ b/addons/transifex/models/transifex_translation.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import werkzeug.urls +from configparser import ConfigParser +from os import pardir +from os.path import isfile, join as opj + +import odoo +from odoo import models, tools + + +class TransifexTranslation(models.AbstractModel): + _name = "transifex.translation" + _description = "Transifex Translation" + + @tools.ormcache() + def _get_transifex_projects(self): + """ get the transifex project name for each module + + .tx/config files contains the project reference + first section is [main], after '[odoo-16.sale]' + + :rtype: dict + :return: {module_name: tx_project_name} + """ + tx_config_file = ConfigParser() + projects = {} + for addon_path in odoo.addons.__path__: + for tx_path in ( + opj(addon_path, '.tx', 'config'), + opj(addon_path, pardir, '.tx', 'config'), + ): + if isfile(tx_path): + tx_config_file.read(tx_path) + # tx_config_file.sections(): ['main', 'odoo-16.sale', ...] + projects.update(sec.split('.')[::-1] for sec in tx_config_file.sections()[1:]) + return projects + + def _update_transifex_url(self, translations): + """ Update translations' Transifex URL + + :param translations: the translations to update, may be a recordset or a list of dicts. + The elements of `translations` must have the fields/keys 'source', 'module', 'lang', + and the field/key 'transifex_url' is updated on them. + """ + + # e.g. 'https://www.transifex.com/odoo/' + base_url = self.env['ir.config_parameter'].sudo().get_param('transifex.project_url') + if not base_url: + return + base_url = base_url.rstrip('/') + + res_langs = self.env['res.lang'].search([]) + lang_to_iso = {l.code: l.iso_code for l in res_langs} + if not lang_to_iso: + return + + projects = self._get_transifex_projects() + if not projects: + return + + for translation in translations: + if not translation['source'] or translation['lang'] == 'en_US': + continue + + lang_iso = lang_to_iso.get(translation['lang']) + if not lang_iso: + continue + + project = projects.get(translation['module']) + if not project: + continue + + # e.g. https://www.transifex.com/odoo/odoo-16/translate/#fr_FR/sale/42?q=text:'Sale+Order' + # 42 is an arbitrary number to satisfy the transifex URL format + source = werkzeug.urls.url_quote_plus(translation['source'][:50].replace("\n", "").replace("'", "\\'")) + source = f"'{source}'" if "+" in source else source + translation['transifex_url'] = f"{base_url}/{project}/translate/#{lang_iso}/{translation['module']}/42?q=text%3A{source}" diff --git a/addons/transifex/security/ir.model.access.csv b/addons/transifex/security/ir.model.access.csv new file mode 100644 index 00000000000..ae973ec18fb --- /dev/null +++ b/addons/transifex/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_transifex_code_translation,transifex.code.translation,model_transifex_code_translation,base.group_system,1,0,0,0 diff --git a/addons/transifex/static/description/icon.png b/addons/transifex/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9e6c95560049132809ad2c2343fe19e0f54f5001 GIT binary patch literal 7344 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 M>lka-YocQR52KbahX4Qo literal 0 HcmV?d00001 diff --git a/addons/transifex/static/description/icon.svg b/addons/transifex/static/description/icon.svg new file mode 100644 index 00000000000..a1c957930b5 --- /dev/null +++ b/addons/transifex/static/description/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/addons/transifex/static/src/views/fields/translation_dialog.xml b/addons/transifex/static/src/views/fields/translation_dialog.xml new file mode 100644 index 00000000000..c7343f4bfa6 --- /dev/null +++ b/addons/transifex/static/src/views/fields/translation_dialog.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/addons/transifex/static/src/views/reload_code_translations_views.js b/addons/transifex/static/src/views/reload_code_translations_views.js new file mode 100644 index 00000000000..ee4f474d958 --- /dev/null +++ b/addons/transifex/static/src/views/reload_code_translations_views.js @@ -0,0 +1,25 @@ +/** @odoo-module **/ + +import { registry } from "@web/core/registry"; +import { ListController } from "@web/views/list/list_controller"; +import { listView } from "@web/views/list/list_view"; +import { browser } from "@web/core/browser/browser"; +import { useService } from "@web/core/utils/hooks"; + +export class TransifexCodeTranslationListController extends ListController { + setup() { + super.setup(); + this.orm = useService("orm"); + } + + async onClickReloadCodeTranslations() { + await this.orm.call("transifex.code.translation", "reload", [], {}); + browser.location.reload(); + } +} + +registry.category("views").add("transifex_code_translation_tree", { + ...listView, + Controller: TransifexCodeTranslationListController, + buttonTemplate: "transifex.CodeTranslationListView.Buttons", +}); diff --git a/addons/transifex/static/src/views/reload_code_translations_views.xml b/addons/transifex/static/src/views/reload_code_translations_views.xml new file mode 100644 index 00000000000..b109b4b2d32 --- /dev/null +++ b/addons/transifex/static/src/views/reload_code_translations_views.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/addons/transifex/views/code_translation_views.xml b/addons/transifex/views/code_translation_views.xml new file mode 100644 index 00000000000..b5ae3c7c5e5 --- /dev/null +++ b/addons/transifex/views/code_translation_views.xml @@ -0,0 +1,47 @@ + + + + transifex.code.translation.tree + transifex.code.translation + + + + + + + + + + + + + transifex.code.translation.view.search + transifex.code.translation + + + + + + + + + + + + + + + + + Transifex Code Translations + + + code + action = model._open_code_translations() + + + + diff --git a/addons/web/static/src/views/fields/translation_dialog.js b/addons/web/static/src/views/fields/translation_dialog.js index fde698b7141..c59cbf7c49a 100644 --- a/addons/web/static/src/views/fields/translation_dialog.js +++ b/addons/web/static/src/views/fields/translation_dialog.js @@ -29,13 +29,8 @@ export class TranslationDialog extends Component { this.terms = translations.map((term) => { const relatedLanguage = languages.find((l) => l[0] === term.lang); return { - id: term.id, - lang: term.lang, + ...term, langName: relatedLanguage[1], - source: term.source, - // we set the translation value coming from the database, except for the language - // the user is currently utilizing. Then we set the translation value coming - // from the value of the field in the form value: term.lang === this.user.lang && !this.props.showSource && diff --git a/odoo/models.py b/odoo/models.py index e6d0daee6a7..36f54d1338f 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -3070,10 +3070,9 @@ class BaseModel(metaclass=MetaModel): :param str field_name: field name :param list langs: languages - :return: list of dicts like [{"lang": lang, "source": source_term, "value": value_term}] - In the UI, translation_dialog.js - for model: val_en will be shown as the translation - for model term: val_en will be shown as the src + :return: (translations, context) where + translations: list of dicts like [{"lang": lang, "source": source_term, "value": value_term}] + context: {"translation_type": "text"/"char", "translation_show_source": True/False} """ self.ensure_one() field = self._fields[field_name] @@ -3098,9 +3097,7 @@ class BaseModel(metaclass=MetaModel): for lang, term_lang in translations.items()] context = {} context['translation_type'] = 'text' if field.type in ['text', 'html'] else 'char' - context['translation_show_source'] = False - if callable(field.translate): - context['translation_show_source'] = True + context['translation_show_source'] = callable(field.translate) return translations, context diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py index 2fee48869d8..05407f7ef46 100644 --- a/odoo/tools/translate.py +++ b/odoo/tools/translate.py @@ -37,7 +37,7 @@ PYTHON_TRANSLATION_COMMENT = 'odoo-python' # translation used for javascript code in web client JAVASCRIPT_TRANSLATION_COMMENT = 'odoo-javascript' # used to notify web client that these translations should be loaded in the UI -# depreciated comment since Odoo 16.0 +# deprecated comment since Odoo 16.0 WEB_TRANSLATION_COMMENT = "openerp-web" SKIPPED_ELEMENTS = ('script', 'style', 'title') @@ -1516,7 +1516,7 @@ class CodeTranslations: def filter_func(row): # In the pot files with new translations, a code translation should have either # PYTHON_TRANSLATION_COMMENT or JAVASCRIPT_TRANSLATION_COMMENT for comments. - # If a comment has neither the above comments, the pot file uses the depreciated + # If a comment has neither the above comments, the pot file uses the deprecated # comments. And all code translations are stored as python translations. return row.get('value') and ( PYTHON_TRANSLATION_COMMENT in row['comments']