from odoo import fields, models, api, _ from odoo.exceptions import UserError from ..models.translation import _is_valid_translatable_value class BulkTranslate(models.TransientModel): _name = 'otk.translation.bulk.translate' _description = "Bulk translate Wizard" model_id = fields.Many2one('ir.model', string="Model", ondelete='cascade') base_language_id = fields.Many2one('res.lang', string="Base language", required=True, ondelete='cascade') target_language_ids = fields.Many2many('res.lang', string="Target languages", required=True, ondelete='cascade') field_ids = fields.Many2many( 'ir.model.fields', string="Fields", domain="[('translate', 'in', ['standard', 'html_translate', 'xml_translate']), ('model_id', '=', model_id), ('related', '=', False), ('store', '=', True)]", required=True ) overwrite_existing = fields.Boolean( string="Overwrite existing translations", default=False, help="Re-translate every term, including terms that already have a " "translation in the target languages.", ) @api.depends('model_id') def _compute_model_name(self): for record in self: record.model_name = record.model_id.model @api.model def default_get(self, fields_list): defaults = super().default_get(fields_list) active_model = self.env.context.get('active_model') if active_model: model = self.env['ir.model'].search([('model', '=', active_model)], limit=1) defaults['model_id'] = model.id defaults['base_language_id'] = self.env['res.lang'].search([('code', '=', self.env.user.lang)], limit=1) defaults['target_language_ids'] = self.env['res.lang'].search([('code', '!=', self.env.user.lang)]) return defaults def _resolve_field_value(self, model_name, record_id, field_name, base_lang_code): """Resolve a translatable field value with language fallback. Tries base_lang first, then en_US. Handles html_translate fields that may return False for a specific lang context. """ candidates = [base_lang_code] if base_lang_code != 'en_US': candidates.append('en_US') for try_lang in candidates: record = self.env[model_name].with_context(lang=try_lang).browse(record_id) if not record.exists(): return False value = getattr(record, field_name, False) if _is_valid_translatable_value(value): return value return False def _pre_filter_records(self, active_ids): """Pre-filter records to separate translatable from empty ones. Uses language fallback (base_lang → en_US) to handle html_translate fields that may not resolve for a specific lang context. Returns: tuple: (translatable_ids, skipped_ids) """ model_name = self.model_id.model base_lang_code = self.base_language_id.code translatable_ids = [] skipped_ids = [] for record_id in active_ids: has_content = False for field in self.field_ids: value = self._resolve_field_value( model_name, record_id, field.name, base_lang_code ) if _is_valid_translatable_value(value): has_content = True break if has_content: translatable_ids.append(record_id) else: skipped_ids.append(record_id) return translatable_ids, skipped_ids def action_translate(self): active_ids = self.env.context.get('active_ids', []) if not active_ids: raise UserError(_("No records selected for translation.")) # Pre-filter: separate records with content from empty ones translatable_ids, skipped_ids = self._pre_filter_records(active_ids) if not translatable_ids: raise UserError(_( "None of the %d selected records have translatable content " "in the selected fields (%s).\n\n" "Please check that the records have content in the base language (%s)." ) % ( len(active_ids), ', '.join(self.field_ids.mapped('field_description')), self.base_language_id.name, )) self.env['otk.translation.action'].sudo().create({ 'model_id': self.model_id.id, 'base_language_id': self.base_language_id.id, 'target_language_ids': self.target_language_ids.ids, 'field_ids': self.field_ids.ids, 'overwrite_existing': self.overwrite_existing, 'pending_record_ids': translatable_ids, 'skipped_record_ids': skipped_ids, 'translation_count': len(translatable_ids), }) # Show summary notification if some records were skipped if skipped_ids: return { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': { 'title': _("Translation Started"), 'message': _( "%d records queued for translation.\n" "%d records skipped (no translatable content in selected fields)." ) % (len(translatable_ids), len(skipped_ids)), 'type': 'warning', 'sticky': True, } }