[eslint] Prohibit l10n phrases consisting only of a sinlge placeholder

R=petermarshall@chromium.org

Bug: chromium:1180760
Change-Id: I11726e1d58835a3c1acbba85d137fb2fa1cfc522
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2734474
Reviewed-by: Peter Marshall <petermarshall@chromium.org>
Commit-Queue: Simon Zünd <szuend@chromium.org>
This commit is contained in:
Simon Zünd
2021-03-04 10:19:02 +00:00
committed by Commit Bot
parent a121a6bae6
commit b904628c78
9 changed files with 25 additions and 56 deletions
+3 -8
View File
@@ -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;
-12
View File
@@ -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}.'"
},
-12
View File
@@ -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}.'"
},
+1 -8
View File
@@ -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'),
},
{
+1 -7
View File
@@ -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<HeapSna
_toUIDistance(distance: number): string {
const baseSystemDistance = HeapSnapshotModel.HeapSnapshotModel.baseSystemDistance;
return distance >= 0 && distance < baseSystemDistance ? i18nString(UIStrings.emptyPlaceholder, {PH1: distance}) :
'\u2212';
return distance >= 0 && distance < baseSystemDistance ? distance.toString() : '\u2212';
}
allChildren(): HeapSnapshotGridNode[] {
@@ -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 {
+1 -1
View File
@@ -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
*/
@@ -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.',
});
}
}
},
@@ -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.'}],
},
],
});