diff --git a/front_end/help/ReleaseNoteView.ts b/front_end/help/ReleaseNoteView.ts index e91e854825..286b88a891 100644 --- a/front_end/help/ReleaseNoteView.ts +++ b/front_end/help/ReleaseNoteView.ts @@ -12,11 +12,6 @@ import {latestReleaseNote, releaseNoteViewId} from './HelpImpl.js'; import type {ReleaseNote} from './HelpImpl.js'; const UIStrings = { - /** - *@description Text that only contain a placeholder - *@example {100ms (at 200ms)} PH1 - */ - s: '{PH1}', /** *@description Text that is usually a hyperlink to more documentation */ @@ -37,7 +32,7 @@ export class ReleaseNoteView extends UI.Widget.VBox { this.registerRequiredCSS('help/releaseNote.css', {enableLegacyPatching: true}); this._releaseNoteElement = this._createReleaseNoteElement(latestReleaseNote()); const topSection = this.contentElement.createChild('div', 'release-note-top-section'); - topSection.textContent = i18nString(UIStrings.s, {PH1: latestReleaseNote().header}); + topSection.textContent = latestReleaseNote().header; this.contentElement.appendChild(this._releaseNoteElement); } @@ -58,7 +53,7 @@ export class ReleaseNoteView extends UI.Widget.VBox { hbox.classList.add('hbox'); const container = hbox.createChild('div', 'release-note-container'); const contentContainer = container.createChild('ul'); - UI.ARIAUtils.setAccessibleName(contentContainer, i18nString(UIStrings.s, {PH1: latestReleaseNote().header})); + UI.ARIAUtils.setAccessibleName(contentContainer, latestReleaseNote().header); let linkNumber = 1; for (const highlight of releaseNote.highlights) { @@ -94,7 +89,7 @@ export class ReleaseNoteView extends UI.Widget.VBox { const imageLink = UI.XLink.XLink.create(releaseNote.link, ' ') as HTMLElement; imageLink.classList.add('release-note-image'); - UI.Tooltip.Tooltip.install(imageLink, i18nString(UIStrings.s, {PH1: latestReleaseNote().header})); + UI.Tooltip.Tooltip.install(imageLink, latestReleaseNote().header); hbox.appendChild(imageLink); const image = imageLink.createChild('img') as HTMLImageElement; diff --git a/front_end/i18n/locales/en-US.json b/front_end/i18n/locales/en-US.json index 6826655790..630f94246d 100644 --- a/front_end/i18n/locales/en-US.json +++ b/front_end/i18n/locales/en-US.json @@ -2801,9 +2801,6 @@ "help/ReleaseNoteView.ts | learnMore": { "message": "Learn more" }, - "help/ReleaseNoteView.ts | s": { - "message": "{PH1}" - }, "host/InspectorFrontendHost.ts | devtoolsS": { "message": "DevTools - {PH1}" }, @@ -4937,9 +4934,6 @@ "network/NetworkLogViewColumns.ts | responseTime": { "message": "Response Time" }, - "network/NetworkLogViewColumns.ts | s": { - "message": "{PH1}" - }, "network/NetworkLogViewColumns.ts | scheme": { "message": "Scheme" }, @@ -6092,9 +6086,6 @@ "profiler/HeapSnapshotGridNodes.ts | detachedFromDomTree": { "message": "Detached from DOM tree" }, - "profiler/HeapSnapshotGridNodes.ts | emptyPlaceholder": { - "message": "{PH1}" - }, "profiler/HeapSnapshotGridNodes.ts | genericStringsTwoPlaceholders": { "message": "{PH1}, {PH2}" }, @@ -6443,9 +6434,6 @@ "profiler/ProfileSidebarTreeElement.ts | saveWithEllipsis": { "message": "Save…" }, - "profiler/ProfileSidebarTreeElement.ts | singlePlaceholder": { - "message": "{PH1}" - }, "profiler/ProfilesPanel.ts | cantLoadFileSupportedFile": { "message": "Can’t load file. Supported file extensions: '{PH1}.'" }, diff --git a/front_end/i18n/locales/en-XL.json b/front_end/i18n/locales/en-XL.json index fa4cc0c5db..58237f9a78 100644 --- a/front_end/i18n/locales/en-XL.json +++ b/front_end/i18n/locales/en-XL.json @@ -2801,9 +2801,6 @@ "help/ReleaseNoteView.ts | learnMore": { "message": "L̂éâŕn̂ ḿôŕê" }, - "help/ReleaseNoteView.ts | s": { - "message": "{PH1}" - }, "host/InspectorFrontendHost.ts | devtoolsS": { "message": "D̂év̂T́ôól̂ś - {PH1}" }, @@ -4937,9 +4934,6 @@ "network/NetworkLogViewColumns.ts | responseTime": { "message": "R̂éŝṕôńŝé T̂ím̂é" }, - "network/NetworkLogViewColumns.ts | s": { - "message": "{PH1}" - }, "network/NetworkLogViewColumns.ts | scheme": { "message": "Ŝćĥém̂é" }, @@ -6092,9 +6086,6 @@ "profiler/HeapSnapshotGridNodes.ts | detachedFromDomTree": { "message": "D̂ét̂áĉh́êd́ f̂ŕôḿ D̂ÓM̂ t́r̂éê" }, - "profiler/HeapSnapshotGridNodes.ts | emptyPlaceholder": { - "message": "{PH1}" - }, "profiler/HeapSnapshotGridNodes.ts | genericStringsTwoPlaceholders": { "message": "{PH1}, {PH2}" }, @@ -6443,9 +6434,6 @@ "profiler/ProfileSidebarTreeElement.ts | saveWithEllipsis": { "message": "Ŝáv̂é…" }, - "profiler/ProfileSidebarTreeElement.ts | singlePlaceholder": { - "message": "{PH1}" - }, "profiler/ProfilesPanel.ts | cantLoadFileSupportedFile": { "message": "Ĉán̂’t́ l̂óâd́ f̂íl̂é. Ŝúp̂ṕôŕt̂éd̂ f́îĺê éx̂t́êńŝíôńŝ: '{PH1}.'" }, diff --git a/front_end/network/NetworkLogViewColumns.ts b/front_end/network/NetworkLogViewColumns.ts index 2496b94a60..2b77b1b8db 100644 --- a/front_end/network/NetworkLogViewColumns.ts +++ b/front_end/network/NetworkLogViewColumns.ts @@ -136,11 +136,6 @@ const UIStrings = { */ connectionId: 'Connection ID', /** - *@description Text that only contain a placeholder - *@example {100ms (at 200ms)} PH1 - */ - s: '{PH1}', - /** *@description Text in Network Log View Columns of the Network panel */ remoteAddressSpace: 'Remote Address Space', @@ -1011,9 +1006,7 @@ const _temporaryDefaultColumns = [ { id: 'connection', isResponseHeader: true, - // until IDs are supported for strings, the placeholder is used to workaround the limitation that - // having multiple translations for a string is not supported - title: i18nLazyString(UIStrings.s, {PH1: 'Connection'}), + title: i18n.i18n.lockedLazyString('Connection'), sortingFunction: NetworkRequestNode.ResponseHeaderStringComparator.bind(null, 'connection'), }, { diff --git a/front_end/profiler/HeapSnapshotGridNodes.ts b/front_end/profiler/HeapSnapshotGridNodes.ts index febe8bcf96..687f05a3bc 100644 --- a/front_end/profiler/HeapSnapshotGridNodes.ts +++ b/front_end/profiler/HeapSnapshotGridNodes.ts @@ -45,11 +45,6 @@ import type {HeapSnapshotProviderProxy, HeapSnapshotProxy} from './HeapSnapshotP import type {DataDisplayDelegate} from './ProfileHeader.js'; const UIStrings = { - /** - *@description Text in Heap Snapshot Grid Nodes of a profiler tool - *@example {2} PH1 - */ - emptyPlaceholder: '{PH1}', /** *@description Generic text with two placeholders separated by a comma *@example {1 613 680} PH1 @@ -211,8 +206,7 @@ export class HeapSnapshotGridNode extends DataGrid.DataGrid.DataGridNode= 0 && distance < baseSystemDistance ? i18nString(UIStrings.emptyPlaceholder, {PH1: distance}) : - '\u2212'; + return distance >= 0 && distance < baseSystemDistance ? distance.toString() : '\u2212'; } allChildren(): HeapSnapshotGridNode[] { diff --git a/front_end/profiler/ProfileSidebarTreeElement.ts b/front_end/profiler/ProfileSidebarTreeElement.ts index 7d84858e84..68fc5eae44 100644 --- a/front_end/profiler/ProfileSidebarTreeElement.ts +++ b/front_end/profiler/ProfileSidebarTreeElement.ts @@ -21,11 +21,6 @@ const UIStrings = { */ saveWithEllipsis: 'Save…', /** - *@description Text that only contain a placeholder - *@example {100ms (at 200ms)} PH1 - */ - singlePlaceholder: '{PH1}', - /** *@description A context menu item in the Profiles Panel of a profiler tool */ load: 'Load…', @@ -154,8 +149,7 @@ export class ProfileSidebarTreeElement extends UI.TreeOutline.TreeElement { this.listItemElement.append(this._iconElement, this._titlesElement); this.listItemElement.addEventListener('contextmenu', this._handleContextMenuEvent.bind(this), true); - UI.ARIAUtils.setDescription( - this.listItemElement, i18nString(UIStrings.singlePlaceholder, {PH1: this.profile.profileType().name})); + UI.ARIAUtils.setDescription(this.listItemElement, this.profile.profileType().name); } _handleContextMenuEvent(event: Event): void { diff --git a/front_end/timeline/TimelineUIUtils.js b/front_end/timeline/TimelineUIUtils.js index 39aa8c5c5d..66d84e6269 100644 --- a/front_end/timeline/TimelineUIUtils.js +++ b/front_end/timeline/TimelineUIUtils.js @@ -1016,7 +1016,7 @@ const UIStrings = { *@description Text that only contain a placeholder *@example {100ms (at 200ms)} PH1 */ - emptyPlaceholder: '{PH1}', + emptyPlaceholder: '{PH1}', // eslint-disable-line rulesdir/l10n_no_locked_only_phrase /** *@description Text in Timeline UIUtils of the Performance panel */ diff --git a/scripts/eslint_rules/lib/l10n_no_locked_only_phrase.js b/scripts/eslint_rules/lib/l10n_no_locked_only_phrase.js index 6ae65866db..055582540b 100644 --- a/scripts/eslint_rules/lib/l10n_no_locked_only_phrase.js +++ b/scripts/eslint_rules/lib/l10n_no_locked_only_phrase.js @@ -9,12 +9,14 @@ function isModuleScope(context) { } const FULLY_LOCKED_PHRASE_REGEX = /^`[^`]*`$/; +const SINGLE_PLACEHOLDER_REGEX = /^\{\w+\}$/; // Matches the PH regex in `collect-strings.js`. module.exports = { meta: { type: 'problem', docs: { - description: 'UIStrings object literals are not allowed to have phrases that are fully locked.', + description: + 'UIStrings object literals are not allowed to have phrases that are fully locked, or consist only of a single placeholder.', category: 'Possible Errors', }, schema: [] // no options @@ -44,6 +46,11 @@ module.exports = { node: property.value, message: 'Locking whole phrases is not allowed. Use i18n.i18n.lockedString instead.', }); + } else if (SINGLE_PLACEHOLDER_REGEX.test(property.value.value)) { + context.report({ + node: property.value, + message: 'Single placeholder-only phrases are not allowed. Use i18n.i18n.lockedString instead.', + }); } } }, diff --git a/scripts/eslint_rules/tests/l10n_no_locked_only_phrase_test.js b/scripts/eslint_rules/tests/l10n_no_locked_only_phrase_test.js index 7480f907a8..b3058a9b28 100644 --- a/scripts/eslint_rules/tests/l10n_no_locked_only_phrase_test.js +++ b/scripts/eslint_rules/tests/l10n_no_locked_only_phrase_test.js @@ -18,11 +18,21 @@ ruleTester.run('l10n_no_locked_only_phrase', rule, { { code: 'const UIStrings = { foo: \'Some `locked` part\' };', }, + { + code: 'const UIStrings = { foo: \'One {PH} placeholder\' };', + }, + { + code: 'const UIStrings = { foo: \'{PH} two {PH} placeholders\' };', + }, ], invalid: [ { code: 'const UIStrings = { foo: \'`whole phrase is locked`\'};', errors: [{message: 'Locking whole phrases is not allowed. Use i18n.i18n.lockedString instead.'}], }, + { + code: 'const UIStrings = { foo: \'{PH}\'};', + errors: [{message: 'Single placeholder-only phrases are not allowed. Use i18n.i18n.lockedString instead.'}], + }, ], });