Chore: create the no-untranslated-literals rule (#88271)

This commit is contained in:
Laura Fernández 2024-05-29 13:03:59 +02:00 committed by GitHub
parent 543f0ae37e
commit a47e71fd34
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 98 additions and 0 deletions

View File

@ -111,3 +111,53 @@ const getStyles = (theme: GrafanaTheme2) => ({
### `theme-token-usage`
Used to find all instances of `theme` tokens being used in the codebase and emit the counts as metrics. Should **not** be used as an actual lint rule!
### `no-untranslated-strings`
Check if strings are marked for translation.
```tsx
// Bad ❌
<InlineToast placement="top" referenceElement={buttonRef.current}>
Copied
</InlineToast>
// Good ✅
<InlineToast placement="top" referenceElement={buttonRef.current}>
<Trans i18nKey="clipboard-button.inline-toast.success">Copied</Trans>
</InlineToast>
```
#### Passing variables to translations
```tsx
// Bad ❌
const SearchTitle = ({ term }) => (
<div>
Results for <em>{term}</em>
</div>
);
//Good ✅
const SearchTitle = ({ term }) => (
<Trans i18nKey="search-page.results-title">
Results for <em>{{ term }}</em>
</Trans>
);
```
#### How to translate props or attributes
Right now, we only check if a string is wrapped up by the `Trans` tag. We currently do not apply this rule to props, attributes or similar, but we also ask for them to be translated with the `t()` function.
```tsx
// Bad ❌
<input type="value" placeholder={'Username'} />;
// Good ✅
const placeholder = t('form.username-placeholder', 'Username');
return <input type="value" placeholder={placeholder} />;
```
Check more info about how translations work in Grafana in [Internationalization.md](https://github.com/grafana/grafana/blob/main/contribute/internationalization.md)

View File

@ -1,6 +1,7 @@
const noAriaLabelSelectors = require('./rules/no-aria-label-e2e-selectors.cjs');
const noBorderRadiusLiteral = require('./rules/no-border-radius-literal.cjs');
const noUnreducedMotion = require('./rules/no-unreduced-motion.cjs');
const noUntranslatedStrings = require('./rules/no-utranslated-strings.cjs');
const themeTokenUsage = require('./rules/theme-token-usage.cjs');
module.exports = {
@ -9,5 +10,6 @@ module.exports = {
'no-aria-label-selectors': noAriaLabelSelectors,
'no-border-radius-literal': noBorderRadiusLiteral,
'theme-token-usage': themeTokenUsage,
'no-untranslated-strings': noUntranslatedStrings,
},
};

View File

@ -0,0 +1,46 @@
// @ts-check
const { ESLintUtils, AST_NODE_TYPES } = require('@typescript-eslint/utils');
const createRule = ESLintUtils.RuleCreator(
(name) => `https://github.com/grafana/grafana/blob/main/packages/grafana-eslint-rules/README.md#${name}`
);
const noUntranslatedStrings = createRule({
create(context) {
return {
JSXText(node) {
const ancestors = context.getAncestors();
const isEmpty = !node.value.trim();
const hasTransAncestor = ancestors.some((ancestor) => {
return (
ancestor.type === AST_NODE_TYPES.JSXElement &&
ancestor.openingElement.type === AST_NODE_TYPES.JSXOpeningElement &&
ancestor.openingElement.name.type === AST_NODE_TYPES.JSXIdentifier &&
ancestor.openingElement.name.name === 'Trans'
);
});
if (!isEmpty && !hasTransAncestor) {
context.report({
node,
messageId: 'noUntranslatedStrings',
});
}
},
};
},
name: 'no-untranslated-strings',
meta: {
type: 'suggestion',
docs: {
description: 'Check untranslated strings',
},
messages: {
noUntranslatedStrings: 'No untranslated strings. Wrap text with <Trans />',
},
schema: [],
},
defaultOptions: [],
});
module.exports = noUntranslatedStrings;