From 3c3b88f6768e31417263b3828a874ca204e29514 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Fri, 29 May 2020 10:41:36 +0200 Subject: [PATCH 1/4] [IMP] tools: add placeholder support to _ Mimic the syntax of logging to use placeholders in translatable message. The main advantage is to be able to fallback on the source term if the translation can not be formatted properly. It is very common to have errors in translations with missing placeholders or badly translated (ie. the source "%(subject)s" -> "%(sujet)s"). Instead of blocking the execution of the code, fallback on source string. Task-id: 1853119 --- doc/reference/guidelines.rst | 32 +++++---- doc/reference/translations.rst | 11 ++-- .../tests/test_term_count.py | 66 +++++++++++++++++++ odoo/tools/translate.py | 14 +++- 4 files changed, 102 insertions(+), 21 deletions(-) diff --git a/doc/reference/guidelines.rst b/doc/reference/guidelines.rst index 2b6dc4a0a23..6ffd19f5c78 100644 --- a/doc/reference/guidelines.rst +++ b/doc/reference/guidelines.rst @@ -837,6 +837,7 @@ in the code, it will not work to translate field values, such as Product names, etc. This must be done instead using the translate flag on the corresponding field. +The method accepts optional positional or named parameter The rule is very simple: calls to the underscore method should always be in the form ``_('literal string')`` and nothing else: @@ -846,19 +847,23 @@ the form ``_('literal string')`` and nothing else: error = _('This record is locked!') # good: strings with formatting patterns included - error = _('Record %s cannot be modified!') % record + error = _('Record %s cannot be modified!', record) # ok too: multi-line literal strings error = _("""This is a bad multiline example - about record %s!""") % record + about record %s!""", record) error = _('Record %s cannot be modified' \ - 'after being validated!') % record + 'after being validated!', record) # bad: tries to translate after string formatting # (pay attention to brackets!) # This does NOT work and messes up the translations! error = _('Record %s cannot be modified!' % record) + # bad: formatting outside of translation + # This won't benefit from fallback mechanism in case of bad translation + error = _('Record %s cannot be modified!') % record + # bad: dynamic string, string concatenation, etc are forbidden! # This does NOT work and messes up the translations! error = _("'" + que_rec['question'] + "' \n") @@ -869,32 +874,31 @@ the form ``_('literal string')`` and nothing else: # and the following will of course not work as already explained: error = _("Product %s is out of stock!" % product.name) - # bad: field values are automatically translated by the framework - # This is useless and will not work the way you think: - error = _("Product %s is not available!") % _(product.name) - # and the following will of course not work as already explained: - error = _("Product %s is not available!" % product.name) - # Instead you can do the following and everything will be translated, # including the product name if its field definition has the # translate flag properly set: - error = _("Product %s is not available!") % product.name + error = _("Product %s is not available!", product.name) Also, keep in mind that translators will have to work with the literal values that are passed to the underscore function, so please try to make them easy to understand and keep spurious characters and formatting to a minimum. Translators -must be aware that formatting patterns such as %s or %d, newlines, etc. need -to be preserved, but it's important to use these in a sensible and obvious manner: +must be aware that formatting patterns such as ``%s`` or ``%d``, newlines, etc. +need to be preserved, but it's important to use these in a sensible and obvious +manner: .. code-block:: python # Bad: makes the translations hard to work with error = "'" + question + _("' \nPlease enter an integer value ") - # Better (pay attention to position of the brackets too!) + # Ok (pay attention to position of the brackets too!) error = _("Answer to question %s is not valid.\n" \ - "Please enter an integer value.") % question + "Please enter an integer value.", question) + + # Better + error = _("Answer to question %(title)s is not valid.\n" \ + "Please enter an integer value.", title=question) In general in Odoo, when manipulating strings, prefer ``%`` over ``.format()`` (when only one variable to replace in a string), and prefer ``%(varname)`` instead diff --git a/doc/reference/translations.rst b/doc/reference/translations.rst index c7997e1e966..cc6c92cbb5d 100644 --- a/doc/reference/translations.rst +++ b/doc/reference/translations.rst @@ -104,9 +104,10 @@ Variables _("Scheduled meeting with %s" % invitee.name) -**Do** set the dynamic variables outside of the translation lookup:: +**Do** set the dynamic variables as a parameter of the translation lookup (this +will fallback on source in case of missing placeholder in the translation):: - _("Scheduled meeting with %s") % invitee.name + _("Scheduled meeting with %s", invitee.name) Blocks @@ -135,16 +136,16 @@ Plural ^^^^^^ **Don't** pluralize terms the English-way:: - msg = _("You have %s invoice") % invoice_count + msg = _("You have %(count)s invoice", count=invoice_count) if invoice_count > 1: msg += _("s") **Do** keep in mind every language has different plural forms:: if invoice_count > 1: - msg = _("You have %s invoices") % invoice_count + msg = _("You have %(count)s invoices", count=invoice_count) else: - msg = _("You have %s invoice") % invoice_count + msg = _("You have one invoice") Read vs Run Time ^^^^^^^^^^^^^^^^ diff --git a/odoo/addons/test_translation_import/tests/test_term_count.py b/odoo/addons/test_translation_import/tests/test_term_count.py index a417436c39f..ce23a1b899d 100644 --- a/odoo/addons/test_translation_import/tests/test_term_count.py +++ b/odoo/addons/test_translation_import/tests/test_term_count.py @@ -269,6 +269,72 @@ class TestTermCount(common.TransactionCase): self.assertEqual(row['src'], "Accounting") self.assertEqual(row['value'], "samva") + def test_translation_placeholder(self): + """Verify placeholder use in _()""" + context = {'lang': "fr_BE"} + self.env.ref("base.lang_fr_BE").active = True + + # translation with positional placeholders + translation = self.env['ir.translation'].create({ + 'src': 'Text with %s placeholder', + 'value': 'Text avec %s marqueur', + 'type': 'code', + 'name': 'addons/test_translation_import/tests/test_count_term.py', + 'res_id': 0, + 'lang': 'fr_BE', + }) + + # correctly translate + self.assertEqual( + _("Text with %s placeholder", 1), + "Text avec 1 marqueur", + "Translation placeholders were not applied" + ) + + # source error: wrong arguments + with self.assertRaises(TypeError), self.cr.savepoint(): + _("Text with %s placeholder", 1, "🧀") + + # translation error: log error and fallback on source + translation.value = "Text avec s% marqueur" + with self.assertLogs('odoo.tools.translate', 'ERROR'): + self.assertEqual( + _("Text with %s placeholder", 1), + "Text with 1 placeholder", + "Fallback to source was not used for bad translation" + ) + + + # translation with named placeholders + translation = self.env['ir.translation'].create({ + 'src': 'Text with %(num)s placeholders %(symbol)s', + 'value': 'Text avec %(num)s marqueurs %(symbol)s', + 'type': 'code', + 'name': 'addons/test_translation_import/tests/test_count_term.py', + 'res_id': 0, + 'lang': 'fr_BE', + }) + + # correctly translate + self.assertEqual( + _("Text with %(num)s placeholders %(symbol)s", num=2, symbol="🧀"), + "Text avec 2 marqueurs 🧀", + "Translation placeholders were not applied" + ) + + # source error: wrong arguments + with self.assertRaises(KeyError), self.cr.savepoint(): + _("Text with %(num)s placeholders %(symbol)s", symbol="🧀") + + # translation error: log error and fallback on source + translation.value = "Text avec %(num)s marqueurs %(symbole)s" + with self.assertLogs('odoo.tools.translate', 'ERROR'): + self.assertEqual( + _("Text with %(num)s placeholders %(symbol)s", num=2, symbol="🧀"), + "Text with 2 placeholders 🧀", + "Fallback to source was not used for bad translation" + ) + @tagged('post_install', '-at_install') class TestTranslationFlow(common.TransactionCase): diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py index 95524718f35..745f2cd8cb0 100644 --- a/odoo/tools/translate.py +++ b/odoo/tools/translate.py @@ -443,8 +443,18 @@ class GettextAlias(object): lang = env['res.users'].context_get()['lang'] return lang - def __call__(self, source): - return self._get_translation(source) + def __call__(self, source, *args, **kwargs): + translation = self._get_translation(source) + assert not (args and kwargs) + if args or kwargs: + try: + return translation % (args or kwargs) + except (TypeError, ValueError, KeyError): + bad = translation + # fallback: apply to source before logging exception (in case source fails) + translation = source % (args or kwargs) + _logger.exception('Bad translation %r for string %r', bad, source) + return translation def _get_translation(self, source): res = source From ba244cef01132347df4d2d9553ea3105ba13a938 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Thu, 4 Jun 2020 12:39:11 +0200 Subject: [PATCH 2/4] [IMP] *: replace to new _() syntax Using a few regex like \((_\(.*%s.*)(\) % )([\w\[\]][\w .\[\]\(\)'"]*)\) ($1, $3)) Old syntax is still compatible but starts the migration to the new syntax that catches error. --- addons/account/models/account_account.py | 6 +-- .../account/models/account_bank_statement.py | 10 ++-- addons/account/models/account_journal.py | 6 +-- addons/account/models/account_move.py | 14 +++--- .../models/account_partial_reconcile.py | 2 +- addons/account/models/account_payment.py | 4 +- addons/account/models/account_tax.py | 2 +- addons/account/models/chart_template.py | 2 +- addons/account/models/company.py | 2 +- addons/account/models/res_bank.py | 2 +- addons/account/models/sequence_mixin.py | 2 +- .../wizard/account_accrual_accounting.py | 2 +- .../account/wizard/account_transfer_wizard.py | 4 +- addons/auth_signup/models/res_partner.py | 4 +- addons/auth_signup/models/res_users.py | 4 +- .../models/res_partner.py | 4 +- .../wizard/base_gengo_translations.py | 2 +- addons/base_import/models/base_import.py | 6 +-- addons/base_import_module/controllers/main.py | 2 +- addons/base_import_module/models/ir_module.py | 2 +- addons/base_sparse_field/models/models.py | 4 +- addons/calendar/models/calendar_recurrence.py | 6 +-- addons/delivery/models/sale_order.py | 2 +- addons/event/models/event_ticket.py | 2 +- addons/fetchmail/models/fetchmail.py | 2 +- addons/gamification/models/challenge.py | 6 +-- addons/gamification/models/goal.py | 2 +- addons/google_calendar/models/res_users.py | 2 +- addons/hr/models/hr_plan.py | 12 ++--- addons/hr_contract/models/hr_contract.py | 2 +- .../hr_recruitment/models/hr_recruitment.py | 2 +- .../iot_handlers/drivers/SerialBaseDriver.py | 2 +- addons/iap/models/iap.py | 2 +- addons/im_livechat/models/mail_channel.py | 2 +- addons/l10n_ar/models/account_move.py | 2 +- addons/l10n_ar/models/res_partner.py | 6 +-- addons/l10n_fr_fec/wizard/account_fr_fec.py | 2 +- addons/l10n_fr_pos_cert/models/pos.py | 2 +- addons/l10n_fr_pos_cert/models/res_company.py | 4 +- addons/l10n_id_efaktur/models/account_move.py | 2 +- .../l10n_it_edi/models/account_edi_format.py | 4 +- addons/l10n_it_edi/models/account_invoice.py | 2 +- .../models/account_move.py | 2 +- addons/mail/controllers/main.py | 2 +- addons/mail/models/mail_blacklist.py | 2 +- addons/mail/models/mail_channel.py | 4 +- addons/mail/models/mail_render_mixin.py | 2 +- addons/mail/models/mail_template.py | 6 +-- addons/mail/models/mail_thread.py | 14 +++--- addons/mail/models/mail_thread_blacklist.py | 4 +- addons/maintenance/models/maintenance.py | 2 +- addons/mass_mailing/models/mailing.py | 4 +- .../models/mailing_mailing.py | 2 +- .../wizard/mailing_sms_test.py | 2 +- .../mass_mailing_sms/wizard/sms_composer.py | 2 +- addons/mrp/models/mrp_bom.py | 2 +- addons/mrp/models/mrp_production.py | 8 ++-- addons/mrp/models/mrp_workorder.py | 4 +- addons/payment_sips/models/payment.py | 2 +- addons/payment_stripe/models/payment.py | 4 +- .../models/mail_thread_phone.py | 4 +- .../tools/phone_validation.py | 4 +- addons/point_of_sale/models/pos_config.py | 4 +- addons/point_of_sale/models/pos_order.py | 6 +-- addons/portal/controllers/mail.py | 2 +- addons/portal/controllers/portal.py | 8 ++-- addons/pos_adyen/models/pos_config.py | 2 +- .../models/pos_mercury_transaction.py | 2 +- addons/product/models/product_pricelist.py | 2 +- addons/product/models/product_template.py | 2 +- addons/project/models/project.py | 4 +- addons/purchase/models/purchase.py | 6 +-- .../models/purchase_requisition.py | 4 +- addons/purchase_stock/models/purchase.py | 6 +-- addons/repair/models/repair.py | 4 +- addons/resource/models/resource.py | 2 +- addons/sale/controllers/portal.py | 2 +- addons/sale/models/account_invoice.py | 2 +- addons/sale/models/payment.py | 2 +- addons/sale/models/sale.py | 4 +- addons/sale_timesheet/models/project.py | 2 +- addons/sales_team/models/crm_team.py | 4 +- addons/sms/models/sms_template.py | 4 +- addons/sms/wizard/sms_composer.py | 4 +- .../wizard/account_invoice_send.py | 2 +- addons/stock/models/product.py | 6 +-- addons/stock/models/stock_move.py | 2 +- addons/stock/models/stock_move_line.py | 2 +- addons/stock/models/stock_quant.py | 4 +- addons/stock/models/stock_rule.py | 2 +- addons/stock/wizard/stock_picking_return.py | 2 +- .../stock_valuation_layer_revaluation.py | 2 +- .../models/stock_landed_cost.py | 4 +- addons/web/controllers/main.py | 4 +- addons/website/models/website.py | 8 ++-- addons/website/models/website_visitor.py | 2 +- addons/website_blog/models/website.py | 4 +- addons/website_blog/models/website_blog.py | 2 +- .../wizard/crm_forward_to_partner.py | 2 +- addons/website_forum/models/forum.py | 48 +++++++++---------- .../website_livechat/models/mail_channel.py | 2 +- addons/website_sale/models/product_image.py | 2 +- addons/website_sale/models/sale_order.py | 4 +- addons/website_slides/controllers/main.py | 4 +- addons/website_slides/models/slide_channel.py | 2 +- .../website_slides/models/slide_question.py | 4 +- addons/website_slides/models/slide_slide.py | 2 +- odoo/addons/base/models/ir_actions.py | 8 ++-- odoo/addons/base/models/ir_fields.py | 6 +-- odoo/addons/base/models/ir_filters.py | 2 +- odoo/addons/base/models/ir_mail_server.py | 4 +- odoo/addons/base/models/ir_model.py | 12 ++--- odoo/addons/base/models/ir_module.py | 2 +- odoo/addons/base/models/ir_translation.py | 2 +- odoo/addons/base/models/ir_ui_view.py | 10 ++-- odoo/addons/base/models/res_config.py | 6 +-- odoo/addons/base/models/res_partner.py | 2 +- odoo/addons/base/models/res_users.py | 6 +-- .../base/wizard/base_update_translations.py | 2 +- odoo/models.py | 18 +++---- odoo/service/model.py | 2 +- odoo/tools/image.py | 2 +- odoo/tools/template_inheritance.py | 2 +- odoo/tools/translate.py | 2 +- 124 files changed, 256 insertions(+), 256 deletions(-) diff --git a/addons/account/models/account_account.py b/addons/account/models/account_account.py index 68a903b2760..980e58a8ef7 100644 --- a/addons/account/models/account_account.py +++ b/addons/account/models/account_account.py @@ -42,7 +42,7 @@ class AccountAccount(models.Model): def _check_reconcile(self): for account in self: if account.internal_type in ('receivable', 'payable') and account.reconcile == False: - raise ValidationError(_('You cannot have a receivable/payable account that is not reconcilable. (account code: %s)') % account.code) + raise ValidationError(_('You cannot have a receivable/payable account that is not reconcilable. (account code: %s)', account.code)) @api.constrains('user_type_id') def _check_user_type_id(self): @@ -52,7 +52,7 @@ class AccountAccount(models.Model): if res.get('company_id_count', 0) >= 2: account_unaffected_earnings = self.search([('company_id', '=', res['company_id'][0]), ('user_type_id', '=', data_unaffected_earnings.id)]) - raise ValidationError(_('You cannot have more than one account with "Current Year Earnings" as type. (accounts: %s)') % [a.code for a in account_unaffected_earnings]) + raise ValidationError(_('You cannot have more than one account with "Current Year Earnings" as type. (accounts: %s)', [a.code for a in account_unaffected_earnings])) name = fields.Char(string="Account Name", required=True, index=True) currency_id = fields.Many2one('res.currency', string='Account Currency', @@ -445,7 +445,7 @@ class AccountAccount(models.Model): if partner_prop_acc: account_name = partner_prop_acc.get_by_record().display_name raise UserError( - _('You cannot remove/deactivate the account %s which is set on a customer or vendor.') % account_name + _('You cannot remove/deactivate the account %s which is set on a customer or vendor.', account_name) ) return super(AccountAccount, self).unlink() diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index 71d6f336dac..4bddca46a3a 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -75,7 +75,7 @@ class AccountBankStmtCashWizard(models.Model): def name_get(self): result = [] for cashbox in self: - result.append((cashbox.id, _("%s")%(cashbox.total))) + result.append((cashbox.id, str(cashbox.total))) return result @api.model_create_multi @@ -308,7 +308,7 @@ class AccountBankStatement(models.Model): 'statement_id': stmt.id, 'journal_id': stmt.journal_id.id, 'amount': stmt.difference, - 'payment_ref': _("Cash difference observed during the counting (%s)") % name, + 'payment_ref': _("Cash difference observed during the counting (%s)", name), 'date': stmt.date, } self.env['account.bank.statement.line'].with_context(counterpart_account_id=account.id).create(st_line_vals) @@ -392,13 +392,13 @@ class AccountBankStatement(models.Model): for statement in self: # Chatter. - statement.message_post(body=_('Statement %s confirmed.') % statement.name) + statement.message_post(body=_('Statement %s confirmed.', statement.name)) # Bank statement report. if statement.journal_id.type == 'bank': content, content_type = self.env.ref('account.action_report_account_statement')._render(statement.id) self.env['ir.attachment'].create({ - 'name': statement.name and _("Bank Statement %s.pdf") % statement.name or _("Bank Statement.pdf"), + 'name': statement.name and _("Bank Statement %s.pdf", statement.name) or _("Bank Statement.pdf"), 'type': 'binary', 'datas': base64.encodebytes(content), 'res_model': statement._name, @@ -873,7 +873,7 @@ class AccountBankStatementLine(models.Model): if st_line.currency_id.is_zero(st_line.amount): raise ValidationError(_("The amount of a statement line can't be equal to zero.")) if st_line.foreign_currency_id == st_line.currency_id: - raise ValidationError(_("The foreign currency must be different than the journal one: %s") % st_line.currency_id.name) + raise ValidationError(_("The foreign currency must be different than the journal one: %s", st_line.currency_id.name)) if st_line.foreign_currency_id and st_line.foreign_currency_id.is_zero(st_line.amount_currency): raise ValidationError(_("The amount in foreign currency must be set if the amount is not equal to zero.")) if not st_line.foreign_currency_id and st_line.amount_currency: diff --git a/addons/account/models/account_journal.py b/addons/account/models/account_journal.py index db72d23f5f0..d8da1deea14 100644 --- a/addons/account/models/account_journal.py +++ b/addons/account/models/account_journal.py @@ -213,11 +213,11 @@ class AccountJournal(models.Model): for journal in self: if journal.type == 'bank' and journal.bank_account_id: if journal.bank_account_id.company_id and journal.bank_account_id.company_id != journal.company_id: - raise ValidationError(_('The bank account of a bank journal must belong to the same company (%s).') % journal.company_id.name) + raise ValidationError(_('The bank account of a bank journal must belong to the same company (%s).', journal.company_id.name)) # A bank account can belong to a customer/supplier, in which case their partner_id is the customer/supplier. # Or they are part of a bank journal and their partner_id must be the company's partner_id. if journal.bank_account_id.partner_id != journal.company_id.partner_id: - raise ValidationError(_('The holder of a journal\'s bank account must be the company (%s).') % journal.company_id.name) + raise ValidationError(_('The holder of a journal\'s bank account must be the company (%s).', journal.company_id.name)) @api.constrains('company_id') def _check_company_consistency(self): @@ -365,7 +365,7 @@ class AccountJournal(models.Model): journal_entry = self.env['account.move'].search([('journal_id', '=', self.id), ('state', '=', 'posted'), ('secure_sequence_number', '!=', 0)], limit=1) if len(journal_entry) > 0: field_string = self._fields['restrict_mode_hash_table'].get_description(self.env)['string'] - raise UserError(_("You cannot modify the field %s of a journal that already has accounting entries.") % field_string) + raise UserError(_("You cannot modify the field %s of a journal that already has accounting entries.", field_string)) result = super(AccountJournal, self).write(vals) for journal in self: diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index e197d28d037..aee49964d71 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -377,7 +377,7 @@ class AccountMove(models.Model): if p.invoice_warn != 'block' and p.parent_id and p.parent_id.invoice_warn == 'block': p = p.parent_id warning = { - 'title': _("Warning for %s") % p.name, + 'title': _("Warning for %s", p.name), 'message': p.invoice_warn_msg } if p.invoice_warn == 'block': @@ -758,7 +758,7 @@ class AccountMove(models.Model): return rounding_line_vals.update({ - 'name': _('%s (rounding)') % biggest_tax_line.name, + 'name': _('%s (rounding)', biggest_tax_line.name), 'account_id': biggest_tax_line.account_id.id, 'tax_repartition_line_id': biggest_tax_line.tax_repartition_line_id.id, 'tax_exigible': biggest_tax_line.tax_exigible, @@ -1582,9 +1582,9 @@ class AccountMove(models.Model): lock_date = move.company_id._get_user_fiscal_lock_date() if move.date <= lock_date: if self.user_has_groups('account.group_account_manager'): - message = _("You cannot add/modify entries prior to and inclusive of the lock date %s.") % format_date(self.env, lock_date) + message = _("You cannot add/modify entries prior to and inclusive of the lock date %s.", format_date(self.env, lock_date)) else: - message = _("You cannot add/modify entries prior to and inclusive of the lock date %s. Check the company settings or ask someone with the 'Adviser' role") % format_date(self.env, lock_date) + message = _("You cannot add/modify entries prior to and inclusive of the lock date %s. Check the company settings or ask someone with the 'Adviser' role", format_date(self.env, lock_date)) raise UserError(message) return True @@ -2284,7 +2284,7 @@ class AccountMove(models.Model): raise UserError(_('You need to add a line before posting.')) if move.auto_post and move.date > fields.Date.today(): date_msg = move.date.strftime(get_lang(self.env).date_format) - raise UserError(_("This move is configured to be auto-posted on %s") % date_msg) + raise UserError(_("This move is configured to be auto-posted on %s", date_msg)) if not move.partner_id: if move.is_sale_document(): @@ -3466,9 +3466,9 @@ class AccountMoveLine(models.Model): control_type_failed = journal.type_control_ids and account.user_type_id not in journal.type_control_ids control_account_failed = journal.account_control_ids and account not in journal.account_control_ids if control_journal_failed: - raise UserError(_('You cannot use this account (%s) in this journal, check the field \'Allowed Journals\' on the related account.') % account.display_name) + raise UserError(_('You cannot use this account (%s) in this journal, check the field \'Allowed Journals\' on the related account.', account.display_name)) if control_type_failed or control_account_failed: - raise UserError(_('You cannot use this account (%s) in this journal, check the section \'Control-Access\' under tab \'Advanced Settings\' on the related journal.') % account.display_name) + raise UserError(_('You cannot use this account (%s) in this journal, check the section \'Control-Access\' under tab \'Advanced Settings\' on the related journal.', account.display_name)) @api.constrains('account_id', 'tax_ids', 'tax_line_id', 'reconciled') def _check_off_balance(self): diff --git a/addons/account/models/account_partial_reconcile.py b/addons/account/models/account_partial_reconcile.py index 6e445c20c85..29c0c145944 100644 --- a/addons/account/models/account_partial_reconcile.py +++ b/addons/account/models/account_partial_reconcile.py @@ -67,7 +67,7 @@ class AccountPartialReconcile(models.Model): def _check_required_computed_currencies(self): bad_partials = self.filtered(lambda partial: not partial.debit_currency_id or not partial.credit_currency_id) if bad_partials: - raise ValidationError(_("Missing foreign currencies on partials having ids: %s") % bad_partials.ids) + raise ValidationError(_("Missing foreign currencies on partials having ids: %s", bad_partials.ids)) # ------------------------------------------------------------------------- # COMPUTE METHODS diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py index e1581932463..ba2e519a914 100644 --- a/addons/account/models/account_payment.py +++ b/addons/account/models/account_payment.py @@ -205,9 +205,9 @@ class AccountPayment(models.Model): if self.is_internal_transfer: if self.payment_type == 'inbound': - liquidity_line_name = _('Transfer to %s') % self.journal_id.name + liquidity_line_name = _('Transfer to %s', self.journal_id.name) else: # payment.payment_type == 'outbound': - liquidity_line_name = _('Transfer from %s') % self.journal_id.name + liquidity_line_name = _('Transfer from %s', self.journal_id.name) else: liquidity_line_name = self.payment_reference diff --git a/addons/account/models/account_tax.py b/addons/account/models/account_tax.py index 0ce074b4622..7e5572aea39 100644 --- a/addons/account/models/account_tax.py +++ b/addons/account/models/account_tax.py @@ -184,7 +184,7 @@ class AccountTax(models.Model): @api.returns('self', lambda value: value.id) def copy(self, default=None): - default = dict(default or {}, name=_("%s (Copy)") % self.name) + default = dict(default or {}, name=_("%s (Copy)", self.name)) return super(AccountTax, self).copy(default=default) def name_get(self): diff --git a/addons/account/models/chart_template.py b/addons/account/models/chart_template.py index 2b880c8b122..649962c411e 100644 --- a/addons/account/models/chart_template.py +++ b/addons/account/models/chart_template.py @@ -1040,7 +1040,7 @@ class AccountTaxRepartitionLineTemplate(models.Model): all_tax_rep_lines = self.mapped('plus_report_line_ids') + self.mapped('minus_report_line_ids') lines_without_tag = all_tax_rep_lines.filtered(lambda x: not x.tag_name) if lines_without_tag: - raise ValidationError(_("The following tax report lines are used in some tax repartition template though they don't generate any tag: %s . This probably means you forgot to set a tag_name on these lines.") % str(lines_without_tag.mapped('name'))) + raise ValidationError(_("The following tax report lines are used in some tax repartition template though they don't generate any tag: %s . This probably means you forgot to set a tag_name on these lines.", str(lines_without_tag.mapped('name')))) def get_repartition_line_create_vals(self, company): rslt = [(5, 0, 0)] diff --git a/addons/account/models/company.py b/addons/account/models/company.py index 853b2321252..a793aa148e1 100644 --- a/addons/account/models/company.py +++ b/addons/account/models/company.py @@ -537,7 +537,7 @@ class ResCompany(models.Model): hash_corrupted = False for move in moves: if move.inalterable_hash != move._compute_hash(previous_hash=previous_hash): - rslt.update({'msg_cover': _('Corrupted data on journal entry with id %s.') % move.id}) + rslt.update({'msg_cover': _('Corrupted data on journal entry with id %s.', move.id)}) results_by_journal['results'].append(rslt) hash_corrupted = True break diff --git a/addons/account/models/res_bank.py b/addons/account/models/res_bank.py index 5b587866eef..226e1c889ad 100644 --- a/addons/account/models/res_bank.py +++ b/addons/account/models/res_bank.py @@ -36,7 +36,7 @@ class ResPartnerBank(models.Model): return self._get_qr_code_url(candidate_method, amount, currency, debtor_partner, free_communication, structured_communication) elif not silent_errors: - error_header = _("The following error prevented '%s' QR-code to be generated though it was detected as eligible: ") % candidate_name + error_header = _("The following error prevented '%s' QR-code to be generated though it was detected as eligible: ", candidate_name) raise UserError( error_header + error_message) return None diff --git a/addons/account/models/sequence_mixin.py b/addons/account/models/sequence_mixin.py index f6f4520661d..e75842f8115 100644 --- a/addons/account/models/sequence_mixin.py +++ b/addons/account/models/sequence_mixin.py @@ -96,7 +96,7 @@ class SequenceMixin(models.AbstractModel): """ self.ensure_one() if self._sequence_field not in self._fields or not self._fields[self._sequence_field].store: - raise ValidationError(_('%s is not a stored field') % self._sequence_field) + raise ValidationError(_('%s is not a stored field', self._sequence_field)) where_string, param = self._get_last_sequence_domain(relaxed) if self.id or self.id.origin: where_string += " AND id != %(id)s " diff --git a/addons/account/wizard/account_accrual_accounting.py b/addons/account/wizard/account_accrual_accounting.py index cc6dd3c98ba..b48b5d95a4b 100644 --- a/addons/account/wizard/account_accrual_accounting.py +++ b/addons/account/wizard/account_accrual_accounting.py @@ -202,7 +202,7 @@ class AccrualAccountingWizard(models.TransientModel): record.preview_data = json.dumps({ 'groups_vals': preview_vals, 'options': { - 'discarded_number': (_("%d moves") % preview_discarded) if preview_discarded else False, + 'discarded_number': _("%d moves", preview_discarded) if preview_discarded else False, 'columns': preview_columns, }, }) diff --git a/addons/account/wizard/account_transfer_wizard.py b/addons/account/wizard/account_transfer_wizard.py index c6d73973abf..709adc51f74 100644 --- a/addons/account/wizard/account_transfer_wizard.py +++ b/addons/account/wizard/account_transfer_wizard.py @@ -81,7 +81,7 @@ class AccountTransferWizard(models.TransientModel): # Generate counterpart lines' vals for (counterpart_partner, counterpart_currency), counterpart_vals in counterpart_balances.items(): source_accounts = self.move_line_ids.mapped('account_id') - counterpart_label = len(source_accounts) == 1 and _("Transfer from %s") % source_accounts.display_name or _("Transfer counterpart") + counterpart_label = len(source_accounts) == 1 and _("Transfer from %s", source_accounts.display_name) or _("Transfer counterpart") if not self.company_id.currency_id.is_zero(counterpart_vals['balance']) or (counterpart_currency and not counterpart_currency.is_zero(counterpart_vals['amount_currency'])): line_vals.append({ @@ -112,7 +112,7 @@ class AccountTransferWizard(models.TransientModel): return { 'journal_id': self.journal_id.id, 'date': self.date, - 'ref': self.destination_account_id.display_name and _("Transfer entry to %s") % self.destination_account_id.display_name or '', + 'ref': self.destination_account_id.display_name and _("Transfer entry to %s", self.destination_account_id.display_name) or '', 'line_ids': [(0, 0, line) for line in line_vals], } diff --git a/addons/auth_signup/models/res_partner.py b/addons/auth_signup/models/res_partner.py index 94e8471e00b..fe68ae5380d 100644 --- a/addons/auth_signup/models/res_partner.py +++ b/addons/auth_signup/models/res_partner.py @@ -143,11 +143,11 @@ class ResPartner(models.Model): partner = self.search([('signup_token', '=', token)], limit=1) if not partner: if raise_exception: - raise exceptions.UserError(_("Signup token '%s' is not valid") % token) + raise exceptions.UserError(_("Signup token '%s' is not valid", token)) return False if check_validity and not partner.signup_valid: if raise_exception: - raise exceptions.UserError(_("Signup token '%s' is no longer valid") % token) + raise exceptions.UserError(_("Signup token '%s' is no longer valid", token)) return False return partner diff --git a/addons/auth_signup/models/res_users.py b/addons/auth_signup/models/res_users.py index c600d969e9d..3aaa43f0f18 100644 --- a/addons/auth_signup/models/res_users.py +++ b/addons/auth_signup/models/res_users.py @@ -123,7 +123,7 @@ class ResUsers(models.Model): invite_partner = user.create_uid.partner_id if invite_partner: # notify invite user that new user is connected - title = _("%s connected") % user.name + title = _("%s connected", user.name) message = _("This is his first connection. Wish him welcome") self.env['bus.bus'].sendone( (self._cr.dbname, 'res.partner', invite_partner.id), @@ -196,7 +196,7 @@ class ResUsers(models.Model): for user in self: if not user.email: - raise UserError(_("Cannot send email: user %s has no email address.") % user.name) + raise UserError(_("Cannot send email: user %s has no email address.", user.name)) # TDE FIXME: make this template technical (qweb) with self.env.cr.savepoint(): force_send = not(self.env.context.get('import_file', False)) diff --git a/addons/base_address_extended/models/res_partner.py b/addons/base_address_extended/models/res_partner.py index 963da5e9b72..7ddb2acc6c6 100644 --- a/addons/base_address_extended/models/res_partner.py +++ b/addons/base_address_extended/models/res_partner.py @@ -35,7 +35,7 @@ class Partner(models.Model): field_name = re_match.group()[2:-2] field_pos = re_match.start() if field_name not in street_fields: - raise UserError(_("Unrecognized field %s in street format.") % field_name) + raise UserError(_("Unrecognized field %s in street format.", field_name)) if not previous_field: # first iteration: add heading chars in street_format if partner[field_name]: @@ -104,7 +104,7 @@ class Partner(models.Model): # value not found: keep looking for the same field pass if field_name not in street_fields: - raise UserError(_("Unrecognized field %s in street format.") % field_name) + raise UserError(_("Unrecognized field %s in street format.", field_name)) previous_pos = re_match.end() # last field value is what remains in street_raw minus trailing chars in street_format diff --git a/addons/base_gengo/wizard/base_gengo_translations.py b/addons/base_gengo/wizard/base_gengo_translations.py index 72d23c5ad2d..7f070c00135 100644 --- a/addons/base_gengo/wizard/base_gengo_translations.py +++ b/addons/base_gengo/wizard/base_gengo_translations.py @@ -91,7 +91,7 @@ class BaseGengoTranslations(models.TransientModel): return (True, gengo) except Exception as e: _logger.exception('Gengo connection failed') - return (False, _("Gengo connection failed with this message:\n``%s``") % e) + return (False, _("Gengo connection failed with this message:\n``%s``", e)) def act_update(self): ''' diff --git a/addons/base_import/models/base_import.py b/addons/base_import/models/base_import.py index 041d5ef2dd1..d126bb4268e 100644 --- a/addons/base_import/models/base_import.py +++ b/addons/base_import/models/base_import.py @@ -297,7 +297,7 @@ class Import(models.TransientModel): _("Invalid cell value at row %(row)s, column %(col)s: %(cell_value)s") % { 'row': rowx, 'col': colx, - 'cell_value': xlrd.error_text_from_code.get(cell.value, _("unknown error code %s") % cell.value) + 'cell_value': xlrd.error_text_from_code.get(cell.value, _("unknown error code %s", cell.value)) } ) else: @@ -856,13 +856,13 @@ class Import(models.TransientModel): response.raise_for_status() if response.headers.get('Content-Length') and int(response.headers['Content-Length']) > maxsize: - raise ValueError(_("File size exceeds configured maximum (%s bytes)") % maxsize) + raise ValueError(_("File size exceeds configured maximum (%s bytes)", maxsize)) content = bytearray() for chunk in response.iter_content(DEFAULT_IMAGE_CHUNK_SIZE): content += chunk if len(content) > maxsize: - raise ValueError(_("File size exceeds configured maximum (%s bytes)") % maxsize) + raise ValueError(_("File size exceeds configured maximum (%s bytes)", maxsize)) image = Image.open(io.BytesIO(content)) w, h = image.size diff --git a/addons/base_import_module/controllers/main.py b/addons/base_import_module/controllers/main.py index d382d360961..1671798c20c 100644 --- a/addons/base_import_module/controllers/main.py +++ b/addons/base_import_module/controllers/main.py @@ -30,7 +30,7 @@ class ImportModule(Controller): @webservice def login_upload(self, login, password, db=None, force='', mod_file=None, **kw): if db and db != request.db: - raise Exception(_("Could not select database '%s'") % db) + raise Exception(_("Could not select database '%s'", db)) uid = request.session.authenticate(request.db, login, password) self.check_user(uid) force = True if force == '1' else False diff --git a/addons/base_import_module/models/ir_module.py b/addons/base_import_module/models/ir_module.py index 9fff8448ffd..9e2116507a6 100644 --- a/addons/base_import_module/models/ir_module.py +++ b/addons/base_import_module/models/ir_module.py @@ -121,7 +121,7 @@ class IrModule(models.Model): with zipfile.ZipFile(module_file, "r") as z: for zf in z.filelist: if zf.file_size > MAX_FILE_SIZE: - raise UserError(_("File '%s' exceed maximum allowed file size") % zf.filename) + raise UserError(_("File '%s' exceed maximum allowed file size", zf.filename)) with tempfile.TemporaryDirectory() as module_dir: import odoo.modules.module as module diff --git a/addons/base_sparse_field/models/models.py b/addons/base_sparse_field/models/models.py index 1991306a631..179f6fbdc36 100644 --- a/addons/base_sparse_field/models/models.py +++ b/addons/base_sparse_field/models/models.py @@ -32,9 +32,9 @@ class IrModelFields(models.Model): if 'serialization_field_id' in vals or 'name' in vals: for field in self: if 'serialization_field_id' in vals and field.serialization_field_id.id != vals['serialization_field_id']: - raise UserError(_('Changing the storing system for field "%s" is not allowed.') % field.name) + raise UserError(_('Changing the storing system for field "%s" is not allowed.', field.name)) if field.serialization_field_id and (field.name != vals['name']): - raise UserError(_('Renaming sparse field "%s" is not allowed') % field.name) + raise UserError(_('Renaming sparse field "%s" is not allowed', field.name)) return super(IrModelFields, self).write(vals) diff --git a/addons/calendar/models/calendar_recurrence.py b/addons/calendar/models/calendar_recurrence.py index 9ae8c4fc3de..73c671c611a 100644 --- a/addons/calendar/models/calendar_recurrence.py +++ b/addons/calendar/models/calendar_recurrence.py @@ -127,9 +127,9 @@ class RecurrenceRule(models.Model): every = _("Every %s %s, ") % (recurrence.interval, period) if recurrence.end_type == 'count': - end = _("for %s events") % recurrence.count + end = _("for %s events", recurrence.count) elif recurrence.end_type == 'end_date': - end = _("until %s") % recurrence.until + end = _("until %s", recurrence.until) else: end = '' @@ -142,7 +142,7 @@ class RecurrenceRule(models.Model): weekday_label = dict(BYDAY_SELECTION)[recurrence.byday] on = _("on the %(position)s %(weekday)s, ") % {'position': recurrence.byday, 'weekday': weekday_label} else: - on = _("day %s, ") % recurrence.day + on = _("day %s, ", recurrence.day) else: on = '' recurrence.name = every + on + end diff --git a/addons/delivery/models/sale_order.py b/addons/delivery/models/sale_order.py index d303623bb6e..10fd1d9cea4 100644 --- a/addons/delivery/models/sale_order.py +++ b/addons/delivery/models/sale_order.py @@ -101,7 +101,7 @@ class SaleOrder(models.Model): } if carrier.invoice_policy == 'real': values['price_unit'] = 0 - values['name'] += _(' (Estimated Cost: %s )') % self._format_currency_amount(price_unit) + values['name'] += _(' (Estimated Cost: %s )', self._format_currency_amount(price_unit)) else: values['price_unit'] = price_unit if carrier.free_over and self.currency_id.is_zero(price_unit) : diff --git a/addons/event/models/event_ticket.py b/addons/event/models/event_ticket.py index 3e96eb78d8c..598179d2070 100644 --- a/addons/event/models/event_ticket.py +++ b/addons/event/models/event_ticket.py @@ -51,7 +51,7 @@ class EventTicket(models.Model): def default_get(self, fields): res = super(EventTicket, self).default_get(fields) if 'name' in fields and (not res.get('name') or res['name'] == _('Registration')) and self.env.context.get('default_event_name'): - res['name'] = _('Registration for %s') % self.env.context['default_event_name'] + res['name'] = _('Registration for %s', self.env.context['default_event_name']) return res # description diff --git a/addons/fetchmail/models/fetchmail.py b/addons/fetchmail/models/fetchmail.py index 67dce8c145b..da83677a02c 100644 --- a/addons/fetchmail/models/fetchmail.py +++ b/addons/fetchmail/models/fetchmail.py @@ -126,7 +126,7 @@ odoo_mailgate: "|/path/to/odoo-mailgate.py --host=localhost -u %(uid)d -p PASSWO server.write({'state': 'done'}) except Exception as err: _logger.info("Failed to connect to %s server %s.", server.server_type, server.name, exc_info=True) - raise UserError(_("Connection test failed: %s") % tools.ustr(err)) + raise UserError(_("Connection test failed: %s", tools.ustr(err))) finally: try: if connection: diff --git a/addons/gamification/models/challenge.py b/addons/gamification/models/challenge.py index c424a8c4b28..72fcf48ebad 100644 --- a/addons/gamification/models/challenge.py +++ b/addons/gamification/models/challenge.py @@ -596,7 +596,7 @@ class Challenge(models.Model): def accept_challenge(self): user = self.env.user sudoed = self.sudo() - sudoed.message_post(body=_("%s has joined the challenge") % user.name) + sudoed.message_post(body=_("%s has joined the challenge", user.name)) sudoed.write({'invited_user_ids': [(3, user.id)], 'user_ids': [(4, user.id)]}) return sudoed._generate_goals_from_challenge() @@ -604,7 +604,7 @@ class Challenge(models.Model): """The user discard the suggested challenge""" user = self.env.user sudoed = self.sudo() - sudoed.message_post(body=_("%s has refused the challenge") % user.name) + sudoed.message_post(body=_("%s has refused the challenge", user.name)) return sudoed.write({'invited_user_ids': (3, user.id)}) def _check_challenge_reward(self, force=False): @@ -652,7 +652,7 @@ class Challenge(models.Model): if challenge_ended: # open chatter message - message_body = _("The challenge %s is finished.") % challenge.name + message_body = _("The challenge %s is finished.", challenge.name) if rewarded_users: user_names = rewarded_users.name_get() diff --git a/addons/gamification/models/goal.py b/addons/gamification/models/goal.py index 9a14a1438c3..2236a5baa45 100644 --- a/addons/gamification/models/goal.py +++ b/addons/gamification/models/goal.py @@ -430,7 +430,7 @@ class Goal(models.Model): if self.computation_mode == 'manually': # open a wizard window to update the value manually action = { - 'name': _("Update %s") % self.definition_id.name, + 'name': _("Update %s", self.definition_id.name), 'id': self.id, 'type': 'ir.actions.act_window', 'views': [[False, 'form']], diff --git a/addons/google_calendar/models/res_users.py b/addons/google_calendar/models/res_users.py index c2d2dfaf9ac..416cc9f8667 100644 --- a/addons/google_calendar/models/res_users.py +++ b/addons/google_calendar/models/res_users.py @@ -74,7 +74,7 @@ class User(models.Model): with self.pool.cursor() as cr: self.env.user.with_env(self.env(cr=cr)).write({'google_calendar_rtoken': False}) error_key = error.response.json().get("error", "nc") - error_msg = _("Something went wrong during your token generation. Maybe your Authorization Code is invalid or already expired [%s]") % error_key + error_msg = _("Something went wrong during your token generation. Maybe your Authorization Code is invalid or already expired [%s]", error_key) raise UserError(error_msg) def _sync_google_calendar(self, calendar_service: GoogleCalendarService): diff --git a/addons/hr/models/hr_plan.py b/addons/hr/models/hr_plan.py index a9361f531ac..47b4ecc9ef5 100644 --- a/addons/hr/models/hr_plan.py +++ b/addons/hr/models/hr_plan.py @@ -34,24 +34,24 @@ class HrPlanActivityType(models.Model): def get_responsible_id(self, employee): if self.responsible == 'coach': if not employee.coach_id: - raise UserError(_('Coach of employee %s is not set.') % employee.name) + raise UserError(_('Coach of employee %s is not set.', employee.name)) responsible = employee.coach_id.user_id if not responsible: - raise UserError(_('User of coach of employee %s is not set.') % employee.name) + raise UserError(_('User of coach of employee %s is not set.', employee.name)) elif self.responsible == 'manager': if not employee.parent_id: - raise UserError(_('Manager of employee %s is not set.') % employee.name) + raise UserError(_('Manager of employee %s is not set.', employee.name)) responsible = employee.parent_id.user_id if not responsible: - raise UserError(_('User of manager of employee %s is not set.') % employee.name) + raise UserError(_('User of manager of employee %s is not set.', employee.name)) elif self.responsible == 'employee': responsible = employee.user_id if not responsible: - raise UserError(_('User linked to employee %s is required.') % employee.name) + raise UserError(_('User linked to employee %s is required.', employee.name)) elif self.responsible == 'other': responsible = self.responsible_id if not responsible: - raise UserError(_('No specific user given on activity %s.') % self.activity_type_id.name) + raise UserError(_('No specific user given on activity %s.', self.activity_type_id.name)) return responsible diff --git a/addons/hr_contract/models/hr_contract.py b/addons/hr_contract/models/hr_contract.py index 9e79cd89b32..365f0149288 100644 --- a/addons/hr_contract/models/hr_contract.py +++ b/addons/hr_contract/models/hr_contract.py @@ -144,7 +144,7 @@ class Contract(models.Model): for contract in contracts: contract.activity_schedule( 'mail.mail_activity_data_todo', contract.date_end, - _("The contract of %s is about to expire.") % contract.employee_id.name, + _("The contract of %s is about to expire.", contract.employee_id.name), user_id=contract.hr_responsible_id.id or self.env.uid) contracts.write({'kanban_state': 'blocked'}) diff --git a/addons/hr_recruitment/models/hr_recruitment.py b/addons/hr_recruitment/models/hr_recruitment.py index 3623eaebd2c..35357ebd49d 100644 --- a/addons/hr_recruitment/models/hr_recruitment.py +++ b/addons/hr_recruitment/models/hr_recruitment.py @@ -466,7 +466,7 @@ class Applicant(models.Model): if applicant.job_id: applicant.job_id.write({'no_of_hired_employee': applicant.job_id.no_of_hired_employee + 1}) applicant.job_id.message_post( - body=_('New Employee %s Hired') % applicant.partner_name if applicant.partner_name else applicant.name, + body=_('New Employee %s Hired', applicant.partner_name if applicant.partner_name else applicant.name), subtype_xmlid="hr_recruitment.mt_job_applicant_hired") applicant.message_post_with_view( 'hr_recruitment.applicant_hired_template', diff --git a/addons/hw_drivers/iot_handlers/drivers/SerialBaseDriver.py b/addons/hw_drivers/iot_handlers/drivers/SerialBaseDriver.py index 481a11d1309..37a63f57548 100644 --- a/addons/hw_drivers/iot_handlers/drivers/SerialBaseDriver.py +++ b/addons/hw_drivers/iot_handlers/drivers/SerialBaseDriver.py @@ -142,7 +142,7 @@ class SerialDriver(Driver): self._take_measure() time.sleep(self._protocol.newMeasureDelay) except Exception: - msg = _('Error while reading %s') % self.device_name + msg = _('Error while reading %s', self.device_name) _logger.exception(msg) self._status = {'status': self.STATUS_ERROR, 'message_title': msg, 'message_body': traceback.format_exc()} self._push_status() diff --git a/addons/iap/models/iap.py b/addons/iap/models/iap.py index 34911b8a42b..3bd79296faa 100644 --- a/addons/iap/models/iap.py +++ b/addons/iap/models/iap.py @@ -68,7 +68,7 @@ def jsonrpc(url, method='call', params=None, timeout=15): return response.get('result') except (ValueError, requests.exceptions.ConnectionError, requests.exceptions.MissingSchema, requests.exceptions.Timeout, requests.exceptions.HTTPError) as e: raise exceptions.AccessError( - _('The url that this service requested returned an error. Please contact the author of the app. The url it tried to contact was %s') % url + _('The url that this service requested returned an error. Please contact the author of the app. The url it tried to contact was %s', url) ) #---------------------------------------------------------- diff --git a/addons/im_livechat/models/mail_channel.py b/addons/im_livechat/models/mail_channel.py index a1110b30c4b..783e576e71f 100644 --- a/addons/im_livechat/models/mail_channel.py +++ b/addons/im_livechat/models/mail_channel.py @@ -207,7 +207,7 @@ class MailChannel(models.Model): mail_body = template._render(render_context, engine='ir.qweb', minimal_qcontext=True) mail_body = self.env['mail.render.mixin']._replace_local_links(mail_body) mail = self.env['mail.mail'].sudo().create({ - 'subject': _('Conversation with %s') % self.livechat_operator_id.name, + 'subject': _('Conversation with %s', self.livechat_operator_id.name), 'email_from': company.catchall_formatted or company.email_formatted, 'author_id': self.env.user.partner_id.id, 'email_to': email, diff --git a/addons/l10n_ar/models/account_move.py b/addons/l10n_ar/models/account_move.py index 178f5644f34..5184de1cb8d 100644 --- a/addons/l10n_ar/models/account_move.py +++ b/addons/l10n_ar/models/account_move.py @@ -98,7 +98,7 @@ class AccountMove(models.Model): for line in inv.mapped('invoice_line_ids').filtered(lambda x: x.display_type not in ('line_section', 'line_note')): vat_taxes = line.tax_ids.filtered(lambda x: x.tax_group_id.l10n_ar_vat_afip_code) if len(vat_taxes) != 1: - raise UserError(_('There must be one and only one VAT tax per line. Check line "%s"') % line.name) + raise UserError(_('There must be one and only one VAT tax per line. Check line "%s"', line.name)) elif purchase_aliquots == 'zero' and vat_taxes.tax_group_id.l10n_ar_vat_afip_code != '0': raise UserError(_('On invoice id "%s" you must use VAT Not Applicable on every line.') % inv.id) elif purchase_aliquots == 'not_zero' and vat_taxes.tax_group_id.l10n_ar_vat_afip_code == '0': diff --git a/addons/l10n_ar/models/res_partner.py b/addons/l10n_ar/models/res_partner.py index 50adb05dc1f..68bb218730b 100644 --- a/addons/l10n_ar/models/res_partner.py +++ b/addons/l10n_ar/models/res_partner.py @@ -103,10 +103,10 @@ class ResPartner(models.Model): try: module.validate(rec.vat) except module.InvalidChecksum: - raise ValidationError(_('The validation digit is not valid for "%s"') % rec.l10n_latam_identification_type_id.name) + raise ValidationError(_('The validation digit is not valid for "%s"', rec.l10n_latam_identification_type_id.name)) except module.InvalidLength: - raise ValidationError(_('Invalid length for "%s"') % rec.l10n_latam_identification_type_id.name) + raise ValidationError(_('Invalid length for "%s"', rec.l10n_latam_identification_type_id.name)) except module.InvalidFormat: - raise ValidationError(_('Only numbers allowed for "%s"') % rec.l10n_latam_identification_type_id.name) + raise ValidationError(_('Only numbers allowed for "%s"', rec.l10n_latam_identification_type_id.name)) except Exception as error: raise ValidationError(repr(error)) diff --git a/addons/l10n_fr_fec/wizard/account_fr_fec.py b/addons/l10n_fr_fec/wizard/account_fr_fec.py index e09b63e7340..0484c10ce1c 100644 --- a/addons/l10n_fr_fec/wizard/account_fr_fec.py +++ b/addons/l10n_fr_fec/wizard/account_fr_fec.py @@ -88,7 +88,7 @@ class AccountFrFec(models.TransientModel): is_dom_tom = company.country_id.code in dom_tom_group.country_ids.mapped('code') if not is_dom_tom and not company.vat: raise Warning( - _("Missing VAT number for company %s") % company.name) + _("Missing VAT number for company %s", company.name)) if not is_dom_tom and company.vat[0:2] != 'FR': raise Warning( _("FEC is for French companies only !")) diff --git a/addons/l10n_fr_pos_cert/models/pos.py b/addons/l10n_fr_pos_cert/models/pos.py index 4e4b6f2d61e..c8b539a5521 100644 --- a/addons/l10n_fr_pos_cert/models/pos.py +++ b/addons/l10n_fr_pos_cert/models/pos.py @@ -28,7 +28,7 @@ class pos_session(models.Model): date_today = datetime.utcnow() session_start = Datetime.from_string(self.start_at) if not date_today - timedelta(hours=24) <= session_start: - raise UserError(_("This session has been opened another day. To comply with the French law, you should close sessions on a daily basis. Please close session %s and open a new one.") % self.name) + raise UserError(_("This session has been opened another day. To comply with the French law, you should close sessions on a daily basis. Please close session %s and open a new one.", self.name)) return True def open_frontend_cb(self): diff --git a/addons/l10n_fr_pos_cert/models/res_company.py b/addons/l10n_fr_pos_cert/models/res_company.py index 5cacda61c1d..c701c6f348e 100644 --- a/addons/l10n_fr_pos_cert/models/res_company.py +++ b/addons/l10n_fr_pos_cert/models/res_company.py @@ -64,14 +64,14 @@ class ResCompany(models.Model): ('l10n_fr_secure_sequence_number', '!=', 0)], order="l10n_fr_secure_sequence_number ASC") if not orders: - msg_alert = (_('There isn\'t any order flagged for data inalterability yet for the company %s. This mechanism only runs for point of sale orders generated after the installation of the module France - Certification CGI 286 I-3 bis. - POS') % self.env.company.name) + msg_alert = (_('There isn\'t any order flagged for data inalterability yet for the company %s. This mechanism only runs for point of sale orders generated after the installation of the module France - Certification CGI 286 I-3 bis. - POS', self.env.company.name)) hash_verified = False previous_hash = u'' start_order_info = [] for order in orders: if order.l10n_fr_hash != order._compute_hash(previous_hash=previous_hash): - msg_alert = (_('Corrupted data on point of sale order with id %s.') % order.id) + msg_alert = (_('Corrupted data on point of sale order with id %s.', order.id)) hash_verified = False break previous_hash = order.l10n_fr_hash diff --git a/addons/l10n_id_efaktur/models/account_move.py b/addons/l10n_id_efaktur/models/account_move.py index 2374be57c1f..e19b6d17367 100644 --- a/addons/l10n_id_efaktur/models/account_move.py +++ b/addons/l10n_id_efaktur/models/account_move.py @@ -102,7 +102,7 @@ class AccountMove(models.Model): """Reset E-Faktur, so it can be use for other invoice.""" for move in self: if move.l10n_id_attachment_id: - raise UserError(_('You have already generated the tax report for this document: %s') % move.name) + raise UserError(_('You have already generated the tax report for this document: %s', move.name)) self.env['l10n_id_efaktur.efaktur.range'].push_number(move.company_id.id, move.l10n_id_tax_number[3:]) move.message_post( body='e-Faktur Reset: %s ' % (move.l10n_id_tax_number), diff --git a/addons/l10n_it_edi/models/account_edi_format.py b/addons/l10n_it_edi/models/account_edi_format.py index ab2d7230407..6d52e5cf08e 100644 --- a/addons/l10n_it_edi/models/account_edi_format.py +++ b/addons/l10n_it_edi/models/account_edi_format.py @@ -83,13 +83,13 @@ class AccountEdiFormat(models.Model): else: company = self.env.company if elements: - _logger.info(_('Company not found with codice fiscale: %s. The company\'s user is set by default.') % elements[0].text) + _logger.info(_('Company not found with codice fiscale: %s. The company\'s user is set by default.', elements[0].text)) else: _logger.info(_('Company not found. The company\'s user is set by default.')) if not self.env.is_superuser(): if self.env.company != company: - raise UserError(_("You can only import invoice concern your current company: %s") % self.env.company.display_name) + raise UserError(_("You can only import invoice concern your current company: %s", self.env.company.display_name)) # Refund type. # TD01 == invoice diff --git a/addons/l10n_it_edi/models/account_invoice.py b/addons/l10n_it_edi/models/account_invoice.py index 62e40420247..3e7c910e7b0 100644 --- a/addons/l10n_it_edi/models/account_invoice.py +++ b/addons/l10n_it_edi/models/account_invoice.py @@ -168,7 +168,7 @@ class AccountMove(models.Model): } data = b"" + invoice._export_as_xml() - description = _('Italian invoice: %s') % invoice.move_type + description = _('Italian invoice: %s', invoice.move_type) invoice.edi_document_ids = self.env['ir.attachment'].create({ 'name': report_name, 'res_id': invoice.id, diff --git a/addons/l10n_latam_invoice_document/models/account_move.py b/addons/l10n_latam_invoice_document/models/account_move.py index 7472a1f79d5..23705dcae76 100644 --- a/addons/l10n_latam_invoice_document/models/account_move.py +++ b/addons/l10n_latam_invoice_document/models/account_move.py @@ -156,7 +156,7 @@ class AccountMove(models.Model): invoice_type = rec.move_type if internal_type in ['debit_note', 'invoice'] and invoice_type in ['out_refund', 'in_refund'] and \ rec.l10n_latam_document_type_id.code != '99': - raise ValidationError(_('You can not use a %s document type with a refund invoice') % internal_type) + raise ValidationError(_('You can not use a %s document type with a refund invoice', internal_type)) elif internal_type == 'credit_note' and invoice_type in ['out_invoice', 'in_invoice']: raise ValidationError(_('You can not use a %s document type with a invoice') % (internal_type)) diff --git a/addons/mail/controllers/main.py b/addons/mail/controllers/main.py index 8f3b1694098..46d14831dd5 100644 --- a/addons/mail/controllers/main.py +++ b/addons/mail/controllers/main.py @@ -37,7 +37,7 @@ class MailController(http.Controller): def _check_token_and_record_or_redirect(cls, model, res_id, token): comparison = cls._check_token(token) if not comparison: - _logger.warning(_('Invalid token in route %s') % request.httprequest.url) + _logger.warning(_('Invalid token in route %s', request.httprequest.url)) return comparison, None, cls._redirect_to_messaging() try: record = request.env[model].browse(res_id).exists() diff --git a/addons/mail/models/mail_blacklist.py b/addons/mail/models/mail_blacklist.py index cbe5cc6693b..059c6e9e629 100644 --- a/addons/mail/models/mail_blacklist.py +++ b/addons/mail/models/mail_blacklist.py @@ -28,7 +28,7 @@ class MailBlackList(models.Model): for value in values: email = tools.email_normalize(value.get('email')) if not email: - raise UserError(_('Invalid email address %r') % value['email']) + raise UserError(_('Invalid email address %r', value['email'])) if email in all_emails: continue all_emails.append(email) diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py index 1d7c35fabc9..e2ea498ef0e 100644 --- a/addons/mail/models/mail_channel.py +++ b/addons/mail/models/mail_channel.py @@ -427,7 +427,7 @@ class Channel(models.Model): 'email_from': company.catchall_formatted or company.email_formatted, 'author_id': self.env.user.partner_id.id, 'body_html': view._render({'channel': self, 'partner': partner}, engine='ir.qweb', minimal_qcontext=True), - 'subject': _("Guidelines of channel %s") % self.name, + 'subject': _("Guidelines of channel %s", self.name), 'recipient_ids': [(4, partner.id)] } mail = self.env['mail.mail'].sudo().create(create_values) @@ -976,7 +976,7 @@ class Channel(models.Model): def _execute_command_help(self, **kwargs): partner = self.env.user.partner_id if self.channel_type == 'channel': - msg = _("You are in channel #%s.") % self.name + msg = _("You are in channel #%s.", self.name) if self.public == 'private': msg += _(" This channel is private. People must be invited to join it.") else: diff --git a/addons/mail/models/mail_render_mixin.py b/addons/mail/models/mail_render_mixin.py index ddd32659a03..79b66e55f8b 100644 --- a/addons/mail/models/mail_render_mixin.py +++ b/addons/mail/models/mail_render_mixin.py @@ -316,7 +316,7 @@ class MailRenderMixin(models.AbstractModel): render_result = template.render(variables) except Exception as e: _logger.info("Failed to render template : %s" % e, exc_info=True) - raise UserError(_("Failed to render template : %s") % e) + raise UserError(_("Failed to render template : %s", e)) if render_result == u"False": render_result = u"" results[record.id] = render_result diff --git a/addons/mail/models/mail_template.py b/addons/mail/models/mail_template.py index f8f90aea5fd..92783104b65 100644 --- a/addons/mail/models/mail_template.py +++ b/addons/mail/models/mail_template.py @@ -74,7 +74,7 @@ class MailTemplate(models.Model): @api.returns('self', lambda value: value.id) def copy(self, default=None): default = dict(default or {}, - name=_("%s (copy)") % self.name) + name=_("%s (copy)", self.name)) return super(MailTemplate, self).copy(default=default) def unlink_action(self): @@ -88,7 +88,7 @@ class MailTemplate(models.Model): view = self.env.ref('mail.email_compose_message_wizard_form') for template in self: - button_name = _('Send Mail (%s)') % template.name + button_name = _('Send Mail (%s)', template.name) action = ActWindow.create({ 'name': button_name, 'type': 'ir.actions.act_window', @@ -200,7 +200,7 @@ class MailTemplate(models.Model): else: res = report._render([res_id]) if not res: - raise UserError(_('Unsupported report type %s found.') % report.report_type) + raise UserError(_('Unsupported report type %s found.', report.report_type)) result, format = res # TODO in trunk, change return format to binary to match message_post expected format diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index ee16931f25e..3f8818bc386 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -544,7 +544,7 @@ class MailThread(models.AbstractModel): """ self.ensure_one() doc_name = self.env['ir.model']._get(self._name).name - return _('%s created') % doc_name + return _('%s created', doc_name) def _track_subtype(self, init_values): """ Give the subtypes triggered by the changes on the record according @@ -671,7 +671,7 @@ class MailThread(models.AbstractModel): def _routing_warn(self, error_message, message_id, route, raise_exception=True): """ Tools method used in _routing_check_route: whether to log a warning or raise an error """ - short_message = _("Mailbox unavailable - %s") % error_message + short_message = _("Mailbox unavailable - %s", error_message) full_message = ('Routing mail with Message-Id %s: route %s: %s' % (message_id, route, error_message)) _logger.info(full_message) @@ -791,7 +791,7 @@ class MailThread(models.AbstractModel): self._routing_warn(_('target model unspecified'), message_id, route, raise_exception) return () elif model not in self.env: - self._routing_warn(_('unknown target model %s') % model, message_id, route, raise_exception) + self._routing_warn(_('unknown target model %s', model), message_id, route, raise_exception) return () record_set = self.env[model].browse(thread_id) if thread_id else self.env[model] @@ -801,12 +801,12 @@ class MailThread(models.AbstractModel): self._routing_warn(_('reply to missing document (%s,%s), fall back on document creation') % (model, thread_id), message_id, route, False) thread_id = None elif not hasattr(record_set, 'message_update'): - self._routing_warn(_('reply to model %s that does not accept document update, fall back on document creation') % model, message_id, route, False) + self._routing_warn(_('reply to model %s that does not accept document update, fall back on document creation', model), message_id, route, False) thread_id = None # New Document: check model accepts the mailgateway if not thread_id and model and not hasattr(record_set, 'message_new'): - self._routing_warn(_('model %s does not accept document creation') % model, message_id, route, raise_exception) + self._routing_warn(_('model %s does not accept document creation', model), message_id, route, raise_exception) return () # Update message author. We do it now because we need it for aliases (contact settings) @@ -2595,7 +2595,7 @@ class MailThread(models.AbstractModel): access_link = self._notify_get_action_link('view') if model_name: - view_title = _('View %s') % model_name + view_title = _('View %s', model_name) else: view_title = _('View') @@ -2859,7 +2859,7 @@ class MailThread(models.AbstractModel): assignation_msg = view._render(values, engine='ir.qweb', minimal_qcontext=True) assignation_msg = self.env['mail.render.mixin']._replace_local_links(assignation_msg) record.message_notify( - subject=_('You have been assigned to %s') % record.display_name, + subject=_('You have been assigned to %s', record.display_name), body=assignation_msg, partner_ids=partner_ids, record_name=record.display_name, diff --git a/addons/mail/models/mail_thread_blacklist.py b/addons/mail/models/mail_thread_blacklist.py index 00f6bb95518..f17302f360e 100644 --- a/addons/mail/models/mail_thread_blacklist.py +++ b/addons/mail/models/mail_thread_blacklist.py @@ -93,9 +93,9 @@ class MailBlackListMixin(models.AbstractModel): def _assert_primary_email(self): if not hasattr(self, "_primary_email") or not isinstance(self._primary_email, str): - raise UserError(_('Invalid primary email field on model %s') % self._name) + raise UserError(_('Invalid primary email field on model %s', self._name)) if self._primary_email not in self._fields or self._fields[self._primary_email].type != 'char': - raise UserError(_('Invalid primary email field on model %s') % self._name) + raise UserError(_('Invalid primary email field on model %s', self._name)) def _message_receive_bounce(self, email, partner): """ Override of mail.thread generic method. Purpose is to increment the diff --git a/addons/maintenance/models/maintenance.py b/addons/maintenance/models/maintenance.py index 49d27a2b182..8224cc52d93 100644 --- a/addons/maintenance/models/maintenance.py +++ b/addons/maintenance/models/maintenance.py @@ -223,7 +223,7 @@ class MaintenanceEquipment(models.Model): def _create_new_request(self, date): self.ensure_one() self.env['maintenance.request'].create({ - 'name': _('Preventive Maintenance - %s') % self.name, + 'name': _('Preventive Maintenance - %s', self.name), 'request_date': date, 'schedule_date': date, 'category_id': self.category_id.id, diff --git a/addons/mass_mailing/models/mailing.py b/addons/mass_mailing/models/mailing.py index 31c2acc8dc3..7146c1cc77d 100644 --- a/addons/mass_mailing/models/mailing.py +++ b/addons/mass_mailing/models/mailing.py @@ -259,7 +259,7 @@ class MassMailing(models.Model): def copy(self, default=None): self.ensure_one() default = dict(default or {}, - name=_('%s (copy)') % self.name, + name=_('%s (copy)', self.name), contact_list_ids=self.contact_list_ids.ids) return super(MassMailing, self).copy(default=default) @@ -469,7 +469,7 @@ class MassMailing(models.Model): elif 'email' in target._fields: mail_field = 'email' else: - raise UserError(_("Unsupported mass mailing model %s") % self.mailing_model_id.name) + raise UserError(_("Unsupported mass mailing model %s", self.mailing_model_id.name)) if self.unique_ab_testing: query +=""" diff --git a/addons/mass_mailing_sms/models/mailing_mailing.py b/addons/mass_mailing_sms/models/mailing_mailing.py index 3893f11a091..cd0e6060f6f 100644 --- a/addons/mass_mailing_sms/models/mailing_mailing.py +++ b/addons/mass_mailing_sms/models/mailing_mailing.py @@ -171,7 +171,7 @@ class Mailing(models.Model): if 'phone' in target._fields: phone_fields.append('phone') if not phone_fields: - raise UserError(_("Unsupported %s for mass SMS") % self.mailing_model_id.name) + raise UserError(_("Unsupported %s for mass SMS", self.mailing_model_id.name)) query = """ SELECT %(select_query)s diff --git a/addons/mass_mailing_sms/wizard/mailing_sms_test.py b/addons/mass_mailing_sms/wizard/mailing_sms_test.py index c9fe95ec8c5..198304dc2c6 100644 --- a/addons/mass_mailing_sms/wizard/mailing_sms_test.py +++ b/addons/mass_mailing_sms/wizard/mailing_sms_test.py @@ -23,7 +23,7 @@ class MassSMSTest(models.TransientModel): sanitized_numbers = [info['sanitized'] for info in sanitize_res.values() if info['sanitized']] invalid_numbers = [number for number, info in sanitize_res.items() if info['code']] if invalid_numbers: - raise exceptions.UserError(_('Following numbers are not correctly encoded: %s, example : "+32 495 85 85 77, +33 545 55 55 55"') % repr(invalid_numbers)) + raise exceptions.UserError(_('Following numbers are not correctly encoded: %s, example : "+32 495 85 85 77, +33 545 55 55 55"', repr(invalid_numbers))) self.env['sms.api']._send_sms_batch([{ 'res_id': 0, 'number': number, diff --git a/addons/mass_mailing_sms/wizard/sms_composer.py b/addons/mass_mailing_sms/wizard/sms_composer.py index b3e50234036..b8d13b7f056 100644 --- a/addons/mass_mailing_sms/wizard/sms_composer.py +++ b/addons/mass_mailing_sms/wizard/sms_composer.py @@ -45,7 +45,7 @@ class SMSComposer(models.TransientModel): trace_values['ignored'] = fields.Datetime.now() else: if self.mass_sms_allow_unsubscribe: - sms_values['body'] = '%s\n%s' % (sms_values['body'] or '', _('STOP SMS : %s') % self._get_unsubscribe_url(record.id, trace_code, sms_values['number'])) + sms_values['body'] = '%s\n%s' % (sms_values['body'] or '', _('STOP SMS : %s', self._get_unsubscribe_url(record.id, trace_code, sms_values['number']))) return trace_values def _get_blacklist_record_ids(self, records, recipients_info): diff --git a/addons/mrp/models/mrp_bom.py b/addons/mrp/models/mrp_bom.py index 6f0603c028f..43bce93cf71 100644 --- a/addons/mrp/models/mrp_bom.py +++ b/addons/mrp/models/mrp_bom.py @@ -90,7 +90,7 @@ class MrpBom(models.Model): for bom in self: for bom_line in bom.bom_line_ids: if bom.product_id and bom_line.product_id == bom.product_id: - raise ValidationError(_("BoM line product %s should not be the same as BoM product.") % bom.display_name) + raise ValidationError(_("BoM line product %s should not be the same as BoM product.", bom.display_name)) if bom.product_id and bom_line.bom_product_template_attribute_value_ids: raise ValidationError(_("BoM cannot concern product %s and have a line with attributes (%s) at the same time.") % (bom.product_id.display_name, ", ".join([ptav.display_name for ptav in bom_line.bom_product_template_attribute_value_ids]))) diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index 7a58b069198..229d93f5b5d 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -718,7 +718,7 @@ class MrpProduction(models.Model): not_cancel = self.filtered(lambda m: m.state != 'cancel') if not_cancel: productions_name = ', '.join([prod.display_name for prod in not_cancel]) - raise UserError(_('%s cannot be deleted. Try to cancel them before.') % productions_name) + raise UserError(_('%s cannot be deleted. Try to cancel them before.', productions_name)) workorders_to_delete = self.workorder_ids.filtered(lambda wo: wo.state != 'done') if workorders_to_delete: @@ -794,7 +794,7 @@ class MrpProduction(models.Model): moves = [] for production in self: if production.product_id in production.bom_id.byproduct_ids.mapped('product_id'): - raise UserError(_("You cannot have %s as the finished product and in the Byproducts") % self.product_id.name) + raise UserError(_("You cannot have %s as the finished product and in the Byproducts", self.product_id.name)) moves = [production._get_move_finished_values(production.product_id.id, production.product_qty, production.product_uom_id.id)] for byproduct in production.bom_id.byproduct_ids: product_uom_factor = production.product_uom_id._compute_quantity(production.product_qty, production.bom_id.product_uom_id) @@ -1554,7 +1554,7 @@ class MrpProduction(models.Model): def button_unbuild(self): self.ensure_one() return { - 'name': _('Unbuild: %s') % self.product_id.display_name, + 'name': _('Unbuild: %s', self.product_id.display_name), 'view_mode': 'form', 'res_model': 'mrp.unbuild', 'view_id': self.env.ref('mrp.mrp_unbuild_form_view_simplified').id, @@ -1585,7 +1585,7 @@ class MrpProduction(models.Model): ('state', '=', 'done') ]) if sml: - raise UserError(_('This serial number for product %s has already been produced') % self.product_id.name) + raise UserError(_('This serial number for product %s has already been produced', self.product_id.name)) for move in self.move_finished_ids: if move.has_tracking != 'serial' or move.product_id == self.product_id: diff --git a/addons/mrp/models/mrp_workorder.py b/addons/mrp/models/mrp_workorder.py index 0c1407c7a44..ad8711bd420 100644 --- a/addons/mrp/models/mrp_workorder.py +++ b/addons/mrp/models/mrp_workorder.py @@ -751,7 +751,7 @@ class MrpWorkorder(models.Model): qty_done += line.product_uom_id._compute_quantity(line.qty_done, move.product_uom) rounding = move.product_uom_id.rounding if float_compare(qty_done, move.product_uom_qty, precision_rounding=rounding) != 0: - raise UserError(_('You should consume the quantity of %s defined in the BoM. If you want to consume more or less components, change the consumption setting on the BoM.') % move.product_id.name) + raise UserError(_('You should consume the quantity of %s defined in the BoM. If you want to consume more or less components, change the consumption setting on the BoM.', move.product_id.name)) def _check_sn_uniqueness(self): """ Alert the user if the serial number as already been produced """ @@ -763,4 +763,4 @@ class MrpWorkorder(models.Model): ('state', '=', 'done') ]) if sml: - raise UserError(_('This serial number for product %s has already been produced') % self.product_id.name) + raise UserError(_('This serial number for product %s has already been produced', self.product_id.name)) diff --git a/addons/payment_sips/models/payment.py b/addons/payment_sips/models/payment.py index bc44b5c68b0..49dbd5b4dbd 100644 --- a/addons/payment_sips/models/payment.py +++ b/addons/payment_sips/models/payment.py @@ -159,7 +159,7 @@ class TxSips(models.Model): payment_tx = self.search([('reference', '=', reference)]) if not payment_tx or len(payment_tx) > 1: - error_msg = _('Sips: received data for reference %s') % reference + error_msg = _('Sips: received data for reference %s', reference) if not payment_tx: error_msg += _('; no order found') else: diff --git a/addons/payment_stripe/models/payment.py b/addons/payment_stripe/models/payment.py index d13f80bec46..a1a7da752bc 100644 --- a/addons/payment_stripe/models/payment.py +++ b/addons/payment_stripe/models/payment.py @@ -82,7 +82,7 @@ class PaymentAcquirerStripe(models.Model): except HTTPError: _logger.error(resp.text) stripe_error = resp.json().get('error', {}).get('message', '') - error_msg = " " + (_("Stripe gave us the following info about the problem: '%s'") % stripe_error) + error_msg = " " + (_("Stripe gave us the following info about the problem: '%s'", stripe_error)) raise ValidationError(error_msg) return resp.json() @@ -249,7 +249,7 @@ class PaymentTransactionStripe(models.Model): tx = self.search([('reference', '=', reference)]) if not tx: - error_msg = (_('Stripe: no order found for reference %s') % reference) + error_msg = _('Stripe: no order found for reference %s', reference) _logger.error(error_msg) raise ValidationError(error_msg) elif len(tx) > 1: diff --git a/addons/phone_validation/models/mail_thread_phone.py b/addons/phone_validation/models/mail_thread_phone.py index 9b608c8b601..d560de1911a 100644 --- a/addons/phone_validation/models/mail_thread_phone.py +++ b/addons/phone_validation/models/mail_thread_phone.py @@ -106,9 +106,9 @@ class PhoneMixin(models.AbstractModel): def _assert_phone_field(self): if not hasattr(self, "_phone_get_number_fields"): - raise UserError(_('Invalid primary phone field on model %s') % self._name) + raise UserError(_('Invalid primary phone field on model %s', self._name)) if not any(fname in self and self._fields[fname].type == 'char' for fname in self._phone_get_number_fields()): - raise UserError(_('Invalid primary phone field on model %s') % self._name) + raise UserError(_('Invalid primary phone field on model %s', self._name)) def _phone_get_number_fields(self): """ This method returns the fields to use to find the number to use to diff --git a/addons/phone_validation/tools/phone_validation.py b/addons/phone_validation/tools/phone_validation.py index 97eb7bac05c..87fe5498188 100644 --- a/addons/phone_validation/tools/phone_validation.py +++ b/addons/phone_validation/tools/phone_validation.py @@ -20,9 +20,9 @@ try: raise UserError(_('Unable to parse %s.: %s') % (number, str(e))) if not phonenumbers.is_possible_number(phone_nbr): - raise UserError(_('Impossible number %s: probably invalid number of digits.') % number) + raise UserError(_('Impossible number %s: probably invalid number of digits.', number)) if not phonenumbers.is_valid_number(phone_nbr): - raise UserError(_('Invalid number %s: probably incorrect prefix.') % number) + raise UserError(_('Invalid number %s: probably incorrect prefix.', number)) return phone_nbr diff --git a/addons/point_of_sale/models/pos_config.py b/addons/point_of_sale/models/pos_config.py index 235c97bb806..d63bced8eb9 100644 --- a/addons/point_of_sale/models/pos_config.py +++ b/addons/point_of_sale/models/pos_config.py @@ -459,7 +459,7 @@ class PosConfig(models.Model): def create(self, values): IrSequence = self.env['ir.sequence'].sudo() val = { - 'name': _('POS Order %s') % values['name'], + 'name': _('POS Order %s', values['name']), 'padding': 4, 'prefix': "%s/" % values['name'], 'code': "pos.order", @@ -468,7 +468,7 @@ class PosConfig(models.Model): # force sequence_id field to new pos.order sequence values['sequence_id'] = IrSequence.create(val).id - val.update(name=_('POS order line %s') % values['name'], code='pos.order.line') + val.update(name=_('POS order line %s', values['name']), code='pos.order.line') values['sequence_line_id'] = IrSequence.create(val).id pos_config = super(PosConfig, self).create(values) pos_config.sudo()._check_modules_to_install() diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index 085ba223a04..ccb05ff7d42 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -363,7 +363,7 @@ class PosOrder(models.Model): total = float_round(self.amount_total, precision_rounding=self.config_id.rounding_method.rounding, rounding_method=self.config_id.rounding_method.rounding_method) if not float_is_zero(total - self.amount_paid, precision_rounding=self.currency_id.rounding): - raise UserError(_("Order %s is not fully paid.") % self.name) + raise UserError(_("Order %s is not fully paid.", self.name)) self.write({'state': 'paid'}) @@ -479,7 +479,7 @@ class PosOrder(models.Model): # order. It can be the same session, or if it has been closed the new one that has been opened. current_session = order.session_id.config_id.current_session_id if not current_session: - raise UserError(_('To return product(s), you need to open a session in the POS %s') % order.session_id.config_id.display_name) + raise UserError(_('To return product(s), you need to open a session in the POS %s', order.session_id.config_id.display_name)) refund_order = order.copy({ 'name': order.name + _(' REFUND'), 'session_id': current_session.id, @@ -533,7 +533,7 @@ class PosOrder(models.Model): 'mimetype': 'image/jpeg', }) mail_values = { - 'subject': _('Receipt %s') % name, + 'subject': _('Receipt %s', name), 'body_html': message, 'author_id': self.env.user.partner_id.id, 'email_from': self.env.company.email or self.env.user.email_formatted, diff --git a/addons/portal/controllers/mail.py b/addons/portal/controllers/mail.py index c64859c1ad6..612191b705e 100644 --- a/addons/portal/controllers/mail.py +++ b/addons/portal/controllers/mail.py @@ -111,7 +111,7 @@ class PortalChatter(http.Controller): try: CustomerPortal._document_check_access(self, 'ir.attachment', attachment_id, access_token) except (AccessError, MissingError): - raise UserError(_("The attachment %s does not exist or you do not have the rights to access it.") % attachment_id) + raise UserError(_("The attachment %s does not exist or you do not have the rights to access it.", attachment_id)) @http.route(['/mail/chatter_post'], type='http', methods=['POST'], auth='public', website=True) def portal_chatter_post(self, res_model, res_id, message, redirect=None, attachment_ids='', attachment_tokens='', **kw): diff --git a/addons/portal/controllers/portal.py b/addons/portal/controllers/portal.py index 6bec53e7f74..6034a933e77 100644 --- a/addons/portal/controllers/portal.py +++ b/addons/portal/controllers/portal.py @@ -273,10 +273,10 @@ class CustomerPortal(Controller): raise UserError(_("The attachment does not exist or you do not have the rights to access it.")) if attachment_sudo.res_model != 'mail.compose.message' or attachment_sudo.res_id != 0: - raise UserError(_("The attachment %s cannot be removed because it is not in a pending state.") % attachment_sudo.name) + raise UserError(_("The attachment %s cannot be removed because it is not in a pending state.", attachment_sudo.name)) if attachment_sudo.env['mail.message'].search([('attachment_ids', 'in', attachment_sudo.ids)]): - raise UserError(_("The attachment %s cannot be removed because it is linked to a message.") % attachment_sudo.name) + raise UserError(_("The attachment %s cannot be removed because it is linked to a message.", attachment_sudo.name)) return attachment_sudo.unlink() @@ -364,12 +364,12 @@ class CustomerPortal(Controller): def _show_report(self, model, report_type, report_ref, download=False): if report_type not in ('html', 'pdf', 'text'): - raise UserError(_("Invalid report type: %s") % report_type) + raise UserError(_("Invalid report type: %s", report_type)) report_sudo = request.env.ref(report_ref).sudo() if not isinstance(report_sudo, type(request.env['ir.actions.report'])): - raise UserError(_("%s is not the reference of a report") % report_ref) + raise UserError(_("%s is not the reference of a report", report_ref)) method_name = '_render_qweb_%s' % (report_type) report = getattr(report_sudo, method_name)([model.id], data={'report_type': report_type})[0] diff --git a/addons/pos_adyen/models/pos_config.py b/addons/pos_adyen/models/pos_config.py index b0d9176c628..7578f844ed7 100644 --- a/addons/pos_adyen/models/pos_config.py +++ b/addons/pos_adyen/models/pos_config.py @@ -17,7 +17,7 @@ class PosConfig(models.Model): def _check_adyen_ask_customer_for_tip(self): for config in self: if config.adyen_ask_customer_for_tip and (not config.tip_product_id or not config.iface_tipproduct): - raise ValidationError(_("Please configure a tip product for POS %s to support tipping with Adyen.") % config.name) + raise ValidationError(_("Please configure a tip product for POS %s to support tipping with Adyen.", config.name)) @api.onchange('adyen_ask_customer_for_tip') def _onchange_adyen_ask_customer_for_tip(self): diff --git a/addons/pos_mercury/models/pos_mercury_transaction.py b/addons/pos_mercury/models/pos_mercury_transaction.py index 44ca5b89b23..c30d0c64514 100644 --- a/addons/pos_mercury/models/pos_mercury_transaction.py +++ b/addons/pos_mercury/models/pos_mercury_transaction.py @@ -19,7 +19,7 @@ class MercuryTransaction(models.Model): def _get_pos_session(self): pos_session = self.env['pos.session'].search([('state', '=', 'opened'), ('user_id', '=', self.env.uid)], limit=1) if not pos_session: - raise UserError(_("No opened point of sale session for user %s found.") % self.env.user.name) + raise UserError(_("No opened point of sale session for user %s found.", self.env.user.name)) pos_session.login() diff --git a/addons/product/models/product_pricelist.py b/addons/product/models/product_pricelist.py index 880766ce545..1b2b0640284 100644 --- a/addons/product/models/product_pricelist.py +++ b/addons/product/models/product_pricelist.py @@ -519,7 +519,7 @@ class PricelistItem(models.Model): ), ) elif item.compute_price == 'percentage': - item.price = _("%s %% discount") % (item.percent_price) + item.price = _("%s %% discount", item.percent_price) else: item.price = _("%s %% discount and %s surcharge") % (item.price_discount, item.price_surcharge) diff --git a/addons/product/models/product_template.py b/addons/product/models/product_template.py index cb9cd14ee32..8da67700c14 100644 --- a/addons/product/models/product_template.py +++ b/addons/product/models/product_template.py @@ -436,7 +436,7 @@ class ProductTemplate(models.Model): if default is None: default = {} if 'name' not in default: - default['name'] = _("%s (copy)") % self.name + default['name'] = _("%s (copy)", self.name) return super(ProductTemplate, self).copy(default=default) def name_get(self): diff --git a/addons/project/models/project.py b/addons/project/models/project.py index 70c5f58f4dc..dee727dfcfb 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -639,7 +639,7 @@ class Task(models.Model): portal_users = task.allowed_user_ids.filtered('share') if portal_users: user_names = ', '.join(portal_users[:10].mapped('name')) - raise ValidationError(_("The project visibility setting doesn't allow portal users to see the project's tasks. (%s)") % user_names) + raise ValidationError(_("The project visibility setting doesn't allow portal users to see the project's tasks. (%s)", user_names)) def _compute_attachment_ids(self): for task in self: @@ -752,7 +752,7 @@ class Task(models.Model): if default is None: default = {} if not default.get('name'): - default['name'] = _("%s (copy)") % self.name + default['name'] = _("%s (copy)", self.name) return super(Task, self).copy(default) @api.constrains('parent_id') diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py index e2d254d56fe..7afe483ceaa 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -284,7 +284,7 @@ class PurchaseOrder(models.Model): # Block if partner only has warning but parent company is blocked if partner.purchase_warn != 'block' and partner.parent_id and partner.parent_id.purchase_warn == 'block': partner = partner.parent_id - title = _("Warning for %s") % partner.name + title = _("Warning for %s", partner.name) message = partner.purchase_warn_msg warning = { 'title': title, @@ -721,7 +721,7 @@ class PurchaseOrder(models.Model): def _compose_note(self, updated_dates): """Helper method for creating log note when user update scheduled date on portal website.""" - note = _('

%s modified receipt dates for the following products:

') % self.partner_id.name + note = _('

%s modified receipt dates for the following products:

', self.partner_id.name) for line, date in updated_dates: note += _('

  - %s from %s to %s

') % (line.product_id.display_name, line.date_planned, date) return note @@ -980,7 +980,7 @@ class PurchaseOrderLine(models.Model): product_info = self.product_id if product_info.purchase_line_warn != 'no-message': - title = _("Warning for %s") % product_info.name + title = _("Warning for %s", product_info.name) message = product_info.purchase_line_warn_msg warning['title'] = title warning['message'] = message diff --git a/addons/purchase_requisition/models/purchase_requisition.py b/addons/purchase_requisition/models/purchase_requisition.py index 0ee207103c4..0a4e042a6e4 100644 --- a/addons/purchase_requisition/models/purchase_requisition.py +++ b/addons/purchase_requisition/models/purchase_requisition.py @@ -89,7 +89,7 @@ class PurchaseRequisition(models.Model): ('company_id', '=', self.company_id.id), ]) if any(requisitions): - title = _("Warning for %s") % self.vendor_id.name + title = _("Warning for %s", self.vendor_id.name) message = _("There is already an open blanket order for this supplier. We suggest you to use to complete this open blanket order instead of creating a new one.") warning = { 'title': title, @@ -115,7 +115,7 @@ class PurchaseRequisition(models.Model): def action_in_progress(self): self.ensure_one() if not all(obj.line_ids for obj in self): - raise UserError(_("You cannot confirm agreement '%s' because there is no product line.") % self.name) + raise UserError(_("You cannot confirm agreement '%s' because there is no product line.", self.name)) if self.type_id.quantity_copy == 'none' and self.vendor_id: for requisition_line in self.line_ids: if requisition_line.price_unit <= 0.0: diff --git a/addons/purchase_stock/models/purchase.py b/addons/purchase_stock/models/purchase.py index 67fa411ed57..7d4e80430f3 100644 --- a/addons/purchase_stock/models/purchase.py +++ b/addons/purchase_stock/models/purchase.py @@ -218,7 +218,7 @@ class PurchaseOrder(models.Model): 'partner_id': self.partner_id.id }) if not self.partner_id.property_stock_supplier.id: - raise UserError(_("You must set a Vendor Location for this partner %s") % self.partner_id.name) + raise UserError(_("You must set a Vendor Location for this partner %s", self.partner_id.name)) return { 'picking_type_id': self.picking_type_id.id, 'partner_id': self.partner_id.id, @@ -258,9 +258,9 @@ class PurchaseOrder(models.Model): validated_picking = self.picking_ids.filtered(lambda p: p.state == 'done') if validated_picking: - note += _("

Those dates couldn’t be modified accordingly on the receipt %s which had already been validated.

") % validated_picking.name + note += _("

Those dates couldn’t be modified accordingly on the receipt %s which had already been validated.

", validated_picking.name) else: - note += _("

Those dates have been updated accordingly on the receipt %s.

") % self.picking_ids[0].name + note += _("

Those dates have been updated accordingly on the receipt %s.

", self.picking_ids[0].name) for line, date in updated_dates: date = datetime.strptime(date, '%Y-%m-%d %H:%M:%S') line._update_date_planned(date) diff --git a/addons/repair/models/repair.py b/addons/repair/models/repair.py index 9e1d2ce85d3..f6a6aa25747 100644 --- a/addons/repair/models/repair.py +++ b/addons/repair/models/repair.py @@ -360,7 +360,7 @@ class Repair(models.Model): account = operation.product_id.product_tmpl_id._get_product_accounts()['income'] if not account: - raise UserError(_('No account defined for product "%s".') % operation.product_id.name) + raise UserError(_('No account defined for product "%s".', operation.product_id.name)) invoice_line_vals = { 'name': name, @@ -402,7 +402,7 @@ class Repair(models.Model): account = fee.product_id.product_tmpl_id._get_product_accounts()['income'] if not account: - raise UserError(_('No account defined for product "%s".') % fee.product_id.name) + raise UserError(_('No account defined for product "%s".', fee.product_id.name)) invoice_line_vals = { 'name': name, diff --git a/addons/resource/models/resource.py b/addons/resource/models/resource.py index 7c497a61856..e590bdc0643 100644 --- a/addons/resource/models/resource.py +++ b/addons/resource/models/resource.py @@ -160,7 +160,7 @@ class ResourceCalendar(models.Model): def default_get(self, fields): res = super(ResourceCalendar, self).default_get(fields) if not res.get('name') and res.get('company_id'): - res['name'] = _('Working Hours of %s') % self.env['res.company'].browse(res['company_id']).name + res['name'] = _('Working Hours of %s', self.env['res.company'].browse(res['company_id']).name) if 'attendance_ids' in fields and not res.get('attendance_ids'): res['attendance_ids'] = [ (0, 0, {'name': _('Monday Morning'), 'dayofweek': '0', 'hour_from': 8, 'hour_to': 12, 'day_period': 'morning'}), diff --git a/addons/sale/controllers/portal.py b/addons/sale/controllers/portal.py index 40e854af7c5..868235fde64 100644 --- a/addons/sale/controllers/portal.py +++ b/addons/sale/controllers/portal.py @@ -160,7 +160,7 @@ class CustomerPortal(CustomerPortal): session_obj_date = request.session.get('view_quote_%s' % order_sudo.id) if session_obj_date != now and request.env.user.share and access_token: request.session['view_quote_%s' % order_sudo.id] = now - body = _('Quotation viewed by customer %s') % order_sudo.partner_id.name + body = _('Quotation viewed by customer %s', order_sudo.partner_id.name) _message_post_helper( "sale.order", order_sudo.id, diff --git a/addons/sale/models/account_invoice.py b/addons/sale/models/account_invoice.py index 5a9667330ea..c02f918e333 100644 --- a/addons/sale/models/account_invoice.py +++ b/addons/sale/models/account_invoice.py @@ -96,7 +96,7 @@ class AccountMove(models.Model): for sale_line in line.sale_line_ids: todo.add((sale_line.order_id, invoice.name)) for (order, name) in todo: - order.message_post(body=_("Invoice %s paid") % name) + order.message_post(body=_("Invoice %s paid", name)) return res def _get_invoice_delivery_partner_id(self): diff --git a/addons/sale/models/payment.py b/addons/sale/models/payment.py index 30d63d02cec..4904f51e396 100644 --- a/addons/sale/models/payment.py +++ b/addons/sale/models/payment.py @@ -83,7 +83,7 @@ class PaymentTransaction(models.Model): order.amount_total, self.amount, ) order.message_post( - subject=_("Amount Mismatch (%s)") % self.acquirer_id.provider, + subject=_("Amount Mismatch (%s)", self.acquirer_id.provider), body=_("The order was not confirmed despite response from the acquirer (%s): order total is %r but acquirer replied with %r.") % ( self.acquirer_id.provider, order.amount_total, diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index cd6abfd9f5e..aba5569b04c 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -449,7 +449,7 @@ class SaleOrder(models.Model): lines_to_update.append((1, line.id, {'price_unit': price_unit})) self.update({'order_line': lines_to_update}) self.show_update_pricelist = False - self.message_post(body=_("Product prices have been recomputed according to pricelist %s ") % self.pricelist_id.display_name) + self.message_post(body=_("Product prices have been recomputed according to pricelist %s ", self.pricelist_id.display_name)) @api.model def create(self, vals): @@ -1595,7 +1595,7 @@ class SaleOrderLine(models.Model): result = {} warning = {} if product.sale_line_warn != 'no-message': - title = _("Warning for %s") % product.name + title = _("Warning for %s", product.name) message = product.sale_line_warn_msg warning['title'] = title warning['message'] = message diff --git a/addons/sale_timesheet/models/project.py b/addons/sale_timesheet/models/project.py index da7a5926053..b31f89dad69 100644 --- a/addons/sale_timesheet/models/project.py +++ b/addons/sale_timesheet/models/project.py @@ -110,7 +110,7 @@ class Project(models.Model): return self.action_view_timesheet_plan() return { 'type': 'ir.actions.act_window', - 'name': _('Timesheets of %s') % self.name, + 'name': _('Timesheets of %s', self.name), 'domain': [('project_id', '!=', False)], 'res_model': 'account.analytic.line', 'view_id': False, diff --git a/addons/sales_team/models/crm_team.py b/addons/sales_team/models/crm_team.py index ef919653fea..e26a05abe7f 100644 --- a/addons/sales_team/models/crm_team.py +++ b/addons/sales_team/models/crm_team.py @@ -133,7 +133,7 @@ class CrmTeam(models.Model): def _graph_get_model(self): """ skeleton function defined here because it'll be called by crm and/or sale """ - raise UserError(_('Undefined graph model for Sales Team: %s') % self.name) + raise UserError(_('Undefined graph model for Sales Team: %s', self.name)) def _graph_get_dates(self, today): """ return a coherent start and end date for the dashboard graph covering a month period grouped by week. @@ -151,7 +151,7 @@ class CrmTeam(models.Model): return 'EXTRACT(WEEK FROM %s)' % self._graph_date_column() def _graph_y_query(self): - raise UserError(_('Undefined graph model for Sales Team: %s') % self.name) + raise UserError(_('Undefined graph model for Sales Team: %s', self.name)) def _extra_sql_conditions(self): return '' diff --git a/addons/sms/models/sms_template.py b/addons/sms/models/sms_template.py index fb5b8877e87..f3e777f77c4 100644 --- a/addons/sms/models/sms_template.py +++ b/addons/sms/models/sms_template.py @@ -32,7 +32,7 @@ class SMSTemplate(models.Model): @api.returns('self', lambda value: value.id) def copy(self, default=None): default = dict(default or {}, - name=_("%s (copy)") % self.name) + name=_("%s (copy)", self.name)) return super(SMSTemplate, self).copy(default=default) def unlink(self): @@ -44,7 +44,7 @@ class SMSTemplate(models.Model): view = self.env.ref('sms.sms_composer_view_form') for template in self: - button_name = _('Send SMS (%s)') % template.name + button_name = _('Send SMS (%s)', template.name) action = ActWindow.create({ 'name': button_name, 'type': 'ir.actions.act_window', diff --git a/addons/sms/wizard/sms_composer.py b/addons/sms/wizard/sms_composer.py index 0d3d822ed4b..7467d99a21a 100644 --- a/addons/sms/wizard/sms_composer.py +++ b/addons/sms/wizard/sms_composer.py @@ -155,7 +155,7 @@ class SendSMS(models.TransientModel): sanitized_numbers = [info['sanitized'] for info in sanitize_res.values() if info['sanitized']] invalid_numbers = [number for number, info in sanitize_res.items() if info['code']] if invalid_numbers: - raise UserError(_('Following numbers are not correctly encoded: %s') % repr(invalid_numbers)) + raise UserError(_('Following numbers are not correctly encoded: %s', repr(invalid_numbers))) composer.sanitized_numbers = ','.join(sanitized_numbers) else: composer.sanitized_numbers = False @@ -193,7 +193,7 @@ class SendSMS(models.TransientModel): if self.comment_single_recipient and not self.recipient_single_valid: raise UserError(_('Invalid recipient number. Please update it.')) elif not self.comment_single_recipient and self.recipient_invalid_count: - raise UserError(_('%s invalid recipients') % self.recipient_invalid_count) + raise UserError(_('%s invalid recipients', self.recipient_invalid_count)) self._action_send_sms() return False diff --git a/addons/snailmail_account/wizard/account_invoice_send.py b/addons/snailmail_account/wizard/account_invoice_send.py index c4c74f9b9df..d694ca56586 100644 --- a/addons/snailmail_account/wizard/account_invoice_send.py +++ b/addons/snailmail_account/wizard/account_invoice_send.py @@ -80,7 +80,7 @@ class AccountInvoiceSend(models.TransientModel): self.env['bus.bus'].sendone( (self._cr.dbname, 'res.partner', self.env.user.partner_id.id), {'type': 'snailmail_invalid_address', 'title': _("Invalid Addresses"), - 'message': _("%s of the selected invoice(s) had an invalid address and were not sent") % self.invalid_addresses} + 'message': _("%s of the selected invoice(s) had an invalid address and were not sent", self.invalid_addresses)} ) def invalid_addresses_action(self): diff --git a/addons/stock/models/product.py b/addons/stock/models/product.py index ea9529e58b3..b7f9ce7e06c 100644 --- a/addons/stock/models/product.py +++ b/addons/stock/models/product.py @@ -329,11 +329,11 @@ class Product(models.Model): # TDE FIXME: should probably clean the search methods # to prevent sql injections if field not in ('qty_available', 'virtual_available', 'incoming_qty', 'outgoing_qty', 'free_qty'): - raise UserError(_('Invalid domain left operand %s') % field) + raise UserError(_('Invalid domain left operand %s', field)) if operator not in ('<', '>', '=', '!=', '<=', '>='): - raise UserError(_('Invalid domain operator %s') % operator) + raise UserError(_('Invalid domain operator %s', operator)) if not isinstance(value, (float, int)): - raise UserError(_('Invalid domain right operand %s') % value) + raise UserError(_('Invalid domain right operand %s', value)) # TODO: Still optimization possible when searching virtual quantities ids = [] diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py index 8155141c7ca..7256e54c5e6 100644 --- a/addons/stock/models/stock_move.py +++ b/addons/stock/models/stock_move.py @@ -499,7 +499,7 @@ class StockMove(models.Model): return msg = _("The scheduled date has been automatically updated due to a delay on %s.") % (doc_orig[0]._name, doc_orig[0].id, doc_orig[0].name) - msg_subject = _("Scheduled date update due to delay on %s") % doc_orig[0].name + msg_subject = _("Scheduled date update due to delay on %s", doc_orig[0].name) # write the message on each document for doc in documents: last_message = doc.message_ids[:1] diff --git a/addons/stock/models/stock_move_line.py b/addons/stock/models/stock_move_line.py index 7e0f88d6b36..0ba3384d755 100644 --- a/addons/stock/models/stock_move_line.py +++ b/addons/stock/models/stock_move_line.py @@ -157,7 +157,7 @@ class StockMoveLine(models.Model): if self.qty_done and self.product_id.tracking == 'serial': qty_done = self.product_uom_id._compute_quantity(self.qty_done, self.product_id.uom_id) if float_compare(qty_done, 1.0, precision_rounding=self.product_id.uom_id.rounding) != 0: - message = _('You can only process 1.0 %s of products with unique serial number.') % self.product_id.uom_id.name + message = _('You can only process 1.0 %s of products with unique serial number.', self.product_id.uom_id.name) res['warning'] = {'title': _('Warning'), 'message': message} return res diff --git a/addons/stock/models/stock_quant.py b/addons/stock/models/stock_quant.py index 10ce708554b..0be79095598 100644 --- a/addons/stock/models/stock_quant.py +++ b/addons/stock/models/stock_quant.py @@ -456,12 +456,12 @@ class StockQuant(models.Model): # if we want to reserve available_quantity = self._get_available_quantity(product_id, location_id, lot_id=lot_id, package_id=package_id, owner_id=owner_id, strict=strict) if float_compare(quantity, available_quantity, precision_rounding=rounding) > 0: - raise UserError(_('It is not possible to reserve more products of %s than you have in stock.') % product_id.display_name) + raise UserError(_('It is not possible to reserve more products of %s than you have in stock.', product_id.display_name)) elif float_compare(quantity, 0, precision_rounding=rounding) < 0: # if we want to unreserve available_quantity = sum(quants.mapped('reserved_quantity')) if float_compare(abs(quantity), available_quantity, precision_rounding=rounding) > 0: - raise UserError(_('It is not possible to unreserve more products of %s than you have in stock.') % product_id.display_name) + raise UserError(_('It is not possible to unreserve more products of %s than you have in stock.', product_id.display_name)) else: return reserved_quants diff --git a/addons/stock/models/stock_rule.py b/addons/stock/models/stock_rule.py index ff49323f5af..1e7bda0be53 100644 --- a/addons/stock/models/stock_rule.py +++ b/addons/stock/models/stock_rule.py @@ -140,7 +140,7 @@ class StockRule(models.Model): if self.procure_method == 'make_to_order' and self.location_src_id: suffix = _("
A need is created in %s and a rule will be triggered to fulfill it.") % (source) if self.procure_method == 'mts_else_mto' and self.location_src_id: - suffix = _("
If the products are not available in %s, a rule will be triggered to bring products in this location.") % source + suffix = _("
If the products are not available in %s, a rule will be triggered to bring products in this location.", source) message_dict = { 'pull': _('When products are needed in %s,
%s are created from %s to fulfill the need.') % (destination, operation, source) + suffix, 'push': _('When products arrive in %s,
%s are created to send them in %s.') % (source, operation, destination) diff --git a/addons/stock/wizard/stock_picking_return.py b/addons/stock/wizard/stock_picking_return.py index a66749b81db..7bcda6b4d72 100644 --- a/addons/stock/wizard/stock_picking_return.py +++ b/addons/stock/wizard/stock_picking_return.py @@ -118,7 +118,7 @@ class ReturnPicking(models.TransientModel): 'move_lines': [], 'picking_type_id': picking_type_id, 'state': 'draft', - 'origin': _("Return of %s") % self.picking_id.name, + 'origin': _("Return of %s", self.picking_id.name), 'location_id': self.picking_id.location_dest_id.id, 'location_dest_id': self.location_id.id}) new_picking.message_post_with_view('mail.message_origin_link', diff --git a/addons/stock_account/wizard/stock_valuation_layer_revaluation.py b/addons/stock_account/wizard/stock_valuation_layer_revaluation.py index 49d693b728a..77f22d27cc8 100644 --- a/addons/stock_account/wizard/stock_valuation_layer_revaluation.py +++ b/addons/stock_account/wizard/stock_valuation_layer_revaluation.py @@ -118,7 +118,7 @@ class StockValuationLayerRevaluation(models.TransientModel): move_vals = { 'journal_id': self.account_journal_id.id or accounts['stock_journal'].id, 'company_id': self.company_id.id, - 'ref': _("Revaluation of %s") % product_id.display_name, + 'ref': _("Revaluation of %s", product_id.display_name), 'stock_valuation_layer_ids': [(6, None, [revaluation_svl.id])], 'date': self.date or fields.Date.today(), 'move_type': 'entry', diff --git a/addons/stock_landed_costs/models/stock_landed_cost.py b/addons/stock_landed_costs/models/stock_landed_cost.py index 63222bc1385..076ead97c0c 100644 --- a/addons/stock_landed_costs/models/stock_landed_cost.py +++ b/addons/stock_landed_costs/models/stock_landed_cost.py @@ -204,7 +204,7 @@ class StockLandedCost(models.Model): if not lines: target_model_descriptions = dict(self._fields['target_model']._description_selection(self.env)) - raise UserError(_("You cannot apply landed costs on the chosen %s(s). Landed costs can only be applied for products with automated inventory valuation and FIFO or average costing method.") % target_model_descriptions[self.target_model]) + raise UserError(_("You cannot apply landed costs on the chosen %s(s). Landed costs can only be applied for products with automated inventory valuation and FIFO or average costing method.", target_model_descriptions[self.target_model])) return lines def compute_landed_cost(self): @@ -285,7 +285,7 @@ class StockLandedCost(models.Model): for cost in self: if not cost._get_targeted_move_ids(): target_model_descriptions = dict(self._fields['target_model']._description_selection(self.env)) - raise UserError(_('Please define %s on which those additional costs should apply.') % target_model_descriptions[cost.target_model]) + raise UserError(_('Please define %s on which those additional costs should apply.', target_model_descriptions[cost.target_model])) def _check_sum(self): """ Check if each cost line its valuation lines sum to the correct amount diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index c69ee0d66ce..383308061de 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -773,11 +773,11 @@ class ExportXlsxWriter: # fails note that you can't export cell_value = pycompat.to_text(cell_value) except UnicodeDecodeError: - raise UserError(_("Binary fields can not be exported to Excel unless their content is base64-encoded. That does not seem to be the case for %s.") % self.field_names[column]) + raise UserError(_("Binary fields can not be exported to Excel unless their content is base64-encoded. That does not seem to be the case for %s.", self.field_names)[column]) if isinstance(cell_value, str): if len(cell_value) > self.worksheet.xls_strmax: - cell_value = _("The content of this cell is too long for an XLSX file (more than %s characters). Please use the CSV format for this export.") % self.worksheet.xls_strmax + cell_value = _("The content of this cell is too long for an XLSX file (more than %s characters). Please use the CSV format for this export.", self.worksheet.xls_strmax) else: cell_value = cell_value.replace("\r", " ") elif isinstance(cell_value, datetime.datetime): diff --git a/addons/website/models/website.py b/addons/website/models/website.py index afe67a8a9ef..3e810dfa729 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -284,7 +284,7 @@ class Website(models.Model): copy_menu(submenu, new_menu) for website in self: new_top_menu = top_menu.copy({ - 'name': _('Top Menu for Website %s') % website.id, + 'name': _('Top Menu for Website %s', website.id), 'website_id': website.id, }) for submenu in top_menu.child_id: @@ -408,7 +408,7 @@ class Website(models.Model): for page in pages: dependencies.setdefault(page_key, []) dependencies[page_key].append({ - 'text': _('Page %s contains a link to this page') % page.url, + 'text': _('Page %s contains a link to this page', page.url), 'item': page.name, 'link': page.url, }) @@ -436,7 +436,7 @@ class Website(models.Model): menu_key = _('Menus') for menu in menus: dependencies.setdefault(menu_key, []).append({ - 'text': _('This page is in the menu %s') % menu.name, + 'text': _('This page is in the menu %s', menu.name), 'link': '/web#id=%s&view_type=form&model=website.menu' % menu.id, 'item': menu.name, }) @@ -472,7 +472,7 @@ class Website(models.Model): for p in pages: dependencies.setdefault(page_key, []) dependencies[page_key].append({ - 'text': _('Page %s is calling this file') % p.url, + 'text': _('Page %s is calling this file', p.url), 'item': p.name, 'link': p.url, }) diff --git a/addons/website/models/website_visitor.py b/addons/website/models/website_visitor.py index 3af252bda6e..7ff7702df94 100644 --- a/addons/website/models/website_visitor.py +++ b/addons/website/models/website_visitor.py @@ -67,7 +67,7 @@ class WebsiteVisitor(models.Model): def name_get(self): return [( record.id, - (record.name or _('Website Visitor #%s') % record.id) + (record.name or _('Website Visitor #%s', record.id)) ) for record in self] @api.depends('partner_id.email_normalized', 'partner_id.mobile', 'partner_id.phone') diff --git a/addons/website_blog/models/website.py b/addons/website_blog/models/website.py index a647326c340..fb153e3d756 100644 --- a/addons/website_blog/models/website.py +++ b/addons/website_blog/models/website.py @@ -26,7 +26,7 @@ class Website(models.Model): dep[page_key] = [] for p in posts: dep[page_key].append({ - 'text': _('Blog Post %s seems to have a link to this page !') % p.name, + 'text': _('Blog Post %s seems to have a link to this page !', p.name), 'item': p.name, 'link': p.website_url, }) @@ -51,7 +51,7 @@ class Website(models.Model): dep[page_key] = [] for p in posts: dep[page_key].append({ - 'text': _('Blog Post %s seems to be calling this file !') % p.name, + 'text': _('Blog Post %s seems to be calling this file !', p.name), 'item': p.name, 'link': p.website_url, }) diff --git a/addons/website_blog/models/website_blog.py b/addons/website_blog/models/website_blog.py index c99d2c0b1d0..42c07b289c7 100644 --- a/addons/website_blog/models/website_blog.py +++ b/addons/website_blog/models/website_blog.py @@ -215,7 +215,7 @@ class BlogPost(models.Model): @api.returns('self', lambda value: value.id) def copy_data(self, default=None): self.ensure_one() - name = _("%s (copy)") % self.name + name = _("%s (copy)", self.name) default = dict(default or {}, name=name) return super(BlogPost, self).copy_data(default) diff --git a/addons/website_crm_partner_assign/wizard/crm_forward_to_partner.py b/addons/website_crm_partner_assign/wizard/crm_forward_to_partner.py index 9f6a466ef92..9267f6300e3 100644 --- a/addons/website_crm_partner_assign/wizard/crm_forward_to_partner.py +++ b/addons/website_crm_partner_assign/wizard/crm_forward_to_partner.py @@ -69,7 +69,7 @@ class CrmLeadForwardToPartner(models.TransientModel): if no_email: raise UserError(_('Set an email address for the partner(s): %s') % ", ".join(no_email)) if self.forward_type == 'single' and not self.partner_id.email: - raise UserError(_('Set an email address for the partner %s') % self.partner_id.name) + raise UserError(_('Set an email address for the partner %s', self.partner_id.name)) partners_leads = {} for lead in self.assignation_lines: diff --git a/addons/website_forum/models/forum.py b/addons/website_forum/models/forum.py index f8cf3c6ea02..abe6d4c39da 100644 --- a/addons/website_forum/models/forum.py +++ b/addons/website_forum/models/forum.py @@ -464,7 +464,7 @@ class Post(models.Model): filter_regexp = r'()|(]*?href[^>]*?>)|(<[a-z|A-Z]+[^>]*style\s*=\s*[\'"][^\'"]*\s*background[^:]*:[^url;]*url)' content_match = re.search(filter_regexp, content, re.I) if content_match: - raise AccessError(_('%d karma required to post an image or link.') % forum.karma_editor) + raise AccessError(_('%d karma required to post an image or link.', forum.karma_editor)) return content def _default_website_meta(self): @@ -492,9 +492,9 @@ class Post(models.Model): raise UserError(_('Posting answer on a [Deleted] or [Closed] question is not possible.')) # karma-based access if not post.parent_id and not post.can_ask: - raise AccessError(_('%d karma required to create a new question.') % post.forum_id.karma_ask) + raise AccessError(_('%d karma required to create a new question.', post.forum_id.karma_ask)) elif post.parent_id and not post.can_answer: - raise AccessError(_('%d karma required to answer a question.') % post.forum_id.karma_answer) + raise AccessError(_('%d karma required to answer a question.', post.forum_id.karma_answer)) if not post.parent_id and not post.can_post: post.sudo().state = 'pending' @@ -511,7 +511,7 @@ class Post(models.Model): # Make sure only author or moderator can edit/delete messages for post in self.browse(res_ids): if not post.can_edit: - raise AccessError(_('%d karma required to edit a post.') % post.karma_edit) + raise AccessError(_('%d karma required to edit a post.', post.karma_edit)) return super(Post, self).get_mail_message_access(res_ids, operation, model_name=model_name) def write(self, vals): @@ -527,18 +527,18 @@ class Post(models.Model): if 'state' in vals: if vals['state'] in ['active', 'close']: if not post.can_close: - raise AccessError(_('%d karma required to close or reopen a post.') % post.karma_close) + raise AccessError(_('%d karma required to close or reopen a post.', post.karma_close)) trusted_keys += ['state', 'closed_uid', 'closed_date', 'closed_reason_id'] elif vals['state'] == 'flagged': if not post.can_flag: - raise AccessError(_('%d karma required to flag a post.') % post.forum_id.karma_flag) + raise AccessError(_('%d karma required to flag a post.', post.forum_id.karma_flag)) trusted_keys += ['state', 'flag_user_id'] if 'active' in vals: if not post.can_unlink: - raise AccessError(_('%d karma required to delete or reactivate a post.') % post.karma_unlink) + raise AccessError(_('%d karma required to delete or reactivate a post.', post.karma_unlink)) if 'is_correct' in vals: if not post.can_accept: - raise AccessError(_('%d karma required to accept or refuse an answer.') % post.karma_accept) + raise AccessError(_('%d karma required to accept or refuse an answer.', post.karma_accept)) # update karma except for self-acceptance mult = 1 if vals['is_correct'] else -1 if vals['is_correct'] != post.is_correct and post.create_uid.id != self._uid: @@ -546,9 +546,9 @@ class Post(models.Model): self.env.user.sudo().add_karma(post.forum_id.karma_gen_answer_accept * mult) if tag_ids: if set(post.tag_ids.ids) != tag_ids and self.env.user.karma < post.forum_id.karma_edit_retag: - raise AccessError(_('%d karma required to retag.') % post.forum_id.karma_edit_retag) + raise AccessError(_('%d karma required to retag.', post.forum_id.karma_edit_retag)) if any(key not in trusted_keys for key in vals) and not post.can_edit: - raise AccessError(_('%d karma required to edit a post.') % post.karma_edit) + raise AccessError(_('%d karma required to edit a post.', post.karma_edit)) res = super(Post, self).write(vals) @@ -575,7 +575,7 @@ class Post(models.Model): if post.state == 'active' and post.parent_id: post.parent_id.message_post_with_view( 'website_forum.forum_post_template_new_answer', - subject=_('Re: %s') % post.parent_id.name, + subject=_('Re: %s', post.parent_id.name), partner_ids=[(4, p.id) for p in tag_partners], subtype_id=self.env['ir.model.data'].xmlid_to_res_id('website_forum.mt_answer_new')) elif post.state == 'active' and not post.parent_id: @@ -647,7 +647,7 @@ class Post(models.Model): def validate(self): for post in self: if not post.can_moderate: - raise AccessError(_('%d karma required to validate a post.') % post.forum_id.karma_moderate) + raise AccessError(_('%d karma required to validate a post.', post.forum_id.karma_moderate)) # if state == pending, no karma previously added for the new question if post.state == 'pending': post.create_uid.sudo().add_karma(post.forum_id.karma_gen_question_new) @@ -662,7 +662,7 @@ class Post(models.Model): def refuse(self): for post in self: if not post.can_moderate: - raise AccessError(_('%d karma required to refuse a post.') % post.forum_id.karma_moderate) + raise AccessError(_('%d karma required to refuse a post.', post.forum_id.karma_moderate)) post.moderator_id = self.env.user return True @@ -670,7 +670,7 @@ class Post(models.Model): res = [] for post in self: if not post.can_flag: - raise AccessError(_('%d karma required to flag a post.') % post.forum_id.karma_flag) + raise AccessError(_('%d karma required to flag a post.', post.forum_id.karma_flag)) if post.state == 'flagged': res.append({'error': 'post_already_flagged'}) elif post.state == 'active': @@ -691,7 +691,7 @@ class Post(models.Model): def mark_as_offensive(self, reason_id): for post in self: if not post.can_moderate: - raise AccessError(_('%d karma required to mark a post as offensive.') % post.forum_id.karma_moderate) + raise AccessError(_('%d karma required to mark a post as offensive.', post.forum_id.karma_moderate)) # remove some karma _logger.info('Downvoting user <%s> for posting spam/offensive contents', post.create_uid) post.create_uid.sudo().add_karma(post.forum_id.karma_gen_answer_flagged) @@ -721,7 +721,7 @@ class Post(models.Model): def unlink(self): for post in self: if not post.can_unlink: - raise AccessError(_('%d karma required to unlink a post.') % post.karma_unlink) + raise AccessError(_('%d karma required to unlink a post.', post.karma_unlink)) # if unlinking an answer with accepted answer: remove provided karma for post in self: if post.is_correct: @@ -766,7 +766,7 @@ class Post(models.Model): # karma-based action check: use the post field that computed own/all value if not self.can_comment_convert: - raise AccessError(_('%d karma required to convert an answer to a comment.') % self.karma_comment_convert) + raise AccessError(_('%d karma required to convert an answer to a comment.', self.karma_comment_convert)) # post the message question = self.parent_id @@ -806,9 +806,9 @@ class Post(models.Model): can_convert = self.env.user.karma >= karma_convert if not can_convert: if is_author and karma_own < karma_all: - raise AccessError(_('%d karma required to convert your comment to an answer.') % karma_own) + raise AccessError(_('%d karma required to convert your comment to an answer.', karma_own)) else: - raise AccessError(_('%d karma required to convert a comment to an answer.') % karma_all) + raise AccessError(_('%d karma required to convert a comment to an answer.', karma_all)) # check the message's author has not already an answer question = post.parent_id if post.parent_id else post @@ -846,7 +846,7 @@ class Post(models.Model): ) can_unlink = user.karma >= karma_unlink if not can_unlink: - raise AccessError(_('%d karma required to unlink a comment.') % karma_unlink) + raise AccessError(_('%d karma required to unlink a comment.', karma_unlink)) result.append(comment.unlink()) return result @@ -893,7 +893,7 @@ class Post(models.Model): self.ensure_one() if not self.can_comment: - raise AccessError(_('%d karma required to comment.') % self.karma_comment) + raise AccessError(_('%d karma required to comment.', self.karma_comment)) if not kwargs.get('record_name') and self.parent_id: kwargs['record_name'] = self.parent_id.name return super(Post, self).message_post(message_type=message_type, **kwargs) @@ -1005,9 +1005,9 @@ class Vote(models.Model): def _check_karma_rights(self, upvote=None): # karma check if upvote and not self.post_id.can_upvote: - raise AccessError(_('%d karma required to upvote.') % self.post_id.forum_id.karma_upvote) + raise AccessError(_('%d karma required to upvote.', self.post_id.forum_id.karma_upvote)) elif not upvote and not self.post_id.can_downvote: - raise AccessError(_('%d karma required to downvote.') % self.post_id.forum_id.karma_downvote) + raise AccessError(_('%d karma required to downvote.', self.post_id.forum_id.karma_downvote)) def _vote_update_karma(self, old_vote, new_vote): if self.post_id.parent_id: @@ -1042,5 +1042,5 @@ class Tags(models.Model): def create(self, vals): forum = self.env['forum.forum'].browse(vals.get('forum_id')) if self.env.user.karma < forum.karma_tag_create: - raise AccessError(_('%d karma required to create a new Tag.') % forum.karma_tag_create) + raise AccessError(_('%d karma required to create a new Tag.', forum.karma_tag_create)) return super(Tags, self.with_context(mail_create_nolog=True, mail_create_nosubscribe=True)).create(vals) diff --git a/addons/website_livechat/models/mail_channel.py b/addons/website_livechat/models/mail_channel.py index 804571bde42..86ab7aeb2c3 100644 --- a/addons/website_livechat/models/mail_channel.py +++ b/addons/website_livechat/models/mail_channel.py @@ -58,7 +58,7 @@ class MailChannel(models.Model): message = _("""%s has started a conversation with %s. The chat request has been canceled.""") % (name, operator or _('an operator')) else: - message = _('%s has left the conversation.') % name + message = _('%s has left the conversation.', name) return message diff --git a/addons/website_sale/models/product_image.py b/addons/website_sale/models/product_image.py index 5fe484a6a5c..eea9afb6967 100644 --- a/addons/website_sale/models/product_image.py +++ b/addons/website_sale/models/product_image.py @@ -40,7 +40,7 @@ class ProductImage(models.Model): def _check_valid_video_url(self): for image in self: if image.video_url and not image.embed_code: - raise ValidationError(_("Provided video URL for '%s' is not valid. Please enter a valid video URL.") % image.name) + raise ValidationError(_("Provided video URL for '%s' is not valid. Please enter a valid video URL.", image.name)) @api.model_create_multi def create(self, vals_list): diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py index 73e52810c61..c71a6201f9f 100644 --- a/addons/website_sale/models/sale_order.py +++ b/addons/website_sale/models/sale_order.py @@ -361,9 +361,9 @@ class SaleOrderLine(models.Model): def get_sale_order_line_multiline_description_sale(self, product): description = super(SaleOrderLine, self).get_sale_order_line_multiline_description_sale(product) if self.linked_line_id: - description += "\n" + _("Option for: %s") % self.linked_line_id.product_id.display_name + description += "\n" + _("Option for: %s", self.linked_line_id.product_id.display_name) if self.option_line_ids: - description += "\n" + '\n'.join([_("Option: %s") % option_line.product_id.display_name for option_line in self.option_line_ids]) + description += "\n" + '\n'.join([_("Option: %s", option_line.product_id.display_name) for option_line in self.option_line_ids]) return description @api.depends('product_id.display_name') diff --git a/addons/website_slides/controllers/main.py b/addons/website_slides/controllers/main.py index 1e896d3f754..68648b7ff9d 100644 --- a/addons/website_slides/controllers/main.py +++ b/addons/website_slides/controllers/main.py @@ -993,7 +993,7 @@ class WebsiteSlides(WebsiteProfile): return preview existing_slide = Slide.search([('channel_id', '=', int(data['channel_id'])), ('document_id', '=', document_id)], limit=1) if existing_slide: - preview['error'] = _('This video already exists in this channel on the following slide: %s') % existing_slide.name + preview['error'] = _('This video already exists in this channel on the following slide: %s', existing_slide.name) return preview values = Slide._parse_document_url(data['url'], only_preview_fields=True) if values.get('error'): @@ -1051,7 +1051,7 @@ class WebsiteSlides(WebsiteProfile): return {'error': e.args[0]} except Exception as e: _logger.error(e) - return {'error': _('Internal server error, please try again later or contact administrator.\nHere is the error message: %s') % e} + return {'error': _('Internal server error, please try again later or contact administrator.\nHere is the error message: %s', e)} # ensure correct ordering by re sequencing slides in front-end (backend should be ok thanks to list view) channel._resequence_slides(slide, force_category=category) diff --git a/addons/website_slides/models/slide_channel.py b/addons/website_slides/models/slide_channel.py index 36ba1c351f7..401878ee94b 100644 --- a/addons/website_slides/models/slide_channel.py +++ b/addons/website_slides/models/slide_channel.py @@ -446,7 +446,7 @@ class Channel(models.Model): action = self.env.ref('website_slides.slide_channel_partner_action').read()[0] action['domain'] = [('channel_id', 'in', self.ids)] if len(self) == 1: - action['display_name'] = _('Attendees of %s') % self.name + action['display_name'] = _('Attendees of %s', self.name) action['context'] = {'active_test': False, 'default_channel_id': self.id} if state: action['domain'] += [('completed', '=', state == 'completed')] diff --git a/addons/website_slides/models/slide_question.py b/addons/website_slides/models/slide_question.py index 96dc76c8c63..2ebe650043a 100644 --- a/addons/website_slides/models/slide_question.py +++ b/addons/website_slides/models/slide_question.py @@ -24,9 +24,9 @@ class SlideQuestion(models.Model): def _check_answers_integrity(self): for question in self: if len(question.answer_ids.filtered(lambda answer: answer.is_correct)) != 1: - raise ValidationError(_('Question "%s" must have 1 correct answer') % question.question) + raise ValidationError(_('Question "%s" must have 1 correct answer', question.question)) if len(question.answer_ids) < 2: - raise ValidationError(_('Question "%s" must have 1 correct answer and at least 1 invalid answer') % question.question) + raise ValidationError(_('Question "%s" must have 1 correct answer and at least 1 invalid answer', question.question)) @api.depends('slide_id') def _compute_statistics(self): diff --git a/addons/website_slides/models/slide_slide.py b/addons/website_slides/models/slide_slide.py index 6e600f90b44..45bf3a722be 100644 --- a/addons/website_slides/models/slide_slide.py +++ b/addons/website_slides/models/slide_slide.py @@ -795,7 +795,7 @@ class Slide(models.Model): if error == 'keyInvalid': return _('Your Google API key is invalid, please update it into your settings.\nSettings > Website > Features > API Key') - return _('Could not fetch data from url. Document or access right not available:\n%s') % error + return _('Could not fetch data from url. Document or access right not available:\n%s', error) @api.model def _parse_google_document(self, document_id, only_preview_fields): diff --git a/odoo/addons/base/models/ir_actions.py b/odoo/addons/base/models/ir_actions.py index fb0fa446b5d..478d2723679 100644 --- a/odoo/addons/base/models/ir_actions.py +++ b/odoo/addons/base/models/ir_actions.py @@ -147,9 +147,9 @@ class IrActionsActWindow(models.Model): def _check_model(self): for action in self: if action.res_model not in self.env: - raise ValidationError(_('Invalid model name %r in action definition.') % action.res_model) + raise ValidationError(_('Invalid model name %r in action definition.', action.res_model)) if action.binding_model_id and action.binding_model_id.model not in self.env: - raise ValidationError(_('Invalid model name %r in action definition.') % action.binding_model_id.model) + raise ValidationError(_('Invalid model name %r in action definition.', action.binding_model_id.model)) @api.depends('view_ids.view_mode', 'view_mode', 'view_id.type') def _compute_views(self): @@ -178,9 +178,9 @@ class IrActionsActWindow(models.Model): for rec in self: modes = rec.view_mode.split(',') if len(modes) != len(set(modes)): - raise ValidationError(_('The modes in view_mode must not be duplicated: %s') % modes) + raise ValidationError(_('The modes in view_mode must not be duplicated: %s', modes)) if ' ' in modes: - raise ValidationError(_('No spaces allowed in view_mode: %r') % modes) + raise ValidationError(_('No spaces allowed in view_mode: %r', modes)) @api.depends('res_model', 'search_view_id') def _compute_search_view(self): diff --git a/odoo/addons/base/models/ir_fields.py b/odoo/addons/base/models/ir_fields.py index dd8375902ea..116e4c9aaa6 100644 --- a/odoo/addons/base/models/ir_fields.py +++ b/odoo/addons/base/models/ir_fields.py @@ -207,7 +207,7 @@ class IrFieldsConverter(models.AbstractModel): ValueError, _(u"'%s' does not seem to be a valid date for field '%%(field)s'"), value, - {'moreinfo': _(u"Use the format '%s'") % u"2012-12-31"} + {'moreinfo': _(u"Use the format '%s'", u"2012-12-31")} ) @api.model @@ -239,7 +239,7 @@ class IrFieldsConverter(models.AbstractModel): ValueError, _(u"'%s' does not seem to be a valid datetime for field '%%(field)s'"), value, - {'moreinfo': _(u"Use the format '%s'") % u"2012-12-31 23:59:59"} + {'moreinfo': _(u"Use the format '%s'", u"2012-12-31 23:59:59")} ) input_tz = self._input_tz()# Apply input tz to the parsed naive datetime @@ -363,7 +363,7 @@ class IrFieldsConverter(models.AbstractModel): try: id, _name = RelatedModel.name_create(name=value) except (Exception, psycopg2.IntegrityError): - error_msg = _(u"Cannot create new '%s' records from their name alone. Please create those records manually and try importing again.") % RelatedModel._description + error_msg = _(u"Cannot create new '%s' records from their name alone. Please create those records manually and try importing again.", RelatedModel._description) else: raise self._format_import_error( Exception, diff --git a/odoo/addons/base/models/ir_filters.py b/odoo/addons/base/models/ir_filters.py index 5e080bf8b4a..0c948d6c0f2 100644 --- a/odoo/addons/base/models/ir_filters.py +++ b/odoo/addons/base/models/ir_filters.py @@ -35,7 +35,7 @@ class IrFilters(models.Model): def copy(self, default=None): self.ensure_one() - default = dict(default or {}, name=_('%s (copy)') % self.name) + default = dict(default or {}, name=_('%s (copy)', self.name)) return super(IrFilters, self).copy(default) def _get_eval_domain(self): diff --git a/odoo/addons/base/models/ir_mail_server.py b/odoo/addons/base/models/ir_mail_server.py index af83fee5805..9c72e54fdbb 100644 --- a/odoo/addons/base/models/ir_mail_server.py +++ b/odoo/addons/base/models/ir_mail_server.py @@ -115,7 +115,7 @@ class IrMailServer(models.Model): # let UserErrors (messages) bubble up raise e except Exception as e: - raise UserError(_("Connection Test Failed! Here is what we got instead:\n %s") % ustr(e)) + raise UserError(_("Connection Test Failed! Here is what we got instead:\n %s", ustr(e))) finally: try: if smtp: @@ -437,7 +437,7 @@ class IrMailServer(models.Model): raise except Exception as e: params = (ustr(smtp_server), e.__class__.__name__, ustr(e)) - msg = _("Mail delivery failed via SMTP server '%s'.\n%s: %s") % params + msg = _("Mail delivery failed via SMTP server '%s'.\n%s: %s", *params) _logger.info(msg) raise MailDeliveryException(_("Mail Delivery Failed"), msg) return message_id diff --git a/odoo/addons/base/models/ir_model.py b/odoo/addons/base/models/ir_model.py index 3cac7511402..37acf600cae 100644 --- a/odoo/addons/base/models/ir_model.py +++ b/odoo/addons/base/models/ir_model.py @@ -230,7 +230,7 @@ class IrModel(models.Model): order_fields = RE_ORDER_FIELDS.findall(model.order) for field in order_fields: if field not in stored_fields: - raise ValidationError(_("Unable to order by %s: fields used for ordering must be present on the model and stored.") % field) + raise ValidationError(_("Unable to order by %s: fields used for ordering must be present on the model and stored.", field)) _sql_constraints = [ ('obj_name_uniq', 'unique (model)', 'Each model must be unique!'), @@ -286,7 +286,7 @@ class IrModel(models.Model): if not self._context.get(MODULE_UNINSTALL_FLAG): for model in self: if model.state != 'manual': - raise UserError(_("Model '%s' contains module data and cannot be removed.") % model.name) + raise UserError(_("Model '%s' contains module data and cannot be removed.", model.name)) # prevent screwing up fields that depend on these models' fields model.field_id._prepare_update() @@ -649,7 +649,7 @@ class IrModelFields(models.Model): if self.relation not in self.env: return { 'warning': { - 'title': _('Model %s does not exist') % self.relation, + 'title': _('Model %s does not exist', self.relation), 'message': _('Please specify a valid model for the object relation'), } } @@ -676,7 +676,7 @@ class IrModelFields(models.Model): return return {'warning': { 'title': _("Warning"), - 'message': _("The table %r if used for other, possibly incompatible fields.") % self.relation_table, + 'message': _("The table %r if used for other, possibly incompatible fields.", self.relation_table), }} @api.onchange('required', 'ttype', 'on_delete') @@ -791,7 +791,7 @@ class IrModelFields(models.Model): raise UserError("\n".join([ _("Cannot rename/delete fields that are still present in views:"), _("Fields: %s") % ", ".join(str(f) for f in fields), - _("View: %s") % view.name, + _("View: %s", view.name), ])) else: # uninstall mode @@ -866,7 +866,7 @@ class IrModelFields(models.Model): if vals.get('state', 'manual') == 'manual': if vals.get('relation') and not self.env['ir.model'].search([('model', '=', vals['relation'])]): - raise UserError(_("Model %s does not exist!") % vals['relation']) + raise UserError(_("Model %s does not exist!", vals['relation'])) if vals.get('ttype') == 'one2many': if not self.search([('model_id', '=', vals['relation']), ('name', '=', vals['relation_field']), ('ttype', '=', 'many2one')]): diff --git a/odoo/addons/base/models/ir_module.py b/odoo/addons/base/models/ir_module.py index 4d6b22cc42c..fbc68d33a82 100644 --- a/odoo/addons/base/models/ir_module.py +++ b/odoo/addons/base/models/ir_module.py @@ -799,7 +799,7 @@ class Module(models.Model): content = response.content except Exception: _logger.exception('Failed to fetch module %s', module_name) - raise UserError(_('The `%s` module appears to be unavailable at the moment, please try again later.') % module_name) + raise UserError(_('The `%s` module appears to be unavailable at the moment, please try again later.', module_name)) else: zipfile.ZipFile(io.BytesIO(content)).extractall(tmp) assert os.path.isdir(os.path.join(tmp, module_name)) diff --git a/odoo/addons/base/models/ir_translation.py b/odoo/addons/base/models/ir_translation.py index 590c9fd64f3..47054542a82 100644 --- a/odoo/addons/base/models/ir_translation.py +++ b/odoo/addons/base/models/ir_translation.py @@ -547,7 +547,7 @@ class IrTranslation(models.Model): continue value2 = field.translate({val: src}.get, value1) if value2 != value0: - raise ValidationError(_("Translation is not valid:\n%s") % val) + raise ValidationError(_("Translation is not valid:\n%s", val)) @api.model_create_multi def create(self, vals_list): diff --git a/odoo/addons/base/models/ir_ui_view.py b/odoo/addons/base/models/ir_ui_view.py index c5b3749ffee..8d9925258c7 100644 --- a/odoo/addons/base/models/ir_ui_view.py +++ b/odoo/addons/base/models/ir_ui_view.py @@ -402,7 +402,7 @@ actual arch. if check == "Warning": _logger.warning(_('Invalid view %s definition in %s \n%s'), view_name, view.arch_fs, view.arch) except ValueError as e: - raise ValidationError(_("Error while validating view:\n\n%s") % tools.ustr(e)).with_traceback(e.__traceback__) from None + raise ValidationError(_("Error while validating view:\n\n%s", tools.ustr(e))).with_traceback(e.__traceback__) from None return True @@ -622,7 +622,7 @@ actual arch. error = _("View '%s' accessible only to groups %s ") % \ (self.key, ", ".join([g.name for g in self.groups_id])) else: - error = _("View '%s' is private") % self.key + error = _("View '%s' is private", self.key) raise AccessError(error) def handle_view_error(self, message, *, raise_exception=True, from_exception=None, from_traceback=None): @@ -1085,7 +1085,7 @@ actual arch. type_ = node.get('type') if special: if special not in ('cancel', 'save', 'add'): - self.handle_view_error(_("Invalid special '%s' in button") % special) + self.handle_view_error(_("Invalid special '%s' in button", special)) elif type_: if type_ == 'edit': # list_renderer, used in kanban view return @@ -1291,7 +1291,7 @@ actual arch. # further improvement: add all groups to name_manager in # order to batch check them at the end if not self.env['ir.model.data'].xmlid_to_res_id(group.strip(), raise_if_not_found=False): - msg = _("The group %r defined in view does not exist!") % group + msg = _("The group %r defined in view does not exist!", group) self.handle_view_error(msg, raise_exception=False) elif attr == 'group': @@ -1887,7 +1887,7 @@ class NameManager: for field_name in self.available_fields: if field_name not in self.fields_get: - message = _("Field `%s` does not exist") % field_name + message = _("Field `%s` does not exist", field_name) view.handle_view_error(message) for field, use in self.mandatory_fields.items(): diff --git a/odoo/addons/base/models/res_config.py b/odoo/addons/base/models/res_config.py index 1cba371e188..76737ee03aa 100644 --- a/odoo/addons/base/models/res_config.py +++ b/odoo/addons/base/models/res_config.py @@ -425,7 +425,7 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin) return { 'warning': { 'title': _('Warning!'), - 'message': _('Disabling this option will also uninstall the following modules \n%s') % message, + 'message': _('Disabling this option will also uninstall the following modules \n%s', message), } } return {} @@ -549,7 +549,7 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin) # The other methods that start with `get_default_` are deprecated for method in dir(self): if method.startswith('get_default_'): - _logger.warning(_('Methods that start with `get_default_` are deprecated. Override `get_values` instead(Method %s)') % method) + _logger.warning(_('Methods that start with `get_default_` are deprecated. Override `get_values` instead(Method %s)', method)) res.update(self.get_values()) return res @@ -607,7 +607,7 @@ class ResConfigSettings(models.TransientModel, ResConfigModuleInstallationMixin) # Methods that start with `set_` are now deprecated for method in dir(self): if method.startswith('set_') and method != 'set_values': - _logger.warning(_('Methods that start with `set_` are deprecated. Override `set_values` instead (Method %s)') % method) + _logger.warning(_('Methods that start with `set_` are deprecated. Override `set_values` instead (Method %s)', method)) def execute(self): self.ensure_one() diff --git a/odoo/addons/base/models/res_partner.py b/odoo/addons/base/models/res_partner.py index 6d3e86839b7..6e76e9b0295 100644 --- a/odoo/addons/base/models/res_partner.py +++ b/odoo/addons/base/models/res_partner.py @@ -318,7 +318,7 @@ class Partner(models.Model): def copy(self, default=None): self.ensure_one() chosen_name = default.get('name') if default else '' - new_name = chosen_name or _('%s (copy)') % self.name + new_name = chosen_name or _('%s (copy)', self.name) default = dict(default or {}, name=new_name) return super(Partner, self).copy(default) diff --git a/odoo/addons/base/models/res_users.py b/odoo/addons/base/models/res_users.py index f0941d134fb..d8d00b125c3 100644 --- a/odoo/addons/base/models/res_users.py +++ b/odoo/addons/base/models/res_users.py @@ -166,7 +166,7 @@ class Groups(models.Model): def copy(self, default=None): self.ensure_one() chosen_name = default.get('name') if default else '' - default_name = chosen_name or _('%s (copy)') % self.name + default_name = chosen_name or _('%s (copy)', self.name) default = dict(default or {}, name=default_name) return super(Groups, self).copy(default) @@ -585,9 +585,9 @@ class Users(models.Model): self.ensure_one() default = dict(default or {}) if ('name' not in default) and ('partner_id' not in default): - default['name'] = _("%s (copy)") % self.name + default['name'] = _("%s (copy)", self.name) if 'login' not in default: - default['login'] = _("%s (copy)") % self.login + default['login'] = _("%s (copy)", self.login) return super(Users, self).copy(default) @api.model diff --git a/odoo/addons/base/wizard/base_update_translations.py b/odoo/addons/base/wizard/base_update_translations.py index c05d85b7517..0ac9b301d7b 100644 --- a/odoo/addons/base/wizard/base_update_translations.py +++ b/odoo/addons/base/wizard/base_update_translations.py @@ -22,7 +22,7 @@ class BaseUpdateTranslations(models.TransientModel): def _get_lang_name(self, lang_code): lang = self.env['res.lang']._lang_get(lang_code) if not lang: - raise UserError(_('No language with code "%s" exists') % lang_code) + raise UserError(_('No language with code "%s" exists', lang_code)) return lang.name def act_update(self): diff --git a/odoo/models.py b/odoo/models.py index 397d13f2691..1ab766d9b07 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -991,7 +991,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): # broken transaction, exit and hope the source error was already logged if not any(message['type'] == 'error' for message in messages): info = data_list[0]['info'] - messages.append(dict(info, type='error', message=_(u"Unknown database error: '%s'") % e)) + messages.append(dict(info, type='error', message=_(u"Unknown database error: '%s'", e))) return except Exception: pass @@ -1188,7 +1188,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): type='error', record=stream.index, field='.id', - message=_(u"Unknown database identifier '%s'") % dbid)) + message=_(u"Unknown database identifier '%s'", dbid))) dbid = False converted = convert(record, functools.partial(_log, extras, stream.index)) @@ -1442,7 +1442,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): self._fields, 'date_stop'): if not set_first_of(["date_delay", "planned_hours", "x_date_delay", "x_planned_hours"], self._fields, 'date_delay'): - raise UserError(_("Insufficient fields to generate a Calendar View for %s, missing a date_stop or a date_delay") % self._name) + raise UserError(_("Insufficient fields to generate a Calendar View for %s, missing a date_stop or a date_delay", self._name)) return view @@ -1521,7 +1521,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): result['type'] = view_type result['name'] = 'default' except AttributeError: - raise UserError(_("No default view of type '%s' could be found !") % view_type) + raise UserError(_("No default view of type '%s' could be found !", view_type)) return result @api.model @@ -2221,7 +2221,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): match = regex_field_agg.match(fspec) if not match: - raise UserError(_("Invalid field specification %r.") % fspec) + raise UserError(_("Invalid field specification %r.", fspec)) name, func, fname = match.groups() if func: @@ -2231,9 +2231,9 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): if not field: raise ValueError("Invalid field %r on model %r" % (fname, self._name)) if not (field.base_field.store and field.base_field.column_type): - raise UserError(_("Cannot aggregate field %r.") % fname) + raise UserError(_("Cannot aggregate field %r.", fname)) if func not in VALID_AGGREGATE_FUNCTIONS: - raise UserError(_("Invalid aggregation function %r.") % func) + raise UserError(_("Invalid aggregation function %r.", func)) else: # we have 'name', retrieve the aggregator on the field field = self._fields.get(name) @@ -2249,7 +2249,7 @@ class BaseModel(MetaModel('DummyModel', (object,), {'_register': False})): if fname in groupby_fields: continue if name in aggregated_fields: - raise UserError(_("Output name %r is used twice.") % name) + raise UserError(_("Output name %r is used twice.", name)) aggregated_fields.append(name) expr = self._inherits_join_calc(self._table, fname, query) @@ -3657,7 +3657,7 @@ Record ids: %(records)s cr.execute(query, params + [sub_ids]) if cr.rowcount != len(sub_ids): raise MissingError( - _('One of the records you are trying to modify has already been deleted (Document type: %s).') % self._description + _('One of the records you are trying to modify has already been deleted (Document type: %s).', self._description) + '\n\n({} {}, {} {})'.format(_('Records:'), sub_ids[:6], _('User:'), self._uid) ) diff --git a/odoo/service/model.py b/odoo/service/model.py index 6cc05476195..3165c6c0ead 100644 --- a/odoo/service/model.py +++ b/odoo/service/model.py @@ -155,7 +155,7 @@ def execute_cr(cr, uid, obj, method, *args, **kw): odoo.api.Environment.reset() # clean cache etc if we retry the same transaction recs = odoo.api.Environment(cr, uid, {}).get(obj) if recs is None: - raise UserError(_("Object %s doesn't exist") % obj) + raise UserError(_("Object %s doesn't exist", obj)) result = odoo.api.call_kw(recs, method, args, kw) # force evaluation of lazy values before the cursor is closed, as it would # error afterwards if the lazy isn't already evaluated (and cached) diff --git a/odoo/tools/image.py b/odoo/tools/image.py index 590bdf1bbfa..3cde458e2db 100644 --- a/odoo/tools/image.py +++ b/odoo/tools/image.py @@ -86,7 +86,7 @@ class ImageProcess(): w, h = self.image.size if verify_resolution and w * h > IMAGE_MAX_RESOLUTION: - raise ValueError(_("Image size excessive, uploaded images must be smaller than %s million pixels.") % str(IMAGE_MAX_RESOLUTION / 10e6)) + raise ValueError(_("Image size excessive, uploaded images must be smaller than %s million pixels.", str(IMAGE_MAX_RESOLUTION / 10e6))) def image_base64(self, quality=0, output_format=''): """Return the base64 encoded image resulting of all the image processing diff --git a/odoo/tools/template_inheritance.py b/odoo/tools/template_inheritance.py index 33cedf7c12d..20a7d2aeee7 100644 --- a/odoo/tools/template_inheritance.py +++ b/odoo/tools/template_inheritance.py @@ -231,7 +231,7 @@ def apply_inheritance_specs(source, specs_tree, inherit_branding=False, pre_loca ]) tag = "<%s%s>" % (spec.tag, attrs) raise ValueError( - _("Element '%s' cannot be located in parent view") % tag + _("Element '%s' cannot be located in parent view", tag) ) return source diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py index 745f2cd8cb0..e98ad4e9b7a 100644 --- a/odoo/tools/translate.py +++ b/odoo/tools/translate.py @@ -555,7 +555,7 @@ def TranslationFileReader(source, fileformat='po'): if fileformat == 'po': return PoFileReader(source) _logger.info('Bad file format: %s', fileformat) - raise Exception(_('Bad file format: %s') % fileformat) + raise Exception(_('Bad file format: %s', fileformat)) class CSVFileReader: def __init__(self, source): From 4a92a183e8631512a8b018eedaf9bf4956ca85e9 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Mon, 15 Jun 2020 15:23:02 +0200 Subject: [PATCH 3/4] [IMP] *: better construction of translatable content Do not construct a message by putting together translatable bits, it makes it hard to translate (out of context) and may not work for some languages constructing the sentences differently. Do not try to save bits, be explict. Clarify constructions for long oneliners. --- .../account/models/account_bank_statement.py | 26 ++++++++++--------- addons/l10n_ar/models/account_move.py | 10 +++---- .../stock_valuation_layer_revaluation.py | 14 +++++++--- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index 4bddca46a3a..85064116369 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -294,24 +294,26 @@ class AccountBankStatement(models.Model): for stmt in self: if not stmt.currency_id.is_zero(stmt.difference): if stmt.journal_type == 'cash': - if stmt.difference < 0.0: - account = stmt.journal_id.loss_account_id - name = _('Loss') - else: - # statement.difference > 0.0 - account = stmt.journal_id.profit_account_id - name = _('Profit') - if not account: - raise UserError(_('Please go on the %s journal and define a %s Account. This account will be used to record cash difference.') % (stmt.journal_id.name, name)) - st_line_vals = { 'statement_id': stmt.id, 'journal_id': stmt.journal_id.id, 'amount': stmt.difference, - 'payment_ref': _("Cash difference observed during the counting (%s)", name), 'date': stmt.date, } - self.env['account.bank.statement.line'].with_context(counterpart_account_id=account.id).create(st_line_vals) + + if stmt.difference < 0.0: + if not stmt.journal_id.loss_account_id: + raise UserError(_('Please go on the %s journal and define a Loss Account. This account will be used to record cash difference.', stmt.journal_id.name)) + + st_line_vals['payment_ref'] = _("Cash difference observed during the counting (Loss)") + self.env['account.bank.statement.line'].with_context(counterpart_account_id=stmt.journal_id.loss_account_id).create(st_line_vals) + else: + # statement.difference > 0.0 + if not stmt.journal_id.profit_account_id: + raise UserError(_('Please go on the %s journal and define a Profit Account. This account will be used to record cash difference.', stmt.journal_id.name)) + + st_line_vals['payment_ref'] = _("Cash difference observed during the counting (Profit)") + self.env['account.bank.statement.line'].with_context(counterpart_account_id=stmt.journal_id.profit_account_id).create(st_line_vals) else: balance_end_real = formatLang(self.env, stmt.balance_end_real, currency_obj=stmt.currency_id) balance_end = formatLang(self.env, stmt.balance_end, currency_obj=stmt.currency_id) diff --git a/addons/l10n_ar/models/account_move.py b/addons/l10n_ar/models/account_move.py index 5184de1cb8d..afb6ac9ee56 100644 --- a/addons/l10n_ar/models/account_move.py +++ b/addons/l10n_ar/models/account_move.py @@ -129,22 +129,20 @@ class AccountMove(models.Model): res_code = rec.partner_id.l10n_ar_afip_responsibility_type_id.code domain = [('company_id', '=', rec.company_id.id), ('l10n_latam_use_documents', '=', True), ('type', '=', 'sale')] journal = self.env['account.journal'] - partner_type = journal_type = False + msg = False if res_code in ['9', '10'] and rec.journal_id.l10n_ar_afip_pos_system not in expo_journals: # if partner is foregin and journal is not of expo, we try to change to expo journal journal = journal.search(domain + [('l10n_ar_afip_pos_system', 'in', expo_journals)], limit=1) - partner_type, journal_type = (_('foreign partner'), _('exportation')) + msg = _('You are trying to create an invoice for foreign partner but you don\'t have an exportation journal') elif res_code not in ['9', '10'] and rec.journal_id.l10n_ar_afip_pos_system in expo_journals: # if partner is NOT foregin and journal is for expo, we try to change to local journal journal = journal.search(domain + [('l10n_ar_afip_pos_system', 'not in', expo_journals)], limit=1) - partner_type, journal_type = (_('domestic partner'), _('domestic market')) + msg = _('You are trying to create an invoice for domestic partner but you don\'t have an domestic market journal') if journal: rec.journal_id = journal.id - elif partner_type and journal_type: + elif msg: # Throw an error to user in order to proper configure the journal for the type of operation action = self.env.ref('account.action_account_journal_form') - msg = _('You are trying to create an invoice for %s but you dont have an %s journal') % ( - partner_type, journal_type) raise RedirectWarning(msg, action.id, _('Go to Journals')) def post(self): diff --git a/addons/stock_account/wizard/stock_valuation_layer_revaluation.py b/addons/stock_account/wizard/stock_valuation_layer_revaluation.py index 77f22d27cc8..50309cf1fd9 100644 --- a/addons/stock_account/wizard/stock_valuation_layer_revaluation.py +++ b/addons/stock_account/wizard/stock_valuation_layer_revaluation.py @@ -74,12 +74,20 @@ class StockValuationLayerRevaluation(models.TransientModel): ]) # Create a manual stock valuation layer + if self.reason: + description = _("Manual Stock Valuation: %s.", self.reason) + else: + description = _("Manual Stock Valuation: No Reason Given.") + if product_id.categ_id.property_cost_method == 'average': + description += _( + " Product cost updated from %(previous)s to %(new_cost)s.", + previous=product_id.standard_price, + new_cost=product_id.standard_price + self.added_value / self.current_quantity_svl + ) revaluation_svl_vals = { 'company_id': self.company_id.id, 'product_id': product_id.id, - 'description': _("Manual Stock Valuation: %s.%s") % ( - self.reason or _("No Reason Given"), - product_id.categ_id.property_cost_method == 'average' and (_(" Product cost updated from %s to %s.") % (product_id.standard_price, product_id.standard_price + self.added_value / self.current_quantity_svl)) or ''), + 'description': description, 'value': self.added_value, 'quantity': 0, } From 80e97e98ce76067db1079af8feeef667cfadf837 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Mon, 15 Jun 2020 15:16:16 +0200 Subject: [PATCH 4/4] [IMP] *: use named placeholders in translated message When more than one parameter is present in a message, it helps the translation to use named placeholder. This way, the order can be changed. It also helps the comprehension of the message. --- addons/account/models/account_account.py | 6 +- .../account/models/account_bank_statement.py | 7 ++- addons/account/models/account_journal.py | 6 +- addons/account/models/account_move.py | 13 +++-- .../account/wizard/account_move_reversal.py | 4 +- addons/base_vat/models/res_partner.py | 14 ++++- addons/calendar/models/calendar_event.py | 38 ++++++++----- addons/calendar/models/calendar_recurrence.py | 4 +- addons/calendar_sms/models/calendar.py | 2 +- addons/crm/models/crm_lead.py | 6 +- addons/delivery/models/stock_picking.py | 8 ++- addons/gamification/models/challenge.py | 6 +- addons/gamification/models/goal.py | 14 +++-- addons/google_calendar/models/calendar.py | 18 +++++- .../hr_expense_sheet_register_payment.py | 9 ++- addons/hr_fleet/controllers/main.py | 7 ++- addons/hr_holidays/models/hr_leave.py | 55 ++++++++++++------- .../hr_holidays/models/hr_leave_allocation.py | 24 +++++--- addons/hr_skills/models/hr_skills.py | 4 +- addons/hr_timesheet/models/project.py | 7 ++- addons/mail/models/mail_activity.py | 4 +- addons/mail/models/mail_channel.py | 2 +- addons/mail/models/mail_thread.py | 14 ++++- addons/mail/wizard/invite.py | 2 +- addons/mrp/models/mrp_bom.py | 10 ++-- addons/mrp/models/mrp_production.py | 8 ++- addons/mrp/models/mrp_workorder.py | 14 ++--- addons/mrp/wizard/change_production_qty.py | 6 +- addons/payment_stripe/models/payment.py | 2 +- .../tools/phone_validation.py | 2 +- addons/point_of_sale/models/pos_payment.py | 2 +- addons/portal/models/mail_thread.py | 6 +- addons/product/models/product_pricelist.py | 2 +- addons/purchase/models/purchase.py | 14 ++++- addons/sale/models/sale.py | 7 ++- addons/sale_coupon/models/coupon.py | 6 +- addons/sale_coupon/models/coupon_program.py | 6 +- addons/sale_management/models/sale_order.py | 7 ++- .../models/sale_order_template.py | 6 +- addons/sale_stock/models/sale_order.py | 8 ++- addons/stock/models/stock_warehouse.py | 2 +- addons/stock_account/models/product.py | 16 +++++- .../stock_valuation_layer_revaluation.py | 16 ++++-- addons/website_sale_stock/controllers/main.py | 6 +- 44 files changed, 309 insertions(+), 111 deletions(-) diff --git a/addons/account/models/account_account.py b/addons/account/models/account_account.py index 980e58a8ef7..1622338baf2 100644 --- a/addons/account/models/account_account.py +++ b/addons/account/models/account_account.py @@ -147,7 +147,11 @@ class AccountAccount(models.Model): if res: account = self.env['account.account'].browse(res[0]) journal = self.env['account.journal'].browse(res[1]) - raise ValidationError(_("The foreign currency set on the journal '%s' and the account '%s' must be the same.") % (journal.display_name, account.display_name)) + raise ValidationError(_( + "The foreign currency set on the journal '%(journal)s' and the account '%(account)s' must be the same.", + journal=journal.display_name, + account=account.display_name + )) @api.constrains('company_id') def _check_company_consistency(self): diff --git a/addons/account/models/account_bank_statement.py b/addons/account/models/account_bank_statement.py index 85064116369..502d91c563b 100644 --- a/addons/account/models/account_bank_statement.py +++ b/addons/account/models/account_bank_statement.py @@ -317,8 +317,11 @@ class AccountBankStatement(models.Model): else: balance_end_real = formatLang(self.env, stmt.balance_end_real, currency_obj=stmt.currency_id) balance_end = formatLang(self.env, stmt.balance_end, currency_obj=stmt.currency_id) - raise UserError(_('The ending balance is incorrect !\nThe expected balance (%s) is different from the computed one. (%s)') - % (balance_end_real, balance_end)) + raise UserError(_( + 'The ending balance is incorrect !\nThe expected balance (%(real_balance)s) is different from the computed one (%(computed_balance)s).', + real_balance=balance_end_real, + computed_balance=balance_end + )) return True def unlink(self): diff --git a/addons/account/models/account_journal.py b/addons/account/models/account_journal.py index d8da1deea14..f424d2fb738 100644 --- a/addons/account/models/account_journal.py +++ b/addons/account/models/account_journal.py @@ -270,7 +270,11 @@ class AccountJournal(models.Model): ''', [tuple(accounts.ids)]) res = self._cr.fetchone() if res: - raise ValidationError(_("The account %s can't be shared between multiple journals: %s") % (res[0], ', '.join(res[1]))) + raise ValidationError(_( + "The account %(account_name)s can't be shared between multiple journals: %(journals)s", + account_name=res[0], + journals=', '.join(res[1]) + )) @api.constrains('type', 'default_credit_account_id', 'default_debit_account_id') def _check_type_default_credit_account_id_type(self): diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index aee49964d71..9263f9519f4 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -65,9 +65,10 @@ class AccountMove(models.Model): if not journal: company = self.env['res.company'].browse(company_id) - error_msg = _("No journal could be found in company %s for any of those types: %s") % ( - company.display_name, - ', '.join(journal_types), + error_msg = _( + "No journal could be found in company %(company_name)s for any of those types: %(journal_types)s", + company_name=company.display_name, + journal_types=', '.join(journal_types), ) raise UserError(error_msg) @@ -91,7 +92,11 @@ class AccountMove(models.Model): journal = self.env['account.journal'].browse(self._context['default_journal_id']) if move_type != 'entry' and journal.type not in journal_types: - raise UserError(_("Cannot create an invoice of type %s with a journal having %s as type.") % (move_type, journal.type)) + raise UserError(_( + "Cannot create an invoice of type %(move_type)s with a journal having %(journal_type)s as type.", + move_type=move_type, + journal_type=journal.type, + )) else: journal = self._search_default_journal(journal_types) diff --git a/addons/account/wizard/account_move_reversal.py b/addons/account/wizard/account_move_reversal.py index e7c51be9aaf..f83fc3a950c 100644 --- a/addons/account/wizard/account_move_reversal.py +++ b/addons/account/wizard/account_move_reversal.py @@ -57,7 +57,9 @@ class AccountMoveReversal(models.TransientModel): def _prepare_default_reversal(self, move): reverse_date = self.date if self.date_mode == 'custom' else move.date return { - 'ref': _('Reversal of: %s, %s') % (move.name, self.reason) if self.reason else _('Reversal of: %s') % (move.name), + 'ref': _('Reversal of: %(move_name)s, %(reason)s', move_name=move.name, reason=self.reason) + if self.reason + else _('Reversal of: %s', move.name), 'date': reverse_date, 'invoice_date': move.is_invoice(include_receipts=True) and (self.date or move.date) or False, 'journal_id': self.journal_id and self.journal_id.id or move.journal_id.id, diff --git a/addons/base_vat/models/res_partner.py b/addons/base_vat/models/res_partner.py index 0b54f0e0286..17f379012bc 100644 --- a/addons/base_vat/models/res_partner.py +++ b/addons/base_vat/models/res_partner.py @@ -162,8 +162,18 @@ class ResPartner(models.Model): else: company = self.env.company if company.vat_check_vies: - return '\n' + _('The VAT number [%s] for partner [%s] either failed the VIES VAT validation check or did not respect the expected format %s.') % (self.vat, self.name, vat_no) - return '\n' + _('The VAT number [%s] for partner [%s] does not seem to be valid. \nNote: the expected format is %s') % (self.vat, self.name, vat_no) + return '\n' + _( + 'The VAT number [%(vat)s] for partner [%(name)s] either failed the VIES VAT validation check or did not respect the expected format %(format)s.', + vat=self.vat, + name=self.name, + format=vat_no + ) + return '\n' + _( + 'The VAT number [%(vat)s] for partner [%(name)s] does not seem to be valid. \nNote: the expected format is %(format)s', + vat=self.vat, + name=self.name, + format=vat_no + ) __check_vat_ch_re1 = re.compile(r'(MWST|TVA|IVA)[0-9]{6}$') __check_vat_ch_re2 = re.compile(r'E([0-9]{9}|-[0-9]{3}\.[0-9]{3}\.[0-9]{3})(MWST|TVA|IVA)$') diff --git a/addons/calendar/models/calendar_event.py b/addons/calendar/models/calendar_event.py index df6347a678c..68740cae04d 100644 --- a/addons/calendar/models/calendar_event.py +++ b/addons/calendar/models/calendar_event.py @@ -135,25 +135,27 @@ class Meeting(models.Model): time_str = to_text(date.strftime(format_time)) if zallday: - display_time = _("AllDay , %s") % (date_str) + display_time = _("AllDay , %(day)s", day=date_str) elif zduration < 24: duration = date + timedelta(minutes=round(zduration*60)) duration_time = to_text(duration.strftime(format_time)) - display_time = _(u"%s at (%s To %s) (%s)") % ( - date_str, - time_str, - duration_time, - timezone, + display_time = _( + u"%(day)s at (%(start)s To %(end)s) (%(timezone)s)", + day=date_str, + start=time_str, + end=duration_time, + timezone=timezone, ) else: dd_date = to_text(date_deadline.strftime(format_date)) dd_time = to_text(date_deadline.strftime(format_time)) - display_time = _(u"%s at %s To\n %s at %s (%s)") % ( - date_str, - time_str, - dd_date, - dd_time, - timezone, + display_time = _( + u"%(date_start)s at %(time_start)s To\n %(date_end)s at %(time_end)s (%(timezone)s)", + date_start=date_str, + time_start=time_str, + date_end=dd_date, + time_end=dd_time, + timezone=timezone, ) return display_time @@ -356,12 +358,20 @@ class Meeting(models.Model): if meeting.start and meeting.stop and meeting.stop < meeting.start: raise ValidationError( _('The ending date and time cannot be earlier than the starting date and time.') + '\n' + - _("Meeting '%s' starts '%s' and ends '%s'") % (meeting.name, meeting.start, meeting.stop) + _("Meeting '%(name)s' starts '%(start_datetime)s' and ends '%(end_datetime)s'", + name=meeting.name, + start_datetime=meeting.start, + end_datetime=meeting.stop + ) ) if meeting.start_date and meeting.stop_date and meeting.stop_date < meeting.start_date: raise ValidationError( _('The ending date cannot be earlier than the starting date.') + '\n' + - _("Meeting '%s' starts '%s' and ends '%s'") % (meeting.name, meeting.start_date, meeting.stop_date) + _("Meeting '%(name)s' starts '%(start_datetime)s' and ends '%(end_datetime)s'", + name=meeting.name, + start_datetime=meeting.start, + end_datetime=meeting.stop + ) ) #################################################### diff --git a/addons/calendar/models/calendar_recurrence.py b/addons/calendar/models/calendar_recurrence.py index 73c671c611a..9dae012b6d4 100644 --- a/addons/calendar/models/calendar_recurrence.py +++ b/addons/calendar/models/calendar_recurrence.py @@ -124,7 +124,7 @@ class RecurrenceRule(models.Model): def _compute_name(self): for recurrence in self: period = dict(RRULE_TYPE_SELECTION)[recurrence.rrule_type] - every = _("Every %s %s, ") % (recurrence.interval, period) + every = _("Every %(count)s %(period)s, ", count=recurrence.interval, period=period) if recurrence.end_type == 'count': end = _("for %s events", recurrence.count) @@ -140,7 +140,7 @@ class RecurrenceRule(models.Model): elif recurrence.rrule_type == 'monthly': if recurrence.month_by == 'day': weekday_label = dict(BYDAY_SELECTION)[recurrence.byday] - on = _("on the %(position)s %(weekday)s, ") % {'position': recurrence.byday, 'weekday': weekday_label} + on = _("on the %(position)s %(weekday)s, ", position=recurrence.byday, weekday=weekday_label) else: on = _("day %s, ", recurrence.day) else: diff --git a/addons/calendar_sms/models/calendar.py b/addons/calendar_sms/models/calendar.py index 6b08ddcbe9d..b47a28b0916 100644 --- a/addons/calendar_sms/models/calendar.py +++ b/addons/calendar_sms/models/calendar.py @@ -22,7 +22,7 @@ class CalendarEvent(models.Model): for event in self: event._message_sms_with_template( template_xmlid='calendar_sms.sms_template_data_calendar_reminder', - template_fallback=_("Event reminder: %s, %s.") % (event.name, event.display_time), + template_fallback=_("Event reminder: %(name)s, %(time)s.", name=event.name, time=event.display_time), partner_ids=self._sms_get_default_partners().ids, put_in_queue=False ) diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index 2c7b2b541f7..52f669bb9ba 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -894,7 +894,11 @@ class Lead(models.Model): for message in opportunity.message_ids: message.write({ 'res_id': self.id, - 'subject': _("From %s : %s") % (opportunity.name, message.subject) + 'subject': _( + "From %(source_name)s : %(source_subject)s", + source_name=opportunity.name, + source_subject=message.subject + ) }) return True diff --git a/addons/delivery/models/stock_picking.py b/addons/delivery/models/stock_picking.py index f7f10919a7c..d4c90d77d55 100644 --- a/addons/delivery/models/stock_picking.py +++ b/addons/delivery/models/stock_picking.py @@ -172,7 +172,13 @@ class StockPicking(models.Model): if res['tracking_number']: self.carrier_tracking_ref = res['tracking_number'] order_currency = self.sale_id.currency_id or self.company_id.currency_id - msg = _("Shipment sent to carrier %s for shipping with tracking number %s
Cost: %.2f %s") % (self.carrier_id.name, self.carrier_tracking_ref, self.carrier_price, order_currency.name) + msg = _( + "Shipment sent to carrier %(carrier_name)s for shipping with tracking number %(ref)s
Cost: %(price).2f %(currency)s", + carrier_name=self.carrier_id.name, + ref=self.carrier_tracking_ref, + price=self.carrier_price, + currency=order_currency.name + ) self.message_post(body=msg) self._add_delivery_cost_to_so() diff --git a/addons/gamification/models/challenge.py b/addons/gamification/models/challenge.py index 72fcf48ebad..93efdef0d9b 100644 --- a/addons/gamification/models/challenge.py +++ b/addons/gamification/models/challenge.py @@ -656,7 +656,11 @@ class Challenge(models.Model): if rewarded_users: user_names = rewarded_users.name_get() - message_body += _("
Reward (badge %s) for every succeeding user was sent to %s.") % (challenge.reward_id.name, ", ".join(name for (user_id, name) in user_names)) + message_body += _( + "
Reward (badge %(badge_name)s) for every succeeding user was sent to %(users)s.", + badge_name=challenge.reward_id.name, + users=", ".join(name for (user_id, name) in user_names) + ) else: message_body += _("
Nobody has succeeded to reach every goal, no badge is rewarded for this challenge.") diff --git a/addons/gamification/models/goal.py b/addons/gamification/models/goal.py index 2236a5baa45..e26ac0b37ff 100644 --- a/addons/gamification/models/goal.py +++ b/addons/gamification/models/goal.py @@ -111,11 +111,17 @@ class GoalDefinition(models.Model): Model = self.env[definition.model_id.model] field = Model._fields.get(definition.field_id.name) if not (field and field.store): - raise exceptions.UserError( - _("The model configuration for the definition %s seems incorrect, please check it.\n\n%s not stored") % (definition.name, definition.field_id.name)) + raise exceptions.UserError(_( + "The model configuration for the definition %(name)s seems incorrect, please check it.\n\n%(field_name)s not stored", + name=definition.name, + field_name=definition.field_id.name + )) except KeyError as e: - raise exceptions.UserError( - _("The model configuration for the definition %s seems incorrect, please check it.\n\n%s not found") % (definition.name, e)) + raise exceptions.UserError(_( + "The model configuration for the definition %(name)s seems incorrect, please check it.\n\n%(error)s not found", + name=definition.name, + error=e + )) @api.model def create(self, vals): diff --git a/addons/google_calendar/models/calendar.py b/addons/google_calendar/models/calendar.py index 57d9f63a440..765473509ba 100644 --- a/addons/google_calendar/models/calendar.py +++ b/addons/google_calendar/models/calendar.py @@ -131,15 +131,27 @@ class Meeting(models.Model): if minutes % (60*24) == 0: interval = 'days' duration = minutes / 60 / 24 - name = _("%s - %s Days") % (alarm_type_label, duration) + name = _( + "%(reminder_type)s - %(duration)s Days", + reminder_type=alarm_type_label, + duration=duration, + ) elif minutes % 60 == 0: interval = 'hours' duration = minutes / 60 - name = _("%s - %s Hours") % (alarm_type_label, duration) + name = _( + "%(reminder_type)s - %(duration)s Hours", + reminder_type=alarm_type_label, + duration=duration, + ) else: interval = 'minutes' duration = minutes - name = _("%s - %s Minutes") % (alarm_type_label, duration) + name = _( + "%(reminder_type)s - %(duration)s Minutes", + reminder_type=alarm_type_label, + duration=duration, + ) commands += [(0, 0, {'duration': duration, 'interval': interval, 'name': name, 'alarm_type': alarm_type})] return commands diff --git a/addons/hr_expense/wizard/hr_expense_sheet_register_payment.py b/addons/hr_expense/wizard/hr_expense_sheet_register_payment.py index ff586f68491..37e1f0bf68d 100644 --- a/addons/hr_expense/wizard/hr_expense_sheet_register_payment.py +++ b/addons/hr_expense/wizard/hr_expense_sheet_register_payment.py @@ -113,7 +113,14 @@ class HrExpenseSheetRegisterPaymentWizard(models.TransientModel): payment.action_post() # Log the payment in the chatter - body = (_("A payment of %s %s with the reference %s related to your expense %s has been made.") % (payment.amount, payment.currency_id.symbol, url_encode({'model': 'account.payment', 'res_id': payment.id}), payment.name, expense_sheet.name)) + body = _( + "A payment of %(amount)s %(currency)s with the reference %(payment_name)s related to your expense %(expense_name)s has been made.", + amount=payment.amount, + currency=payment.currency_id.symbol, + url=url_encode({'model': 'account.payment', 'res_id': payment.id}), + payment_name=payment.name, + expense_name=expense_sheet.name + ) expense_sheet.message_post(body=body) # Reconcile the payment and the expense, i.e. lookup on the payable account move lines diff --git a/addons/hr_fleet/controllers/main.py b/addons/hr_fleet/controllers/main.py index 175139e04da..80748991fda 100644 --- a/addons/hr_fleet/controllers/main.py +++ b/addons/hr_fleet/controllers/main.py @@ -52,7 +52,12 @@ class HrFleet(Controller): date_start = car_line_doc.date_start date_end = car_line_doc.date_end or '...' - text_to_print = _("%s (driven from: %s to %s)") % (car_name, date_start, date_end) + text_to_print = _( + "%(car_name)s (driven from: %(date_start)s to %(date_end)s)", + car_name=car_name, + date_start=date_start, + date_end=date_end + ) can.drawCentredString(width / 2, height - normal_font_size, text_to_print) can.save() header_pdf = PdfFileReader(header, overwriteWarnings=False) diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py index 5e88edf967e..9da4c75eb71 100644 --- a/addons/hr_holidays/models/hr_leave.py +++ b/addons/hr_holidays/models/hr_leave.py @@ -621,11 +621,11 @@ class HolidaysRequest(models.Model): if leave.leave_type_request_unit == 'hour': res.append(( leave.id, - _("%s on %s: %.2f hours on %s") % ( - target, - leave.holiday_status_id.name, - leave.number_of_hours_display, - fields.Date.to_string(leave.date_from), + _("%(person)s on %(leave_type)s: %(duration).2f hours on %(date)s", + person=target, + leave_type=leave.holiday_status_id.name, + duration=leave.number_of_hours_display, + date=fields.Date.to_string(leave.date_from), ) )) else: @@ -634,11 +634,11 @@ class HolidaysRequest(models.Model): display_date += ' ⇨ %s' % fields.Date.to_string(leave.date_to) res.append(( leave.id, - _("%s on %s: %.2f days (%s)") % ( - target, - leave.holiday_status_id.name, - leave.number_of_days, - display_date, + _("%(person)s on %(leave_type)s: %(duration).2f days (%(start)s)", + person=target, + leave_type=leave.holiday_status_id.name, + duration=leave.number_of_days, + start=display_date, ) )) return res @@ -657,19 +657,26 @@ class HolidaysRequest(models.Model): dto = leave.date_to if leave.holiday_status_id.validity_start and leave.holiday_status_id.validity_stop: if dfrom and dto and (dfrom.date() < vstart or dto.date() > vstop): - raise ValidationError( - _('%s are only valid between %s and %s') % ( - leave.holiday_status_id.display_name, leave.holiday_status_id.validity_start, leave.holiday_status_id.validity_stop)) + raise ValidationError(_( + '%(leave_type)s are only valid between %(start)s and %(end)s', + leave_type=leave.holiday_status_id.display_name, + start=leave.holiday_status_id.validity_start, + end=leave.holiday_status_id.validity_stop + )) elif leave.holiday_status_id.validity_start: if dfrom and (dfrom.date() < vstart): - raise ValidationError( - _('%s are only valid starting from %s') % ( - leave.holiday_status_id.display_name, leave.holiday_status_id.validity_start)) + raise ValidationError(_( + '%(leave_type)s are only valid starting from %(date)s', + leave_type=leave.holiday_status_id.display_name, + date=leave.holiday_status_id.validity_start + )) elif leave.holiday_status_id.validity_stop: if dto and (dto.date() > vstop): - raise ValidationError( - _('%s are only valid until %s') % ( - leave.holiday_status_id.display_name, leave.holiday_status_id.validity_stop)) + raise ValidationError(_( + '%(leave_type)s are only valid until %(date)s', + leave_type=leave.holiday_status_id.display_name, + date=leave.holiday_status_id.validity_stop + )) def _check_double_validation_rules(self, employees, state): if self.user_has_groups('hr_holidays.group_hr_holidays_manager'): @@ -1032,7 +1039,7 @@ class HolidaysRequest(models.Model): for holiday in self: if holiday.employee_id.user_id: holiday.message_post( - body=_('Your %s planned on %s has been refused') % (holiday.holiday_status_id.display_name, holiday.date_from), + body=_('Your %(leave_type)s planned on %(date)s has been refused', leave_type=holiday.holiday_status_id.display_name, date=holiday.date_from), partner_ids=holiday.employee_id.user_id.partner_id.ids) self._remove_resource_leave() @@ -1101,7 +1108,13 @@ class HolidaysRequest(models.Model): for holiday in self: start = UTC.localize(holiday.date_from).astimezone(timezone(holiday.employee_id.tz or 'UTC')) end = UTC.localize(holiday.date_to).astimezone(timezone(holiday.employee_id.tz or 'UTC')) - note = _('New %s Request created by %s from %s to %s') % (holiday.holiday_status_id.name, holiday.create_uid.name, start, end) + note = _( + 'New %(leave_type)s Request created by %(user)s from %(start)s to %(end)s', + leave_type=holiday.holiday_status_id.name, + user=holiday.create_uid.name, + start=start, + end=end + ) if holiday.state == 'draft': to_clean |= holiday elif holiday.state == 'confirm': diff --git a/addons/hr_holidays/models/hr_leave_allocation.py b/addons/hr_holidays/models/hr_leave_allocation.py index 6fda4311739..5c39867ba50 100644 --- a/addons/hr_holidays/models/hr_leave_allocation.py +++ b/addons/hr_holidays/models/hr_leave_allocation.py @@ -389,11 +389,12 @@ class HolidaysAllocation(models.Model): res.append( (allocation.id, - _("Allocation of %s : %.2f %s to %s") % - (allocation.holiday_status_id.sudo().name, - allocation.number_of_hours_display if allocation.type_request_unit == 'hour' else allocation.number_of_days, - 'hours' if allocation.type_request_unit == 'hour' else 'days', - target)) + _("Allocation of %(allocation_name)s : %(duration).2f %(duration_type)s to %(person)s", + allocation_name=allocation.holiday_status_id.sudo().name, + duration=allocation.number_of_hours_display if allocation.type_request_unit == 'hour' else allocation.number_of_days, + duration_type='hours' if allocation.type_request_unit == 'hour' else 'days', + person=target + )) ) return res @@ -410,7 +411,11 @@ class HolidaysAllocation(models.Model): today = fields.Date.today() if vstop < today: - raise ValidationError(_('You can allocate %s only before %s.') % (allocation.holiday_status_id.display_name, allocation.holiday_status_id.validity_stop)) + raise ValidationError(_( + 'You can allocate %(allocation_type)s only before %(date)s.', + allocation_type=allocation.holiday_status_id.display_name, + date=allocation.holiday_status_id.validity_stop + )) @api.model def create(self, values): @@ -613,7 +618,12 @@ class HolidaysAllocation(models.Model): def activity_update(self): to_clean, to_do = self.env['hr.leave.allocation'], self.env['hr.leave.allocation'] for allocation in self: - note = _('New Allocation Request created by %s: %s Days of %s') % (allocation.create_uid.name, allocation.number_of_days, allocation.holiday_status_id.name) + note = _( + 'New Allocation Request created by %(user)s: %(count)s Days of %(allocation_type)s', + user=allocation.create_uid.name, + count=allocation.number_of_days, + allocation_type=allocation.holiday_status_id.name + ) if allocation.state == 'draft': to_clean |= allocation elif allocation.state == 'confirm': diff --git a/addons/hr_skills/models/hr_skills.py b/addons/hr_skills/models/hr_skills.py index e562dd58449..759607a8e72 100644 --- a/addons/hr_skills/models/hr_skills.py +++ b/addons/hr_skills/models/hr_skills.py @@ -33,13 +33,13 @@ class EmployeeSkill(models.Model): def _check_skill_type(self): for record in self: if record.skill_id not in record.skill_type_id.skill_ids: - raise ValidationError(_("The skill %s and skill type %s doesn't match") % (record.skill_id.name, record.skill_type_id.name)) + raise ValidationError(_("The skill %(name)s and skill type %(type)s doesn't match", name=record.skill_id.name, type=record.skill_type_id.name)) @api.constrains('skill_type_id', 'skill_level_id') def _check_skill_level(self): for record in self: if record.skill_level_id not in record.skill_type_id.skill_level_ids: - raise ValidationError(_("The skill level %s is not valid for skill type: %s ") % (record.skill_level_id.name, record.skill_type_id.name)) + raise ValidationError(_("The skill level %(level)s is not valid for skill type: %(type)s", level=record.skill_level_id.name, type=record.skill_type_id.name)) class SkillLevel(models.Model): diff --git a/addons/hr_timesheet/models/project.py b/addons/hr_timesheet/models/project.py index ed0ac3a5bad..c9d294a1a61 100644 --- a/addons/hr_timesheet/models/project.py +++ b/addons/hr_timesheet/models/project.py @@ -186,7 +186,12 @@ class Task(models.Model): for task in self: if task.allow_timesheets and task.planned_hours > 0: hours, mins = (str(int(duration)).rjust(2, '0') for duration in divmod(abs(task.remaining_hours) * 60, 60)) - hours_left = _("(%s%s:%s remaining)") % ('-' if task.remaining_hours < 0 else '', hours, mins) + hours_left = _( + "(%(sign)s%(hours)s:%(minutes)s remaining)", + sign='-' if task.remaining_hours < 0 else '', + hours=hours, + minutes=mins, + ) name_mapping[task.id] = name_mapping.get(task.id, '') + " ‒ " + hours_left return list(name_mapping.items()) return super().name_get() diff --git a/addons/mail/models/mail_activity.py b/addons/mail/models/mail_activity.py index dd92b4a17b3..81e04898477 100644 --- a/addons/mail/models/mail_activity.py +++ b/addons/mail/models/mail_activity.py @@ -433,7 +433,9 @@ class MailActivity(models.Model): record.message_notify( partner_ids=activity.user_id.partner_id.ids, body=body, - subject=_('%s: %s assigned to you') % (activity.res_name, activity.summary or activity.activity_type_id.name), + subject=_('%(activity_name)s: %(summary)s assigned to you', + activity_name=activity.res_name, + summary=activity.summary or activity.activity_type_id.name), record_name=activity.res_name, model_description=model_description, email_layout_xmlid='mail.mail_notification_light', diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py index e2ea498ef0e..23f63f4dad3 100644 --- a/addons/mail/models/mail_channel.py +++ b/addons/mail/models/mail_channel.py @@ -1016,6 +1016,6 @@ class Channel(models.Model): msg = _("You are alone in this channel.") else: dots = "..." if len(members) != len(self.channel_partner_ids) - 1 else "" - msg = _("Users in this channel: %s %s and you.") % (", ".join(members), dots) + msg = _("Users in this channel: %(members)s %(dots)s and you.", members=", ".join(members), dots=dots) self._send_transient_message(partner, msg) diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py index 3f8818bc386..dcadca5b281 100644 --- a/addons/mail/models/mail_thread.py +++ b/addons/mail/models/mail_thread.py @@ -798,7 +798,12 @@ class MailThread(models.AbstractModel): # Existing Document: check if exists and model accepts the mailgateway; if not, fallback on create if allowed if thread_id: if not record_set.exists(): - self._routing_warn(_('reply to missing document (%s,%s), fall back on document creation') % (model, thread_id), message_id, route, False) + self._routing_warn( + _('reply to missing document (%(model)s,%(thread)s), fall back on document creation', model=model, thread=thread_id), + message_id, + route, + False + ) thread_id = None elif not hasattr(record_set, 'message_update'): self._routing_warn(_('reply to model %s that does not accept document update, fall back on document creation', model), message_id, route, False) @@ -834,7 +839,12 @@ class MailThread(models.AbstractModel): else: error_code = self.env['mail.alias.mixin']._alias_check_contact_on_record(obj, message, message_dict, alias) if error_code is not True: - self._routing_warn(_('alias %s: %s') % (alias.alias_name, error_code or _('unknown error')), message_id, route, False) + self._routing_warn( + _('alias %(name)s: %(error)s', name=alias.alias_name, error=error_code or _('unknown error')), + message_id, + route, + False + ) body = alias._get_alias_bounced_body(message_dict) self._routing_create_bounce_email(email_from, body, message, references=message_id) return False diff --git a/addons/mail/wizard/invite.py b/addons/mail/wizard/invite.py index bbe67a37cdc..3d96ebe25b6 100644 --- a/addons/mail/wizard/invite.py +++ b/addons/mail/wizard/invite.py @@ -64,7 +64,7 @@ class Invite(models.TransientModel): # send an email if option checked and if a message exists (do not send void emails) if wizard.send_mail and wizard.message and not wizard.message == '
': # when deleting the message, cleditor keeps a
message = self.env['mail.message'].create({ - 'subject': _('Invitation to follow %s: %s') % (model_name, document.display_name), + 'subject': _('Invitation to follow %(document_model)s: %(document_name)s', document_model=model_name, document_name=document.display_name), 'body': wizard.message, 'record_name': document.display_name, 'email_from': email_from, diff --git a/addons/mrp/models/mrp_bom.py b/addons/mrp/models/mrp_bom.py index 43bce93cf71..7a154b16d0f 100644 --- a/addons/mrp/models/mrp_bom.py +++ b/addons/mrp/models/mrp_bom.py @@ -96,10 +96,12 @@ class MrpBom(models.Model): % (bom.product_id.display_name, ", ".join([ptav.display_name for ptav in bom_line.bom_product_template_attribute_value_ids]))) for ptav in bom_line.bom_product_template_attribute_value_ids: if ptav.product_tmpl_id != bom.product_tmpl_id: - raise ValidationError( - _("The attribute value %s set on product %s does not match the BoM product %s.") % - (ptav.display_name, ptav.product_tmpl_id.display_name, bom_line.parent_product_tmpl_id.display_name) - ) + raise ValidationError(_( + "The attribute value %(attribute)s set on product %(product)s does not match the BoM product %(bom_product)s.", + attribute=ptav.display_name, + product=ptav.product_tmpl_id.display_name, + bom_product=bom_line.parent_product_tmpl_id.display_name + )) @api.onchange('product_uom_id') def onchange_product_uom_id(self): diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index 229d93f5b5d..491c775f6c3 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -1596,7 +1596,9 @@ class MrpProduction(models.Model): ('qty_done', '=', 1), ('state', '=', 'done') ] - message = _('The serial number %s used for byproduct %s has already been produced') % (move_line.lot_id.name, move_line.product_id.name) + message = _('The serial number %(number)s used for byproduct %(product_name)s has already been produced', + number=move_line.lot_id.name, + product_name=move_line.product_id.name) co_prod_move_lines = self.move_finished_ids.move_line_ids.filtered(lambda ml: ml.product_id != self.product_id) domain_unbuild = domain + [ ('production_id', '=', False), @@ -1628,7 +1630,9 @@ class MrpProduction(models.Model): ('qty_done', '=', 1), ('state', '=', 'done') ] - message = _('The serial number %s used for component %s has already been consumed') % (move_line.lot_id.name, move_line.product_id.name) + message = _('The serial number %(number)s used for component %(component)s has already been consumed', + number=move_line.lot_id.name, + component=move_line.product_id.name) co_prod_move_lines = self.move_raw_ids.move_line_ids domain_unbuild = domain + [ ('production_id', '=', False), diff --git a/addons/mrp/models/mrp_workorder.py b/addons/mrp/models/mrp_workorder.py index ad8711bd420..d9e89060c5c 100644 --- a/addons/mrp/models/mrp_workorder.py +++ b/addons/mrp/models/mrp_workorder.py @@ -175,9 +175,9 @@ class MrpWorkorder(models.Model): if wo.state == 'pending' and prev_start and not (prev_start > wo.date_planned_start): infos.append({ 'color': 'text-primary', - 'msg': _("Waiting the previous work order, planned from %s to %s") % ( - format_datetime(self.env, prev_start, dt_format=False), - format_datetime(self.env, prev_finished, dt_format=False)) + 'msg': _("Waiting the previous work order, planned from %(start)s to %(end)s", + start=format_datetime(self.env, prev_start, dt_format=False), + end=format_datetime(self.env, prev_finished, dt_format=False)) }) if wo.date_planned_finished < fields.Datetime.now(): infos.append({ @@ -187,14 +187,14 @@ class MrpWorkorder(models.Model): if prev_start and prev_start > wo.date_planned_start: infos.append({ 'color': 'text-danger', - 'msg': _("Scheduled before the previous work order, planned from %s to %s") % ( - format_datetime(self.env, prev_start, dt_format=False), - format_datetime(self.env, prev_finished, dt_format=False)) + 'msg': _("Scheduled before the previous work order, planned from %(start)s to %(end)s", + start=format_datetime(self.env, prev_start, dt_format=False), + end=format_datetime(self.env, prev_finished, dt_format=False)) }) if conflicted_dict.get(wo.id): infos.append({ 'color': 'text-danger', - 'msg': _("Planned at the same time than other workorder(s) at %s" % wo.workcenter_id.display_name) + 'msg': _("Planned at the same time than other workorder(s) at %s", wo.workcenter_id.display_name) }) color_icon = infos and infos[-1]['color'] or False wo.show_json_popover = bool(color_icon) diff --git a/addons/mrp/wizard/change_production_qty.py b/addons/mrp/wizard/change_production_qty.py index 29657577a4f..cd82ca389f6 100644 --- a/addons/mrp/wizard/change_production_qty.py +++ b/addons/mrp/wizard/change_production_qty.py @@ -48,7 +48,11 @@ class ChangeProductionQty(models.TransientModel): produced = sum(production.move_finished_ids.filtered(lambda m: m.product_id == production.product_id).mapped('quantity_done')) if wizard.product_qty < produced: format_qty = '%.{precision}f'.format(precision=precision) - raise UserError(_("You have already processed %s. Please input a quantity higher than %s ") % (format_qty % produced, format_qty % produced)) + raise UserError(_( + "You have already processed %(quantity)s. Please input a quantity higher than %(minimum)s ", + quantity=format_qty % produced, + minimum=format_qty % produced + )) old_production_qty = production.product_qty new_production_qty = wizard.product_qty done_moves = production.move_finished_ids.filtered(lambda x: x.state == 'done' and x.product_id == production.product_id) diff --git a/addons/payment_stripe/models/payment.py b/addons/payment_stripe/models/payment.py index a1a7da752bc..936e8d1c601 100644 --- a/addons/payment_stripe/models/payment.py +++ b/addons/payment_stripe/models/payment.py @@ -253,7 +253,7 @@ class PaymentTransactionStripe(models.Model): _logger.error(error_msg) raise ValidationError(error_msg) elif len(tx) > 1: - error_msg = (_('Stripe: %s orders found for reference %s') % (len(tx), reference)) + error_msg = _('Stripe: %(count)s orders found for reference %(reference)s', count=len(tx), reference=reference) _logger.error(error_msg) raise ValidationError(error_msg) return tx[0] diff --git a/addons/phone_validation/tools/phone_validation.py b/addons/phone_validation/tools/phone_validation.py index 87fe5498188..ac96c82a87a 100644 --- a/addons/phone_validation/tools/phone_validation.py +++ b/addons/phone_validation/tools/phone_validation.py @@ -17,7 +17,7 @@ try: try: phone_nbr = phonenumbers.parse(number, region=country_code, keep_raw_input=True) except phonenumbers.phonenumberutil.NumberParseException as e: - raise UserError(_('Unable to parse %s.: %s') % (number, str(e))) + raise UserError(_('Unable to parse %(phone)s: %(error)s', phone=number, error=str(e))) if not phonenumbers.is_possible_number(phone_nbr): raise UserError(_('Impossible number %s: probably invalid number of digits.', number)) diff --git a/addons/point_of_sale/models/pos_payment.py b/addons/point_of_sale/models/pos_payment.py index a87762f501a..8dea1f62b92 100644 --- a/addons/point_of_sale/models/pos_payment.py +++ b/addons/point_of_sale/models/pos_payment.py @@ -33,7 +33,7 @@ class PosPayment(models.Model): res = [] for payment in self: if payment.name: - res.append((payment.id, _('%s %s') % (payment.name, formatLang(self.env, payment.amount, currency_obj=payment.currency_id)))) + res.append((payment.id, '%s %s' % (payment.name, formatLang(self.env, payment.amount, currency_obj=payment.currency_id)))) else: res.append((payment.id, formatLang(self.env, payment.amount, currency_obj=payment.currency_id))) return res diff --git a/addons/portal/models/mail_thread.py b/addons/portal/models/mail_thread.py index 424e3de0c25..0e9b58b9fca 100644 --- a/addons/portal/models/mail_thread.py +++ b/addons/portal/models/mail_thread.py @@ -28,7 +28,11 @@ class MailThread(models.AbstractModel): self.ensure_one() # check token field exists if self._mail_post_token_field not in self._fields: - raise NotImplementedError(_("Model %s does not support token signature, as it does not have %s field.") % (self._name, self._mail_post_token_field)) + raise NotImplementedError(_( + "Model %(model_name)s does not support token signature, as it does not have %(field_name)s field.", + model_name=self._name, + field_name=self._mail_post_token_field + )) # sign token secret = self.env["ir.config_parameter"].sudo().get_param("database.secret") token = (self.env.cr.dbname, self[self._mail_post_token_field], pid) diff --git a/addons/product/models/product_pricelist.py b/addons/product/models/product_pricelist.py index 1b2b0640284..d1ef0ec714c 100644 --- a/addons/product/models/product_pricelist.py +++ b/addons/product/models/product_pricelist.py @@ -521,7 +521,7 @@ class PricelistItem(models.Model): elif item.compute_price == 'percentage': item.price = _("%s %% discount", item.percent_price) else: - item.price = _("%s %% discount and %s surcharge") % (item.price_discount, item.price_surcharge) + item.price = _("%(percentage)s %% discount and %(price)s surcharge", percentage=item.price_discount, price=item.price_surcharge) @api.onchange('compute_price') def _onchange_compute_price(self): diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py index 7afe483ceaa..b0d8316f342 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -138,7 +138,12 @@ class PurchaseOrder(models.Model): companies = order.order_line.product_id.company_id if companies and companies != order.company_id: bad_products = order.order_line.product_id.filtered(lambda p: p.company_id and p.company_id != order.company_id) - raise ValidationError((_("Your quotation contains products from company %s whereas your quotation belongs to company %s. \n Please change the company of your quotation or remove the products from other companies (%s).") % (', '.join(companies.mapped('display_name')), order.company_id.display_name, ', '.join(bad_products.mapped('display_name'))))) + raise ValidationError(_( + "Your quotation contains products from company %(product_company)s whereas your quotation belongs to company %(quote_company)s. \n Please change the company of your quotation or remove the products from other companies (%(bad_products)s).", + product_company=', '.join(companies.mapped('display_name')), + quote_company=order.company_id.display_name, + bad_products=', '.join(bad_products.mapped('display_name')), + )) def _compute_access_url(self): super(PurchaseOrder, self)._compute_access_url() @@ -723,7 +728,12 @@ class PurchaseOrder(models.Model): on portal website.""" note = _('

%s modified receipt dates for the following products:

', self.partner_id.name) for line, date in updated_dates: - note += _('

  - %s from %s to %s

') % (line.product_id.display_name, line.date_planned, date) + note += _( + '

  - %(product_name)s from %(date_start)s to %(date_end)s

', + product_name=line.product_id.display_name, + date_start=line.date_planned, + date_end=date + ) return note diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index aba5569b04c..b52e0a640e2 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -263,7 +263,12 @@ class SaleOrder(models.Model): companies = order.order_line.product_id.company_id if companies and companies != order.company_id: bad_products = order.order_line.product_id.filtered(lambda p: p.company_id and p.company_id != order.company_id) - raise ValidationError((_("Your quotation contains products from company %s whereas your quotation belongs to company %s. \n Please change the company of your quotation or remove the products from other companies (%s).") % (', '.join(companies.mapped('display_name')), order.company_id.display_name, ', '.join(bad_products.mapped('display_name'))))) + raise ValidationError(_( + "Your quotation contains products from company %(product_company)s whereas your quotation belongs to company %(quote_company)s. \n Please change the company of your quotation or remove the products from other companies (%(bad_products)s).", + product_company=', '.join(companies.mapped('display_name')), + quote_company=order.company_id.display_name, + bad_products=', '.join(bad_products.mapped('display_name')), + )) @api.depends('pricelist_id', 'date_order', 'company_id') def _compute_currency_rate(self): diff --git a/addons/sale_coupon/models/coupon.py b/addons/sale_coupon/models/coupon.py index 6fbd5ce451f..bc92f4366a2 100644 --- a/addons/sale_coupon/models/coupon.py +++ b/addons/sale_coupon/models/coupon.py @@ -25,7 +25,11 @@ class Coupon(models.Model): message = {'error': _('This coupon is expired (%s).') % (self.code)} # Minimum requirement should not be checked if the coupon got generated by a promotion program (the requirement should have only be checked to generate the coupon) elif self.program_id.program_type == 'coupon_program' and not self.program_id._filter_on_mimimum_amount(order): - message = {'error': _('A minimum of %s %s should be purchased to get the reward') % (self.program_id.rule_minimum_amount, self.program_id.currency_id.name)} + message = {'error': _( + 'A minimum of %(amount)s %(currency)s should be purchased to get the reward', + amount=self.program_id.rule_minimum_amount, + currency=self.program_id.currency_id.name + )} elif not self.program_id.active: message = {'error': _('The coupon program for %s is in draft or closed state') % (self.code)} elif self.partner_id and self.partner_id != order.partner_id: diff --git a/addons/sale_coupon/models/coupon_program.py b/addons/sale_coupon/models/coupon_program.py index 5bc83d493e0..35fa15cb72e 100644 --- a/addons/sale_coupon/models/coupon_program.py +++ b/addons/sale_coupon/models/coupon_program.py @@ -34,7 +34,11 @@ class CouponProgram(models.Model): if self.maximum_use_number != 0 and self.order_count >= self.maximum_use_number: message = {'error': _('Promo code %s has been expired.') % (coupon_code)} elif not self._filter_on_mimimum_amount(order): - message = {'error': _('A minimum of %s %s should be purchased to get the reward') % (self.rule_minimum_amount, self.currency_id.name)} + message = {'error': _( + 'A minimum of %(amount)s %(currency)s should be purchased to get the reward', + amount=self.rule_minimum_amount, + currency=self.currency_id.name + )} elif self.promo_code and self.promo_code == order.promo_code: message = {'error': _('The promo code is already applied on this order')} elif not self.promo_code and self in order.no_code_promo_program_ids: diff --git a/addons/sale_management/models/sale_order.py b/addons/sale_management/models/sale_order.py index 04a9e87128a..f2ea0512f8b 100644 --- a/addons/sale_management/models/sale_order.py +++ b/addons/sale_management/models/sale_order.py @@ -35,7 +35,12 @@ class SaleOrder(models.Model): companies = order.sale_order_option_ids.product_id.company_id if companies and companies != order.company_id: bad_products = order.sale_order_option_ids.product_id.filtered(lambda p: p.company_id and p.company_id != order.company_id) - raise ValidationError((_("Your quotation contains products from company %s whereas your quotation belongs to company %s. \n Please change the company of your quotation or remove the products from other companies (%s).") % (', '.join(companies.mapped('display_name')), order.company_id.display_name, ', '.join(bad_products.mapped('display_name'))))) + raise ValidationError(_( + "Your quotation contains products from company %(product_company)s whereas your quotation belongs to company %(quote_company)s. \n Please change the company of your quotation or remove the products from other companies (%(bad_products)s).", + product_company=', '.join(companies.mapped('display_name')), + quote_company=order.company_id.display_name, + bad_products=', '.join(bad_products.mapped('display_name')), + )) @api.returns('self', lambda value: value.id) def copy(self, default=None): diff --git a/addons/sale_management/models/sale_order_template.py b/addons/sale_management/models/sale_order_template.py index 5eaed0176f0..0552938bde3 100644 --- a/addons/sale_management/models/sale_order_template.py +++ b/addons/sale_management/models/sale_order_template.py @@ -37,7 +37,11 @@ class SaleOrderTemplate(models.Model): if len(companies) > 1: raise ValidationError(_("Your template cannot contain products from multiple companies.")) elif companies and companies != template.company_id: - raise ValidationError((_("Your template contains products from company %s whereas your template belongs to company %s. \n Please change the company of your template or remove the products from other companies.") % (companies.mapped('display_name'), template.company_id.display_name))) + raise ValidationError(_( + "Your template contains products from company %(product_company)s whereas your template belongs to company %(template_company)s. \n Please change the company of your template or remove the products from other companies.", + product_company=', '.join(companies.mapped('display_name')), + template_company=template.company_id.display_name, + )) @api.onchange('sale_order_template_line_ids', 'sale_order_template_option_ids') def _onchange_template_line_ids(self): diff --git a/addons/sale_stock/models/sale_order.py b/addons/sale_stock/models/sale_order.py index 41e4e6d5bb4..fde99c982c2 100644 --- a/addons/sale_stock/models/sale_order.py +++ b/addons/sale_stock/models/sale_order.py @@ -584,7 +584,13 @@ class SaleOrderLine(models.Model): return { 'warning': { 'title': _('Warning'), - 'message': _("This product is packaged by %.2f %s. You should sell %.2f %s.") % (pack.qty, default_uom.name, newqty, self.product_uom.name), + 'message': _( + "This product is packaged by %(pack_size).2f %(pack_name)s. You should sell %(quantity).2f %(unit)s.", + pack_size=pack.qty, + pack_name=default_uom.name, + quantity=newqty, + unit=self.product_uom.name + ), }, } return {} diff --git a/addons/stock/models/stock_warehouse.py b/addons/stock/models/stock_warehouse.py index ead232b7cdd..0ca8576dae4 100644 --- a/addons/stock/models/stock_warehouse.py +++ b/addons/stock/models/stock_warehouse.py @@ -686,7 +686,7 @@ class Warehouse(models.Model): def _get_inter_warehouse_route_values(self, supplier_warehouse): return { - 'name': _('%s: Supply Product from %s') % (self.name, supplier_warehouse.name), + 'name': _('%(warehouse)s: Supply Product from %(supplier)s', warehouse=self.name, supplier=supplier_warehouse.name), 'warehouse_selectable': True, 'product_selectable': True, 'product_categ_selectable': True, diff --git a/addons/stock_account/models/product.py b/addons/stock_account/models/product.py index a87cdcd7510..fa954ec8f50 100644 --- a/addons/stock_account/models/product.py +++ b/addons/stock_account/models/product.py @@ -253,13 +253,25 @@ class ProductProduct(models.Model): 'stock_valuation_layer_ids': [(6, None, [stock_valuation_layer.id])], 'move_type': 'entry', 'line_ids': [(0, 0, { - 'name': _('%s changed cost from %s to %s - %s') % (self.env.user.name, product.standard_price, new_price, product.display_name), + 'name': _( + '%(user)s changed cost from %(previous)s to %(new_price)s - %(product)s', + user=self.env.user.name, + previous=product.standard_price, + new_price=new_price, + product=product.display_name + ), 'account_id': debit_account_id, 'debit': abs(value), 'credit': 0, 'product_id': product.id, }), (0, 0, { - 'name': _('%s changed cost from %s to %s - %s') % (self.env.user.name, product.standard_price, new_price, product.display_name), + 'name': _( + '%(user)s changed cost from %(previous)s to %(new_price)s - %(product)s', + user=self.env.user.name, + previous=product.standard_price, + new_price=new_price, + product=product.display_name + ), 'account_id': credit_account_id, 'debit': 0, 'credit': abs(value), diff --git a/addons/stock_account/wizard/stock_valuation_layer_revaluation.py b/addons/stock_account/wizard/stock_valuation_layer_revaluation.py index 50309cf1fd9..35989e80b67 100644 --- a/addons/stock_account/wizard/stock_valuation_layer_revaluation.py +++ b/addons/stock_account/wizard/stock_valuation_layer_revaluation.py @@ -131,15 +131,23 @@ class StockValuationLayerRevaluation(models.TransientModel): 'date': self.date or fields.Date.today(), 'move_type': 'entry', 'line_ids': [(0, 0, { - 'name': _('%s changed stock valuation from %s to %s of %s') % ( - self.env.user.name, self.current_value_svl, self.current_value_svl + self.added_value, product_id.display_name), + 'name': _('%(user)s changed stock valuation from %(previous)s to %(new_value)s - %(product)s', + user=self.env.user.name, + previous=self.current_value_svl, + new_value=self.current_value_svl + self.added_value, + product=product_id.display_name, + ), 'account_id': debit_account_id, 'debit': abs(self.added_value), 'credit': 0, 'product_id': product_id.id, }), (0, 0, { - 'name': _('%s changed stock valuation from %s to %s of %s') % ( - self.env.user.name, self.current_value_svl, self.current_value_svl + self.added_value, product_id.display_name), + 'name': _('%(user)s changed stock valuation from %(previous)s to %(new_value)s - %(product)s', + user=self.env.user.name, + previous=self.current_value_svl, + new_value=self.current_value_svl + self.added_value, + product=product_id.display_name, + ), 'account_id': credit_account_id, 'debit': 0, 'credit': abs(self.added_value), diff --git a/addons/website_sale_stock/controllers/main.py b/addons/website_sale_stock/controllers/main.py index 43f70fa4e9f..da26a11173c 100644 --- a/addons/website_sale_stock/controllers/main.py +++ b/addons/website_sale_stock/controllers/main.py @@ -20,7 +20,11 @@ class WebsiteSaleStock(WebsiteSale): cart_qty = sum(order.order_line.filtered(lambda p: p.product_id.id == line.product_id.id).mapped('product_uom_qty')) avl_qty = line.product_id.with_context(warehouse=order.warehouse_id.id).virtual_available if cart_qty > avl_qty: - values.append(_('You ask for %s products but only %s is available') % (cart_qty, avl_qty if avl_qty > 0 else 0)) + values.append(_( + 'You ask for %(quantity)s products but only %(available_qty)s is available', + quantity=cart_qty, + available_qty=avl_qty if avl_qty > 0 else 0 + )) if values: raise ValidationError('. '.join(values) + '.') return super(WebsiteSaleStock, self).payment_transaction(**kwargs)