[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
@@ -12,7 +12,6 @@ import {OptimizeSEODialog} from '@website/components/dialog/seo';
import { WebsiteDialog } from "@website/components/dialog/dialog";
import { routeToUrl } from "@web/core/browser/router_service";
import { getActiveHotkey } from "@web/core/hotkeys/hotkey_service";
import { sprintf } from "@web/core/utils/strings";
import wUtils from '@website/js/utils';
import config from "@web/legacy/js/services/config";
@@ -70,10 +69,7 @@ export class WebsitePreview extends Component {
// same session and CORS errors won't be a thing in such a case)
this.dialogService.add(WebsiteDialog, {
title: this.env._t("Redirecting..."),
body: sprintf(this.env._t(
"You are about to be redirected to the domain configured for your website ( %s ). " +
"This is necessary to edit or view your website from the Website app. You might need to log back in."
), this.websiteDomain),
body: this.env._t("You are about to be redirected to the domain configured for your website ( %s ). This is necessary to edit or view your website from the Website app. You might need to log back in.", this.websiteDomain),
showSecondaryButton: false,
}, {
onClose: () => {
@@ -5,7 +5,6 @@ import {registry} from '@web/core/registry';
import {listView} from '@web/views/list/list_view';
import {ConfirmationDialog} from "@web/core/confirmation_dialog/confirmation_dialog";
import {useService} from "@web/core/utils/hooks";
import {sprintf} from "@web/core/utils/strings";
import {DeletePageDialog} from '@website/components/dialog/page_properties';
import {SearchDropdownItem} from "@web/search/search_dropdown_item/search_dropdown_item";
@@ -44,12 +43,7 @@ export class PageListController extends PageControllerMixin(listView.Controller)
callback: async () => {
this.dialogService.add(ConfirmationDialog, {
title: this.env._t("Publish Website Content"),
body: sprintf(
this.env._t(
"%s record(s) selected, are you sure you want to publish them all?"
),
this.model.root.selection.length
),
body: this.env._t("%s record(s) selected, are you sure you want to publish them all?", this.model.root.selection.length),
confirm: () => this.togglePublished(true),
});
},
@@ -3,7 +3,7 @@
import core from "@web/legacy/js/services/core";
import { loadBundle, loadCSS } from "@web/core/assets";
import Dialog from "@web/legacy/js/core/dialog";
import { Markup, sprintf } from "@web/legacy/js/core/utils";
import { Markup } from "@web/legacy/js/core/utils";
import weUtils from "@web_editor/js/common/utils";
import options from "@web_editor/js/editor/snippets.options";
import { NavbarLinkPopoverWidget } from "@website/js/widgets/link_popover_widget";
@@ -2807,7 +2807,7 @@ options.registry.anchor = options.Class.extend({
_buildClipboard(buttonEl) {
const clipboard = new ClipboardJS(buttonEl, {text: () => this._getAnchorLink()});
clipboard.on("success", () => {
const message = sprintf(Markup(_t("Anchor copied to clipboard<br>Link: %s")), this._getAnchorLink());
const message = Markup(_t("Anchor copied to clipboard<br>Link: %s", this._getAnchorLink()));
this.displayNotification({
type: "success",
message: message,
@@ -4,7 +4,6 @@ import { _t } from "@web/core/l10n/translation";
import { Markup } from "@web/legacy/js/core/utils";
import { registry } from "@web/core/registry";
import { sprintf } from "@web/core/utils/strings";
function addMedia(position = "right") {
return {
@@ -106,7 +105,7 @@ function changeOption(optionName, weName = '', optionTooltipLabel = '', position
const option_block = `${noPalette} we-customizeblock-option[class='snippet-option-${optionName}']`;
return {
trigger: `${option_block} ${weName}, ${option_block} [title='${weName}']`,
content: Markup(sprintf(_t("<b>Click</b> on this option to change the %s of the block."), optionTooltipLabel)),
content: Markup(_t("<b>Click</b> on this option to change the %s of the block.", optionTooltipLabel)),
position: position,
in_modal: false,
run: "click",
@@ -118,7 +117,7 @@ function selectNested(trigger, optionName, alt_trigger = null, optionTooltipLabe
const option_block = `${noPalette} we-customizeblock-option[class='snippet-option-${optionName}']`;
return {
trigger: trigger,
content: Markup(sprintf(_t("<b>Select</b> a %s."), optionTooltipLabel)),
content: Markup(_t("<b>Select</b> a %s.", optionTooltipLabel)),
alt_trigger: alt_trigger == null ? undefined : `${option_block} ${alt_trigger}`,
position: position,
run: 'click',
@@ -135,7 +134,7 @@ function changePaddingSize(direction) {
}
return {
trigger: `iframe .oe_overlay.ui-draggable.o_we_overlay_sticky.oe_active .o_handle.${paddingDirection}`,
content: Markup(sprintf(_t("<b>Slide</b> this button to change the %s padding"), direction)),
content: Markup(_t("<b>Slide</b> this button to change the %s padding", direction)),
consumeEvent: 'mousedown',
position: position,
};
@@ -235,7 +234,7 @@ function dragNDrop(snippet, position = "bottom") {
return {
trigger: `#oe_snippets .oe_snippet[name="${snippet.name}"] .oe_snippet_thumbnail:not(.o_we_already_dragging)`,
extra_trigger: ".o_website_preview.editor_enable.editor_has_snippets",
content: Markup(sprintf(_t("Drag the <b>%s</b> building block and drop it at the bottom of the page."), snippet.name)),
content: Markup(_t("Drag the <b>%s</b> building block and drop it at the bottom of the page.", snippet.name)),
position: position,
// Normally no main snippet can be dropped in the default footer but
// targeting it allows to force "dropping at the end of the page".
@@ -4,7 +4,6 @@ import { registry } from "@web/core/registry";
import { EditMenuDialog } from '@website/components/dialog/edit_menu';
import { OptimizeSEODialog } from '@website/components/dialog/seo';
import {PagePropertiesDialog} from '@website/components/dialog/page_properties';
import {sprintf} from '@web/core/utils/strings';
/**
* This service displays contextual menus, depending of the state of the
@@ -53,7 +52,7 @@ export const websiteCustomMenus = {
// 'navbar menus' display system.
filteredSections.push(...website.currentWebsite.metadata.contentMenus.map((menu, index) => ({
...section,
name: sprintf(env._t("Edit %s"), menu[0]),
name: env._t("Edit %s", menu[0]),
dynamicProps: {rootID: parseInt(menu[1], 10)},
// Prevent a 't-foreach' duplicate key on menus template.
id: `${section.id}-${index}`,
@@ -8,7 +8,6 @@
import publicWidget from "@web/legacy/js/public/public_widget";
import dom from "@web/legacy/js/core/dom";
import concurrency from "@web/legacy/js/core/concurrency";
import { sprintf } from "@web/core/utils/strings";
import { debounce } from "@web/core/utils/timing";
import { _t } from "@web/core/l10n/translation";
@@ -284,12 +283,15 @@
if (!self.check_error_fields({})) {
if (this.fileInputError) {
const errorMessage = this.fileInputError.type === "number"
? sprintf(_t(
"Please fill in the form correctly. You uploaded too many files. (Maximum %s files)"
), this.fileInputError.limit)
: sprintf(_t(
"Please fill in the form correctly. The file \"%s\" is too big. (Maximum %s MB)"
), this.fileInputError.fileName, this.fileInputError.limit);
? _t(
"Please fill in the form correctly. You uploaded too many files. (Maximum %s files)",
this.fileInputError.limit
)
: _t(
"Please fill in the form correctly. The file \"%s\" is too big. (Maximum %s MB)",
this.fileInputError.fileName,
this.fileInputError.limit
);
this.update_status("error", errorMessage);
delete this.fileInputError;
} else {
@@ -580,13 +582,13 @@
if (datetime.isValid()) {
return time.datetime_to_str(datetime.toDate());
}
throw new Error(sprintf(_t("'%s' is not a correct datetime"), value));
throw new Error(_t("'%s' is not a correct datetime", value));
case 'date':
var date = moment(value, [date_pattern, date_pattern_wo_zero], true);
if (date.isValid()) {
return time.date_to_str(date.toDate());
}
throw new Error(sprintf(_t("'%s' is not a correct date"), value));
throw new Error(_t("'%s' is not a correct date", value));
}
return value;
},
@@ -7,7 +7,6 @@ import Dialog from "@web/legacy/js/core/dialog";
import dom from "@web/legacy/js/core/dom";
import weUtils from "@web_editor/js/common/utils";
import "@website/js/editor/snippets.options";
import { sprintf } from "@web/core/utils/strings";
import { unique } from "@web/core/utils/arrays";
import { _t } from "@web/core/l10n/translation";
@@ -1370,7 +1369,7 @@ options.registry.WebsiteFieldEditor = FieldEditor.extend({
const list = document.createElement('we-list');
const optionText = select ? 'Option' : type === 'selection' ? 'Radio' : 'Checkbox';
list.setAttribute('string', `${optionText} List`);
list.dataset.addItemTitle = sprintf(_t("Add new %s"), optionText);
list.dataset.addItemTitle = _t("Add new %s", optionText);
list.dataset.renderListItems = '';
list.dataset.hasDefault = ['one2many', 'many2many'].includes(type) ? 'multiple' : 'unique';
@@ -1586,8 +1585,7 @@ options.registry.WebsiteFormFieldRequired = DisableOverlayButtonOption.extend({
const fieldName = this.$target[0]
.querySelector("input.s_website_form_input").getAttribute("name");
const spanEl = document.createElement("span");
spanEl.innerText = sprintf(_t(
"The field '%s' is mandatory for the action '%s'."), fieldName, currentActionName);
spanEl.innerText = _t("The field '%s' is mandatory for the action '%s'.", fieldName, currentActionName);
uiFragment.querySelector("we-alert").appendChild(spanEl);
},
});
@@ -216,9 +216,7 @@ export class NewContentModal extends Component {
}
return el;
});
this.website.showLoader({
title: sprintf(this.env._t("Building your %s"), name),
});
this.website.showLoader({ title: this.env._t("Building your %s", name) });
try {
await this.installModule(id, element.redirectUrl);
} catch (error) {