mirror of https://github.com/grafana/grafana
I18n: Move eslint rule to package (#105860)
parent
1de0cd5d68
commit
abb885c585
@ -0,0 +1,185 @@ |
|||||||
|
# Grafana Internationalization ESLint Rules |
||||||
|
|
||||||
|
This package also contains custom i18n eslint rules for use within the Grafana codebase and plugins. |
||||||
|
|
||||||
|
## Rules |
||||||
|
|
||||||
|
### `no-untranslated-strings` |
||||||
|
|
||||||
|
Check if strings are marked for translation inside JSX Elements, in certain JSX props, and in certain object properties. |
||||||
|
|
||||||
|
### Options |
||||||
|
|
||||||
|
#### `forceFix` |
||||||
|
|
||||||
|
Allows specifying directories that, if the file is present within, then the rule will automatically fix the errors. This is primarily a workaround to allow for automatic mark up of new violations as the rule evolves. |
||||||
|
|
||||||
|
#### Example: |
||||||
|
|
||||||
|
```ts |
||||||
|
{ |
||||||
|
'@grafana/i18n/no-untranslated-strings': ['error', { forceFix: ['app/features/some-feature'] }], |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
#### `calleesToIgnore` |
||||||
|
|
||||||
|
Allows specifying regexes for methods that should be ignored when checking if object properties are untranslated. |
||||||
|
|
||||||
|
This is particularly useful to exclude references to properties such as `label` inside `css()` calls. |
||||||
|
|
||||||
|
#### Example: |
||||||
|
|
||||||
|
```ts |
||||||
|
{ |
||||||
|
'@grafana/i18n/no-untranslated-strings': ['error', { calleesToIgnore: ['^css$'] }], |
||||||
|
} |
||||||
|
|
||||||
|
// The below would not be reported as an error |
||||||
|
const foo = css({ |
||||||
|
label: 'test', |
||||||
|
}); |
||||||
|
|
||||||
|
// The below would still be reported as an error |
||||||
|
const bar = { |
||||||
|
label: 'test', |
||||||
|
}; |
||||||
|
``` |
||||||
|
|
||||||
|
#### JSXText |
||||||
|
|
||||||
|
```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> |
||||||
|
``` |
||||||
|
|
||||||
|
#### JSXAttributes |
||||||
|
|
||||||
|
```tsx |
||||||
|
// Bad ❌ |
||||||
|
<div title="foo bar" /> |
||||||
|
|
||||||
|
// Good ✅ |
||||||
|
<div title={t('some.key.foo-bar', 'foo bar')} /> |
||||||
|
``` |
||||||
|
|
||||||
|
#### Object properties |
||||||
|
|
||||||
|
```tsx |
||||||
|
// Bad ❌ |
||||||
|
const someConfig = { |
||||||
|
label: 'Some label', |
||||||
|
}; |
||||||
|
|
||||||
|
// Good ✅ |
||||||
|
const getSomeConfig = () => ({ |
||||||
|
label: t('some.key.label', 'Some label'), |
||||||
|
}); |
||||||
|
``` |
||||||
|
|
||||||
|
#### Passing variables to translations |
||||||
|
|
||||||
|
```tsx |
||||||
|
// Bad ❌ |
||||||
|
const SearchTitle = ({ term }) => <div>Results for {term}</div>; |
||||||
|
|
||||||
|
// Good ✅ |
||||||
|
const SearchTitle = ({ term }) => <Trans i18nKey="search-page.results-title">Results for {{ term }}</Trans>; |
||||||
|
|
||||||
|
// Good ✅ (if you need to interpolate variables inside nested components) |
||||||
|
const SearchTerm = ({ term }) => <Text color="success">{term}</Text>; |
||||||
|
const SearchTitle = ({ term }) => ( |
||||||
|
<Trans i18nKey="search-page.results-title"> |
||||||
|
Results for <SearchTerm term={term} /> |
||||||
|
</Trans> |
||||||
|
); |
||||||
|
|
||||||
|
// Good ✅ (if you need to interpolate variables and additional translated strings inside nested components) |
||||||
|
const SearchTitle = ({ term }) => ( |
||||||
|
<Trans i18nKey="search-page.results-title" values={{ myVariable: term }}> |
||||||
|
Results for <Text color="success">{'{{ myVariable }}'} and this translated text is also in green</Text> |
||||||
|
</Trans> |
||||||
|
); |
||||||
|
``` |
||||||
|
|
||||||
|
#### How to translate props or attributes |
||||||
|
|
||||||
|
This rule checks if a string is wrapped up by the `Trans` tag, or if certain props contain untranslated strings. |
||||||
|
We ask for such props to be translated with the `t()` function. |
||||||
|
|
||||||
|
The below props are checked for untranslated strings: |
||||||
|
|
||||||
|
- `label` |
||||||
|
- `description` |
||||||
|
- `placeholder` |
||||||
|
- `aria-label` |
||||||
|
- `title` |
||||||
|
- `subTitle` |
||||||
|
- `text` |
||||||
|
- `tooltip` |
||||||
|
- `message` |
||||||
|
- `name` |
||||||
|
|
||||||
|
```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) |
||||||
|
|
||||||
|
### `no-translation-top-level` |
||||||
|
|
||||||
|
Ensure that `t()` translation method is not used at the top level of a file, outside of a component of method. |
||||||
|
This is to prevent calling the translation method before it's been instantiated. |
||||||
|
|
||||||
|
This does not cause an error if a file is lazily loaded, but refactors can cause errors, and it can cause problems in tests. |
||||||
|
|
||||||
|
```tsx |
||||||
|
// Bad ❌ |
||||||
|
const someTranslatedText = t('some.key', 'Some text'); |
||||||
|
const SomeComponent = () => { |
||||||
|
return <div title={someTranslatedText} />; |
||||||
|
}; |
||||||
|
|
||||||
|
// Good ✅ |
||||||
|
const SomeComponent = () => { |
||||||
|
const someTranslatedText = t('some.key', 'Some text'); |
||||||
|
return <div title={someTranslatedText} />; |
||||||
|
}; |
||||||
|
|
||||||
|
// Bad ❌ |
||||||
|
const someConfigThatHasToBeShared = [{ foo: t('some.key', 'Some text') }]; |
||||||
|
const SomeComponent = () => { |
||||||
|
return ( |
||||||
|
<div> |
||||||
|
{someConfigThatHasToBeShared.map((cfg) => { |
||||||
|
return <div>{cfg.foo}</div>; |
||||||
|
})} |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
// Good ✅ |
||||||
|
const getSomeConfigThatHasToBeShared = () => [{ foo: t('some.key', 'Some text') }]; |
||||||
|
const SomeComponent = () => { |
||||||
|
const configs = getSomeConfigThatHasToBeShared(); |
||||||
|
return ( |
||||||
|
<div> |
||||||
|
{configs.map((cfg) => { |
||||||
|
return <div>{cfg.foo}</div>; |
||||||
|
})} |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
||||||
|
``` |
@ -0,0 +1,9 @@ |
|||||||
|
const noUntranslatedStrings = require('./no-untranslated-strings/no-untranslated-strings.cjs'); |
||||||
|
const noTranslationTopLevel = require('./no-translation-top-level/no-translation-top-level.cjs'); |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
rules: { |
||||||
|
'no-untranslated-strings': noUntranslatedStrings, |
||||||
|
'no-translation-top-level': noTranslationTopLevel, |
||||||
|
}, |
||||||
|
}; |
Loading…
Reference in new issue