From 754c0e1534ce321c924b22d73c34009240b72868 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Mon, 14 Apr 2025 14:24:47 +0100 Subject: [PATCH] Internationalisation: Check if strings are entirely non-alphanumeric before reporting as issues (#103900) --- .betterer.results | 101 +++--------------- .../rules/no-untranslated-strings.cjs | 17 +-- .../rules/translation-utils.cjs | 10 +- .../tests/no-untranslated-strings.test.js | 18 ++-- 4 files changed, 44 insertions(+), 102 deletions(-) diff --git a/.betterer.results b/.betterer.results index 2a993529fb0..976a2137c61 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1029,9 +1029,6 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] ], - "public/app/features/alerting/unified/components/RuleLocation.tsx:5381": [ - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"] - ], "public/app/features/alerting/unified/components/alert-groups/AlertDetails.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], @@ -1088,8 +1085,7 @@ exports[`better eslint`] = { "public/app/features/alerting/unified/components/expressions/Expression.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] ], "public/app/features/alerting/unified/components/mute-timings/MuteTimingTimeInterval.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], @@ -1245,9 +1241,6 @@ exports[`better eslint`] = { "public/app/features/alerting/unified/components/rule-editor/RecordingRuleEditor.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], - "public/app/features/alerting/unified/components/rule-editor/RuleEditorSection.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], "public/app/features/alerting/unified/components/rule-editor/RuleInspector.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] @@ -1264,10 +1257,6 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] ], - "public/app/features/alerting/unified/components/rule-editor/labels/LabelsField.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] - ], "public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationRoute.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], @@ -1333,19 +1322,11 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], [0, 0, 0, "No untranslated strings. Wrap text with ", "5"] ], - "public/app/features/alerting/unified/components/rules/RuleListStateSection.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], "public/app/features/alerting/unified/components/rules/RuleState.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], - "public/app/features/alerting/unified/components/rules/RuleStats.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], "public/app/features/alerting/unified/components/rules/RulesGroup.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], "public/app/features/alerting/unified/components/rules/state-history/LogRecordViewer.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] @@ -1356,10 +1337,6 @@ exports[`better eslint`] = { "public/app/features/alerting/unified/components/rules/state-history/StateHistory.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], - "public/app/features/alerting/unified/components/settings/VersionManager.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] - ], "public/app/features/alerting/unified/components/silences/SilencedAlertsTableRow.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], @@ -1519,9 +1496,6 @@ exports[`better eslint`] = { "public/app/features/dashboard-scene/edit-pane/DashboardEditPane.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], - "public/app/features/dashboard-scene/edit-pane/DashboardOutline.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], "public/app/features/dashboard-scene/inspect/HelpWizard/utils.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], @@ -1542,9 +1516,6 @@ exports[`better eslint`] = { "public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataPane.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], - "public/app/features/dashboard-scene/panel-edit/PanelDataPane/TransformationsDrawer.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], "public/app/features/dashboard-scene/panel-edit/PanelOptionsPane.test.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], @@ -1766,8 +1737,7 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], "public/app/features/dashboard/components/DashboardRow/DashboardRow.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], "public/app/features/dashboard/components/DashboardRow/index.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./DashboardRow\`)", "0"] @@ -1789,8 +1759,7 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "5"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "4"] ], "public/app/features/dashboard/components/Inspector/PanelInspector.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] @@ -1886,8 +1855,7 @@ exports[`better eslint`] = { ], "public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] + [0, 0, 0, "Do not use any type assertions.", "1"] ], "public/app/features/dashboard/components/VersionHistory/RevertDashboardModal.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] @@ -2132,15 +2100,13 @@ exports[`better eslint`] = { ], "public/app/features/dimensions/editors/ThresholdsEditor/ThresholdsEditor.tsx:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] + [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"] ], "public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingEditRow.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], "public/app/features/dimensions/editors/ValueMappingsEditor/ValueMappingsEditor.tsx:5381": [ - [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] + [0, 0, 0, "\'VerticalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], "public/app/features/dimensions/editors/index.ts:5381": [ [0, 0, 0, "Do not use export all (\`export * from ...\`)", "0"], @@ -2173,8 +2139,7 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "4"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] ], "public/app/features/explore/CorrelationTransformationAddModal.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], @@ -2193,9 +2158,6 @@ exports[`better eslint`] = { "public/app/features/explore/Logs/LiveLogs.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], - "public/app/features/explore/Logs/Logs.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], "public/app/features/explore/Logs/LogsFeedback.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] @@ -2209,15 +2171,9 @@ exports[`better eslint`] = { "public/app/features/explore/Logs/LogsTableMultiSelect.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], - "public/app/features/explore/Logs/LogsTableNavField.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], "public/app/features/explore/Logs/LogsVolumePanelList.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], - "public/app/features/explore/MetaInfoText.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], "public/app/features/explore/NodeGraph/NodeGraphContainer.tsx:5381": [ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] @@ -2227,24 +2183,12 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] ], - "public/app/features/explore/PrometheusListView/RawListItem.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], - "public/app/features/explore/PrometheusListView/RawListItemAttributes.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] - ], "public/app/features/explore/RichHistory/RichHistorySettingsTab.tsx:5381": [ [0, 0, 0, "Do not use the t() function outside of a component or function", "0"], [0, 0, 0, "Do not use the t() function outside of a component or function", "1"], [0, 0, 0, "Do not use the t() function outside of a component or function", "2"], [0, 0, 0, "Do not use the t() function outside of a component or function", "3"] ], - "public/app/features/explore/RichHistory/RichHistoryStarredTab.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], "public/app/features/explore/ShortLinkButtonMenu.tsx:5381": [ [0, 0, 0, "Do not use the t() function outside of a component or function", "0"] ], @@ -2272,8 +2216,7 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] ], "public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], "public/app/features/explore/TraceView/components/TracePageHeader/index.tsx:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./TracePageHeader\`)", "0"] @@ -2281,19 +2224,10 @@ exports[`better eslint`] = { "public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanBar.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], - "public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianKeyValues.tsx:5381": [ + "public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], - "public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianLogs.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] - ], "public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianReferences.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] - ], - "public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/AccordianText.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], "public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.tsx:5381": [ @@ -2417,10 +2351,6 @@ exports[`better eslint`] = { "public/app/features/expressions/guards.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], - "public/app/features/geo/editor/GazetteerPathEditor.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] - ], "public/app/features/geo/editor/locationModeEditor.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], @@ -2466,10 +2396,9 @@ exports[`better eslint`] = { "public/app/features/inspector/QueryInspector.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"], - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "4"], - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "5"] + [0, 0, 0, "Unexpected any. Specify a different type.", "2"], + [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "3"], + [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "4"] ], "public/app/features/invites/SignupInvited.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], @@ -2742,9 +2671,7 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], [0, 0, 0, "No untranslated strings. Wrap text with ", "5"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "6"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "7"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "8"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "6"] ], "public/app/features/query/state/DashboardQueryRunner/AnnotationsQueryRunner.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] diff --git a/packages/grafana-eslint-rules/rules/no-untranslated-strings.cjs b/packages/grafana-eslint-rules/rules/no-untranslated-strings.cjs index 53c4215ac26..2ea64533af2 100644 --- a/packages/grafana-eslint-rules/rules/no-untranslated-strings.cjs +++ b/packages/grafana-eslint-rules/rules/no-untranslated-strings.cjs @@ -11,7 +11,7 @@ const { canBeFixed, elementIsTrans, shouldBeFixed, - isStringLiteral, + isStringNonAlphanumeric, } = require('./translation-utils.cjs'); const { ESLintUtils, AST_NODE_TYPES } = require('@typescript-eslint/utils'); @@ -32,11 +32,12 @@ const noUntranslatedStrings = createRule({ return; } - const isUntranslatedProp = - (node.value.type === 'Literal' && node.value.value !== '') || - (node.value.type === AST_NODE_TYPES.JSXExpressionContainer && - ((isStringLiteral(node.value.expression) && node.value.expression.value !== '') || - node.value.expression.type === 'TemplateLiteral')); + const nodeValue = getNodeValue(node); + const isAlphaNumeric = !isStringNonAlphanumeric(nodeValue); + const isTemplateLiteral = + node.value.type === AST_NODE_TYPES.JSXExpressionContainer && node.value.expression.type === 'TemplateLiteral'; + + const isUntranslatedProp = (nodeValue.trim() && isAlphaNumeric) || isTemplateLiteral; if (isUntranslatedProp) { const errorShouldBeFixed = shouldBeFixed(context); @@ -64,8 +65,8 @@ const noUntranslatedStrings = createRule({ const children = node.children; const untranslatedTextNodes = children.filter((child) => { if (child.type === AST_NODE_TYPES.JSXText) { - const hasValue = child.value.trim(); - if (!hasValue) { + const nodeValue = child.value.trim(); + if (!nodeValue || isStringNonAlphanumeric(nodeValue)) { return false; } const ancestors = context.sourceCode.getAncestors(node); diff --git a/packages/grafana-eslint-rules/rules/translation-utils.cjs b/packages/grafana-eslint-rules/rules/translation-utils.cjs index 8625e4f44cf..a5e3aad8c63 100644 --- a/packages/grafana-eslint-rules/rules/translation-utils.cjs +++ b/packages/grafana-eslint-rules/rules/translation-utils.cjs @@ -41,6 +41,14 @@ function toKebabCase(str) { .replace(/\s+/g, '-'); } +/** + * Checks if a string is non-alphanumeric + * @param {string} str The string to check + * @returns {boolean} + */ +function isStringNonAlphanumeric(str) { + return !/[a-zA-Z0-9]/.test(str); +} /** * Checks if we _should_ fix an error automatically * @param {RuleContextWithOptions} context @@ -321,5 +329,5 @@ module.exports = { canBeFixed, shouldBeFixed, elementIsTrans, - isStringLiteral, + isStringNonAlphanumeric, }; diff --git a/packages/grafana-eslint-rules/tests/no-untranslated-strings.test.js b/packages/grafana-eslint-rules/tests/no-untranslated-strings.test.js index 970f0065eba..35b2d1cb251 100644 --- a/packages/grafana-eslint-rules/tests/no-untranslated-strings.test.js +++ b/packages/grafana-eslint-rules/tests/no-untranslated-strings.test.js @@ -68,6 +68,18 @@ ruleTester.run('eslint no-untranslated-strings', noUntranslatedStrings, { name: 'Variable interpolation', code: `
{variable}
`, }, + { + name: 'Entirely non-alphanumeric text (prop)', + code: `
`, + }, + { + name: 'Entirely non-alphanumeric text', + code: `
-
`, + }, + { + name: 'Non-alphanumeric siblings', + code: `
({variable})
`, + }, ], invalid: [ /** @@ -416,12 +428,6 @@ const Foo = () => { ], }, - { - name: 'Cannot fix entirely non-alphanumeric text', - code: `const Foo = () =>
-
`, - filename, - errors: [{ messageId: 'noUntranslatedStrings' }], - }, { name: 'Cannot fix text with expression sibling', code: `const Foo = () =>
{name} Hello
`,