diff --git a/addons/iap_mail/static/src/js/services/iap_notification_service.js b/addons/iap_mail/static/src/js/services/iap_notification_service.js index d4285940226..93e99a08157 100644 --- a/addons/iap_mail/static/src/js/services/iap_notification_service.js +++ b/addons/iap_mail/static/src/js/services/iap_notification_service.js @@ -1,6 +1,7 @@ /** @odoo-module **/ import { Markup } from 'web.utils'; +import { _t } from "@web/core/l10n/translation"; import { registry } from "@web/core/registry"; export const iapNotificationService = { @@ -33,7 +34,10 @@ export const iapNotificationService = { * Displays the IAP failure notification on user's screen */ function displayFailureIapNotification(notif) { - const message = Markup` ${env._t("Buy more credits")}`; + // ℹ️ `_t` can only be inlined directly inside JS template literals + // after Babel has been updated to version 2.12. + const translatedText = _t("Buy more credits"); + const message = Markup` ${translatedText}`; notification.add(message, { type: notif.error_type, title: notif.title diff --git a/addons/web/static/src/legacy/js/control_panel/control_panel_model_extension.js b/addons/web/static/src/legacy/js/control_panel/control_panel_model_extension.js index 32f8c654801..89f899b3082 100644 --- a/addons/web/static/src/legacy/js/control_panel/control_panel_model_extension.js +++ b/addons/web/static/src/legacy/js/control_panel/control_panel_model_extension.js @@ -1,5 +1,7 @@ /** @odoo-module alias=web/static/src/js/control_panel/control_panel_model_extension.js **/ - + + import { _t } from "@web/core/l10n/translation"; + import { sprintf } from "@web/core/utils/strings"; import ActionModel from "web.ActionModel"; import Domain from "web.Domain"; import pyUtils from "web.py_utils"; @@ -436,7 +438,10 @@ ); } catch (err) { throw new Error( - `${this.env._t("Control panel model extension failed to evaluate domain")}:/n${JSON.stringify(err)}` + sprintf( + _t("Control panel model extension failed to evaluate domain:\n%(error)s"), + { error: JSON.stringify(err) } + ) ); } } diff --git a/addons/web/static/src/legacy/js/fields/relational_fields.js b/addons/web/static/src/legacy/js/fields/relational_fields.js index 9d11189800f..9c94cab0850 100644 --- a/addons/web/static/src/legacy/js/fields/relational_fields.js +++ b/addons/web/static/src/legacy/js/fields/relational_fields.js @@ -663,9 +663,14 @@ var FieldMany2One = AbstractField.extend({ const [id, fullName] = result; const displayName = this._getDisplayName(fullName).trim(); result[1] = displayName; + // ℹ️ `_t` can only be inlined directly inside JS template literals + // after Babel has been updated to version 2.12. + const translatedText = _t("Unnamed"); return { id, - label: escape(displayName) || `${escape(_t("Unnamed"))}`, + label: + escape(displayName) || + `${escape(translatedText)}`, value: displayName, name: displayName, }; diff --git a/addons/web/static/src/search/search_model.js b/addons/web/static/src/search/search_model.js index f70db93be5e..843ad1505a9 100644 --- a/addons/web/static/src/search/search_model.js +++ b/addons/web/static/src/search/search_model.js @@ -5,6 +5,7 @@ import { Domain } from "@web/core/domain"; import { evaluateExpr } from "@web/core/py_js/py"; import { sortBy } from "@web/core/utils/arrays"; import { deepCopy } from "@web/core/utils/objects"; +import { sprintf } from "@web/core/utils/strings"; import { SearchArchParser } from "./search_arch_parser"; import { constructDateDomain, @@ -1590,7 +1591,10 @@ export class SearchModel extends EventBus { return context; } catch (error) { throw new Error( - `${this.env._t("Failed to evaluate the context")} ${context}.\n${error.message}` + sprintf(_t("Failed to evaluate the context: %(context)s.\n%(error)s"), { + context, + error: error.message, + }) ); } } @@ -1675,9 +1679,10 @@ export class SearchModel extends EventBus { return params.raw ? domain : domain.toList(this.domainEvalContext); } catch (error) { throw new Error( - `${this.env._t("Failed to evaluate the domain")} ${domain.toString()}.\n${ - error.message - }` + sprintf(_t("Failed to evaluate the domain: %(domain)s.\n%(error)s"), { + domain: domain.toString(), + error: error.message, + }) ); } } @@ -2075,9 +2080,10 @@ export class SearchModel extends EventBus { } } catch (error) { throw new Error( - `${this.env._t("Failed to evaluate the context")} "${ - searchItem.context - }".\n${error.message}` + sprintf(_t("Failed to evaluate the context: %(context)s.\n%(error)s"), { + context: searchItem.context, + error: error.message, + }) ); } } diff --git a/addons/web/static/src/views/calendar/calendar_controller.js b/addons/web/static/src/views/calendar/calendar_controller.js index 7944d6c62a3..1e34b5bc224 100644 --- a/addons/web/static/src/views/calendar/calendar_controller.js +++ b/addons/web/static/src/views/calendar/calendar_controller.js @@ -3,6 +3,7 @@ import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog"; import { _lt, _t } from "@web/core/l10n/translation"; import { useOwnedDialogs, useService } from "@web/core/utils/hooks"; +import { sprintf } from "@web/core/utils/strings"; import { Layout } from "@web/search/layout"; import { useModel } from "@web/views/model"; import { ViewScaleSelector } from "@web/views/view_components/view_scale_selector"; @@ -163,7 +164,7 @@ export class CalendarController extends Component { resModel: this.model.resModel, resId: record.id || false, context, - title: record.id ? `${_t("Open")}: ${record.title}` : _t("New Event"), + title: record.id ? sprintf(_t("Open: %s"), record.title) : _t("New Event"), viewId: this.model.formViewId, onRecordSaved: () => this.model.load(), }, diff --git a/addons/web/static/src/views/calendar/filter_panel/calendar_filter_panel.js b/addons/web/static/src/views/calendar/filter_panel/calendar_filter_panel.js index e38caed7dfd..c1ce925ec8a 100644 --- a/addons/web/static/src/views/calendar/filter_panel/calendar_filter_panel.js +++ b/addons/web/static/src/views/calendar/filter_panel/calendar_filter_panel.js @@ -29,7 +29,7 @@ export class CalendarFilterPanel extends Component { return { autoSelect: true, resetOnSelect: true, - placeholder: `+ ${_t("Add")} ${section.label}`, + placeholder: sprintf(_t("+ Add %s"), section.label), sources: [ { placeholder: _t("Loading..."), diff --git a/addons/web/static/src/views/view_dialogs/select_create_dialog.js b/addons/web/static/src/views/view_dialogs/select_create_dialog.js index 65ff76ae508..5642bc6679b 100644 --- a/addons/web/static/src/views/view_dialogs/select_create_dialog.js +++ b/addons/web/static/src/views/view_dialogs/select_create_dialog.js @@ -1,6 +1,7 @@ /** @odoo-module **/ import { Dialog } from "@web/core/dialog/dialog"; +import { _t } from "@web/core/l10n/translation"; import { useService } from "@web/core/utils/hooks"; import { View } from "@web/views/view"; import { escape } from "@web/core/utils/strings"; @@ -14,11 +15,14 @@ export class SelectCreateDialog extends Component { this.viewService = useService("view"); this.dialogService = useService("dialog"); this.state = useState({ resIds: [] }); + // ℹ️ `_t` can only be inlined directly inside JS template literals + // after Babel has been updated to version 2.12. + const translatedText = _t("No records found!"); this.baseViewProps = { display: { searchPanel: false }, editable: false, // readonly noBreadcrumbs: true, - noContentHelp: markup(`
${escape(this.env._t("No records found!"))}
`), + noContentHelp: markup(`${escape(translatedText)}
`), showButtons: false, selectRecord: (resId) => this.select([resId]), onSelectionChanged: (resIds) => { diff --git a/addons/web/static/src/webclient/settings_form_view/widgets/res_config_invite_users.js b/addons/web/static/src/webclient/settings_form_view/widgets/res_config_invite_users.js index 0718acd8d1f..a9944c05d32 100644 --- a/addons/web/static/src/webclient/settings_form_view/widgets/res_config_invite_users.js +++ b/addons/web/static/src/webclient/settings_form_view/widgets/res_config_invite_users.js @@ -4,6 +4,7 @@ import { registry } from "@web/core/registry"; import { _t } from "@web/core/l10n/translation"; import { unique } from "@web/core/utils/arrays"; import { useService } from "@web/core/utils/hooks"; +import { sprintf } from "@web/core/utils/strings"; import { Component, useState, onWillStart } from "@odoo/owl"; import { standardWidgetProps } from "@web/views/widgets/standard_widget_props"; @@ -19,6 +20,7 @@ class ResConfigInviteUsers extends Component { this.invite = useService("user_invite"); this.action = useService("action"); this.notification = useService("notification"); + this.user = useService("user"); this.state = useState({ status: "idle", // idle, inviting @@ -61,11 +63,27 @@ class ResConfigInviteUsers extends Component { } } if (invalidEmails.length) { - throw new Error( - `${_t("Invalid email address")}${ - invalidEmails.length > 1 ? "es" : "" - }: ${invalidEmails.join(", ")}` - ); + const errorMessage = (() => { + const listFormatter = new Intl.ListFormat(this.user.lang.replace("_", "-"), { + type: "conjunction", + style: "long", + }); + switch (invalidEmails.length) { + case 1: + return sprintf(_t("Invalid email address: %(address)s"), { + address: invalidEmails[0], + }); + case 2: + return sprintf(_t("Invalid email addresses: %(2 addresses)s"), { + "2 addresses": listFormatter.format(invalidEmails), + }); + default: + return sprintf(_t("Invalid email addresses: %(addresses)s"), { + addresses: listFormatter.format(invalidEmails), + }); + } + })(); + throw new Error(errorMessage); } } diff --git a/addons/web/static/src/webclient/user_menu/user_menu_items.js b/addons/web/static/src/webclient/user_menu/user_menu_items.js index 11ddd5629cd..03c09995f38 100644 --- a/addons/web/static/src/webclient/user_menu/user_menu_items.js +++ b/addons/web/static/src/webclient/user_menu/user_menu_items.js @@ -2,6 +2,7 @@ import { Component, markup } from "@odoo/owl"; import { isMacOS } from "@web/core/browser/feature_detection"; +import { _t } from "@web/core/l10n/translation"; import { escape } from "@web/core/utils/strings"; import { session } from "@web/session"; import { browser } from "../../core/browser/browser"; @@ -43,13 +44,16 @@ class ShortcutsFooterComponent extends Component { ShortcutsFooterComponent.template = "web.UserMenu.ShortcutsFooterComponent"; function shortCutsItem(env) { + // ℹ️ `_t` can only be inlined directly inside JS template literals after + // Babel has been updated to version 2.12. + const translatedText = _t("Shortcuts"); return { type: "item", id: "shortcuts", hide: env.isSmall, description: markup( `