Skip to content

Commit a66b7e9

Browse files
sadpandajoeclaude
andauthored
feat: Add ESLint rule to enforce sentence case in button text (#34434)
Co-authored-by: Claude <[email protected]>
1 parent 3e12d97 commit a66b7e9

File tree

30 files changed

+183
-121
lines changed

30 files changed

+183
-121
lines changed

superset-frontend/.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@ module.exports = {
403403
'theme-colors/no-literal-colors': 'error',
404404
'icons/no-fa-icons-usage': 'error',
405405
'i18n-strings/no-template-vars': ['error', true],
406+
'i18n-strings/sentence-case-buttons': 'error',
406407
camelcase: [
407408
'error',
408409
{

superset-frontend/cypress-base/cypress/e2e/dashboard/nativeFilters.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {
2222
dataTestChartName,
2323
} from 'cypress/support/directories';
2424

25-
import { waitForChartLoad } from 'cypress/utils';
2625
import {
2726
addParentFilterWithValue,
2827
applyNativeFilterValueWithIndex,
@@ -344,7 +343,7 @@ describe('Native filters', () => {
344343
it('User can delete a native filter', () => {
345344
enterNativeFilterEditModal(false);
346345
cy.get(nativeFilters.filtersList.removeIcon).first().click();
347-
cy.contains('Restore Filter').should('not.exist', { timeout: 10000 });
346+
cy.contains('Restore filter').should('not.exist', { timeout: 10000 });
348347
});
349348

350349
it('User can cancel creating a new filter', () => {

superset-frontend/eslint-rules/eslint-plugin-i18n-strings/index.js

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ module.exports = {
4141
context.report({
4242
node,
4343
message:
44-
"Don't use variables in translation string templates. Flask-babel is a static translation service, so it cant handle strings that include variables",
44+
"Don't use variables in translation string templates. Flask-babel is a static translation service, so it can't handle strings that include variables",
4545
});
4646
}
4747
}
@@ -52,5 +52,67 @@ module.exports = {
5252
};
5353
},
5454
},
55+
'sentence-case-buttons': {
56+
create(context) {
57+
function isTitleCase(str) {
58+
// Match "Delete Dataset", "Create Chart", etc. (2+ title-cased words)
59+
return /^[A-Z][a-z]+(\s+[A-Z][a-z]*)+$/.test(str);
60+
}
61+
62+
function isButtonContext(node) {
63+
const { parent } = node;
64+
if (!parent) return false;
65+
66+
// Check for button-specific props
67+
if (parent.type === 'Property') {
68+
const key = parent.key.name;
69+
return [
70+
'primaryButtonName',
71+
'secondaryButtonName',
72+
'confirmButtonText',
73+
'cancelButtonText',
74+
].includes(key);
75+
}
76+
77+
// Check for Button components
78+
if (parent.type === 'JSXExpressionContainer') {
79+
const jsx = parent.parent;
80+
if (jsx?.type === 'JSXElement') {
81+
const elementName = jsx.openingElement.name.name;
82+
return elementName === 'Button';
83+
}
84+
}
85+
86+
return false;
87+
}
88+
89+
function handler(node) {
90+
if (node.arguments.length) {
91+
const firstArg = node.arguments[0];
92+
if (
93+
firstArg.type === 'Literal' &&
94+
typeof firstArg.value === 'string'
95+
) {
96+
const text = firstArg.value;
97+
98+
if (isButtonContext(node) && isTitleCase(text)) {
99+
const sentenceCase = text
100+
.toLowerCase()
101+
.replace(/^\w/, c => c.toUpperCase());
102+
context.report({
103+
node: firstArg,
104+
message: `Button text should use sentence case: "${text}" should be "${sentenceCase}"`,
105+
});
106+
}
107+
}
108+
}
109+
}
110+
111+
return {
112+
"CallExpression[callee.name='t']": handler,
113+
"CallExpression[callee.name='tn']": handler,
114+
};
115+
},
116+
},
55117
},
56118
};

superset-frontend/src/SqlLab/components/ExploreResultsButton/ExploreResultsButton.test.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,17 @@ describe('ExploreResultsButton', () => {
3636
const { queryByText } = setup(jest.fn(), {
3737
database: { allows_subquery: true },
3838
});
39-
expect(queryByText('Create Chart')).toBeInTheDocument();
39+
expect(queryByText('Create chart')).toBeInTheDocument();
4040
// Updated line to match the actual button name that includes the icon
41-
expect(screen.getByRole('button', { name: /Create Chart/i })).toBeEnabled();
41+
expect(screen.getByRole('button', { name: /Create chart/i })).toBeEnabled();
4242
});
4343

4444
it('renders disabled if subquery not allowed', async () => {
4545
const { queryByText } = setup(jest.fn());
46-
expect(queryByText('Create Chart')).toBeInTheDocument();
46+
expect(queryByText('Create chart')).toBeInTheDocument();
4747
// Updated line to match the actual button name that includes the icon
4848
expect(
49-
screen.getByRole('button', { name: /Create Chart/i }),
49+
screen.getByRole('button', { name: /Create chart/i }),
5050
).toBeDisabled();
5151
});
5252
});

superset-frontend/src/SqlLab/components/ExploreResultsButton/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const ExploreResultsButton = ({
4646
tooltip={t('Explore the result set in the data exploration view')}
4747
data-test="explore-results-button"
4848
>
49-
{t('Create Chart')}
49+
{t('Create chart')}
5050
</Button>
5151
);
5252
};

superset-frontend/src/dashboard/components/nativeFilters/FiltersConfigModal/FiltersConfigForm/RemovedFilter.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const RemovedFilter: FC<RemovedFilterProps> = ({ onClick }) => (
4343
buttonStyle="primary"
4444
onClick={onClick}
4545
>
46-
{t('Restore Filter')}
46+
{t('Restore filter')}
4747
</Button>
4848
</div>
4949
</RemovedContent>

superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -849,7 +849,7 @@ class AnnotationLayer extends PureComponent {
849849
buttonSize="xsmall"
850850
onClick={() => this.setState({ color: AUTOMATIC_COLOR })}
851851
>
852-
{t('Automatic Color')}
852+
{t('Automatic color')}
853853
</Button>
854854
</div>
855855
</div>

superset-frontend/src/explore/components/controls/AnnotationLayerControl/AnnotationLayer.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ test('keeps apply disabled when missing required fields', async () => {
220220
expect(await screen.findByText('Chart A')).toBeInTheDocument();
221221
userEvent.click(screen.getByText('Chart A'));
222222
await screen.findByText(/title column/i);
223-
userEvent.click(screen.getByRole('button', { name: 'Automatic Color' }));
223+
userEvent.click(screen.getByRole('button', { name: 'Automatic color' }));
224224
userEvent.click(
225225
screen.getByRole('combobox', { name: 'Annotation layer title column' }),
226226
);

superset/translations/ar/LC_MESSAGES/messages.po

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -816,11 +816,11 @@ msgid "Add Dashboard"
816816
msgstr "إضافة لوحة معلومات"
817817

818818
#, fuzzy
819-
msgid "Add Divider"
819+
msgid "Add divider"
820820
msgstr "مقسم"
821821

822822
#, fuzzy
823-
msgid "Add Filter"
823+
msgid "Add filter"
824824
msgstr "إضافة فلتر"
825825

826826
#, fuzzy
@@ -1804,7 +1804,7 @@ msgstr "فلاتر الإكمال التلقائي"
18041804
msgid "Autocomplete query predicate"
18051805
msgstr "استعلام الإكمال التلقائي المسند"
18061806

1807-
msgid "Automatic Color"
1807+
msgid "Automatic color"
18081808
msgstr "اللون التلقائي"
18091809

18101810
#, fuzzy
@@ -3253,7 +3253,7 @@ msgstr "خريطة البلد"
32533253
msgid "Create"
32543254
msgstr "خلق"
32553255

3256-
msgid "Create Chart"
3256+
msgid "Create chart"
32573257
msgstr "إنشاء مخطط"
32583258

32593259
msgid "Create a dataset"
@@ -8839,7 +8839,7 @@ msgstr "يحتوي المورد بالفعل على تقرير مرفق."
88398839
msgid "Resource was not found."
88408840
msgstr "لم يتم العثور على المورد."
88418841

8842-
msgid "Restore Filter"
8842+
msgid "Restore filter"
88438843
msgstr "عامل تصفية الاستعادة"
88448844

88458845
msgid "Results"

superset/translations/ca/LC_MESSAGES/messages.po

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -843,10 +843,10 @@ msgstr "Afegir plantilla CSS"
843843
msgid "Add Dashboard"
844844
msgstr "Afegir Dashboard"
845845

846-
msgid "Add Divider"
846+
msgid "Add divider"
847847
msgstr "Afegir Divisor"
848848

849-
msgid "Add Filter"
849+
msgid "Add filter"
850850
msgstr "Afegir Filtre"
851851

852852
msgid "Add Layer"
@@ -1822,7 +1822,7 @@ msgstr "Filtres d'autocompletat"
18221822
msgid "Autocomplete query predicate"
18231823
msgstr "Predicat de consulta d'autocompletat"
18241824

1825-
msgid "Automatic Color"
1825+
msgid "Automatic color"
18261826
msgstr "Color Automàtic"
18271827

18281828
msgid "Autosize Column"
@@ -3234,7 +3234,7 @@ msgstr "Mapa de País"
32343234
msgid "Create"
32353235
msgstr "Crear"
32363236

3237-
msgid "Create Chart"
3237+
msgid "Create chart"
32383238
msgstr "Crear Gràfic"
32393239

32403240
msgid "Create a dataset"
@@ -8642,7 +8642,7 @@ msgstr "El recurs ja té un informe adjunt."
86428642
msgid "Resource was not found."
86438643
msgstr "No s'ha trobat el recurs."
86448644

8645-
msgid "Restore Filter"
8645+
msgid "Restore filter"
86468646
msgstr "Restaurar Filtre"
86478647

86488648
msgid "Results"

0 commit comments

Comments
 (0)