# Part of Odoo. See LICENSE file for full copyright and licensing details. import json import logging import os import polib from odoo import fields, models from odoo.modules.module import get_module_path _logger = logging.getLogger(__name__) dir_path = os.path.dirname(os.path.realpath(__file__)) dir_path = os.path.join(dir_path, "..") class TranslationHelperWizard(models.TransientModel): _name = "translation.helper.wizard" _description = "Translation Helper Wizard" weblate_link = fields.Char( string="Weblate Link", help="Link to current term on Weblate server", ) term_value = fields.Char( string="Term", help="Term value to translate", ) translation_value = fields.Char( string="Translation", help="Translation for term value", ) language_id = fields.Many2one( string="Language", help="The language for which the translation will be applied", comodel_name="res.lang", ) modules = fields.Char( string="In Apps", help="List of modules in which the translation term is defined", ) metadata = fields.Json( string="Metadata", help="Metadata", ) def save_translation(self): for module_name in self.modules.split(", "): self.update_term_translation_in_module( module_name=module_name, term_to_translate=self.term_value, translation_value=self.translation_value, lang=self.language_id.iso_code, ) model_name = self.metadata.get("resModel") field_name = self.metadata.get("field", {}).get("name") translation_field_data = self.metadata.get("translation_field_data", {}) translation_field_data[self.language_id.code] = self.translation_value query = """ UPDATE ir_model_fields SET field_description = %s WHERE model = %s AND name = %s """ self.env.cr.execute( query, [json.dumps(translation_field_data), model_name, field_name] ) return { "type": "ir.actions.client", "tag": "translation_helper.response_from_wizard", "name": "Translate info", "params": { "model_name": model_name, "field_name": field_name, "translation_field_data": translation_field_data, }, } def update_term_translation_in_module( self, module_name, term_to_translate, translation_value, lang ): # Always write to local translations/{lang}/ directory inside translation_helper module local_po_dir = os.path.join(dir_path, "translations", lang.strip()) local_po_file = os.path.join(local_po_dir, f"{module_name}.po") if os.path.exists(local_po_file): # Local override already exists — update it source_pofile = polib.pofile(local_po_file) else: # Try to find the system .po file to use as a base source_pofile = None module_path = get_module_path(module_name, display_warning=False) if module_path: for subdir in ("i18n", "i18n_extra"): candidate = os.path.join(module_path, subdir, f"{lang}.po") if os.path.exists(candidate): source_pofile = polib.pofile(candidate) break if source_pofile is None: _logger.warning( "translation_helper: no .po file found for module %r, lang %r", module_name, lang, ) return # Ensure local directory exists os.makedirs(local_po_dir, exist_ok=True) # Build new pofile with updated translation new_pofile = polib.POFile() new_pofile.metadata = source_pofile.metadata module_prefix = f"module: {module_name}" for old_entry in source_pofile: msgstr = old_entry.msgstr if old_entry.msgid == term_to_translate: msgstr = translation_value # Odoo 19 translate.py requires "module: {name}" in every entry's comment comment = old_entry.comment if comment and "module:" not in comment: comment = f"module: {module_name}\n{comment}" elif not comment: comment = f"module: {module_name}" new_entry = polib.POEntry( comment=comment, occurrences=old_entry.occurrences, flags=old_entry.flags, msgid=old_entry.msgid, msgstr=msgstr, ) new_pofile.append(new_entry) # Always save to local path (never to system directories) new_pofile.save(local_po_file)