mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Chore: create the no-untranslated-literals
rule (#88271)
This commit is contained in:
parent
543f0ae37e
commit
a47e71fd34
@ -111,3 +111,53 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
### `theme-token-usage`
|
### `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!
|
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)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
const noAriaLabelSelectors = require('./rules/no-aria-label-e2e-selectors.cjs');
|
const noAriaLabelSelectors = require('./rules/no-aria-label-e2e-selectors.cjs');
|
||||||
const noBorderRadiusLiteral = require('./rules/no-border-radius-literal.cjs');
|
const noBorderRadiusLiteral = require('./rules/no-border-radius-literal.cjs');
|
||||||
const noUnreducedMotion = require('./rules/no-unreduced-motion.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');
|
const themeTokenUsage = require('./rules/theme-token-usage.cjs');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -9,5 +10,6 @@ module.exports = {
|
|||||||
'no-aria-label-selectors': noAriaLabelSelectors,
|
'no-aria-label-selectors': noAriaLabelSelectors,
|
||||||
'no-border-radius-literal': noBorderRadiusLiteral,
|
'no-border-radius-literal': noBorderRadiusLiteral,
|
||||||
'theme-token-usage': themeTokenUsage,
|
'theme-token-usage': themeTokenUsage,
|
||||||
|
'no-untranslated-strings': noUntranslatedStrings,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -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;
|
Loading…
Reference in New Issue
Block a user