Internationalisation: Allow parameterising basePaths for key generation in lint rule (#111999)
* allow parameterising the basePaths for autofixes * add documentation
This commit is contained in:
+4
-1
@@ -361,7 +361,10 @@ module.exports = [
|
||||
'**/mock*.{ts,tsx}',
|
||||
],
|
||||
rules: {
|
||||
'@grafana/i18n/no-untranslated-strings': ['error', { calleesToIgnore: ['^css$', 'use[A-Z].*'] }],
|
||||
'@grafana/i18n/no-untranslated-strings': [
|
||||
'error',
|
||||
{ calleesToIgnore: ['^css$', 'use[A-Z].*'], basePaths: ['public/app/features'] },
|
||||
],
|
||||
'@grafana/i18n/no-translation-top-level': 'error',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -10,6 +10,30 @@ Check if strings are marked for translation inside JSX Elements, in certain JSX
|
||||
|
||||
### Options
|
||||
|
||||
#### `basePaths`
|
||||
|
||||
Allows specifying base paths that should be stripped when generating i18n keys. Defaults to `['src']`.
|
||||
|
||||
#### Example
|
||||
|
||||
```tsx
|
||||
// For a file located at public/app/features/search/EmptyState.tsx
|
||||
|
||||
// Specifying basePaths:
|
||||
// {
|
||||
// '@grafana/i18n/no-untranslated-strings': ['error', { basePaths: ['public/app/features'] }],
|
||||
// }
|
||||
|
||||
<Trans i18nKey="search.empty-state.no-results-found">No results found</Trans>
|
||||
|
||||
// Without basePaths:
|
||||
// {
|
||||
// '@grafana/i18n/no-untranslated-strings': ['error'],
|
||||
// }
|
||||
|
||||
<Trans i18nKey="public.app.features.search.empty-state.no-results-found">No results found</Trans>
|
||||
```
|
||||
|
||||
#### `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.
|
||||
|
||||
+10
-3
@@ -2,8 +2,8 @@
|
||||
/** @typedef {import('@typescript-eslint/utils').TSESTree.Node} Node */
|
||||
/** @typedef {import('@typescript-eslint/utils').TSESTree.JSXElement} JSXElement */
|
||||
/** @typedef {import('@typescript-eslint/utils').TSESTree.JSXFragment} JSXFragment */
|
||||
/** @typedef {import('@typescript-eslint/utils').TSESLint.RuleModule<'noUntranslatedStrings' | 'noUntranslatedStringsProp' | 'wrapWithTrans' | 'wrapWithT' | 'noUntranslatedStringsProperties', [{ forceFix: string[] , calleesToIgnore: string[] }]>} RuleDefinition */
|
||||
/** @typedef {import('@typescript-eslint/utils/ts-eslint').RuleContext<'noUntranslatedStrings' | 'noUntranslatedStringsProp' | 'wrapWithTrans' | 'wrapWithT' | 'noUntranslatedStringsProperties', [{forceFix: string[], calleesToIgnore: string[]}]>} RuleContextWithOptions */
|
||||
/** @typedef {import('@typescript-eslint/utils').TSESLint.RuleModule<'noUntranslatedStrings' | 'noUntranslatedStringsProp' | 'wrapWithTrans' | 'wrapWithT' | 'noUntranslatedStringsProperties', [{ forceFix: string[] , calleesToIgnore: string[], basePaths: string[] }]>} RuleDefinition */
|
||||
/** @typedef {import('@typescript-eslint/utils/ts-eslint').RuleContext<'noUntranslatedStrings' | 'noUntranslatedStringsProp' | 'wrapWithTrans' | 'wrapWithT' | 'noUntranslatedStringsProperties', [{forceFix: string[], calleesToIgnore: string[], basePaths: string[]}]>} RuleContextWithOptions */
|
||||
|
||||
const {
|
||||
getNodeValue,
|
||||
@@ -301,12 +301,19 @@ const noUntranslatedStrings = createRule({
|
||||
},
|
||||
default: [],
|
||||
},
|
||||
basePaths: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
default: ['src'],
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
defaultOptions: [{ forceFix: [], calleesToIgnore: [] }],
|
||||
defaultOptions: [{ forceFix: [], calleesToIgnore: [], basePaths: ['src'] }],
|
||||
});
|
||||
|
||||
module.exports = noUntranslatedStrings;
|
||||
|
||||
+91
-4
@@ -2,7 +2,7 @@ import { RuleTester } from 'eslint';
|
||||
|
||||
import noUntranslatedStrings from './no-untranslated-strings.cjs';
|
||||
|
||||
const filename = 'public/app/features/some-feature/nested/SomeFile.tsx';
|
||||
const filename = 'src/some-feature/nested/SomeFile.tsx';
|
||||
|
||||
const packageName = '@grafana/i18n';
|
||||
|
||||
@@ -797,7 +797,7 @@ const Foo = () => {
|
||||
name: 'Auto fixes when options are configured',
|
||||
code: `const Foo = () => <div>test</div>`,
|
||||
filename,
|
||||
options: [{ forceFix: ['public/app/features/some-feature'] }],
|
||||
options: [{ forceFix: ['src/some-feature'] }],
|
||||
output: `${TRANS_IMPORT}
|
||||
const Foo = () => <div><Trans i18nKey="some-feature.foo.test">test</Trans></div>`,
|
||||
errors: [
|
||||
@@ -821,7 +821,7 @@ const Foo = () => {
|
||||
return <div title="foo" />
|
||||
}`,
|
||||
filename,
|
||||
options: [{ forceFix: ['public/app/features/some-feature'] }],
|
||||
options: [{ forceFix: ['src/some-feature'] }],
|
||||
output: `
|
||||
${T_IMPORT}
|
||||
const Foo = () => {
|
||||
@@ -853,7 +853,94 @@ const Foo = () => {
|
||||
}
|
||||
}`,
|
||||
filename,
|
||||
options: [{ forceFix: ['public/app/features/some-feature'] }],
|
||||
options: [{ forceFix: ['src/some-feature'] }],
|
||||
output: `
|
||||
${T_IMPORT}
|
||||
const Foo = () => {
|
||||
return {
|
||||
label: t("some-feature.foo.label.test", "test"),
|
||||
}
|
||||
}`,
|
||||
errors: [
|
||||
{
|
||||
messageId: 'noUntranslatedStringsProperties',
|
||||
suggestions: [
|
||||
{
|
||||
messageId: 'wrapWithT',
|
||||
output: `
|
||||
${T_IMPORT}
|
||||
const Foo = () => {
|
||||
return {
|
||||
label: t("some-feature.foo.label.test", "test"),
|
||||
}
|
||||
}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Auto fixes when options are configured for a different basePath',
|
||||
code: `const Foo = () => <div>test</div>`,
|
||||
filename: 'public/app/features/some-feature/nested/SomeFile.tsx',
|
||||
options: [{ forceFix: ['public/app/features/some-feature'], basePaths: ['public/app/features'] }],
|
||||
output: `${TRANS_IMPORT}
|
||||
const Foo = () => <div><Trans i18nKey="some-feature.foo.test">test</Trans></div>`,
|
||||
errors: [
|
||||
{
|
||||
messageId: 'noUntranslatedStrings',
|
||||
suggestions: [
|
||||
{
|
||||
messageId: 'wrapWithTrans',
|
||||
output: `${TRANS_IMPORT}
|
||||
const Foo = () => <div><Trans i18nKey="some-feature.foo.test">test</Trans></div>`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Auto fixes when options are configured for a different basePath - prop',
|
||||
code: `
|
||||
const Foo = () => {
|
||||
return <div title="foo" />
|
||||
}`,
|
||||
filename: 'public/app/features/some-feature/nested/SomeFile.tsx',
|
||||
options: [{ forceFix: ['public/app/features/some-feature'], basePaths: ['public/app/features'] }],
|
||||
output: `
|
||||
${T_IMPORT}
|
||||
const Foo = () => {
|
||||
return <div title={t("some-feature.foo.title-foo", "foo")} />
|
||||
}`,
|
||||
errors: [
|
||||
{
|
||||
messageId: 'noUntranslatedStringsProp',
|
||||
suggestions: [
|
||||
{
|
||||
messageId: 'wrapWithT',
|
||||
output: `
|
||||
${T_IMPORT}
|
||||
const Foo = () => {
|
||||
return <div title={t("some-feature.foo.title-foo", "foo")} />
|
||||
}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Auto fixes object property for a different basePath',
|
||||
code: `
|
||||
const Foo = () => {
|
||||
return {
|
||||
label: 'test',
|
||||
}
|
||||
}`,
|
||||
filename: 'public/app/features/some-feature/nested/SomeFile.tsx',
|
||||
options: [{ forceFix: ['public/app/features/some-feature'], basePaths: ['public/app/features'] }],
|
||||
output: `
|
||||
${T_IMPORT}
|
||||
const Foo = () => {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
/** @typedef {import('@typescript-eslint/utils').TSESTree.JSXChild} JSXChild */
|
||||
/** @typedef {import('@typescript-eslint/utils').TSESTree.Property} Property */
|
||||
/** @typedef {import('@typescript-eslint/utils/ts-eslint').RuleFixer} RuleFixer */
|
||||
/** @typedef {import('@typescript-eslint/utils/ts-eslint').RuleContext<'noUntranslatedStrings' | 'noUntranslatedStringsProp' | 'wrapWithTrans' | 'wrapWithT', [{forceFix: string[]}]>} RuleContextWithOptions */
|
||||
/** @typedef {import('@typescript-eslint/utils/ts-eslint').RuleContext<'noUntranslatedStrings' | 'noUntranslatedStringsProp' | 'wrapWithTrans' | 'wrapWithT', [{forceFix: string[], calleesToIgnore: string[], basePaths: string[]}]>} RuleContextWithOptions */
|
||||
const { AST_NODE_TYPES } = require('@typescript-eslint/utils');
|
||||
|
||||
/**
|
||||
@@ -150,9 +150,12 @@ function getTDeclaration(node, context) {
|
||||
*/
|
||||
function getTranslationPrefix(context) {
|
||||
const filename = context.filename;
|
||||
const match = filename.match(/public\/app\/features\/(.+?)\//);
|
||||
if (match) {
|
||||
return match[1];
|
||||
const basePaths = context.options[0]?.basePaths ?? ['src'];
|
||||
for (const path of basePaths) {
|
||||
const match = filename.match(new RegExp(`${path}/(.+?)/`));
|
||||
if (match) {
|
||||
return match[1];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user