[IMP] *: unify sprintf and gettext

Improve gettext to directly handle value injection within translations,
removing the need for sprintf.

closes odoo/odoo#123932

Related: odoo/enterprise#45370
Signed-off-by: Samuel Degueldre (sad) <sad@odoo.com>
This commit is contained in:
Louis Wicket (wil)
2023-08-10 18:14:04 +02:00
parent 62fba62067
commit f1722c7334
123 changed files with 353 additions and 495 deletions
@@ -7,7 +7,6 @@ import { getNextTabableElement, getPreviousTabableElement } from "@web/core/util
import { usePosition } from "@web/core/position_hook"; import { usePosition } from "@web/core/position_hook";
import { getActiveHotkey } from "@web/core/hotkeys/hotkey_service"; import { getActiveHotkey } from "@web/core/hotkeys/hotkey_service";
import { shallowEqual } from "@web/core/utils/arrays"; import { shallowEqual } from "@web/core/utils/arrays";
import { sprintf } from "@web/core/utils/strings";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { AutoComplete } from "@web/core/autocomplete/autocomplete"; import { AutoComplete } from "@web/core/autocomplete/autocomplete";
import { useRecordObserver } from "@web/model/relational_model/utils"; import { useRecordObserver } from "@web/model/relational_model/utils";
@@ -272,7 +271,7 @@ export class AnalyticDistribution extends Component {
if (searchTerm.length) { if (searchTerm.length) {
dynamicFilters = [ dynamicFilters = [
{ {
description: sprintf(this.env._t("Quick search: %s"), searchTerm), description: this.env._t("Quick search: %s", searchTerm),
domain: this.searchAnalyticDomain(searchTerm), domain: this.searchAnalyticDomain(searchTerm),
}, },
]; ];
@@ -1,16 +1,13 @@
/** @odoo-module **/ /** @odoo-module **/
import { sprintf } from "@web/core/utils/strings";
import { computeScore } from "./password_policy"; import { computeScore } from "./password_policy";
const { Component, xml } = owl; const { Component, xml } = owl;
export class Meter extends Component { export class Meter extends Component {
get title() { get title() {
return sprintf( return this.env._t(
this.env._t( "Required: %s\n\nHint: to increase password strength, increase length, use multiple words, and use non-letter characters.",
"Required: %s\n\nHint: to increase password strength, increase length, use multiple words, and use non-letter characters."
),
String(this.props.required) || this.env._t("no requirements") String(this.props.required) || this.env._t("no requirements")
); );
} }
@@ -1,7 +1,6 @@
/** @odoo-module **/ /** @odoo-module **/
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { sprintf } from "@web/core/utils/strings";
export class Policy { export class Policy {
/** /**
@@ -27,13 +26,13 @@ export class ConcretePolicy extends Policy {
toString() { toString() {
const msgs = []; const msgs = [];
if (this.minlength > 1) { if (this.minlength > 1) {
msgs.push(sprintf(_t("at least %s characters"), this.minlength)); msgs.push(_t("at least %s characters", this.minlength));
} }
if (this.minwords > 1) { if (this.minwords > 1) {
msgs.push(sprintf(_t("at least %s words"), this.minwords)); msgs.push(_t("at least %s words", this.minwords));
} }
if (this.minclasses > 1) { if (this.minclasses > 1) {
msgs.push(sprintf(_t("at least %s character classes"), this.minclasses)); msgs.push(_t("at least %s character classes", this.minclasses));
} }
return msgs.join(", "); return msgs.join(", ");
} }
@@ -2,7 +2,6 @@
import Widget from "@web/legacy/js/core/widget"; import Widget from "@web/legacy/js/core/widget";
import { computeScore } from "@auth_password_policy/password_policy"; import { computeScore } from "@auth_password_policy/password_policy";
import { sprintf } from "@web/core/utils/strings";
import { translationIsReady, _t } from "@web/core/l10n/translation"; import { translationIsReady, _t } from "@web/core/l10n/translation";
export default Widget.extend({ export default Widget.extend({
@@ -27,12 +26,10 @@ export default Widget.extend({
}, },
start() { start() {
var helpMessage = _t( var helpMessage = _t(
"Required: %s.\n\nHint: increase length, use multiple words and use non-letter characters to increase your password's strength." "Required: %s.\n\nHint: increase length, use multiple words and use non-letter characters to increase your password's strength.",
); String(this._required) || _t("no requirements")
this.el.setAttribute(
"title",
sprintf(helpMessage, String(this._required) || _t("no requirements"))
); );
this.el.setAttribute("title", helpMessage);
return this._super().then(function () {}); return this._super().then(function () {});
}, },
/** /**
@@ -3,7 +3,6 @@
import { Component, onWillStart, onMounted, useState } from "@odoo/owl"; import { Component, onWillStart, onMounted, useState } from "@odoo/owl";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
import { FileInput } from "@web/core/file_input/file_input"; import { FileInput } from "@web/core/file_input/file_input";
import { useImportModel } from "../import_model"; import { useImportModel } from "../import_model";
import { ImportDataContent } from "../import_data_content/import_data_content"; import { ImportDataContent } from "../import_data_content/import_data_content";
@@ -171,10 +170,9 @@ export class ImportAction extends Component {
} }
if (!isTest && res.ids.length) { if (!isTest && res.ids.length) {
this.notification.add( this.notification.add(this.env._t("%s records successfully imported", res.ids.length), {
sprintf(this.env._t("%s records successfully imported"), res.ids.length), type: "success",
{ type: "success" } });
);
this.exit(); this.exit();
} }
} }
@@ -3,7 +3,6 @@
import { Component, useState, onWillStart } from "@odoo/owl"; import { Component, useState, onWillStart } from "@odoo/owl";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
export class ImportDataOptions extends Component { export class ImportDataOptions extends Component {
static template = "ImportDataOptions"; static template = "ImportDataOptions";
@@ -45,7 +44,7 @@ export class ImportDataOptions extends Component {
const fields = await this.orm.call(this.currentModel, "fields_get"); const fields = await this.orm.call(this.currentModel, "fields_get");
const selection = fields[this.props.fieldInfo.name].selection.map((opt) => [ const selection = fields[this.props.fieldInfo.name].selection.map((opt) => [
opt[0], opt[0],
sprintf(_t("Set to: %s"), opt[1]), _t("Set to: %s", opt[1]),
]); ]);
options.push(...selection); options.push(...selection);
} else { } else {
@@ -5,7 +5,6 @@ import { registry } from "@web/core/registry";
import { pick } from "@web/core/utils/objects"; import { pick } from "@web/core/utils/objects";
import { groupBy, sortBy } from "@web/core/utils/arrays"; import { groupBy, sortBy } from "@web/core/utils/arrays";
import { memoize } from "@web/core/utils/functions"; import { memoize } from "@web/core/utils/functions";
import { sprintf } from "@web/core/utils/strings";
import { useState } from "@odoo/owl"; import { useState } from "@odoo/owl";
import { ImportBlockUI } from "./import_block_ui"; import { ImportBlockUI } from "./import_block_ui";
@@ -281,8 +280,8 @@ export class BaseImportModel {
if (!importRes.hasError) { if (!importRes.hasError) {
if (importRes.nextrow) { if (importRes.nextrow) {
this._addMessage("warning", [ this._addMessage("warning", [
sprintf( _t(
_t("Click 'Resume' to proceed with the import, resuming at line %s."), "Click 'Resume' to proceed with the import, resuming at line %s.",
importRes.nextrow + 1 importRes.nextrow + 1
), ),
_t("You can test or reload your file before resuming the import."), _t("You can test or reload your file before resuming the import."),
@@ -445,8 +444,8 @@ export class BaseImportModel {
if (error.record !== undefined) { if (error.record !== undefined) {
this._addMessage("danger", [ this._addMessage("danger", [
error.rows.from === error.rows.to error.rows.from === error.rows.to
? sprintf(_t('Error at row %s: "%s"'), error.record, error.message) ? _t('Error at row %s: "%s"', error.record, error.message)
: sprintf(_t("%s at multiple rows"), error.message), : _t("%s at multiple rows", error.message),
]); ]);
} }
// Handle global errors. // Handle global errors.
@@ -3,7 +3,6 @@
import { Dropdown } from "@web/core/dropdown/dropdown"; import { Dropdown } from "@web/core/dropdown/dropdown";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { useAutofocus, useService } from "@web/core/utils/hooks"; import { useAutofocus, useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
const { Component, useState } = owl; const { Component, useState } = owl;
const cogMenuRegistry = registry.category("cogMenu"); const cogMenuRegistry = registry.category("cogMenu");
@@ -63,7 +62,7 @@ export class AddToBoard extends Component {
this.notification.add( this.notification.add(
this.env._t("Please refresh your browser for the changes to take effect."), this.env._t("Please refresh your browser for the changes to take effect."),
{ {
title: sprintf(this.env._t(`"%s" added to dashboard`), this.state.name), title: this.env._t("“%s” added to dashboard", this.state.name),
type: "warning", type: "warning",
} }
); );
@@ -1,7 +1,6 @@
/** @odoo-module **/ /** @odoo-module **/
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { sprintf } from "@web/core/utils/strings";
import { INTERVAL_OPTIONS } from "@web/search/utils/dates"; import { INTERVAL_OPTIONS } from "@web/search/utils/dates";
import { KanbanColumnQuickCreate } from "@web/views/kanban/kanban_column_quick_create"; import { KanbanColumnQuickCreate } from "@web/views/kanban/kanban_column_quick_create";
@@ -12,7 +11,7 @@ export class ForecastKanbanColumnQuickCreate extends KanbanColumnQuickCreate {
get relatedFieldName() { get relatedFieldName() {
const { granularity = "month" } = this.props.groupByField; const { granularity = "month" } = this.props.groupByField;
const { description } = INTERVAL_OPTIONS[granularity]; const { description } = INTERVAL_OPTIONS[granularity];
return sprintf(_t("Add next %s"), description.toLocaleLowerCase()); return _t("Add next %s", description.toLocaleLowerCase());
} }
/** /**
* @override * @override
@@ -5,7 +5,6 @@ import { Component, onWillStart, onWillUnmount, useState } from "@odoo/owl";
import { deserializeDateTime } from "@web/core/l10n/dates"; import { deserializeDateTime } from "@web/core/l10n/dates";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { useBus, useService } from "@web/core/utils/hooks"; import { useBus, useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
const { Duration } = luxon; const { Duration } = luxon;
@@ -73,7 +72,7 @@ export class GreetingMessage extends Component {
.toFormat("hh-mm") .toFormat("hh-mm")
.split("-"); .split("-");
this.hours_today = sprintf(this.env._t("%(hours)s hours, %(minutes)s minutes"), { this.hours_today = this.env._t("%(hours)s hours, %(minutes)s minutes", {
hours: duration[0], hours: duration[0],
minutes: duration[1], minutes: duration[1],
}); });
@@ -4,7 +4,6 @@ import { personaService, PersonaService } from "@mail/core/common/persona_servic
import { deserializeDateTime } from "@web/core/l10n/dates"; import { deserializeDateTime } from "@web/core/l10n/dates";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { patch } from "@web/core/utils/patch"; import { patch } from "@web/core/utils/patch";
import { sprintf } from "@web/core/utils/strings";
const { DateTime } = luxon; const { DateTime } = luxon;
@@ -35,6 +34,6 @@ patch(PersonaService.prototype, {
// const fdate = date.toLocaleString(DateTime.TIME_SHORT); // const fdate = date.toLocaleString(DateTime.TIME_SHORT);
const fdate = date.toLocaleString(DateTime.DATE_MED); const fdate = date.toLocaleString(DateTime.DATE_MED);
// const formattedDate = date.toLocaleDateString(localeCode, options); // const formattedDate = date.toLocaleDateString(localeCode, options);
return sprintf(_t("Out of office until %s"), fdate); return _t("Out of office until %s", fdate);
}, },
}); });
@@ -2,7 +2,6 @@
import { AlertDialog } from "@web/core/confirmation_dialog/confirmation_dialog"; import { AlertDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { sprintf } from "@web/core/utils/strings";
async function actionGetDrive(env, action, type) { async function actionGetDrive(env, action, type) {
const { drive_id, sign_host: host } = action.params; const { drive_id, sign_host: host } = action.params;
@@ -47,9 +46,7 @@ async function actionGetDrive(env, action, type) {
unauthorized: _t("Unauthorized"), unauthorized: _t("Unauthorized"),
}; };
dialog.add(AlertDialog, { dialog.add(AlertDialog, {
body: body: typeToErrorMessage[result.error] || _t("Unexpected error: “%s”", result.error),
typeToErrorMessage[result.error] ||
sprintf(_t(`Unexpected error: "%s"`), result.error),
}); });
} else if (result[key]) { } else if (result[key]) {
await orm.call("l10n_eg_edi.thumb.drive", method, [[drive_id], result[key]]).catch(() => { await orm.call("l10n_eg_edi.thumb.drive", method, [[drive_id], result[key]]).catch(() => {
+1 -2
View File
@@ -4,7 +4,6 @@ import { PosStore } from "@point_of_sale/app/store/pos_store";
import { Order, Orderline } from "@point_of_sale/app/store/models"; import { Order, Orderline } from "@point_of_sale/app/store/models";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { patch } from "@web/core/utils/patch"; import { patch } from "@web/core/utils/patch";
import { sprintf } from "@web/core/utils/strings";
import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup"; import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup";
patch(PosStore.prototype, { patch(PosStore.prototype, {
@@ -13,7 +12,7 @@ patch(PosStore.prototype, {
if (!this.company.country) { if (!this.company.country) {
this.env.services.popup.add(ErrorPopup, { this.env.services.popup.add(ErrorPopup, {
title: _t("Missing Country"), title: _t("Missing Country"),
body: sprintf(_t("The company %s doesn't have a country set."), this.company.name), body: _t("The company %s doesn't have a country set.", this.company.name),
}); });
return false; return false;
} }
@@ -1,5 +1,5 @@
/** @odoo-module **/ /** @odoo-module **/
import { sprintf } from "@web/core/utils/strings";
import { useState } from "@odoo/owl"; import { useState } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
@@ -37,7 +37,7 @@ export function useKEProxy({onAllSent}) {
let progress; // keep track of when an error occurs let progress; // keep track of when an error occurs
for (const index in invoices) { for (const index in invoices) {
let { move_id, messages, proxy_address, company_vat, name } = invoices[index]; let { move_id, messages, proxy_address, company_vat, name } = invoices[index];
state.message = sprintf(_t("Posting invoice: %s"), name); state.message = _t("Posting invoice: %s", name);
try { try {
progress = 'postToDevice'; progress = 'postToDevice';
let deviceResponse = await http.post( let deviceResponse = await http.post(
@@ -59,16 +59,16 @@ export function useKEProxy({onAllSent}) {
state.error = true; state.error = true;
switch (progress) { switch (progress) {
case 'postToDevice': case 'postToDevice':
state.message = sprintf(_t("Error trying to connect to the middleware. Is the middleware running? \n Error message: %s"), e.message); state.message = _t("Error trying to connect to the middleware. Is the middleware running? \n Error message: %s", e.message);
break; break;
case 'parseResponse': case 'parseResponse':
state.message = sprintf(_t("Posting the invoice %s has failed with the message: \n %s"), name, e.message); state.message = _t("Posting the invoice %s has failed with the message: \n %s", name, e.message);
break; break;
case 'updateInvoice': case 'updateInvoice':
state.message = sprintf(_t("Error trying to connect to Odoo. Check your internet connection. Error message: %s"), e.message); state.message = _t("Error trying to connect to Odoo. Check your internet connection. Error message: %s", e.message);
break; break;
default: default:
state.message = sprintf(_t("Unexpected Error:\n %s"), e.message); state.message = _t("Unexpected Error:\n %s", e.message);
} }
break; break;
} }
@@ -6,7 +6,6 @@ import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_d
import { useFileViewer } from "@web/core/file_viewer/file_viewer_hook"; import { useFileViewer } from "@web/core/file_viewer/file_viewer_hook";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
import { url } from "@web/core/utils/urls"; import { url } from "@web/core/utils/urls";
/** /**
@@ -83,7 +82,7 @@ export class AttachmentList extends Component {
return this.props.unlinkAttachment(attachment); return this.props.unlinkAttachment(attachment);
} }
this.dialog.add(ConfirmationDialog, { this.dialog.add(ConfirmationDialog, {
body: sprintf(_t('Do you really want to delete "%s"?'), attachment.filename), body: _t('Do you really want to delete "%s"?', attachment.filename),
cancel: () => {}, cancel: () => {},
confirm: () => this.onConfirmUnlink(attachment), confirm: () => this.onConfirmUnlink(attachment),
}); });
+5 -14
View File
@@ -27,7 +27,6 @@ import {
import { browser } from "@web/core/browser/browser"; import { browser } from "@web/core/browser/browser";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
import { FileUploader } from "@web/views/fields/file_handler"; import { FileUploader } from "@web/views/fields/file_handler";
const EDIT_CLICK_TYPE = { const EDIT_CLICK_TYPE = {
@@ -82,9 +81,7 @@ export class Composer extends Component {
setup() { setup() {
this.SEND_KEYBIND_TO_SEND = markup( this.SEND_KEYBIND_TO_SEND = markup(
sprintf(_t("<samp>%(send_keybind)s</samp><i> to send</i>"), { _t("<samp>%(send_keybind)s</samp><i> to send</i>", { send_keybind: this.sendKeybind })
send_keybind: this.sendKeybind,
})
); );
this.KEYBOARD = { this.KEYBOARD = {
NONE: "None", NONE: "None",
@@ -211,13 +208,9 @@ export class Composer extends Component {
} }
if (this.thread) { if (this.thread) {
if (this.thread.type === "channel") { if (this.thread.type === "channel") {
return sprintf(_t("Message #%(thread name)s…"), { return _t("Message #%(thread name)s…", { "thread name": this.thread.displayName });
"thread name": this.thread.displayName,
});
} }
return sprintf(_t("Message %(thread name)s…"), { return _t("Message %(thread name)s…", { "thread name": this.thread.displayName });
"thread name": this.thread.displayName,
});
} }
return ""; return "";
} }
@@ -233,10 +226,8 @@ export class Composer extends Component {
get CANCEL_OR_SAVE_EDIT_TEXT() { get CANCEL_OR_SAVE_EDIT_TEXT() {
return markup( return markup(
sprintf(
_t( _t(
"<samp>%(cancel_keybind)s</samp><i> to <a href='#' data-type='%(cancel_type)s'>cancel</a></i>, <samp>%(save_keybind)s</samp><i> to <a href='#' data-type='%(save_type)s'>save</a></i>" "<samp>%(cancel_keybind)s</samp><i> to <a href='#' data-type='%(cancel_type)s'>cancel</a></i>, <samp>%(save_keybind)s</samp><i> to <a href='#' data-type='%(save_type)s'>save</a></i>",
),
{ {
cancel_keybind: _t("Escape"), cancel_keybind: _t("Escape"),
cancel_type: EDIT_CLICK_TYPE.CANCEL, cancel_type: EDIT_CLICK_TYPE.CANCEL,
@@ -556,7 +547,7 @@ export class Composer extends Component {
const message = await this.threadService.post(this.thread, value, postData); const message = await this.threadService.post(this.thread, value, postData);
if (this.props.composer.thread.type === "mailbox") { if (this.props.composer.thread.type === "mailbox") {
this.env.services.notification.add( this.env.services.notification.add(
sprintf(_t('Message posted on "%s"'), message.originThread.displayName), _t('Message posted on "%s"', message.originThread.displayName),
{ type: "info" } { type: "info" }
); );
} }
@@ -6,7 +6,6 @@ import { Component, useState } from "@odoo/owl";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
export class MessageReactions extends Component { export class MessageReactions extends Component {
static props = ["message", "openReactionMenu"]; static props = ["message", "openReactionMenu"];
@@ -26,33 +25,33 @@ export class MessageReactions extends Component {
); );
switch (reaction.count) { switch (reaction.count) {
case 1: case 1:
return sprintf(_t("%s has reacted with %s"), firstUserName, reaction.content); return _t("%s has reacted with %s", firstUserName, reaction.content);
case 2: case 2:
return sprintf( return _t(
_t("%s and %s have reacted with %s"), "%s and %s have reacted with %s",
firstUserName, firstUserName,
secondUserName, secondUserName,
reaction.content reaction.content
); );
case 3: case 3:
return sprintf( return _t(
_t("%s, %s, %s have reacted with %s"), "%s, %s, %s have reacted with %s",
firstUserName, firstUserName,
secondUserName, secondUserName,
thirdUserName, thirdUserName,
reaction.content reaction.content
); );
case 4: case 4:
return sprintf( return _t(
_t("%s, %s, %s and 1 other person have reacted with %s"), "%s, %s, %s and 1 other person have reacted with %s",
firstUserName, firstUserName,
secondUserName, secondUserName,
thirdUserName, thirdUserName,
reaction.content reaction.content
); );
default: default:
return sprintf( return _t(
_t("%s, %s, %s and %s other persons have reacted with %s"), "%s, %s, %s and %s other persons have reacted with %s",
firstUserName, firstUserName,
secondUserName, secondUserName,
thirdUserName, thirdUserName,
@@ -13,7 +13,6 @@ import { markup } from "@odoo/owl";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { sprintf } from "@web/core/utils/strings";
const { DateTime } = luxon; const { DateTime } = luxon;
@@ -161,9 +160,7 @@ export class MessageService {
const thread = message.originThread; const thread = message.originThread;
await this.env.services["mail.thread"].removeFollower(thread.selfFollower); await this.env.services["mail.thread"].removeFollower(thread.selfFollower);
this.env.services.notification.add( this.env.services.notification.add(
sprintf(_t('You are no longer following "%(thread_name)s".'), { _t('You are no longer following "%(thread_name)s".', { thread_name: thread.name }),
thread_name: thread.name,
}),
{ type: "success" } { type: "success" }
); );
} }
@@ -5,7 +5,6 @@ import { htmlToTextContentInline } from "@mail/utils/common/format";
import { browser } from "@web/core/browser/browser"; import { browser } from "@web/core/browser/browser";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { sprintf } from "@web/core/utils/strings";
import { url } from "@web/core/utils/urls"; import { url } from "@web/core/utils/urls";
const PREVIEW_MSG_MAX_SIZE = 350; // optimal for native English speakers const PREVIEW_MSG_MAX_SIZE = 350; // optimal for native English speakers
@@ -43,7 +42,7 @@ export class OutOfFocusService {
notificationTitle = _t("New message"); notificationTitle = _t("New message");
} else { } else {
if (channel.channel_type === "channel") { if (channel.channel_type === "channel") {
notificationTitle = sprintf(_t("%(author name)s from %(channel name)s"), { notificationTitle = _t("%(author name)s from %(channel name)s", {
"author name": author.name, "author name": author.name,
"channel name": channel.displayName, "channel name": channel.displayName,
}); });
@@ -61,10 +60,9 @@ export class OutOfFocusService {
type: "info", type: "info",
}); });
this.counter++; this.counter++;
const titlePattern = this.counter === 1 ? _t("%s Message") : _t("%s Messages");
this.busService.trigger("set_title_part", { this.busService.trigger("set_title_part", {
part: "_chat", part: "_chat",
title: sprintf(titlePattern, this.counter), title: this.counter === 1 ? _t("1 Message") : _t("%s Messages", this.counter),
}); });
} }
@@ -5,7 +5,6 @@ import { createLocalId } from "@mail/utils/common/misc";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { Deferred } from "@web/core/utils/concurrency"; import { Deferred } from "@web/core/utils/concurrency";
import { sprintf } from "@web/core/utils/strings";
/** /**
* @typedef SeenInfo * @typedef SeenInfo
@@ -147,7 +146,7 @@ export class Thread {
if (!this.authorizedGroupFullName) { if (!this.authorizedGroupFullName) {
return false; return false;
} }
return sprintf(_t('Access restricted to group "%(groupFullName)s"'), { return _t('Access restricted to group "%(groupFullName)s"', {
groupFullName: this.authorizedGroupFullName, groupFullName: this.authorizedGroupFullName,
}); });
} }
+1 -2
View File
@@ -12,7 +12,6 @@ import { browser } from "@web/core/browser/browser";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { usePopover } from "@web/core/popover/popover_hook"; import { usePopover } from "@web/core/popover/popover_hook";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
import { FileUploader } from "@web/views/fields/file_handler"; import { FileUploader } from "@web/views/fields/file_handler";
/** /**
@@ -46,7 +45,7 @@ export class Activity extends Component {
get displayName() { get displayName() {
if (this.props.data.summary) { if (this.props.data.summary) {
return sprintf(_t("“%s”"), this.props.data.summary); return _t("“%s”", this.props.data.summary);
} }
return this.props.data.display_name; return this.props.data.display_name;
} }
@@ -9,7 +9,6 @@ import { Component, useState } from "@odoo/owl";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
import { url } from "@web/core/utils/urls"; import { url } from "@web/core/utils/urls";
import { FileUploader } from "@web/views/fields/file_handler"; import { FileUploader } from "@web/views/fields/file_handler";
@@ -56,11 +55,11 @@ export class ActivityListPopoverItem extends Component {
} else if (diff === -1) { } else if (diff === -1) {
return _t("Yesterday"); return _t("Yesterday");
} else if (diff < 0) { } else if (diff < 0) {
return sprintf(_t("%s days overdue"), Math.round(Math.abs(diff))); return _t("%s days overdue", Math.round(Math.abs(diff)));
} else if (diff === 1) { } else if (diff === 1) {
return _t("Tomorrow"); return _t("Tomorrow");
} else { } else {
return sprintf(_t("Due in %s days"), Math.round(Math.abs(diff))); return _t("Due in %s days", Math.round(Math.abs(diff)));
} }
} }
@@ -5,7 +5,6 @@ import { Component, onWillStart, useState } from "@odoo/owl";
import { Dialog } from "@web/core/dialog/dialog"; import { Dialog } from "@web/core/dialog/dialog";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
/** /**
* @typedef {Object} SubtypeData * @typedef {Object} SubtypeData
@@ -71,8 +70,6 @@ export class FollowerSubtypeDialog extends Component {
} }
get title() { get title() {
return sprintf(_t("Edit Subscription of %(name)s"), { return _t("Edit Subscription of %(name)s", { name: this.props.follower.partner.name });
name: this.props.follower.partner.name,
});
} }
} }
@@ -13,7 +13,6 @@ import { Dropdown } from "@web/core/dropdown/dropdown";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
export class MessagingMenu extends Component { export class MessagingMenu extends Component {
static components = { Dropdown, NotificationItem, ImStatus }; static components = { Dropdown, NotificationItem, ImStatus };
@@ -90,7 +89,7 @@ export class MessagingMenu extends Component {
get notificationRequest() { get notificationRequest() {
return { return {
body: _t("Enable desktop notifications to chat"), body: _t("Enable desktop notifications to chat"),
displayName: sprintf(_t("%s has a request"), this.store.odoobot.name), displayName: _t("%s has a request", this.store.odoobot.name),
iconSrc: this.threadService.avatarUrl(this.store.odoobot), iconSrc: this.threadService.avatarUrl(this.store.odoobot),
partner: this.store.odoobot, partner: this.store.odoobot,
isLast: this.threads.length === 0 && this.store.notificationGroups.length === 0, isLast: this.threads.length === 0 && this.store.notificationGroups.length === 0,
@@ -4,7 +4,6 @@ import { Component } from "@odoo/owl";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
import { FormViewDialog } from "@web/views/view_dialogs/form_view_dialog"; import { FormViewDialog } from "@web/views/view_dialogs/form_view_dialog";
/** /**
@@ -23,10 +22,7 @@ export class SuggestedRecipient extends Component {
} }
get titleText() { get titleText() {
return sprintf( return _t("Add as recipient and follower (reason: %s)", this.props.recipient.reason);
_t("Add as recipient and follower (reason: %s)"),
this.props.recipient.reason
);
} }
onChangeCheckbox(ev) { onChangeCheckbox(ev) {
@@ -5,7 +5,6 @@ import { Component, onMounted, onWillUnmount, useState } from "@odoo/owl";
import { browser } from "@web/core/browser/browser"; import { browser } from "@web/core/browser/browser";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
const PROTOCOLS_TEXT = { host: "HOST", srflx: "STUN", prflx: "STUN", relay: "TURN" }; const PROTOCOLS_TEXT = { host: "HOST", srflx: "STUN", prflx: "STUN", relay: "TURN" };
@@ -34,7 +33,7 @@ export class CallContextMenu extends Component {
if (!this.props.rtcSession.remoteCandidateType) { if (!this.props.rtcSession.remoteCandidateType) {
return _t("no connection"); return _t("no connection");
} }
return sprintf(_t("%(candidateType)s (%(protocol)s)"), { return _t("%(candidateType)s (%(protocol)s)", {
candidateType: this.props.rtcSession.remoteCandidateType, candidateType: this.props.rtcSession.remoteCandidateType,
protocol: PROTOCOLS_TEXT[this.props.rtcSession.remoteCandidateType], protocol: PROTOCOLS_TEXT[this.props.rtcSession.remoteCandidateType],
}); });
@@ -44,7 +43,7 @@ export class CallContextMenu extends Component {
if (!this.props.rtcSession.localCandidateType) { if (!this.props.rtcSession.localCandidateType) {
return _t("no connection"); return _t("no connection");
} }
return sprintf(_t("%(candidateType)s (%(protocol)s)"), { return _t("%(candidateType)s (%(protocol)s)", {
candidateType: this.props.rtcSession.localCandidateType, candidateType: this.props.rtcSession.localCandidateType,
protocol: PROTOCOLS_TEXT[this.props.rtcSession.localCandidateType], protocol: PROTOCOLS_TEXT[this.props.rtcSession.localCandidateType],
}); });
@@ -11,7 +11,6 @@ import { reactive } from "@odoo/owl";
import { browser } from "@web/core/browser/browser"; import { browser } from "@web/core/browser/browser";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { sprintf } from "@web/core/utils/strings";
import { debounce } from "@web/core/utils/timing"; import { debounce } from "@web/core/utils/timing";
const ORDERED_TRANSCEIVER_NAMES = ["audio", "video"]; const ORDERED_TRANSCEIVER_NAMES = ["audio", "video"];
@@ -492,7 +491,7 @@ export class Rtc {
if (session.raisingHand) { if (session.raisingHand) {
this.addCallNotification({ this.addCallNotification({
id: notificationId, id: notificationId,
text: sprintf(_t("%s raised a hand"), session.name), text: _t("%s raised a hand", session.name),
}); });
} else { } else {
this.removeCallNotification(notificationId); this.removeCallNotification(notificationId);
@@ -1236,9 +1235,9 @@ export class Rtc {
} catch { } catch {
const str = const str =
type === "camera" type === "camera"
? _t('%s" requires "camera" access') ? _t('%s" requires "camera" access', window.location.host)
: _t('%s" requires "screen recording" access'); : _t('%s" requires "screen recording" access', window.location.host);
this.notification.add(sprintf(str, window.location.host), { type: "warning" }); this.notification.add(str, { type: "warning" });
stopVideo(); stopVideo();
return; return;
} }
@@ -1252,10 +1251,7 @@ export class Rtc {
videoStream = await this.blurManager.stream; videoStream = await this.blurManager.stream;
} catch (_e) { } catch (_e) {
this.notification.add( this.notification.add(
sprintf(_t("%(name)s: %(message)s)"), { _t("%(name)s: %(message)s)", { name: _e.name, message: _e.message }),
name: _e.name,
message: _e.message,
}),
{ type: "warning" } { type: "warning" }
); );
this.userSettingsService.useBlur = false; this.userSettingsService.useBlur = false;
@@ -1330,7 +1326,7 @@ export class Rtc {
audioTrack = audioStream.getAudioTracks()[0]; audioTrack = audioStream.getAudioTracks()[0];
} catch { } catch {
this.notification.add( this.notification.add(
sprintf(_t('"%(hostname)s" requires microphone access'), { _t('"%(hostname)s" requires microphone access', {
hostname: window.location.host, hostname: window.location.host,
}), }),
{ type: "warning" } { type: "warning" }
@@ -8,7 +8,6 @@ import { markup, reactive, useState } from "@odoo/owl";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
export class DiscussCoreCommon { export class DiscussCoreCommon {
/** /**
@@ -45,7 +44,7 @@ export class DiscussCoreCommon {
}); });
if (invitedByUserId && invitedByUserId !== this.store.user?.user?.id) { if (invitedByUserId && invitedByUserId !== this.store.user?.user?.id) {
this.notificationService.add( this.notificationService.add(
sprintf(_t("You have been invited to #%s"), thread.displayName), _t("You have been invited to #%s", thread.displayName),
{ type: "info" } { type: "info" }
); );
} }
@@ -69,10 +68,9 @@ export class DiscussCoreCommon {
if (thread.localId === this.store.discuss.threadLocalId) { if (thread.localId === this.store.discuss.threadLocalId) {
this.store.discuss.threadLocalId = undefined; this.store.discuss.threadLocalId = undefined;
} }
this.notificationService.add( this.notificationService.add(_t("You unsubscribed from %s.", thread.displayName), {
sprintf(_t("You unsubscribed from %s."), thread.displayName), type: "info",
{ type: "info" } });
);
}); });
this.busService.subscribe("discuss.channel/legacy_insert", (payload) => { this.busService.subscribe("discuss.channel/legacy_insert", (payload) => {
this.threadService.insert({ this.threadService.insert({
@@ -112,7 +110,7 @@ export class DiscussCoreCommon {
if (thread) { if (thread) {
thread.is_pinned = false; thread.is_pinned = false;
this.notificationService.add( this.notificationService.add(
sprintf(_t("You unpinned your conversation with %s"), thread.displayName), _t("You unpinned your conversation with %s", thread.displayName),
{ type: "info" } { type: "info" }
); );
} }
@@ -5,7 +5,6 @@ import { reactive } from "@odoo/owl";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { sprintf } from "@web/core/utils/strings";
export class DiscussCoreWeb { export class DiscussCoreWeb {
/** /**
@@ -73,8 +72,8 @@ export class DiscussCoreWeb {
// If the current user invited a new user, and the new user is // If the current user invited a new user, and the new user is
// connecting for the first time while the current user is present // connecting for the first time while the current user is present
// then open a chat for the current user with the new user. // then open a chat for the current user with the new user.
const notification = sprintf( const notification = _t(
_t("%(user)s connected. This is their first connection. Wish them luck."), "%(user)s connected. This is their first connection. Wish them luck.",
{ user: username } { user: username }
); );
this.notificationService.add(notification, { type: "info" }); this.notificationService.add(notification, { type: "info" });
@@ -8,7 +8,6 @@ import { markup, reactive, useState } from "@odoo/owl";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
export const OTHER_LONG_TYPING = 60000; export const OTHER_LONG_TYPING = 60000;
@@ -159,10 +158,9 @@ export class MessagePin {
confirmText: _t("Yeah, pin it!"), confirmText: _t("Yeah, pin it!"),
message: message, message: message,
messageComponent: Message, messageComponent: Message,
prompt: sprintf( prompt: _t("You sure want this message pinned to %(conversation)s forever and ever?", {
_t("You sure want this message pinned to %(conversation)s forever and ever?"), conversation: thread.prefix + thread.displayName,
{ conversation: thread.prefix + thread.displayName } }),
),
size: "md", size: "md",
title: _t("Pin It"), title: _t("Pin It"),
onConfirm: () => this.setPin(message, true), onConfirm: () => this.setPin(message, true),
@@ -6,7 +6,6 @@ import { useTypingService } from "@mail/discuss/typing/common/typing_service";
import { Component } from "@odoo/owl"; import { Component } from "@odoo/owl";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { sprintf } from "@web/core/utils/strings";
/** /**
* @typedef {Object} Props * @typedef {Object} Props
@@ -34,19 +33,11 @@ export class Typing extends Component {
.getTypingMembers(this.props.channel) .getTypingMembers(this.props.channel)
.map(({ persona }) => this.props.channel.getMemberName(persona)); .map(({ persona }) => this.props.channel.getMemberName(persona));
if (typingMemberNames.length === 1) { if (typingMemberNames.length === 1) {
return sprintf(_t("%s is typing..."), typingMemberNames[0]); return _t("%s is typing...", typingMemberNames[0]);
} }
if (typingMemberNames.length === 2) { if (typingMemberNames.length === 2) {
return sprintf( return _t("%s and %s are typing...", typingMemberNames[0], typingMemberNames[1]);
_t("%s and %s are typing..."),
typingMemberNames[0],
typingMemberNames[1]
);
} }
return sprintf( return _t("%s, %s and more are typing...", typingMemberNames[0], typingMemberNames[1]);
_t("%s, %s and more are typing..."),
typingMemberNames[0],
typingMemberNames[1]
);
} }
} }
@@ -1,7 +1,6 @@
/* @odoo-module */ /* @odoo-module */
import { Component, useState, onWillUnmount } from "@odoo/owl"; import { Component, useState, onWillUnmount } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { browser } from "@web/core/browser/browser"; import { browser } from "@web/core/browser/browser";
import { Mp3Encoder } from "./mp3_encoder"; import { Mp3Encoder } from "./mp3_encoder";
@@ -85,7 +84,7 @@ export class VoiceRecorder extends Component {
}); });
} catch { } catch {
this.notification.add( this.notification.add(
sprintf(_t('"%(hostname)s" needs to access your microphone'), { _t('"%(hostname)s" needs to access your microphone', {
hostname: window.location.host, hostname: window.location.host,
}), }),
{ type: "warning" } { type: "warning" }
@@ -5,7 +5,6 @@ import { useMessaging } from "@mail/core/common/messaging_hook";
import { Component, useState } from "@odoo/owl"; import { Component, useState } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
import { useModel } from "@web/model/model"; import { useModel } from "@web/model/model";
import { extractFieldsFromArchInfo } from "@web/model/relational_model/utils"; import { extractFieldsFromArchInfo } from "@web/model/relational_model/utils";
import { CogMenu } from "@web/search/cog_menu/cog_menu"; import { CogMenu } from "@web/search/cog_menu/cog_menu";
@@ -48,7 +47,7 @@ export class ActivityController extends Component {
resModel: this.props.resModel, resModel: this.props.resModel,
searchViewId: this.env.searchModel.searchViewId, searchViewId: this.env.searchModel.searchViewId,
domain: this.model.originalDomain, domain: this.model.originalDomain,
title: sprintf(this.env._t("Search: %s"), this.props.archInfo.title), title: this.env._t("Search: %s", this.props.archInfo.title),
multiSelect: false, multiSelect: false,
context: this.props.context, context: this.props.context,
onSelected: async (resIds) => { onSelected: async (resIds) => {
@@ -5,7 +5,6 @@ import { onMounted } from "@odoo/owl";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { TagsList } from "@web/core/tags_list/tags_list"; import { TagsList } from "@web/core/tags_list/tags_list";
import { sprintf } from "@web/core/utils/strings";
import { import {
Many2ManyTagsField, Many2ManyTagsField,
many2ManyTagsField, many2ManyTagsField,
@@ -63,7 +62,7 @@ export class FieldMany2ManyTagsEmail extends Many2ManyTagsField {
this.openMany2xRecord({ this.openMany2xRecord({
resId: record.resId, resId: record.resId,
context: this.props.context, context: this.props.context,
title: sprintf(_t("Edit: %s"), record.data.display_name), title: _t("Edit: %s", record.data.display_name),
}) })
); );
} }
@@ -1,7 +1,7 @@
/** @odoo-module **/ /** @odoo-module **/
import ajax from "@web/legacy/js/core/ajax"; import ajax from "@web/legacy/js/core/ajax";
import { sprintf, escape } from "@web/core/utils/strings"; import { escape } from "@web/core/utils/strings";
import { whenReady } from "@odoo/owl"; import { whenReady } from "@odoo/owl";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import session from "web.session"; import session from "web.session";
@@ -45,10 +45,12 @@ whenReady(() => {
var unsubscribed_list = $("input[name='unsubscribed_list']").val(); var unsubscribed_list = $("input[name='unsubscribed_list']").val();
if (unsubscribed_list){ if (unsubscribed_list){
$('#subscription_info').html(sprintf( $("#subscription_info").html(
_t("You have been <strong>successfully unsubscribed from %s</strong>."), _t(
"You have been <strong>successfully unsubscribed from %s</strong>.",
escape(unsubscribed_list) escape(unsubscribed_list)
)); )
)
} }
else{ else{
$('#subscription_info').html(_t('You have been <strong>successfully unsubscribed</strong>.')); $('#subscription_info').html(_t('You have been <strong>successfully unsubscribed</strong>.'));
@@ -1,6 +1,6 @@
/** @odoo-module **/ /** @odoo-module **/
import { escape, sprintf } from "@web/core/utils/strings"; import { escape } from "@web/core/utils/strings";
import core from "@web/legacy/js/services/core"; import core from "@web/legacy/js/services/core";
import Dialog from "@web/legacy/js/core/dialog"; import Dialog from "@web/legacy/js/core/dialog";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
@@ -77,7 +77,7 @@
const $checkedRadios = this.$('input[name="o_payment_radio"]:checked'); const $checkedRadios = this.$('input[name="o_payment_radio"]:checked');
if ($checkedRadios.length !== 1) { // Cannot find selected payment option, show dialog if ($checkedRadios.length !== 1) { // Cannot find selected payment option, show dialog
return new Dialog(null, { return new Dialog(null, {
title: sprintf(_t("Error: %s"), title), title: _t("Error: %s", title),
size: 'medium', size: 'medium',
$content: `<p>${escape(description) || ''}</p>`, $content: `<p>${escape(description) || ''}</p>`,
buttons: [{text: _t("Ok"), close: true}] buttons: [{text: _t("Ok"), close: true}]
@@ -1,7 +1,6 @@
/** @odoo-module */ /** @odoo-module */
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { sprintf } from "@web/core/utils/strings";
import { renderToElement } from "@web/core/utils/render"; import { renderToElement } from "@web/core/utils/render";
import { useAutofocus, useService } from "@web/core/utils/hooks"; import { useAutofocus, useService } from "@web/core/utils/hooks";
import { parseFloat, InvalidNumberError } from "@web/views/fields/parsers"; import { parseFloat, InvalidNumberError } from "@web/views/fields/parsers";
@@ -50,10 +49,7 @@ export class CashMovePopup extends AbstractAwaitablePopup {
} }
const formattedAmount = this.env.utils.formatCurrency(amount); const formattedAmount = this.env.utils.formatCurrency(amount);
if (!amount) { if (!amount) {
this.notification.add( this.notification.add(_t("Cash in/out of %s is ignored.", formattedAmount), 3000);
sprintf(_t("Cash in/out of %s is ignored."), formattedAmount),
3000
);
return this.props.close(); return this.props.close();
} }
@@ -96,7 +92,7 @@ export class CashMovePopup extends AbstractAwaitablePopup {
} }
this.props.close(); this.props.close();
this.notification.add( this.notification.add(
sprintf(this.env._t("Successfully made a cash %s of %s."), type, formattedAmount), this.env._t("Successfully made a cash %s of %s.", type, formattedAmount),
3000 3000
); );
} }
@@ -12,7 +12,6 @@ import { ConnectionLostError } from "@web/core/network/rpc_service";
import { identifyError } from "@point_of_sale/app/errors/error_handlers"; import { identifyError } from "@point_of_sale/app/errors/error_handlers";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { usePos } from "@point_of_sale/app/store/pos_hook"; import { usePos } from "@point_of_sale/app/store/pos_hook";
import { sprintf } from "@web/core/utils/strings";
import { parseFloat } from "@web/views/fields/parsers"; import { parseFloat } from "@web/views/fields/parsers";
import { useValidateCashInput } from "@point_of_sale/app/utils/hooks"; import { useValidateCashInput } from "@point_of_sale/app/utils/hooks";
@@ -63,11 +62,9 @@ export class ClosePosPopup extends AbstractAwaitablePopup {
} else { } else {
await this.popup.add(ConfirmPopup, { await this.popup.add(ConfirmPopup, {
title: this.env._t("Payments Difference"), title: this.env._t("Payments Difference"),
body: sprintf( body: this.env._t(
this.env._t(
"The maximum difference allowed is %s.\n\ "The maximum difference allowed is %s.\n\
Please contact your manager to accept the closing difference." Please contact your manager to accept the closing difference.",
),
this.env.utils.formatCurrency(this.amountAuthorizedDiff) this.env.utils.formatCurrency(this.amountAuthorizedDiff)
), ),
confirmText: this.env._t("OK"), confirmText: this.env._t("OK"),
@@ -8,7 +8,6 @@ import { PartnerLine } from "@point_of_sale/app/screens/partner_list/partner_lin
import { PartnerDetailsEdit } from "@point_of_sale/app/screens/partner_list/partner_editor/partner_editor"; import { PartnerDetailsEdit } from "@point_of_sale/app/screens/partner_list/partner_editor/partner_editor";
import { usePos } from "@point_of_sale/app/store/pos_hook"; import { usePos } from "@point_of_sale/app/store/pos_hook";
import { Component, onWillUnmount, useRef, useState } from "@odoo/owl"; import { Component, onWillUnmount, useRef, useState } from "@odoo/owl";
import { sprintf } from "@web/core/utils/strings";
/** /**
* Render this screen using `showTempScreen` to select partner. * Render this screen using `showTempScreen` to select partner.
@@ -128,16 +127,12 @@ export class PartnerListScreen extends Component {
const result = await this.searchPartner(); const result = await this.searchPartner();
if (result.length > 0) { if (result.length > 0) {
this.notification.add( this.notification.add(
sprintf( this.env._t('%s customer(s) found for "%s".', result.length, this.state.query),
this.env._t('%s customer(s) found for "%s".'),
result.length,
this.state.query
),
3000 3000
); );
} else { } else {
this.notification.add( this.notification.add(
sprintf(this.env._t('No more customer found for "%s".'), this.state.query), this.env._t('No more customer found for "%s".', this.state.query),
3000 3000
); );
} }
@@ -17,7 +17,6 @@ import { PaymentScreenPaymentLines } from "@point_of_sale/app/screens/payment_sc
import { PaymentScreenStatus } from "@point_of_sale/app/screens/payment_screen/payment_status/payment_status"; import { PaymentScreenStatus } from "@point_of_sale/app/screens/payment_screen/payment_status/payment_status";
import { usePos } from "@point_of_sale/app/store/pos_hook"; import { usePos } from "@point_of_sale/app/store/pos_hook";
import { Component, useState } from "@odoo/owl"; import { Component, useState } from "@odoo/owl";
import { sprintf } from "@web/core/utils/strings";
export class PaymentScreen extends Component { export class PaymentScreen extends Component {
static template = "point_of_sale.PaymentScreen"; static template = "point_of_sale.PaymentScreen";
@@ -343,8 +342,8 @@ export class PaymentScreen extends Component {
const paymentMethod = splitPayments[0].payment_method; const paymentMethod = splitPayments[0].payment_method;
const { confirmed } = await this.popup.add(ConfirmPopup, { const { confirmed } = await this.popup.add(ConfirmPopup, {
title: this.env._t("Customer Required"), title: this.env._t("Customer Required"),
body: sprintf( body: this.env._t(
this.env._t("Customer is required for %s payment method."), "Customer is required for %s payment method.",
paymentMethod.name paymentMethod.name
), ),
}); });
@@ -8,7 +8,6 @@ import { ConnectionLostError, ConnectionAbortedError } from "@web/core/network/r
import { ProductItem } from "@point_of_sale/app/screens/product_screen/product/product"; import { ProductItem } from "@point_of_sale/app/screens/product_screen/product/product";
import { ProductsWidgetControlPanel } from "@point_of_sale/app/screens/product_screen/product_list/control_panel/control_panel"; import { ProductsWidgetControlPanel } from "@point_of_sale/app/screens/product_screen/product_list/control_panel/control_panel";
import { Component, useState } from "@odoo/owl"; import { Component, useState } from "@odoo/owl";
import { sprintf } from "@web/core/utils/strings";
import { OfflineErrorPopup } from "@point_of_sale/app/errors/popups/offline_error_popup"; import { OfflineErrorPopup } from "@point_of_sale/app/errors/popups/offline_error_popup";
import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup"; import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup";
@@ -81,16 +80,12 @@ export class ProductsWidget extends Component {
const result = await this.loadProductFromDB(); const result = await this.loadProductFromDB();
if (result.length > 0) { if (result.length > 0) {
this.notification.add( this.notification.add(
sprintf( this.env._t('%s product(s) found for "%s".', result.length, searchProductWord),
this.env._t('%s product(s) found for "%s".'),
result.length,
searchProductWord
),
3000 3000
); );
} else { } else {
this.notification.add( this.notification.add(
sprintf(this.env._t('No more product found for "%s".'), searchProductWord), this.env._t('No more product found for "%s".', searchProductWord),
3000 3000
); );
} }
@@ -1,7 +1,6 @@
/** @odoo-module */ /** @odoo-module */
import { Component } from "@odoo/owl"; import { Component } from "@odoo/owl";
import { sprintf } from "@web/core/utils/strings";
import { formatFloat } from "@web/views/fields/formatters"; import { formatFloat } from "@web/views/fields/formatters";
import { roundPrecision as round_pr } from "@web/core/utils/numbers"; import { roundPrecision as round_pr } from "@web/core/utils/numbers";
import { usePos } from "@point_of_sale/app/store/pos_hook"; import { usePos } from "@point_of_sale/app/store/pos_hook";
@@ -72,9 +71,9 @@ export class OrderlineDetails extends Component {
return this.env.utils.formatProductQty(toRefundDetail && toRefundDetail.qty); return this.env.utils.formatProductQty(toRefundDetail && toRefundDetail.qty);
} }
getRefundingMessage() { getRefundingMessage() {
return sprintf(this.env._t("Refunding %s in "), this.getFormattedToRefundQty()); return this.env._t("Refunding %s in ", this.getFormattedToRefundQty());
} }
getToRefundMessage() { getToRefundMessage() {
return sprintf(this.env._t("To Refund: %s"), this.getFormattedToRefundQty()); return this.env._t("To Refund: %s", this.getFormattedToRefundQty());
} }
} }
@@ -18,7 +18,6 @@ import { ReprintReceiptButton } from "@point_of_sale/app/screens/ticket_screen/r
import { SearchBar } from "@point_of_sale/app/screens/ticket_screen/search_bar/search_bar"; import { SearchBar } from "@point_of_sale/app/screens/ticket_screen/search_bar/search_bar";
import { usePos } from "@point_of_sale/app/store/pos_hook"; import { usePos } from "@point_of_sale/app/store/pos_hook";
import { Component, onMounted, useState } from "@odoo/owl"; import { Component, onMounted, useState } from "@odoo/owl";
import { sprintf } from "@web/core/utils/strings";
const { DateTime } = luxon; const { DateTime } = luxon;
@@ -128,10 +127,8 @@ export class TicketScreen extends Component {
) { ) {
const { confirmed } = await this.popup.add(ConfirmPopup, { const { confirmed } = await this.popup.add(ConfirmPopup, {
title: this.env._t("Existing orderlines"), title: this.env._t("Existing orderlines"),
body: sprintf( body: this.env._t(
this.env._t( "%s has a total amount of %s, are you sure you want to delete this order?",
"%s has a total amount of %s, are you sure you want to delete this order?"
),
order.name, order.name,
this.getTotal(order) this.getTotal(order)
), ),
@@ -212,10 +209,8 @@ export class TicketScreen extends Component {
this.numberBuffer.reset(); this.numberBuffer.reset();
this.popup.add(ErrorPopup, { this.popup.add(ErrorPopup, {
title: this.env._t("Maximum Exceeded"), title: this.env._t("Maximum Exceeded"),
body: sprintf( body: this.env._t(
this.env._t( "The requested quantity to be refunded is higher than the ordered quantity. %s is requested while only %s can be refunded.",
"The requested quantity to be refunded is higher than the ordered quantity. %s is requested while only %s can be refunded."
),
quantity, quantity,
refundableQty refundableQty
), ),
@@ -14,7 +14,6 @@ import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup";
import { ProductConfiguratorPopup } from "@point_of_sale/app/store/product_configurator_popup/product_configurator_popup"; import { ProductConfiguratorPopup } from "@point_of_sale/app/store/product_configurator_popup/product_configurator_popup";
import { EditListPopup } from "@point_of_sale/app/store/select_lot_popup/select_lot_popup"; import { EditListPopup } from "@point_of_sale/app/store/select_lot_popup/select_lot_popup";
import { ConfirmPopup } from "@point_of_sale/app/utils/confirm_popup/confirm_popup"; import { ConfirmPopup } from "@point_of_sale/app/utils/confirm_popup/confirm_popup";
import { sprintf } from "@web/core/utils/strings";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { renderToElement } from "@web/core/utils/render"; import { renderToElement } from "@web/core/utils/render";
@@ -573,10 +572,8 @@ export class Orderline extends PosModel {
} else { } else {
this.env.services.popup.add(ErrorPopup, { this.env.services.popup.add(ErrorPopup, {
title: _t("Greater than allowed"), title: _t("Greater than allowed"),
body: sprintf( body: _t(
_t( "The requested quantity to be refunded is higher than the refundable quantity of %s.",
"The requested quantity to be refunded is higher than the refundable quantity of %s."
),
this.env.utils.formatProductQty(maxQtyToRefund) this.env.utils.formatProductQty(maxQtyToRefund)
), ),
}); });
@@ -1267,7 +1264,7 @@ export class Order extends PosModel {
this.access_token = uuidv4(); // unique uuid used to identify the authenticity of the request from the QR code. this.access_token = uuidv4(); // unique uuid used to identify the authenticity of the request from the QR code.
this.ticketCode = this._generateTicketCode(); // 5-digits alphanum code shown on the receipt this.ticketCode = this._generateTicketCode(); // 5-digits alphanum code shown on the receipt
this.uid = this.generate_unique_id(); this.uid = this.generate_unique_id();
this.name = sprintf(_t("Order %s"), this.uid); this.name = _t("Order %s", this.uid);
this.validation_date = undefined; this.validation_date = undefined;
this.fiscal_position = this.pos.fiscal_positions.find(function (fp) { this.fiscal_position = this.pos.fiscal_positions.find(function (fp) {
return fp.id === self.pos.config.default_fiscal_position_id[0]; return fp.id === self.pos.config.default_fiscal_position_id[0];
@@ -1314,7 +1311,7 @@ export class Order extends PosModel {
if (json.name) { if (json.name) {
this.name = json.name; this.name = json.name;
} else { } else {
this.name = sprintf(_t("Order %s"), this.uid); this.name = _t("Order %s", this.uid);
} }
this.validation_date = json.creation_date; this.validation_date = json.creation_date;
this.server_id = json.server_id || json.id || false; this.server_id = json.server_id || json.id || false;
@@ -16,7 +16,6 @@ import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup";
import { ConnectionLostError } from "@web/core/network/rpc_service"; import { ConnectionLostError } from "@web/core/network/rpc_service";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { CashOpeningPopup } from "@point_of_sale/app/store/cash_opening_popup/cash_opening_popup"; import { CashOpeningPopup } from "@point_of_sale/app/store/cash_opening_popup/cash_opening_popup";
import { sprintf } from "@web/core/utils/strings";
import { PaymentScreen } from "@point_of_sale/app/screens/payment_screen/payment_screen"; import { PaymentScreen } from "@point_of_sale/app/screens/payment_screen/payment_screen";
import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen"; import { ProductScreen } from "@point_of_sale/app/screens/product_screen/product_screen";
import { renderToString } from "@web/core/utils/render"; import { renderToString } from "@web/core/utils/render";
@@ -790,8 +789,8 @@ export class PosStore extends Reactive {
const pricelistsNames = pricelistsJson.map((pricelist) => { const pricelistsNames = pricelistsJson.map((pricelist) => {
return pricelist.display_name; return pricelist.display_name;
}); });
message = sprintf( message = _t(
_t("%s fiscal position(s) added to the configuration."), "%s fiscal position(s) added to the configuration.",
pricelistsNames.join(", ") pricelistsNames.join(", ")
); );
return message; return message;
@@ -830,8 +829,8 @@ export class PosStore extends Reactive {
const fiscalPositionNames = fiscalPositionJson.map((fp) => { const fiscalPositionNames = fiscalPositionJson.map((fp) => {
return fp.display_name; return fp.display_name;
}); });
message = sprintf( message = _t(
_t("%s fiscal position(s) added to the configuration."), "%s fiscal position(s) added to the configuration.",
fiscalPositionNames.join(", ") fiscalPositionNames.join(", ")
); );
return message; return message;
@@ -1704,10 +1703,8 @@ export class PosStore extends Reactive {
// FIXME POSREF this looks like it's dead code. // FIXME POSREF this looks like it's dead code.
reject({ reject({
title: _t("HTTPS connection to IoT Box failed"), title: _t("HTTPS connection to IoT Box failed"),
body: sprintf( body: _t(
_t( "Make sure you are using IoT Box v18.12 or higher. Navigate to %s to accept the certificate of your IoT Box.",
"Make sure you are using IoT Box v18.12 or higher. Navigate to %s to accept the certificate of your IoT Box."
),
url url
), ),
popup: "alert", popup: "alert",
@@ -1774,10 +1771,8 @@ export class PosStore extends Reactive {
if (currentPartner && currentOrder.getHasRefundLines()) { if (currentPartner && currentOrder.getHasRefundLines()) {
this.popup.add(ErrorPopup, { this.popup.add(ErrorPopup, {
title: _t("Can't change customer"), title: _t("Can't change customer"),
body: sprintf( body: _t(
_t( "This order already has refund lines for %s. We can't change the customer associated to it. Create a new order for the new customer.",
"This order already has refund lines for %s. We can't change the customer associated to it. Create a new order for the new customer."
),
currentPartner.name currentPartner.name
), ),
}); });
@@ -1,7 +1,7 @@
/** @odoo-module **/ /** @odoo-module **/
import { escape, sprintf } from "@web/core/utils/strings";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { escape } from "@web/core/utils/strings";
import ajax from "@web/legacy/js/core/ajax"; import ajax from "@web/legacy/js/core/ajax";
import core from "@web/legacy/js/services/core"; import core from "@web/legacy/js/services/core";
import publicWidget from "@web/legacy/js/public/public_widget"; import publicWidget from "@web/legacy/js/public/public_widget";
@@ -125,8 +125,7 @@ var PortalComposer = publicWidget.Widget.extend({
resolve(); resolve();
}).guardedCatch(function (error) { }).guardedCatch(function (error) {
self.displayNotification({ self.displayNotification({
message: sprintf(_t("Could not save file <strong>%s</strong>"), message: _t("Could not save file <strong>%s</strong>", escape(file.name)),
escape(file.name)),
type: 'warning', type: 'warning',
sticky: true, sticky: true,
}); });
@@ -3,7 +3,6 @@
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import publicWidget from "@web/legacy/js/public/public_widget"; import publicWidget from "@web/legacy/js/public/public_widget";
import session from "web.session"; import session from "web.session";
import { sprintf } from "@web/core/utils/strings";
import { deserializeDateTime } from "@web/core/l10n/dates"; import { deserializeDateTime } from "@web/core/l10n/dates";
const { DateTime } = luxon; const { DateTime } = luxon;
@@ -41,9 +40,9 @@ var PortalSidebar = publicWidget.Widget.extend({
} else if (diff > 0) { } else if (diff > 0) {
// Workaround: force uniqueness of these two translations. We use %1d because the string // Workaround: force uniqueness of these two translations. We use %1d because the string
// with %d is already used in mail and mail's translations are not sent to the frontend. // with %d is already used in mail and mail's translations are not sent to the frontend.
displayStr = sprintf(_t('Due in %s days'), Math.abs(diff).toFixed(1)); displayStr = _t('Due in %s days', Math.abs(diff).toFixed(1));
} else { } else {
displayStr = sprintf(_t('%s days overdue'), Math.abs(diff).toFixed(1)); displayStr = _t('%s days overdue', Math.abs(diff).toFixed(1));
} }
$(el).text(displayStr); $(el).text(displayStr);
}); });
@@ -269,7 +269,7 @@ export class PaymentAdyen extends PaymentInterface {
resolve(true); resolve(true);
} else { } else {
var message = additional_response.get("message"); var message = additional_response.get("message");
self._show_error(sprintf(_t("Message from Adyen: %s"), message)); self._show_error(_t("Message from Adyen: %s", message));
// this means the transaction was cancelled by pressing the cancel button on the device // this means the transaction was cancelled by pressing the cancel button on the device
if (message.startsWith("108 ")) { if (message.startsWith("108 ")) {
@@ -308,9 +308,7 @@ export class PaymentAdyen extends PaymentInterface {
msg = params.get("message"); msg = params.get("message");
} }
this._show_error( this._show_error(_t("An unexpected error occurred. Message from Adyen: %s", msg));
sprintf(_t("An unexpected error occurred. Message from Adyen: %s"), msg)
);
if (line) { if (line) {
line.set_payment_status("force_done"); line.set_payment_status("force_done");
} }
@@ -5,7 +5,6 @@ import { useService } from "@web/core/utils/hooks";
import { NumberPopup } from "@point_of_sale/app/utils/input_popups/number_popup"; import { NumberPopup } from "@point_of_sale/app/utils/input_popups/number_popup";
import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup"; import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup";
import { Component } from "@odoo/owl"; import { Component } from "@odoo/owl";
import { sprintf } from "@web/core/utils/strings";
import { usePos } from "@point_of_sale/app/store/pos_hook"; import { usePos } from "@point_of_sale/app/store/pos_hook";
export class DiscountButton extends Component { export class DiscountButton extends Component {
@@ -73,8 +72,8 @@ export class DiscountButton extends Component {
description: description:
`${pc}%, ` + `${pc}%, ` +
(tax_ids_array.length (tax_ids_array.length
? sprintf( ? this.env._t(
this.env._t("Tax: %s"), "Tax: %s",
tax_ids_array tax_ids_array
.map((taxId) => this.pos.taxes_by_id[taxId].amount + "%") .map((taxId) => this.pos.taxes_by_id[taxId].amount + "%")
.join(", ") .join(", ")
@@ -2,7 +2,6 @@
import { BasePrinter } from "@point_of_sale/app/printer/base_printer"; import { BasePrinter } from "@point_of_sale/app/printer/base_printer";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { sprintf } from "@web/core/utils/strings";
import { templates } from "@web/core/assets"; import { templates } from "@web/core/assets";
import { createElement, append, createTextNode } from "@web/core/utils/xml"; import { createElement, append, createTextNode } from "@web/core/utils/xml";
@@ -166,10 +165,8 @@ export class EpsonPrinter extends BasePrinter {
}, },
}; };
if (window.location.protocol === "https:") { if (window.location.protocol === "https:") {
printRes.message.body += sprintf( printRes.message.body += _t(
_t( "If you are on a secure server (HTTPS) please make sure you manually accepted the certificate by accessing %s",
"If you are on a secure server (HTTPS) please make sure you manually accepted the certificate by accessing %s"
),
this.url this.url
); );
} }
@@ -3,7 +3,6 @@
import { usePos } from "@point_of_sale/app/store/pos_hook"; import { usePos } from "@point_of_sale/app/store/pos_hook";
import { PartnerLine } from "@point_of_sale/app/screens/partner_list/partner_line/partner_line"; import { PartnerLine } from "@point_of_sale/app/screens/partner_list/partner_line/partner_line";
import { patch } from "@web/core/utils/patch"; import { patch } from "@web/core/utils/patch";
import { sprintf } from "@web/core/utils/strings";
import { formatFloat } from "@web/views/fields/formatters"; import { formatFloat } from "@web/views/fields/formatters";
patch(PartnerLine.prototype, { patch(PartnerLine.prototype, {
@@ -20,6 +19,6 @@ patch(PartnerLine.prototype, {
if (program.portal_visible) { if (program.portal_visible) {
return `${balanceRepr} ${program.portal_point_name}`; return `${balanceRepr} ${program.portal_point_name}`;
} }
return sprintf(this.env._t("%s Points"), balanceRepr); return this.env._t("%s Points", balanceRepr);
}, },
}); });
@@ -5,7 +5,6 @@ import { useBarcodeReader } from "@point_of_sale/app/barcode/barcode_reader_hook
import { patch } from "@web/core/utils/patch"; import { patch } from "@web/core/utils/patch";
import { ConfirmPopup } from "@point_of_sale/app/utils/confirm_popup/confirm_popup"; import { ConfirmPopup } from "@point_of_sale/app/utils/confirm_popup/confirm_popup";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
patch(ProductScreen.prototype, { patch(ProductScreen.prototype, {
setup() { setup() {
@@ -43,10 +42,8 @@ patch(ProductScreen.prototype, {
const reward = this.pos.reward_by_id[selectedLine.reward_id]; const reward = this.pos.reward_by_id[selectedLine.reward_id];
const { confirmed } = await this.popup.add(ConfirmPopup, { const { confirmed } = await this.popup.add(ConfirmPopup, {
title: this.env._t("Deactivating reward"), title: this.env._t("Deactivating reward"),
body: sprintf( body: this.env._t(
this.env._t( "Are you sure you want to remove %s from this order?\n You will still be able to claim it through the reward button.",
"Are you sure you want to remove %s from this order?\n You will still be able to claim it through the reward button."
),
reward.description reward.description
), ),
cancelText: this.env._t("No"), cancelText: this.env._t("No"),
@@ -7,7 +7,6 @@ import { roundDecimals, roundPrecision } from "@web/core/utils/numbers";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { patch } from "@web/core/utils/patch"; import { patch } from "@web/core/utils/patch";
import { ConfirmPopup } from "@point_of_sale/app/utils/confirm_popup/confirm_popup"; import { ConfirmPopup } from "@point_of_sale/app/utils/confirm_popup/confirm_popup";
import { sprintf } from "@web/core/utils/strings";
// FIXME: Perhaps MutexedDropPrevious can be replaced by the new KeepLast. // FIXME: Perhaps MutexedDropPrevious can be replaced by the new KeepLast.
// > This might require thorough investigation on how _updateRewards work. // > This might require thorough investigation on how _updateRewards work.
@@ -1556,8 +1555,8 @@ patch(Order.prototype, {
} }
} }
if (!rule && this.orderlines.length === 0 && coupon) { if (!rule && this.orderlines.length === 0 && coupon) {
return sprintf( return _t(
_t("Gift Card: %s\nBalance: %s"), "Gift Card: %s\nBalance: %s",
code, code,
this.env.utils.formatCurrency(coupon.balance) this.env.utils.formatCurrency(coupon.balance)
); );
@@ -7,7 +7,6 @@ import { SelectionPopup } from "@point_of_sale/app/utils/input_popups/selection_
import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup"; import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup";
import { TextInputPopup } from "@point_of_sale/app/utils/input_popups/text_input_popup"; import { TextInputPopup } from "@point_of_sale/app/utils/input_popups/text_input_popup";
import { Domain, InvalidDomainError } from "@web/core/domain"; import { Domain, InvalidDomainError } from "@web/core/domain";
import { sprintf } from "@web/core/utils/strings";
import { PosLoyaltyCard } from "@pos_loyalty/overrides/models/loyalty"; import { PosLoyaltyCard } from "@pos_loyalty/overrides/models/loyalty";
const COUPON_CACHE_MAX_SIZE = 4096; // Maximum coupon cache size, prevents long run memory issues and (to some extent) invalid data const COUPON_CACHE_MAX_SIZE = 4096; // Maximum coupon cache size, prevents long run memory issues and (to some extent) invalid data
@@ -245,10 +244,8 @@ patch(PosStore.prototype, {
if (index != -1) { if (index != -1) {
this.env.services.popup.add(ErrorPopup, { this.env.services.popup.add(ErrorPopup, {
title: _t("A reward could not be loaded"), title: _t("A reward could not be loaded"),
body: sprintf( body: _t(
_t( 'The reward "%s" contain an error in its domain, your domain must be compatible with the PoS client',
'The reward "%s" contain an error in its domain, your domain must be compatible with the PoS client'
),
this.rewards[index].description this.rewards[index].description
), ),
}); });
@@ -6,7 +6,6 @@ import { OnlinePaymentPopup } from "@pos_online_payment/app/utils/online_payment
import { ConfirmPopup } from "@point_of_sale/app/utils/confirm_popup/confirm_popup"; import { ConfirmPopup } from "@point_of_sale/app/utils/confirm_popup/confirm_popup";
import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup"; import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup";
import { floatIsZero } from "@web/core/utils/numbers"; import { floatIsZero } from "@web/core/utils/numbers";
import { sprintf } from "@web/core/utils/strings";
patch(PaymentScreen.prototype, { patch(PaymentScreen.prototype, {
getRemainingOnlinePaymentLines() { getRemainingOnlinePaymentLines() {
@@ -23,7 +22,11 @@ patch(PaymentScreen.prototype, {
if (amount <= 0) { if (amount <= 0) {
this.popup.add(ErrorPopup, { this.popup.add(ErrorPopup, {
title: this.env._t("Invalid online payment"), title: this.env._t("Invalid online payment"),
body: sprintf(this.env._t("Online payments cannot have a negative amount (%s: %s)."), line.payment_method.name, this.env.utils.formatCurrency(amount)), body: this.env._t(
"Online payments cannot have a negative amount (%s: %s).",
line.payment_method.name,
this.env.utils.formatCurrency(amount)
),
}); });
return false; return false;
} }
@@ -32,7 +35,11 @@ patch(PaymentScreen.prototype, {
if (!floatIsZero(unpaidAmount - remainingAmount, this.pos.currency.decimal_places)) { if (!floatIsZero(unpaidAmount - remainingAmount, this.pos.currency.decimal_places)) {
this.popup.add(ErrorPopup, { this.popup.add(ErrorPopup, {
title: this.env._t("Invalid online payments"), title: this.env._t("Invalid online payments"),
body: sprintf(this.env._t("The total amount of remaining online payments to execute (%s) doesn't correspond to the remaining unpaid amount of the order (%s)."), this.env.utils.formatCurrency(remainingAmount), this.env.utils.formatCurrency(unpaidAmount)), body: this.env._t(
"The total amount of remaining online payments to execute (%s) doesn't correspond to the remaining unpaid amount of the order (%s).",
this.env.utils.formatCurrency(remainingAmount),
this.env.utils.formatCurrency(unpaidAmount)
),
}); });
return false; return false;
} }
@@ -5,7 +5,6 @@ import { useService } from "@web/core/utils/hooks";
import { NumberPopup } from "@point_of_sale/app/utils/input_popups/number_popup"; import { NumberPopup } from "@point_of_sale/app/utils/input_popups/number_popup";
import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup"; import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup";
import { Component } from "@odoo/owl"; import { Component } from "@odoo/owl";
import { sprintf } from "@web/core/utils/strings";
import { usePos } from "@point_of_sale/app/store/pos_hook"; import { usePos } from "@point_of_sale/app/store/pos_hook";
export class TableGuestsButton extends Component { export class TableGuestsButton extends Component {
@@ -36,10 +35,7 @@ export class TableGuestsButton extends Component {
if (guestCount > max_capacity) { if (guestCount > max_capacity) {
await this.popup.add(ErrorPopup, { await this.popup.add(ErrorPopup, {
title: this.env._t("Blocked action"), title: this.env._t("Blocked action"),
body: sprintf( body: this.env._t("You cannot put a number that exceeds %s ", max_capacity),
this.env._t("You cannot put a number that exceeds %s "),
max_capacity
),
}); });
return; return;
} }
@@ -23,7 +23,6 @@ import {
useState, useState,
onWillStart, onWillStart,
} from "@odoo/owl"; } from "@odoo/owl";
import { sprintf } from "@web/core/utils/strings";
export class FloorScreen extends Component { export class FloorScreen extends Component {
static components = { EditableTable, EditBar, Table }; static components = { EditableTable, EditBar, Table };
@@ -526,8 +525,8 @@ export class FloorScreen extends Component {
if (this.selectedTables.length == 0) { if (this.selectedTables.length == 0) {
const { confirmed } = await this.popup.add(ConfirmPopup, { const { confirmed } = await this.popup.add(ConfirmPopup, {
title: `Removing floor ${this.activeFloor.name}`, title: `Removing floor ${this.activeFloor.name}`,
body: sprintf( body: this.env._t(
this.env._t("Removing a floor cannot be undone. Do you still wanna remove %s?"), "Removing a floor cannot be undone. Do you still wanna remove %s?",
this.activeFloor.name this.activeFloor.name
), ),
}); });
@@ -141,8 +141,8 @@ export class SaleOrderManagementScreen extends ControlButtonsMixin(Component) {
await this.pos._loadPartners([sale_order.partner_id[0]]); await this.pos._loadPartners([sale_order.partner_id[0]]);
} catch { } catch {
const title = this.env._t("Customer loading error"); const title = this.env._t("Customer loading error");
const body = sprintf( const body = this.env._t(
this.env._t("There was a problem in loading the %s customer."), "There was a problem in loading the %s customer.",
sale_order.partner_id[1] sale_order.partner_id[1]
); );
await this.popup.add(ErrorPopup, { title, body }); await this.popup.add(ErrorPopup, { title, body });
@@ -335,10 +335,8 @@ export class SaleOrderManagementScreen extends ControlButtonsMixin(Component) {
} }
if (down_payment > sale_order.amount_unpaid) { if (down_payment > sale_order.amount_unpaid) {
const errorBody = sprintf( const errorBody = this.env._t(
this.env._t( "You have tried to charge a down payment of %s but only %s remains to be paid, %s will be applied to the purchase order line.",
"You have tried to charge a down payment of %s but only %s remains to be paid, %s will be applied to the purchase order line."
),
this.env.utils.formatCurrency(down_payment), this.env.utils.formatCurrency(down_payment),
this.env.utils.formatCurrency(sale_order.amount_unpaid), this.env.utils.formatCurrency(sale_order.amount_unpaid),
sale_order.amount_unpaid > 0 sale_order.amount_unpaid > 0
@@ -4,7 +4,6 @@
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { PaymentInterface } from "@point_of_sale/app/payment/payment_interface"; import { PaymentInterface } from "@point_of_sale/app/payment/payment_interface";
import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup"; import { ErrorPopup } from "@point_of_sale/app/errors/popups/error_popup";
import { sprintf } from "@web/core/utils/strings";
export class PaymentStripe extends PaymentInterface { export class PaymentStripe extends PaymentInterface {
setup() { setup() {
@@ -38,7 +37,7 @@ export class PaymentStripe extends PaymentInterface {
async discoverReaders() { async discoverReaders() {
const discoverResult = await this.terminal.discoverReaders({}); const discoverResult = await this.terminal.discoverReaders({});
if (discoverResult.error) { if (discoverResult.error) {
this._showError(sprintf(_t("Failed to discover: %s"), discoverResult.error)); this._showError(_t("Failed to discover: %s", discoverResult.error));
} else if (discoverResult.discoveredReaders.length === 0) { } else if (discoverResult.discoveredReaders.length === 0) {
this._showError(_t("No available Stripe readers.")); this._showError(_t("No available Stripe readers."));
} else { } else {
@@ -108,8 +107,8 @@ export class PaymentStripe extends PaymentInterface {
} }
} }
this._showError( this._showError(
sprintf( _t(
_t("Stripe readers %s not listed in your account"), "Stripe readers %s not listed in your account",
this.payment_method.stripe_serial_number this.payment_method.stripe_serial_number
) )
); );
@@ -4,7 +4,6 @@
import { Component, markup, onRendered, onWillStart, useState } from "@odoo/owl"; import { Component, markup, onRendered, onWillStart, useState } from "@odoo/owl";
import { Layout } from "@web/search/layout"; import { Layout } from "@web/search/layout";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { sprintf } from "@web/core/utils/strings";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { useSetupAction } from "@web/webclient/actions/action_hook"; import { useSetupAction } from "@web/webclient/actions/action_hook";
@@ -123,8 +122,8 @@ export class ProductPricelistReport extends Component {
ev.preventDefault(); // avoid automatic reloading of the page ev.preventDefault(); // avoid automatic reloading of the page
if (this.quantities.length >= this.MAX_QTY) { if (this.quantities.length >= this.MAX_QTY) {
let message = sprintf( let message = this.env._t(
this.env._t("At most %s quantities can be displayed simultaneously. Remove a selected quantity to add others."), "At most %s quantities can be displayed simultaneously. Remove a selected quantity to add others.",
this.MAX_QTY this.MAX_QTY
); );
await this.action.doAction(sendCustomNotification("warning", message)); await this.action.doAction(sendCustomNotification("warning", message));
@@ -139,7 +138,7 @@ export class ProductPricelistReport extends Component {
this.quantities = this.quantities.sort((a, b) => a - b); this.quantities = this.quantities.sort((a, b) => a - b);
this.renderHtml(); this.renderHtml();
} else { } else {
let message = sprintf(this.env._t("Quantity already present (%s)."), qty); let message = this.env._t("Quantity already present (%s).", qty);
await this.action.doAction(sendCustomNotification("info", message)); await this.action.doAction(sendCustomNotification("info", message));
} }
} else { } else {
@@ -1,13 +1,12 @@
/** @odoo-module **/ /** @odoo-module **/
import { sprintf } from "@web/core/utils/strings";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { PriorityField, priorityField } from "@web/views/fields/priority/priority_field"; import { PriorityField, priorityField } from "@web/views/fields/priority/priority_field";
export class PrioritySwitchField extends PriorityField { export class PrioritySwitchField extends PriorityField {
get commands() { get commands() {
return this.options.map(([id, name]) => [ return this.options.map(([id, name]) => [
sprintf(this.env._t("Set priority as %s"), name), this.env._t("Set priority as %s", name),
() => this.updateRecord(id), () => this.updateRecord(id),
{ {
category: "smart_action", category: "smart_action",
@@ -4,7 +4,6 @@ import {
StateSelectionField, StateSelectionField,
stateSelectionField, stateSelectionField,
} from "@web/views/fields/state_selection/state_selection_field"; } from "@web/views/fields/state_selection/state_selection_field";
import { sprintf } from "@web/core/utils/strings";
import { useCommand } from "@web/core/commands/command_hook"; import { useCommand } from "@web/core/commands/command_hook";
import { formatSelection } from "@web/views/fields/formatters"; import { formatSelection } from "@web/views/fields/formatters";
@@ -44,7 +43,7 @@ export class ProjectTaskStateSelection extends StateSelectionField {
if (this.props.viewType != 'form') { if (this.props.viewType != 'form') {
super.setup(); super.setup();
} else { } else {
const commandName = sprintf(this.env._t(`Set state as...`)); const commandName = this.env._t("Set state as...");
useCommand( useCommand(
commandName, commandName,
() => { () => {
@@ -2,7 +2,6 @@
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { X2ManyField, x2ManyField } from "@web/views/fields/x2many/x2many_field"; import { X2ManyField, x2ManyField } from "@web/views/fields/x2many/x2many_field";
import { sprintf } from "@web/core/utils/strings";
import { useSelectCreate, useOpenMany2XRecord} from "@web/views/fields/relational_utils"; import { useSelectCreate, useOpenMany2XRecord} from "@web/views/fields/relational_utils";
export class SMLX2ManyField extends X2ManyField { export class SMLX2ManyField extends X2ManyField {
setup() { setup() {
@@ -35,7 +34,7 @@ export class SMLX2ManyField extends X2ManyField {
tree_view_ref: "stock.view_stock_quant_tree_simple", tree_view_ref: "stock.view_stock_quant_tree_simple",
}; };
const productName = this.props.record.data.product_id[1]; const productName = this.props.record.data.product_id[1];
const title = sprintf(this.env._t("Add line: %s"), productName); const title = this.env._t("Add line: %s", productName);
const alreadySelected = this.props.record.data.move_line_ids.records.filter((line) => line.data.quant_id?.[0]); const alreadySelected = this.props.record.data.move_line_ids.records.filter((line) => line.data.quant_id?.[0]);
const domain = [ const domain = [
["product_id", "=", this.props.record.data.product_id[0]], ["product_id", "=", this.props.record.data.product_id[0]],
@@ -2,7 +2,6 @@
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { sprintf } from '@web/core/utils/strings';
import { standardWidgetProps } from "@web/views/widgets/standard_widget_props"; import { standardWidgetProps } from "@web/views/widgets/standard_widget_props";
const { Component, useEffect, useRef, useState } = owl; const { Component, useEffect, useRef, useState } = owl;
@@ -21,21 +20,26 @@ export class SurveyQuestionTriggerWidget extends Component {
const triggerError = this.surveyQuestionTriggerError; const triggerError = this.surveyQuestionTriggerError;
if (triggerError === "MISPLACED_TRIGGER_WARNING") { if (triggerError === "MISPLACED_TRIGGER_WARNING") {
this.state.surveyIconWarning = true; this.state.surveyIconWarning = true;
this.state.triggerTooltip = sprintf( this.state.triggerTooltip = _t(
'⚠ ' + _t('This question is positioned before its trigger ("%s") and will be skipped.'), '⚠ This question is positioned before its trigger ("%s") and will be skipped.',
triggeringQuestionTitle); triggeringQuestionTitle
);
} else if (triggerError === "WRONG_QUESTIONS_SELECTION_WARNING") { } else if (triggerError === "WRONG_QUESTIONS_SELECTION_WARNING") {
this.state.surveyIconWarning = true; this.state.surveyIconWarning = true;
this.state.triggerTooltip = '⚠ ' + _t( this.state.triggerTooltip = _t(
'Conditional display is not available when questions are randomly picked.'); "⚠ Conditional display is not available when questions are randomly picked."
);
} else if (triggerError === "MISSING_TRIGGER_ERROR") { } else if (triggerError === "MISSING_TRIGGER_ERROR") {
// This case must be handled to not temporarily render the "normal" icon if previously // This case must be handled to not temporarily render the "normal" icon if previously
// on an error state, which would cause a flicker as the trigger itself will be removed // on an error state, which would cause a flicker as the trigger itself will be removed
// at next save (auto on survey form and primary list view). // at next save (auto on survey form and primary list view).
} else { } else {
this.state.surveyIconWarning = false; this.state.surveyIconWarning = false;
this.state.triggerTooltip = sprintf(_t('Displayed if "%s: %s"'), this.state.triggerTooltip = _t(
triggeringQuestionTitle, this.props.record.data.triggering_answer_id[1]); 'Displayed if "%s: %s"',
triggeringQuestionTitle,
this.props.record.data.triggering_answer_id[1]
);
} }
} else { } else {
this.state.surveyIconWarning = false; this.state.surveyIconWarning = false;
@@ -7,7 +7,6 @@ import { formatAST, toPyValue } from "@web/core/py_js/py_utils";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { TagsList } from "@web/core/tags_list/tags_list"; import { TagsList } from "@web/core/tags_list/tags_list";
import { useOwnedDialogs, useService } from "@web/core/utils/hooks"; import { useOwnedDialogs, useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
import { Expression } from "@web/core/domain_tree"; import { Expression } from "@web/core/domain_tree";
const SEARCH_LIMIT = 7; const SEARCH_LIMIT = 7;
@@ -89,7 +88,7 @@ class AutoCompleteWithSources extends Component {
const dynamicFilters = ids.length const dynamicFilters = ids.length
? [ ? [
{ {
description: sprintf(_t("Quick search: %s"), name), description: _t("Quick search: %s", name),
domain: [["id", operator, ids]], domain: [["id", operator, ids]],
}, },
] ]
@@ -97,7 +96,7 @@ class AutoCompleteWithSources extends Component {
// fine for now but we don't like this kind of dependence of core to views // fine for now but we don't like this kind of dependence of core to views
const SelectCreateDialog = registry.category("dialogs").get("select_create"); const SelectCreateDialog = registry.category("dialogs").get("select_create");
this.addDialog(SelectCreateDialog, { this.addDialog(SelectCreateDialog, {
title: sprintf(_t("Search: %s"), fieldString), title: _t("Search: %s", fieldString),
dynamicFilters, dynamicFilters,
resModel, resModel,
noCreate: true, noCreate: true,
@@ -140,13 +139,13 @@ const getFormat = (val, displayNames) => {
text = text =
typeof displayNames[val] === "string" typeof displayNames[val] === "string"
? displayNames[val] ? displayNames[val]
: sprintf(_t(`Inaccessible/missing record ID: %s`), val); : _t("Inaccessible/missing record ID: %s", val);
colorIndex = typeof displayNames[val] === "string" ? 0 : 2; // 0 = grey, 2 = orange colorIndex = typeof displayNames[val] === "string" ? 0 : 2; // 0 = grey, 2 = orange
} else { } else {
text = text =
val instanceof Expression val instanceof Expression
? String(val) ? String(val)
: sprintf(_t(`Invalid record ID: %s`), formatAST(toPyValue(val))); : _t("Invalid record ID: %s", formatAST(toPyValue(val)));
colorIndex = val instanceof Expression ? 2 : 1; // 1 = red colorIndex = val instanceof Expression ? 2 : 1; // 1 = red
} }
return { text, colorIndex }; return { text, colorIndex };
@@ -5,7 +5,6 @@ import { getDefaultValue, getDefaultOperator } from "./domain_selector_fields";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { unique, zip } from "@web/core/utils/arrays"; import { unique, zip } from "@web/core/utils/arrays";
import { sprintf } from "@web/core/utils/strings";
import { import {
Expression, Expression,
toValue, toValue,
@@ -183,7 +182,7 @@ function formatValue(val, disambiguate, fieldDef, displayNames) {
if (typeof displayNames[val] === "string") { if (typeof displayNames[val] === "string") {
val = displayNames[val]; val = displayNames[val];
} else { } else {
return sprintf(_t(`Inaccessible/missing record ID: %s`), val); return _t("Inaccessible/missing record ID: %s", val);
} }
} }
if (fieldDef?.type === "selection") { if (fieldDef?.type === "selection") {
@@ -1,7 +1,6 @@
/** @odoo-module **/ /** @odoo-module **/
import { useService } from "../utils/hooks"; import { useService } from "../utils/hooks";
import { sprintf } from "../utils/strings";
import { ConfirmationDialog } from "../confirmation_dialog/confirmation_dialog"; import { ConfirmationDialog } from "../confirmation_dialog/confirmation_dialog";
import { Component } from "@odoo/owl"; import { Component } from "@odoo/owl";
@@ -16,7 +15,10 @@ export class FileUploadProgressBar extends Component {
return; return;
} }
this.dialogService.add(ConfirmationDialog, { this.dialogService.add(ConfirmationDialog, {
body: sprintf(this.env._t("Do you really want to cancel the upload of %s?"), this.props.fileUpload.title), body: this.env._t(
"Do you really want to cancel the upload of %s?",
this.props.fileUpload.title
),
confirm: () => { confirm: () => {
this.props.fileUpload.xhr.abort(); this.props.fileUpload.xhr.abort();
}, },
@@ -1,6 +1,5 @@
/** @odoo-module **/ /** @odoo-module **/
import { sprintf } from "../utils/strings";
import { FileUploadProgressBar } from "./file_upload_progress_bar"; import { FileUploadProgressBar } from "./file_upload_progress_bar";
import { Component } from "@odoo/owl"; import { Component } from "@odoo/owl";
@@ -18,8 +17,8 @@ export class FileUploadProgressRecord extends Component {
const mbLoaded = Math.round(fileUpload.loaded / 1000000); const mbLoaded = Math.round(fileUpload.loaded / 1000000);
const mbTotal = Math.round(fileUpload.total / 1000000); const mbTotal = Math.round(fileUpload.total / 1000000);
return { return {
left: sprintf(this.env._t("Uploading... (%s%)"), percent), left: this.env._t("Uploading... (%s%)", percent),
right: sprintf(this.env._t("(%s/%sMB)"), mbLoaded, mbTotal), right: this.env._t("(%s/%sMB)", mbLoaded, mbTotal),
}; };
} }
} }
@@ -1,7 +1,6 @@
/** @odoo-module **/ /** @odoo-module **/
import { registry } from "../registry"; import { registry } from "../registry";
import { sprintf } from "../utils/strings";
import { EventBus, reactive } from "@odoo/owl"; import { EventBus, reactive } from "@odoo/owl";
@@ -56,7 +55,7 @@ export const fileUploadService = {
loaded: 0, loaded: 0,
total: 0, total: 0,
state: "pending", state: "pending",
title: files.length === 1 ? files[0].name : sprintf(env._t("%s Files"), files.length), title: files.length === 1 ? files[0].name : env._t("%s Files", files.length),
type: files.length === 1 ? files[0].type : undefined, type: files.length === 1 ? files[0].type : undefined,
}); });
uploads[upload.id] = upload; uploads[upload.id] = upload;
+1 -2
View File
@@ -3,7 +3,6 @@
import { localization } from "@web/core/l10n/localization"; import { localization } from "@web/core/l10n/localization";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { memoize } from "@web/core/utils/functions"; import { memoize } from "@web/core/utils/functions";
import { sprintf } from "@web/core/utils/strings";
import { ensureArray } from "../utils/arrays"; import { ensureArray } from "../utils/arrays";
const { DateTime, Settings } = luxon; const { DateTime, Settings } = luxon;
@@ -436,7 +435,7 @@ export function parseDateTime(value, options = {}) {
// No working parsing methods: throw an error // No working parsing methods: throw an error
if (!isValidDate(result)) { if (!isValidDate(result)) {
throw new ConversionError(sprintf(_t("'%s' is not a correct date or datetime"), value)); throw new ConversionError(_t("'%s' is not a correct date or datetime", value));
} }
// Revert to original numbering system // Revert to original numbering system
+19 -5
View File
@@ -1,6 +1,8 @@
/** @odoo-module **/ /** @odoo-module **/
import { Deferred } from "@web/core/utils/concurrency"; import { Deferred } from "@web/core/utils/concurrency";
import { sprintf } from "@web/core/utils/strings";
export const translationLoaded = Symbol("translationLoaded"); export const translationLoaded = Symbol("translationLoaded");
export const translatedTerms = { export const translatedTerms = {
[translationLoaded]: false, [translationLoaded]: false,
@@ -17,11 +19,15 @@ export const translationIsReady = new Deferred();
* @param {string} term * @param {string} term
* @returns {string} * @returns {string}
*/ */
export function _t(term) { export function _t(term, ...values) {
if (translatedTerms[translationLoaded]) { if (translatedTerms[translationLoaded]) {
return translatedTerms[term] || term; const translation = translatedTerms[term] ?? term;
if (values.length === 0) {
return translation;
}
return sprintf(translation, ...values);
} else { } else {
return new LazyTranslatedString(term); return new LazyTranslatedString(term, ...values);
} }
} }
@@ -35,13 +41,21 @@ export function _t(term) {
* @param {string} term * @param {string} term
* @returns {LazyTranslatedString} * @returns {LazyTranslatedString}
*/ */
export const _lt = (term) => _t(term); export const _lt = (term, ...values) => _t(term, ...values);
class LazyTranslatedString extends String { class LazyTranslatedString extends String {
constructor(term, ...values) {
super(term);
this.values = values;
}
valueOf() { valueOf() {
const term = super.valueOf(); const term = super.valueOf();
if (translatedTerms[translationLoaded]) { if (translatedTerms[translationLoaded]) {
return translatedTerms[term] || term; const translation = translatedTerms[term] ?? term;
if (this.values.length === 0) {
return translation;
}
return sprintf(translation, ...this.values);
} else { } else {
throw new Error(`translation error`); throw new Error(`translation error`);
} }
@@ -1,6 +1,5 @@
/** @odoo-module **/ /** @odoo-module **/
import { sprintf } from "@web/core/utils/strings";
import core from "@web/legacy/js/services/core"; import core from "@web/legacy/js/services/core";
import session from "web.session"; import session from "web.session";
import time from "@web/legacy/js/core/time"; import time from "@web/legacy/js/core/time";
@@ -133,7 +132,7 @@ export function parseDate(value, field, options) {
return date; return date;
} }
} }
throw new Error(sprintf(core._t("'%s' is not a correct date"), value)); throw new Error(core._t("'%s' is not a correct date", value));
} }
/** /**
@@ -186,5 +185,5 @@ export function parseDateTime(value, field, options) {
return datetime; return datetime;
} }
} }
throw new Error(sprintf(core._t("'%s' is not a correct datetime"), value)); throw new Error(core._t("'%s' is not a correct datetime", value));
} }
+2 -3
View File
@@ -1,6 +1,5 @@
/** @odoo-module **/ /** @odoo-module **/
import { sprintf } from "@web/core/utils/strings";
import translation from "@web/legacy/js/core/translation"; import translation from "@web/legacy/js/core/translation";
import utils from "@web/legacy/js/core/utils"; import utils from "@web/legacy/js/core/utils";
@@ -190,7 +189,7 @@ export function auto_str_to_date (value) {
try { try {
return str_to_time(value); return str_to_time(value);
} catch {} } catch {}
throw new Error(sprintf(_t("'%s' is not a correct date, datetime nor time"), value)); throw new Error(_t("'%s' is not a correct date, datetime nor time", value));
} }
export function auto_date_to_str (value, type) { export function auto_date_to_str (value, type) {
@@ -202,7 +201,7 @@ export function auto_date_to_str (value, type) {
case 'time': case 'time':
return time_to_str(value); return time_to_str(value);
default: default:
throw new Error(sprintf(_t("'%s' is not convertible to date, datetime nor time"), type)); throw new Error(_t("'%s' is not convertible to date, datetime nor time", type));
} }
} }
@@ -1,6 +1,7 @@
/** @odoo-module **/ /** @odoo-module **/
import { sprintf } from "@web/core/utils/strings";
import Class from "@web/legacy/js/core/class"; import Class from "@web/legacy/js/core/class";
var TranslationDataBase = Class.extend(/** @lends instance.TranslationDataBase# */{ var TranslationDataBase = Class.extend(/** @lends instance.TranslationDataBase# */{
@@ -39,9 +40,12 @@ var TranslationDataBase = Class.extend(/** @lends instance.TranslationDataBase#
}, },
build_translation_function: function() { build_translation_function: function() {
var self = this; var self = this;
var fcnt = function(str) { var fcnt = function(term, ...values) {
var tmp = self.get(str); const translation = self.get(term) ?? term;
return tmp === undefined ? str : tmp; if (values.length === 0) {
return translation;
}
return sprintf(translation, ...values);
}; };
fcnt.database = this; fcnt.database = this;
return fcnt; return fcnt;
@@ -2,7 +2,6 @@
import { AlertDialog } from "@web/core/confirmation_dialog/confirmation_dialog"; import { AlertDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { sprintf } from "@web/core/utils/strings";
import { DataPoint } from "./datapoint"; import { DataPoint } from "./datapoint";
import { Record } from "./record"; import { Record } from "./record";
@@ -200,8 +199,8 @@ export class DynamicList extends DataPoint {
resIds.length === this.model.activeIdsLimit && resIds.length === this.model.activeIdsLimit &&
resIds.length < this.count resIds.length < this.count
) { ) {
const msg = sprintf( const msg = _t(
_t(`Only the first %s records have been deleted (out of %s selected)`), `Only the first %s records have been deleted (out of %s selected)`,
resIds.length, resIds.length,
this.count this.count
); );
@@ -366,8 +365,8 @@ export class DynamicList extends DataPoint {
resIds.length === this.model.activeIdsLimit && resIds.length === this.model.activeIdsLimit &&
resIds.length < this.count resIds.length < this.count
) { ) {
const msg = sprintf( const msg = _t(
_t("Of the %s records selected, only the first %s have been archived/unarchived."), "Of the %s records selected, only the first %s have been archived/unarchived.",
resIds.length, resIds.length,
this.count this.count
); );
@@ -7,7 +7,6 @@ import { useService } from "@web/core/utils/hooks";
import { SearchBar } from "../search_bar/search_bar"; import { SearchBar } from "../search_bar/search_bar";
import { Dropdown } from "@web/core/dropdown/dropdown"; import { Dropdown } from "@web/core/dropdown/dropdown";
import { useCommand } from "@web/core/commands/command_hook"; import { useCommand } from "@web/core/commands/command_hook";
import { sprintf } from "@web/core/utils/strings";
import { DropdownItem } from "@web/core/dropdown/dropdown_item"; import { DropdownItem } from "@web/core/dropdown/dropdown_item";
import { Component, useState, onMounted, useExternalListener, useRef, useEffect } from "@odoo/owl"; import { Component, useState, onMounted, useExternalListener, useRef, useEffect } from "@odoo/owl";
@@ -35,7 +34,7 @@ export class ControlPanel extends Component {
const { viewSwitcherEntries, viewType } = this.env.config; const { viewSwitcherEntries, viewType } = this.env.config;
for (const view of viewSwitcherEntries || []) { for (const view of viewSwitcherEntries || []) {
useCommand( useCommand(
sprintf(this.env._t("Show %s view"), view.name), this.env._t("Show %s view", view.name),
() => this.onViewClicked(view.type), () => this.onViewClicked(view.type),
{ {
category: "view_switcher", category: "view_switcher",
+3 -4
View File
@@ -5,7 +5,6 @@ import { Domain } from "@web/core/domain";
import { evaluateExpr } from "@web/core/py_js/py"; import { evaluateExpr } from "@web/core/py_js/py";
import { sortBy } from "@web/core/utils/arrays"; import { sortBy } from "@web/core/utils/arrays";
import { deepCopy } from "@web/core/utils/objects"; import { deepCopy } from "@web/core/utils/objects";
import { sprintf } from "@web/core/utils/strings";
import { SearchArchParser } from "./search_arch_parser"; import { SearchArchParser } from "./search_arch_parser";
import { import {
constructDateDomain, constructDateDomain,
@@ -1501,7 +1500,7 @@ export class SearchModel extends EventBus {
return context; return context;
} catch (error) { } catch (error) {
throw new Error( throw new Error(
sprintf(_t("Failed to evaluate the context: %(context)s.\n%(error)s"), { _t("Failed to evaluate the context: %(context)s.\n%(error)s", {
context, context,
error: error.message, error: error.message,
}) })
@@ -1589,7 +1588,7 @@ export class SearchModel extends EventBus {
return params.raw ? domain : domain.toList(this.domainEvalContext); return params.raw ? domain : domain.toList(this.domainEvalContext);
} catch (error) { } catch (error) {
throw new Error( throw new Error(
sprintf(_t("Failed to evaluate the domain: %(domain)s.\n%(error)s"), { _t("Failed to evaluate the domain: %(domain)s.\n%(error)s", {
domain: domain.toString(), domain: domain.toString(),
error: error.message, error: error.message,
}) })
@@ -1991,7 +1990,7 @@ export class SearchModel extends EventBus {
} }
} catch (error) { } catch (error) {
throw new Error( throw new Error(
sprintf(_t("Failed to evaluate the context: %(context)s.\n%(error)s"), { _t("Failed to evaluate the context: %(context)s.\n%(error)s", {
context: searchItem.context, context: searchItem.context,
error: error.message, error: error.message,
}) })
@@ -3,7 +3,6 @@
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog"; import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { useOwnedDialogs, useService } from "@web/core/utils/hooks"; import { useOwnedDialogs, useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
import { Layout } from "@web/search/layout"; import { Layout } from "@web/search/layout";
import { useModelWithSampleData } from "@web/model/model"; import { useModelWithSampleData } from "@web/model/model";
import { FormViewDialog } from "@web/views/view_dialogs/form_view_dialog"; import { FormViewDialog } from "@web/views/view_dialogs/form_view_dialog";
@@ -175,7 +174,7 @@ export class CalendarController extends Component {
resModel: this.model.resModel, resModel: this.model.resModel,
resId: record.id || false, resId: record.id || false,
context, context,
title: record.id ? sprintf(_t("Open: %s"), record.title) : _t("New Event"), title: record.id ? _t("Open: %s", record.title) : _t("New Event"),
viewId: this.model.formViewId, viewId: this.model.formViewId,
onRecordSaved: () => this.model.load(), onRecordSaved: () => this.model.load(),
}, },
@@ -4,7 +4,6 @@ import { _t } from "@web/core/l10n/translation";
import { AutoComplete } from "@web/core/autocomplete/autocomplete"; import { AutoComplete } from "@web/core/autocomplete/autocomplete";
import { Transition } from "@web/core/transition"; import { Transition } from "@web/core/transition";
import { useOwnedDialogs, useService } from "@web/core/utils/hooks"; import { useOwnedDialogs, useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
import { SelectCreateDialog } from "@web/views/view_dialogs/select_create_dialog"; import { SelectCreateDialog } from "@web/views/view_dialogs/select_create_dialog";
import { getColor } from "../colors"; import { getColor } from "../colors";
import { Component, useState } from "@odoo/owl"; import { Component, useState } from "@odoo/owl";
@@ -29,7 +28,7 @@ export class CalendarFilterPanel extends Component {
return { return {
autoSelect: true, autoSelect: true,
resetOnSelect: true, resetOnSelect: true,
placeholder: sprintf(_t("+ Add %s"), section.label), placeholder: _t("+ Add %s", section.label),
sources: [ sources: [
{ {
placeholder: _t("Loading..."), placeholder: _t("Loading..."),
@@ -93,11 +92,11 @@ export class CalendarFilterPanel extends Component {
context: {}, context: {},
}); });
dynamicFilters.push({ dynamicFilters.push({
description: sprintf(_t("Quick search: %s"), request), description: _t("Quick search: %s", request),
domain: [["id", "in", nameGets.map((nameGet) => nameGet[0])]], domain: [["id", "in", nameGets.map((nameGet) => nameGet[0])]],
}); });
} }
const title = sprintf(_t("Search: %s"), section.label); const title = _t("Search: %s", section.label);
this.addDialog(SelectCreateDialog, { this.addDialog(SelectCreateDialog, {
title, title,
noCreate: true, noCreate: true,
@@ -1,7 +1,6 @@
/** @odoo-module **/ /** @odoo-module **/
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
import { getDataURLFromFile } from "@web/core/utils/urls"; import { getDataURLFromFile } from "@web/core/utils/urls";
import { session } from "@web/session"; import { session } from "@web/session";
import { formatFloat } from "./formatters"; import { formatFloat } from "./formatters";
@@ -32,14 +31,11 @@ export class FileUploader extends Component {
for (const file of ev.target.files) { for (const file of ev.target.files) {
if (file.size > this.maxUploadSize) { if (file.size > this.maxUploadSize) {
this.notification.add( this.notification.add(
sprintf( this.env._t(
this.env._t("The selected file exceed the maximum file size of %s."), "The selected file exceed the maximum file size of %s.",
formatFloat(this.maxUploadSize, { humanReadable: true }) formatFloat(this.maxUploadSize, { humanReadable: true })
), ),
{ { title: this.env._t("File upload"), type: "danger" }
title: this.env._t("File upload"),
type: "danger",
}
); );
} }
this.state.isUploading = true; this.state.isUploading = true;
@@ -4,7 +4,7 @@ import { formatDate, formatDateTime } from "@web/core/l10n/dates";
import { localization as l10n } from "@web/core/l10n/localization"; import { localization as l10n } from "@web/core/l10n/localization";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { escape, intersperse, nbsp, sprintf } from "@web/core/utils/strings"; import { escape, intersperse, nbsp } from "@web/core/utils/strings";
import { isBinarySize } from "@web/core/utils/binary"; import { isBinarySize } from "@web/core/utils/binary";
import { markup } from "@odoo/owl"; import { markup } from "@odoo/owl";
@@ -364,7 +364,7 @@ export function formatX2many(value) {
} else if (count === 1) { } else if (count === 1) {
return _t("1 record"); return _t("1 record");
} else { } else {
return sprintf(_t("%s records"), count); return _t("%s records", count);
} }
} }
@@ -8,7 +8,6 @@ import { _t } from "@web/core/l10n/translation";
import { evaluateExpr } from "@web/core/py_js/py"; import { evaluateExpr } from "@web/core/py_js/py";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { useChildRef, useOwnedDialogs, useService } from "@web/core/utils/hooks"; import { useChildRef, useOwnedDialogs, useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
import { Many2XAutocomplete, useOpenMany2XRecord } from "@web/views/fields/relational_utils"; import { Many2XAutocomplete, useOpenMany2XRecord } from "@web/views/fields/relational_utils";
import * as BarcodeScanner from "@web/webclient/barcode/barcode_scanner"; import * as BarcodeScanner from "@web/webclient/barcode/barcode_scanner";
import { standardFieldProps } from "../standard_field_props"; import { standardFieldProps } from "../standard_field_props";
@@ -20,7 +19,7 @@ class CreateConfirmationDialog extends Component {
static components = { Dialog }; static components = { Dialog };
get title() { get title() {
return sprintf(this.env._t("New: %s"), this.props.name); return this.env._t("New: %s", this.props.name);
} }
async onCreate() { async onCreate() {
@@ -10,7 +10,6 @@ import { DropdownItem } from "@web/core/dropdown/dropdown_item";
import { PropertyValue } from "./property_value"; import { PropertyValue } from "./property_value";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { usePopover } from "@web/core/popover/popover_hook"; import { usePopover } from "@web/core/popover/popover_hook";
import { sprintf } from "@web/core/utils/strings";
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog"; import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import { reposition } from "@web/core/position_hook"; import { reposition } from "@web/core/position_hook";
import { archParseBoolean } from "@web/views/utils"; import { archParseBoolean } from "@web/views/utils";
@@ -278,10 +277,8 @@ export class PropertiesField extends Component {
this.popover.close(); this.popover.close();
const dialogProps = { const dialogProps = {
title: _t("Delete Property Field"), title: _t("Delete Property Field"),
body: sprintf( body: _t(
_t( 'Are you sure you want to delete this property field? It will be removed for everyone using the "%s" %s.',
'Are you sure you want to delete this property field? It will be removed for everyone using the "%s" %s.'
),
this.parentName, this.parentName,
this.parentString this.parentString
), ),
@@ -325,7 +322,7 @@ export class PropertiesField extends Component {
propertiesDefinitions.push({ propertiesDefinitions.push({
name: uuid(), name: uuid(),
string: sprintf(_t("Property %s"), propertiesDefinitions.length + 1), string: _t("Property %s", propertiesDefinitions.length + 1),
type: "char", type: "char",
definition_changed: true, definition_changed: true,
}); });
@@ -583,7 +580,7 @@ async function actionAddProperty(env) {
if (addProperty) { if (addProperty) {
addProperty.click(); addProperty.click();
} else { } else {
const message = sprintf(env._t("You can not create a new property.")); const message = env._t("You can not create a new property.");
env.services.notification.add(message, { type: "danger" }); env.services.notification.add(message, { type: "danger" });
} }
} }
@@ -12,7 +12,6 @@ import { Many2XAutocomplete } from "@web/views/fields/relational_utils";
import { useService, useOwnedDialogs } from "@web/core/utils/hooks"; import { useService, useOwnedDialogs } from "@web/core/utils/hooks";
import { PropertyDefinitionSelection } from "./property_definition_selection"; import { PropertyDefinitionSelection } from "./property_definition_selection";
import { PropertyTags } from "./property_tags"; import { PropertyTags } from "./property_tags";
import { sprintf } from "@web/core/utils/strings";
import { SelectCreateDialog } from "@web/views/view_dialogs/select_create_dialog"; import { SelectCreateDialog } from "@web/views/view_dialogs/select_create_dialog";
import { uuid } from "../../utils"; import { uuid } from "../../utils";
@@ -341,8 +340,8 @@ export class PropertyDefinition extends Component {
this.state.resModelDescription = result[0].display_name; this.state.resModelDescription = result[0].display_name;
} catch { } catch {
// can not read the ir.model // can not read the ir.model
this.state.resModelDescription = sprintf( this.state.resModelDescription = _t(
_t('You do not have access to the model "%s".'), 'You do not have access to the model "%s".',
newModel newModel
); );
} }
@@ -6,7 +6,6 @@ import { _t } from "@web/core/l10n/translation";
import { usePopover } from "@web/core/popover/popover_hook"; import { usePopover } from "@web/core/popover/popover_hook";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
import { TagsList } from "@web/core/tags_list/tags_list"; import { TagsList } from "@web/core/tags_list/tags_list";
import { Component } from "@odoo/owl"; import { Component } from "@odoo/owl";
@@ -139,7 +138,7 @@ export class PropertyTags extends Component {
return [ return [
{ {
value: { toCreate: true, value: request }, value: { toCreate: true, value: request },
label: sprintf(_t('Create "%s"'), request), label: _t('Create "%s"', request),
classList: "o_field_property_dropdown_add", classList: "o_field_property_dropdown_add",
}, },
]; ];
@@ -13,7 +13,6 @@ import {
useOwnedDialogs, useOwnedDialogs,
useService, useService,
} from "@web/core/utils/hooks"; } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
import { createElement } from "@web/core/utils/xml"; import { createElement } from "@web/core/utils/xml";
import { FormArchParser } from "@web/views/form/form_arch_parser"; import { FormArchParser } from "@web/views/form/form_arch_parser";
import { loadSubViews } from "@web/views/form/form_controller"; import { loadSubViews } from "@web/views/form/form_controller";
@@ -283,7 +282,7 @@ export class Many2XAutocomplete extends Component {
if (this.props.quickCreate && request.length) { if (this.props.quickCreate && request.length) {
options.push({ options.push({
label: sprintf(this.env._t(`Create "%s"`), request), label: this.env._t('Create "%s"', request),
classList: "o_m2o_dropdown_option o_m2o_dropdown_option_create", classList: "o_m2o_dropdown_option o_m2o_dropdown_option_create",
action: async (params) => { action: async (params) => {
try { try {
@@ -363,13 +362,13 @@ export class Many2XAutocomplete extends Component {
dynamicFilters = [ dynamicFilters = [
{ {
description: sprintf(this.env._t("Quick search: %s"), request), description: this.env._t("Quick search: %s", request),
domain: [["id", "in", nameGets.map((nameGet) => nameGet[0])]], domain: [["id", "in", nameGets.map((nameGet) => nameGet[0])]],
}, },
]; ];
} }
const title = sprintf(this.env._t("Search: %s"), fieldString); const title = this.env._t("Search: %s", fieldString);
this.selectCreate({ this.selectCreate({
domain, domain,
context, context,
@@ -461,8 +460,7 @@ export function useOpenMany2XRecord({
let resolve = () => {}; let resolve = () => {};
if (!title) { if (!title) {
title = resId ? env._t("Open: %s") : env._t("Create %s"); title = resId ? env._t("Open: %s", fieldString) : env._t("Create %s", fieldString);
title = sprintf(title, fieldString);
} }
const { create: canCreate, write: canWrite } = activeActions; const { create: canCreate, write: canWrite } = activeActions;
@@ -708,8 +706,9 @@ export function useOpenX2ManyRecord({
async function openRecord({ record, mode, context, title, onClose }) { async function openRecord({ record, mode, context, title, onClose }) {
if (!title) { if (!title) {
title = record ? env._t("Open: %s") : env._t("Create %s"); title = record
title = sprintf(title, activeField.string); ? env._t("Open: %s", activeField.string)
: env._t("Create %s", activeField.string);
} }
const list = getList(); const list = getList();
const { archInfo, fields: _fields } = await getFormViewInfo({ const { archInfo, fields: _fields } = await getFormViewInfo({
@@ -5,7 +5,6 @@ import { formatDate, formatDateTime } from "@web/core/l10n/dates";
import { localization } from "@web/core/l10n/localization"; import { localization } from "@web/core/l10n/localization";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { sprintf } from "@web/core/utils/strings";
import { DateTimeField } from "../datetime/datetime_field"; import { DateTimeField } from "../datetime/datetime_field";
import { standardFieldProps } from "../standard_field_props"; import { standardFieldProps } from "../standard_field_props";
@@ -45,9 +44,9 @@ export class RemainingDaysField extends Component {
return this.formattedValue; return this.formattedValue;
} }
if (this.diffDays < 0) { if (this.diffDays < 0) {
return sprintf(_t("%s days ago"), -this.diffDays); return _t("%s days ago", -this.diffDays);
} }
return sprintf(_t("In %s days"), this.diffDays); return _t("In %s days", this.diffDays);
} }
get formattedValue() { get formattedValue() {
@@ -6,7 +6,6 @@ import { Dropdown } from "@web/core/dropdown/dropdown";
import { DropdownItem } from "@web/core/dropdown/dropdown_item"; import { DropdownItem } from "@web/core/dropdown/dropdown_item";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { sprintf } from "@web/core/utils/strings";
import { formatSelection } from "../formatters"; import { formatSelection } from "../formatters";
import { standardFieldProps } from "../standard_field_props"; import { standardFieldProps } from "../standard_field_props";
@@ -36,7 +35,7 @@ export class StateSelectionField extends Component {
const hotkeys = ["D", "F", "G"]; const hotkeys = ["D", "F", "G"];
for (const [index, [value, label]] of this.options.entries()) { for (const [index, [value, label]] of this.options.entries()) {
useCommand( useCommand(
sprintf(this.env._t("Set kanban state as %s"), label), this.env._t("Set kanban state as %s", label),
() => { () => {
this.updateRecord(value); this.updateRecord(value);
}, },
@@ -8,7 +8,7 @@ import { DropdownItem } from "@web/core/dropdown/dropdown_item";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { groupBy } from "@web/core/utils/arrays"; import { groupBy } from "@web/core/utils/arrays";
import { escape, sprintf } from "@web/core/utils/strings"; import { escape } from "@web/core/utils/strings";
import { useSpecialData } from "@web/views/fields/relational_utils"; import { useSpecialData } from "@web/views/fields/relational_utils";
import { standardFieldProps } from "../standard_field_props"; import { standardFieldProps } from "../standard_field_props";
@@ -35,7 +35,7 @@ export class StatusBarField extends Component {
setup() { setup() {
if (this.props.withCommand) { if (this.props.withCommand) {
const commandName = sprintf(this.env._t(`Move to %s...`), escape(this.displayName)); const commandName = this.env._t("Move to %s...", escape(this.displayName));
useCommand( useCommand(
commandName, commandName,
() => { () => {
@@ -61,7 +61,7 @@ export class StatusBarField extends Component {
} }
); );
useCommand( useCommand(
sprintf(this.env._t(`Move to next %s`), this.displayName), this.env._t("Move to next %s", this.displayName),
() => { () => {
const options = this.computeItems(false); const options = this.computeItems(false);
const nextOption = const nextOption =
@@ -2,7 +2,6 @@
import { Dialog } from "@web/core/dialog/dialog"; import { Dialog } from "@web/core/dialog/dialog";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
import { loadLanguages } from "@web/core/l10n/translation"; import { loadLanguages } from "@web/core/l10n/translation";
import { Component, onWillStart } from "@odoo/owl"; import { Component, onWillStart } from "@odoo/owl";
@@ -10,7 +9,7 @@ import { Component, onWillStart } from "@odoo/owl";
export class TranslationDialog extends Component { export class TranslationDialog extends Component {
setup() { setup() {
super.setup(); super.setup();
this.title = sprintf(this.env._t("Translate: %s"), this.props.fieldName); this.title = this.env._t("Translate: %s", this.props.fieldName);
this.orm = useService("orm"); this.orm = useService("orm");
this.user = useService("user"); this.user = useService("user");
@@ -5,7 +5,6 @@ import { evalDomain } from "@web/core/domain";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { Pager } from "@web/core/pager/pager"; import { Pager } from "@web/core/pager/pager";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { sprintf } from "@web/core/utils/strings";
import { import {
useActiveActions, useActiveActions,
useAddInlineRecord, useAddInlineRecord,
@@ -258,7 +257,7 @@ export class X2ManyField extends Component {
context = makeContext([this.props.context, context]); context = makeContext([this.props.context, context]);
if (this.isMany2Many) { if (this.isMany2Many) {
const { string } = this.props; const { string } = this.props;
const title = sprintf(this.env._t("Add: %s"), string); const title = this.env._t("Add: %s", string);
return this.selectCreate({ domain, context, title }); return this.selectCreate({ domain, context, title });
} }
if (editable) { if (editable) {
@@ -7,7 +7,6 @@ import { getGroupBy } from "@web/search/utils/group_by";
import { GROUPABLE_TYPES } from "@web/search/utils/misc"; import { GROUPABLE_TYPES } from "@web/search/utils/misc";
import { Model } from "@web/model/model"; import { Model } from "@web/model/model";
import { computeReportMeasures, processMeasure } from "@web/views/utils"; import { computeReportMeasures, processMeasure } from "@web/views/utils";
import { sprintf } from "@web/core/utils/strings";
import { Domain } from "@web/core/domain"; import { Domain } from "@web/core/domain";
export const SEP = " / "; export const SEP = " / ";
@@ -306,7 +305,7 @@ export class GraphModel extends Model {
if (!description) { if (!description) {
return this.env._t("Sum"); return this.env._t("Sum");
} else { } else {
return sprintf(this.env._t("Sum (%s)"), description); return this.env._t("Sum (%s)", description);
} }
} }
@@ -7,7 +7,6 @@ import { DropdownItem } from "@web/core/dropdown/dropdown_item";
import { usePopover } from "@web/core/popover/popover_hook"; import { usePopover } from "@web/core/popover/popover_hook";
import { memoize } from "@web/core/utils/functions"; import { memoize } from "@web/core/utils/functions";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
import { useDebounced } from "@web/core/utils/timing"; import { useDebounced } from "@web/core/utils/timing";
import { isRelational } from "@web/model/relational_model/utils"; import { isRelational } from "@web/model/relational_model/utils";
import { isNull } from "@web/views/utils"; import { isNull } from "@web/views/utils";
@@ -168,7 +167,7 @@ export class KanbanHeader extends Component {
context, context,
resId: value, resId: value,
resModel: groupByField.relation, resModel: groupByField.relation,
title: sprintf(this.env._t("Edit: %s"), displayName), title: this.env._t("Edit: %s", displayName),
onRecordSaved: async () => { onRecordSaved: async () => {
await this.props.list.load(); await this.props.list.load();
this.props.list.model.notify(); this.props.list.model.notify();
@@ -7,7 +7,6 @@ import { DropdownItem } from "@web/core/dropdown/dropdown_item";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { useTooltip } from "@web/core/tooltip/tooltip_hook"; import { useTooltip } from "@web/core/tooltip/tooltip_hook";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
import { url } from "@web/core/utils/urls"; import { url } from "@web/core/utils/urls";
import { useRecordObserver } from "@web/model/relational_model/utils"; import { useRecordObserver } from "@web/model/relational_model/utils";
import { Field } from "@web/views/fields/field"; import { Field } from "@web/views/fields/field";
@@ -334,10 +333,8 @@ export class KanbanRecord extends Component {
) { ) {
this.dialog.add(KanbanCoverImageDialog, { autoOpen, fieldName, record }); this.dialog.add(KanbanCoverImageDialog, { autoOpen, fieldName, record });
} else { } else {
const warning = sprintf( const warning = env._t(
env._t( `Could not set the cover image: incorrect field ("%s") is provided in the view.`,
`Could not set the cover image: incorrect field ("%s") is provided in the view.`
),
fieldName fieldName
); );
this.notification.add({ title: warning, type: "danger" }); this.notification.add({ title: warning, type: "danger" });
@@ -6,7 +6,6 @@ import { useHotkey } from "@web/core/hotkeys/hotkey_hook";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { useBus, useService } from "@web/core/utils/hooks"; import { useBus, useService } from "@web/core/utils/hooks";
import { useSortable } from "@web/core/utils/sortable"; import { useSortable } from "@web/core/utils/sortable";
import { sprintf } from "@web/core/utils/strings";
import { isNull } from "@web/views/utils"; import { isNull } from "@web/views/utils";
import { ColumnProgress } from "@web/views/view_components/column_progress"; import { ColumnProgress } from "@web/views/view_components/column_progress";
import { useBounceButton } from "@web/views/view_hook"; import { useBounceButton } from "@web/views/view_hook";
@@ -329,7 +328,7 @@ export class KanbanRenderer extends Component {
if (this.exampleData && this.exampleData.ghostColumns) { if (this.exampleData && this.exampleData.ghostColumns) {
colNames = this.exampleData.ghostColumns; colNames = this.exampleData.ghostColumns;
} else { } else {
colNames = [1, 2, 3, 4].map((num) => sprintf(this.env._t("Column %s"), num)); colNames = [1, 2, 3, 4].map((num) => this.env._t("Column %s", num));
} }
return colNames.map((colName) => ({ return colNames.map((colName) => ({
name: colName, name: colName,
@@ -7,7 +7,6 @@ import { evaluateExpr } from "@web/core/py_js/py";
import { registry } from "@web/core/registry"; import { registry } from "@web/core/registry";
import { Deferred, KeepLast } from "@web/core/utils/concurrency"; import { Deferred, KeepLast } from "@web/core/utils/concurrency";
import { useBus, useService } from "@web/core/utils/hooks"; import { useBus, useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
import { cleanDomFromBootstrap } from "@web/legacy/utils"; import { cleanDomFromBootstrap } from "@web/legacy/utils";
import { View, ViewNotFoundError } from "@web/views/view"; import { View, ViewNotFoundError } from "@web/views/view";
import { ActionDialog } from "./action_dialog"; import { ActionDialog } from "./action_dialog";
@@ -1306,10 +1305,7 @@ function makeActionManager(env) {
const view = _getView(viewType); const view = _getView(viewType);
if (!view) { if (!view) {
throw new ViewNotFoundError( throw new ViewNotFoundError(
sprintf( env._t("No view of type '%s' could be found in the current action.", viewType)
env._t("No view of type '%s' could be found in the current action."),
viewType
)
); );
} }
const newController = controller.action.controllers[viewType] || { const newController = controller.action.controllers[viewType] || {
@@ -33,10 +33,7 @@ class InvalidAction extends Component {
} }
onMounted() { onMounted() {
const message = sprintf( const message = this.env._t("No action with id '%s' could be found", this.props.actionId);
this.env._t("No action with id '%s' could be found"),
this.props.actionId
);
this.notification.add(message, { type: "danger" }); this.notification.add(message, { type: "danger" });
} }
} }
@@ -4,7 +4,6 @@ import { registry } from "@web/core/registry";
import { _t } from "@web/core/l10n/translation"; import { _t } from "@web/core/l10n/translation";
import { unique } from "@web/core/utils/arrays"; import { unique } from "@web/core/utils/arrays";
import { useService } from "@web/core/utils/hooks"; import { useService } from "@web/core/utils/hooks";
import { sprintf } from "@web/core/utils/strings";
import { Component, useState, onWillStart } from "@odoo/owl"; import { Component, useState, onWillStart } from "@odoo/owl";
import { standardWidgetProps } from "@web/views/widgets/standard_widget_props"; import { standardWidgetProps } from "@web/views/widgets/standard_widget_props";
@@ -70,15 +69,15 @@ class ResConfigInviteUsers extends Component {
}); });
switch (invalidEmails.length) { switch (invalidEmails.length) {
case 1: case 1:
return sprintf(_t("Invalid email address: %(address)s"), { return _t("Invalid email address: %(address)s", {
address: invalidEmails[0], address: invalidEmails[0],
}); });
case 2: case 2:
return sprintf(_t("Invalid email addresses: %(2 addresses)s"), { return _t("Invalid email addresses: %(2 addresses)s", {
"2 addresses": listFormatter.format(invalidEmails), "2 addresses": listFormatter.format(invalidEmails),
}); });
default: default:
return sprintf(_t("Invalid email addresses: %(addresses)s"), { return _t("Invalid email addresses: %(addresses)s", {
addresses: listFormatter.format(invalidEmails), addresses: listFormatter.format(invalidEmails),
}); });
} }
@@ -171,3 +171,23 @@ QUnit.test("tamil has the correct numbering system", async (assert) => {
"௧௦/௧௨/௨௦௨௧ ௧௨:௦௦:௦௦" "௧௦/௧௨/௨௦௨௧ ௧௨:௦௦:௦௦"
); );
}); });
QUnit.test(
"_t fills the format specifiers in translated terms with its extra arguments",
async (assert) => {
patchWithCleanup(translatedTerms, { "Due in %s days": "Échéance dans %s jours" });
const translatedStr = _t("Due in %s days", 513);
assert.strictEqual(translatedStr, "Échéance dans 513 jours");
}
);
QUnit.test(
"_t fills the format specifiers in lazy translated terms with its extra arguments",
async (assert) => {
translatedTerms[translationLoaded] = false;
const translatedStr = _t("Due in %s days", 513);
patchWithCleanup(translatedTerms, { "Due in %s days": "Échéance dans %s jours" });
translatedTerms[translationLoaded] = true;
assert.equal(translatedStr, "Échéance dans 513 jours");
}
);
@@ -7,7 +7,6 @@ import dom from "@web/legacy/js/core/dom";
import Dialog from "@web/legacy/js/core/dialog"; import Dialog from "@web/legacy/js/core/dialog";
import Widget from "@web/legacy/js/core/widget"; import Widget from "@web/legacy/js/core/widget";
import localStorage from "@web/legacy/js/core/local_storage"; import localStorage from "@web/legacy/js/core/local_storage";
import { sprintf } from "@web/core/utils/strings";
import { debounce } from "@web/core/utils/timing"; import { debounce } from "@web/core/utils/timing";
import { sortBy } from "@web/core/utils/arrays"; import { sortBy } from "@web/core/utils/arrays";
import { pick } from "@web/core/utils/objects"; import { pick } from "@web/core/utils/objects";
@@ -429,11 +428,11 @@ var ViewEditor = Widget.extend({
this.aceEditor.setSession(editingSession); this.aceEditor.setSession(editingSession);
if (this.currentType === 'xml') { if (this.currentType === 'xml') {
this.$viewID.text(sprintf(_t("Template ID: %s"), this.views[resID].key)); this.$viewID.text(_t("Template ID: %s", this.views[resID].key));
} else if (this.currentType === 'scss') { } else if (this.currentType === 'scss') {
this.$viewID.text(sprintf(_t("SCSS file: %s"), resID)); this.$viewID.text(_t("SCSS file: %s", resID));
} else { } else {
this.$viewID.text(sprintf(_t("JS file: %s"), resID)); this.$viewID.text(_t("JS file: %s", resID));
} }
const isCustomized = this._isCustomResource(resID); const isCustomized = this._isCustomResource(resID);
this.$lists[this.currentType].select2('val', resID); this.$lists[this.currentType].select2('val', resID);

Some files were not shown because too many files have changed in this diff Show More