` from the `@kbn/i18n-react` package.
+
+It provides an autofix that takes into account the context of the translatable string in the JSX tree and to generate a translation ID.
+
+This rule kicks in on:
-## Exemptions and exceptions
+- JSXText elements;
+- specific JSXAttributes (`label` and `aria-label`) which expect a translated value.
+
+### Exemptions and exceptions
A JSXText element or JSXAttribute `label` or `aria-label` of which the value is:
@@ -32,3 +98,52 @@ A JSXText element or JSXAttribute `label` or `aria-label` of which the value is:
are exempt from this rule.
If this rule kicks in on a string value that you don't like, you can escape it by wrapping the string inside a JSXExpression: `{'my escaped value'}`.
+
+---
+
+## `@kbn/i18n/i18n_translate_should_start_with_the_right_id`
+
+This rule checks every instance of `i18n.translate()` if the first parameter passed:
+
+1. has a string value,
+2. if the parameter starts with the correct i18n app identifier for the file.
+
+It checks the repo for the `i18nrc.json` and `/x-pack/i18nrc.json` files and determines what the right i18n identifier should be.
+
+If the parameter is missing or does not start with the right i18n identifier, it can autofix the parameter.
+
+This rule is useful when defining translated values in plain functions (non-JSX), but it works in JSX as well.
+
+### Example
+
+This code:
+
+```
+// Filename: /x-pack/plugins/observability/public/my_function.ts
+
+function myFunction() {
+ const translations = [
+ {
+ id: 'copy';
+ label: i18n.translate()
+ }
+ ]
+}
+```
+
+will be autofixed with:
+
+```
+import { i18n } from '@kbn/i18n';
+
+function myFunction() {
+ const translations = [
+ {
+ id: 'copy';
+ label: i18n.translate('xpack.observability.myFunction.', { defaultMessage: '' })
+ }
+ ]
+}
+```
+
+If `i18n` has not been imported yet, the autofix will automatically add the import statement as well.
diff --git a/packages/kbn-eslint-plugin-i18n/helpers/get_i18n_identifier_from_file_path.test.ts b/packages/kbn-eslint-plugin-i18n/helpers/get_i18n_identifier_from_file_path.test.ts
index 6e01b89b23565..cea9fa1c333d9 100644
--- a/packages/kbn-eslint-plugin-i18n/helpers/get_i18n_identifier_from_file_path.test.ts
+++ b/packages/kbn-eslint-plugin-i18n/helpers/get_i18n_identifier_from_file_path.test.ts
@@ -11,17 +11,15 @@ import { getI18nIdentifierFromFilePath } from './get_i18n_identifier_from_file_p
const SYSTEMPATH = 'systemPath';
const testMap = [
- ['x-pack/plugins/observability/foo/bar/baz/header_actions.tsx', 'xpack.observability'],
- ['x-pack/plugins/apm/public/components/app/correlations/correlations_table.tsx', 'xpack.apm'],
- ['x-pack/plugins/cases/public/components/foo.tsx', 'xpack.cases'],
+ ['x-pack/plugins/observability/public/header_actions.tsx', 'xpack.observability'],
+ ['x-pack/plugins/apm/common/components/app/correlations/correlations_table.tsx', 'xpack.apm'],
+ ['x-pack/plugins/cases/server/components/foo.tsx', 'xpack.cases'],
[
'x-pack/plugins/synthetics/public/apps/synthetics/components/alerts/toggle_alert_flyout_button.tsx',
'xpack.synthetics',
],
- [
- 'packages/kbn-alerts-ui-shared/src/alert_lifecycle_status_badge/index.tsx',
- 'app_not_found_in_i18nrc',
- ],
+ ['src/plugins/vis_types/gauge/public/editor/collections.ts', 'visTypeGauge'],
+ ['packages/kbn-alerts-ui-shared/src/alert_lifecycle_status_badge/index.tsx', 'alertsUIShared'],
];
describe('Get i18n Identifier for file', () => {
diff --git a/packages/kbn-eslint-plugin-i18n/helpers/get_i18n_identifier_from_file_path.ts b/packages/kbn-eslint-plugin-i18n/helpers/get_i18n_identifier_from_file_path.ts
index d23a42f4ebcfb..7b39d119ee845 100644
--- a/packages/kbn-eslint-plugin-i18n/helpers/get_i18n_identifier_from_file_path.ts
+++ b/packages/kbn-eslint-plugin-i18n/helpers/get_i18n_identifier_from_file_path.ts
@@ -14,18 +14,38 @@ export function getI18nIdentifierFromFilePath(fileName: string, cwd: string) {
const { dir } = parse(fileName);
const relativePathToFile = dir.replace(cwd, '');
- const relativePathArray = relativePathToFile.split('/');
+ // We need to match the path of the file that is being worked in with the path
+ // that is noted in the values inside the i18nrc.json object.
+ // These values differ depending on which i18nrc.json object you look at (there are multiple)
+ // so we need to account for both notations.
+ const relativePathArray = relativePathToFile.includes('src')
+ ? relativePathToFile.split('/').slice(1)
+ : relativePathToFile.split('/').slice(2);
- const path = `${relativePathArray[2]}/${relativePathArray[3]}`;
+ const pluginNameIndex = relativePathArray.findIndex(
+ (el) => el === 'public' || el === 'server' || el === 'common'
+ );
+
+ const path = relativePathArray.slice(0, pluginNameIndex).join('/');
const xpackRC = resolve(join(__dirname, '../../../'), 'x-pack/.i18nrc.json');
+ const rootRC = resolve(join(__dirname, '../../../'), '.i18nrc.json');
+
+ const xpackI18nrcFile = fs.readFileSync(xpackRC, 'utf8');
+ const xpackI18nrc = JSON.parse(xpackI18nrcFile);
+
+ const rootI18nrcFile = fs.readFileSync(rootRC, 'utf8');
+ const rootI18nrc = JSON.parse(rootI18nrcFile);
+
+ const allPaths = { ...xpackI18nrc.paths, ...rootI18nrc.paths };
- const i18nrcFile = fs.readFileSync(xpackRC, 'utf8');
- const i18nrc = JSON.parse(i18nrcFile);
+ if (Object.keys(allPaths).length === 0) return 'could_not_find_i18nrc';
- return i18nrc && i18nrc.paths
- ? findKey(i18nrc.paths, (v) =>
- Array.isArray(v) ? v.find((e) => e === path) : typeof v === 'string' && v === path
- ) ?? 'app_not_found_in_i18nrc'
- : 'could_not_find_i18nrc';
+ return (
+ findKey(allPaths, (value) =>
+ Array.isArray(value)
+ ? value.find((el) => el === path)
+ : typeof value === 'string' && value === path
+ ) ?? 'app_not_found_in_i18nrc'
+ );
}
diff --git a/packages/kbn-eslint-plugin-i18n/helpers/get_i18n_import_fixer.ts b/packages/kbn-eslint-plugin-i18n/helpers/get_i18n_import_fixer.ts
index cf3a7330f7584..7b81c0f7a6b8c 100644
--- a/packages/kbn-eslint-plugin-i18n/helpers/get_i18n_import_fixer.ts
+++ b/packages/kbn-eslint-plugin-i18n/helpers/get_i18n_import_fixer.ts
@@ -10,10 +10,10 @@ import { SourceCode } from 'eslint';
export function getI18nImportFixer({
sourceCode,
- mode,
+ translationFunction,
}: {
sourceCode: SourceCode;
- mode: 'i18n.translate' | 'FormattedMessage';
+ translationFunction: 'i18n.translate' | 'FormattedMessage';
}) {
let existingI18nImportLineIndex = -1;
let i18nImportLineToBeAdded = '';
@@ -27,7 +27,7 @@ export function getI18nImportFixer({
*
* */
- if (mode === 'i18n.translate') {
+ if (translationFunction === 'i18n.translate') {
existingI18nImportLineIndex = sourceCode.lines.findIndex((l) => l.includes("from '@kbn/i18n'"));
const i18nImportLineInSource = sourceCode.lines[existingI18nImportLineIndex];
@@ -46,7 +46,7 @@ export function getI18nImportFixer({
}
}
- if (mode === 'FormattedMessage') {
+ if (translationFunction === 'FormattedMessage') {
existingI18nImportLineIndex = sourceCode.lines.findIndex((l) =>
l.includes("from '@kbn/i18n-react'")
);
@@ -83,21 +83,27 @@ export function getI18nImportFixer({
return {
i18nImportLine: i18nImportLineToBeAdded,
rangeToAddI18nImportLine: [start, end] as [number, number],
- mode: 'replace',
+ replaceMode: 'replace',
};
}
// If the file doesn't have an import line for the translation package yet, we need to add it.
// Pretty safe bet to add it underneath the import line for React.
- const lineIndex = sourceCode.lines.findIndex((l) => l.includes("from 'react'"));
+ let lineIndex = sourceCode.lines.findIndex((l) => l.includes("from 'react'") || l.includes('*/'));
+
+ if (lineIndex === -1) {
+ lineIndex = 0;
+ }
+
const targetLine = sourceCode.lines[lineIndex];
+ // `getIndexFromLoc` is 0-based, so we need to add 1 to the line index.
const start = sourceCode.getIndexFromLoc({ line: lineIndex + 1, column: 0 });
const end = start + targetLine.length;
return {
i18nImportLine: i18nImportLineToBeAdded,
rangeToAddI18nImportLine: [start, end] as [number, number],
- mode: 'insert',
+ replaceMode: 'insert',
};
}
diff --git a/packages/kbn-eslint-plugin-i18n/index.ts b/packages/kbn-eslint-plugin-i18n/index.ts
index be5661cf46dec..dd99785204c33 100644
--- a/packages/kbn-eslint-plugin-i18n/index.ts
+++ b/packages/kbn-eslint-plugin-i18n/index.ts
@@ -8,6 +8,7 @@
import { StringsShouldBeTranslatedWithI18n } from './rules/strings_should_be_translated_with_i18n';
import { StringsShouldBeTranslatedWithFormattedMessage } from './rules/strings_should_be_translated_with_formatted_message';
+import { I18nTranslateShouldStartWithTheRightId } from './rules/i18n_translate_should_start_with_the_right_id';
/**
* Custom ESLint rules, add `'@kbn/eslint-plugin-i18n'` to your eslint config to use them
@@ -17,4 +18,5 @@ export const rules = {
strings_should_be_translated_with_i18n: StringsShouldBeTranslatedWithI18n,
strings_should_be_translated_with_formatted_message:
StringsShouldBeTranslatedWithFormattedMessage,
+ i18n_translate_should_start_with_the_right_id: I18nTranslateShouldStartWithTheRightId,
};
diff --git a/packages/kbn-eslint-plugin-i18n/kibana.jsonc b/packages/kbn-eslint-plugin-i18n/kibana.jsonc
index 72e051941db68..b234cc835ed3a 100644
--- a/packages/kbn-eslint-plugin-i18n/kibana.jsonc
+++ b/packages/kbn-eslint-plugin-i18n/kibana.jsonc
@@ -1,6 +1,6 @@
{
"type": "shared-common",
"id": "@kbn/eslint-plugin-i18n",
- "owner": "@elastic/obs-knowledge-team",
+ "owner": ["@elastic/obs-knowledge-team", "@elastic/kibana-operations"],
"devOnly": true
}
diff --git a/packages/kbn-eslint-plugin-i18n/rules/i18n_translate_should_start_with_the_right_id.test.ts b/packages/kbn-eslint-plugin-i18n/rules/i18n_translate_should_start_with_the_right_id.test.ts
new file mode 100644
index 0000000000000..49bdb03a4f476
--- /dev/null
+++ b/packages/kbn-eslint-plugin-i18n/rules/i18n_translate_should_start_with_the_right_id.test.ts
@@ -0,0 +1,134 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { RuleTester } from 'eslint';
+import {
+ I18nTranslateShouldStartWithTheRightId,
+ RULE_WARNING_MESSAGE,
+} from './i18n_translate_should_start_with_the_right_id';
+
+const tsTester = [
+ '@typescript-eslint/parser',
+ new RuleTester({
+ parser: require.resolve('@typescript-eslint/parser'),
+ parserOptions: {
+ sourceType: 'module',
+ ecmaVersion: 2018,
+ ecmaFeatures: {
+ jsx: true,
+ },
+ },
+ }),
+] as const;
+
+const babelTester = [
+ '@babel/eslint-parser',
+ new RuleTester({
+ parser: require.resolve('@babel/eslint-parser'),
+ parserOptions: {
+ sourceType: 'module',
+ ecmaVersion: 2018,
+ requireConfigFile: false,
+ babelOptions: {
+ presets: ['@kbn/babel-preset/node_preset'],
+ },
+ },
+ }),
+] as const;
+
+const invalid: RuleTester.InvalidTestCase[] = [
+ {
+ name: 'When a string literal is passed to i18n.translate, it should start with the correct i18n identifier.',
+ filename: '/x-pack/plugins/observability/public/test_component.ts',
+ code: `
+import { i18n } from '@kbn/i18n';
+
+function TestComponent() {
+ const foo = i18n.translate('foo');
+}`,
+ errors: [
+ {
+ line: 5,
+ message: RULE_WARNING_MESSAGE,
+ },
+ ],
+ output: `
+import { i18n } from '@kbn/i18n';
+
+function TestComponent() {
+ const foo = i18n.translate('xpack.observability.testComponent.', { defaultMessage: '' });
+}`,
+ },
+ {
+ name: 'When no string literal is passed to i18n.translate, it should start with the correct i18n identifier.',
+ filename: '/x-pack/plugins/observability/public/test_component.ts',
+ code: `
+import { i18n } from '@kbn/i18n';
+
+function TestComponent() {
+ const foo = i18n.translate();
+}`,
+ errors: [
+ {
+ line: 5,
+ message: RULE_WARNING_MESSAGE,
+ },
+ ],
+ output: `
+import { i18n } from '@kbn/i18n';
+
+function TestComponent() {
+ const foo = i18n.translate('xpack.observability.testComponent.', { defaultMessage: '' });
+}`,
+ },
+ {
+ name: 'When i18n is not imported yet, the rule should add it.',
+ filename: '/x-pack/plugins/observability/public/test_component.ts',
+ code: `
+function TestComponent() {
+ const foo = i18n.translate();
+}`,
+ errors: [
+ {
+ line: 3,
+ message: RULE_WARNING_MESSAGE,
+ },
+ ],
+ output: `
+import { i18n } from '@kbn/i18n';
+function TestComponent() {
+ const foo = i18n.translate('xpack.observability.testComponent.', { defaultMessage: '' });
+}`,
+ },
+];
+
+const valid: RuleTester.ValidTestCase[] = [
+ {
+ name: invalid[0].name,
+ filename: invalid[0].filename,
+ code: invalid[0].output as string,
+ },
+ {
+ name: invalid[1].name,
+ filename: invalid[1].filename,
+ code: invalid[1].output as string,
+ },
+];
+
+for (const [name, tester] of [tsTester, babelTester]) {
+ describe(name, () => {
+ tester.run(
+ '@kbn/i18n_translate_should_start_with_the_right_id',
+ I18nTranslateShouldStartWithTheRightId,
+ {
+ valid,
+ invalid,
+ }
+ );
+ });
+}
diff --git a/packages/kbn-eslint-plugin-i18n/rules/i18n_translate_should_start_with_the_right_id.ts b/packages/kbn-eslint-plugin-i18n/rules/i18n_translate_should_start_with_the_right_id.ts
new file mode 100644
index 0000000000000..d6510ba588a4e
--- /dev/null
+++ b/packages/kbn-eslint-plugin-i18n/rules/i18n_translate_should_start_with_the_right_id.ts
@@ -0,0 +1,82 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import type { TSESTree } from '@typescript-eslint/typescript-estree';
+import type { Rule } from 'eslint';
+import { getI18nIdentifierFromFilePath } from '../helpers/get_i18n_identifier_from_file_path';
+import { getFunctionName } from '../helpers/get_function_name';
+import { getI18nImportFixer } from '../helpers/get_i18n_import_fixer';
+import { isTruthy } from '../helpers/utils';
+
+export const RULE_WARNING_MESSAGE =
+ 'First parameter passed to i18n.translate should start with the correct i18n identifier for this file. Correct it or use the autofix suggestion.';
+
+export const I18nTranslateShouldStartWithTheRightId: Rule.RuleModule = {
+ meta: {
+ type: 'suggestion',
+ fixable: 'code',
+ },
+ create(context) {
+ const { cwd, filename, getScope, sourceCode, report } = context;
+
+ return {
+ CallExpression: (node: TSESTree.CallExpression) => {
+ const { callee } = node;
+
+ if (
+ !callee ||
+ !('object' in callee) ||
+ !('property' in callee) ||
+ !('name' in callee.object) ||
+ !('name' in callee.property) ||
+ callee.object.name !== 'i18n' ||
+ callee.property.name !== 'translate'
+ )
+ return;
+
+ const identifier =
+ Array.isArray(node.arguments) &&
+ node.arguments.length &&
+ 'value' in node.arguments[0] &&
+ typeof node.arguments[0].value === 'string' &&
+ node.arguments[0].value;
+
+ const i18nAppId = getI18nIdentifierFromFilePath(filename, cwd);
+ const functionDeclaration = getScope().block as TSESTree.FunctionDeclaration;
+ const functionName = getFunctionName(functionDeclaration);
+
+ // Check if i18n has already been imported into the file
+ const { hasI18nImportLine, i18nImportLine, rangeToAddI18nImportLine, replaceMode } =
+ getI18nImportFixer({
+ sourceCode,
+ translationFunction: 'i18n.translate',
+ });
+
+ if (!identifier || (identifier && !identifier.startsWith(`${i18nAppId}.`))) {
+ report({
+ node: node as any,
+ message: RULE_WARNING_MESSAGE,
+ fix(fixer) {
+ return [
+ fixer.replaceTextRange(
+ node.range,
+ `i18n.translate('${i18nAppId}.${functionName}.', { defaultMessage: '' })`
+ ),
+ !hasI18nImportLine && rangeToAddI18nImportLine
+ ? replaceMode === 'replace'
+ ? fixer.replaceTextRange(rangeToAddI18nImportLine, i18nImportLine)
+ : fixer.insertTextAfterRange(rangeToAddI18nImportLine, `\n${i18nImportLine}`)
+ : null,
+ ].filter(isTruthy);
+ },
+ });
+ }
+ },
+ } as Rule.RuleListener;
+ },
+};
diff --git a/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_formatted_message.test.ts b/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_formatted_message.test.ts
index 009fac255fc63..6faf6732f9015 100644
--- a/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_formatted_message.test.ts
+++ b/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_formatted_message.test.ts
@@ -7,7 +7,10 @@
*/
import { RuleTester } from 'eslint';
-import { StringsShouldBeTranslatedWithFormattedMessage } from './strings_should_be_translated_with_formatted_message';
+import {
+ StringsShouldBeTranslatedWithFormattedMessage,
+ RULE_WARNING_MESSAGE,
+} from './strings_should_be_translated_with_formatted_message';
const tsTester = [
'@typescript-eslint/parser',
@@ -41,7 +44,7 @@ const babelTester = [
const invalid: RuleTester.InvalidTestCase[] = [
{
name: 'A JSX element with a string literal should be translated with i18n',
- filename: 'x-pack/plugins/observability/public/test_component.tsx',
+ filename: '/x-pack/plugins/observability/public/test_component.tsx',
code: `
import React from 'react';
@@ -53,7 +56,7 @@ function TestComponent() {
errors: [
{
line: 6,
- message: `Strings should be translated with . Use the autofix suggestion or add your own.`,
+ message: RULE_WARNING_MESSAGE,
},
],
output: `
@@ -64,7 +67,7 @@ function TestComponent() {
return (
)
@@ -72,7 +75,7 @@ function TestComponent() {
},
{
name: 'A JSX element with a string literal that are inside an Eui component should take the component name of the parent into account',
- filename: 'x-pack/plugins/observability/public/another_component.tsx',
+ filename: '/x-pack/plugins/observability/public/another_component.tsx',
code: `
import React from 'react';
@@ -90,7 +93,7 @@ function AnotherComponent() {
errors: [
{
line: 9,
- message: `Strings should be translated with . Use the autofix suggestion or add your own.`,
+ message: RULE_WARNING_MESSAGE,
},
],
output: `
@@ -104,7 +107,7 @@ function AnotherComponent() {
@@ -115,7 +118,7 @@ function AnotherComponent() {
},
{
name: 'When no import of the translation module is present, the import line should be added',
- filename: 'x-pack/plugins/observability/public/yet_another_component.tsx',
+ filename: '/x-pack/plugins/observability/public/yet_another_component.tsx',
code: `
import React from 'react';
@@ -129,7 +132,7 @@ function YetAnotherComponent() {
errors: [
{
line: 7,
- message: `Strings should be translated with . Use the autofix suggestion or add your own.`,
+ message: RULE_WARNING_MESSAGE,
},
],
output: `
@@ -141,7 +144,7 @@ function YetAnotherComponent() {
@@ -150,7 +153,7 @@ function YetAnotherComponent() {
},
{
name: 'Import lines without the necessary translation module should be updated to include i18n',
- filename: 'x-pack/plugins/observability/public/test_component.tsx',
+ filename: '/x-pack/plugins/observability/public/test_component.tsx',
code: `
import React from 'react';
import { SomeOtherModule } from '@kbn/i18n-react';
@@ -163,7 +166,7 @@ function TestComponent() {
errors: [
{
line: 7,
- message: `Strings should be translated with . Use the autofix suggestion or add your own.`,
+ message: RULE_WARNING_MESSAGE,
},
],
output: `
@@ -172,13 +175,13 @@ import { SomeOtherModule, FormattedMessage } from '@kbn/i18n-react';
function TestComponent() {
return (
- } />
+ } />
)
}`,
},
{
name: 'JSX elements that have a label or aria-label prop with a string value should be translated with i18n',
- filename: 'x-pack/plugins/observability/public/test_component.tsx',
+ filename: '/x-pack/plugins/observability/public/test_component.tsx',
code: `
import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
@@ -191,7 +194,7 @@ function TestComponent() {
errors: [
{
line: 7,
- message: `Strings should be translated with . Use the autofix suggestion or add your own.`,
+ message: RULE_WARNING_MESSAGE,
},
],
output: `
@@ -200,13 +203,13 @@ import { FormattedMessage } from '@kbn/i18n-react';
function TestComponent() {
return (
- } />
+ } />
)
}`,
},
{
name: 'JSX elements that have a label or aria-label prop with a JSXExpression value that is a string should be translated with i18n',
- filename: 'x-pack/plugins/observability/public/test_component.tsx',
+ filename: '/x-pack/plugins/observability/public/test_component.tsx',
code: `
import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
@@ -219,7 +222,7 @@ function TestComponent() {
errors: [
{
line: 7,
- message: `Strings should be translated with . Use the autofix suggestion or add your own.`,
+ message: RULE_WARNING_MESSAGE,
},
],
output: `
@@ -228,7 +231,7 @@ function TestComponent() {
function TestComponent() {
return (
- } />
+ } />
)
}`,
},
@@ -237,7 +240,7 @@ function TestComponent() {
const valid: RuleTester.ValidTestCase[] = [
{
name: 'A JSXText element inside a EuiCode component should not be translated',
- filename: 'x-pack/plugins/observability/public/test_component.tsx',
+ filename: '/x-pack/plugins/observability/public/test_component.tsx',
code: `
import React from 'react';
@@ -249,7 +252,7 @@ function TestComponent() {
},
{
name: 'A JSXText element that contains anything other than alpha characters should not be translated',
- filename: 'x-pack/plugins/observability/public/test_component.tsx',
+ filename: '/x-pack/plugins/observability/public/test_component.tsx',
code: `
import React from 'react';
@@ -261,7 +264,7 @@ function TestComponent() {
},
{
name: 'A JSXText element that is wrapped in three backticks (markdown) should not be translated',
- filename: 'x-pack/plugins/observability/public/test_component.tsx',
+ filename: '/x-pack/plugins/observability/public/test_component.tsx',
code: `
import React from 'react';
diff --git a/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_formatted_message.ts b/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_formatted_message.ts
index 77b5918951036..ea96cf313d1b4 100644
--- a/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_formatted_message.ts
+++ b/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_formatted_message.ts
@@ -14,6 +14,8 @@ import { getFunctionName } from '../helpers/get_function_name';
import { getI18nImportFixer } from '../helpers/get_i18n_import_fixer';
import { cleanString, isTruthy } from '../helpers/utils';
+export const RULE_WARNING_MESSAGE =
+ 'Strings should be translated with . Use the autofix suggestion or add your own.';
export const StringsShouldBeTranslatedWithFormattedMessage: Rule.RuleModule = {
meta: {
type: 'suggestion',
@@ -44,17 +46,16 @@ export const StringsShouldBeTranslatedWithFormattedMessage: Rule.RuleModule = {
const translationIdSuggestion = `${i18nAppId}.${functionName}.${intent}`; // 'xpack.observability.overview.logs.loadMoreLabel'
// Check if i18n has already been imported into the file
- const { hasI18nImportLine, i18nImportLine, rangeToAddI18nImportLine, mode } =
+ const { hasI18nImportLine, i18nImportLine, rangeToAddI18nImportLine, replaceMode } =
getI18nImportFixer({
sourceCode,
- mode: 'FormattedMessage',
+ translationFunction: 'FormattedMessage',
});
// Show warning to developer and offer autofix suggestion
report({
node: node as any,
- message:
- 'Strings should be translated with . Use the autofix suggestion or add your own.',
+ message: RULE_WARNING_MESSAGE,
fix(fixer) {
return [
fixer.replaceText(
@@ -65,7 +66,7 @@ export const StringsShouldBeTranslatedWithFormattedMessage: Rule.RuleModule = {
/>`
),
!hasI18nImportLine && rangeToAddI18nImportLine
- ? mode === 'replace'
+ ? replaceMode === 'replace'
? fixer.replaceTextRange(rangeToAddI18nImportLine, i18nImportLine)
: fixer.insertTextAfterRange(rangeToAddI18nImportLine, `\n${i18nImportLine}`)
: null,
@@ -106,17 +107,16 @@ export const StringsShouldBeTranslatedWithFormattedMessage: Rule.RuleModule = {
const translationIdSuggestion = `${i18nAppId}.${functionName}.${intent}`; // 'xpack.observability.overview.logs.loadMoreLabel'
// Check if i18n has already been imported into the file.
- const { hasI18nImportLine, i18nImportLine, rangeToAddI18nImportLine, mode } =
+ const { hasI18nImportLine, i18nImportLine, rangeToAddI18nImportLine, replaceMode } =
getI18nImportFixer({
sourceCode,
- mode: 'FormattedMessage',
+ translationFunction: 'FormattedMessage',
});
// Show warning to developer and offer autofix suggestion
report({
node: node as any,
- message:
- 'Strings should be translated with . Use the autofix suggestion or add your own.',
+ message: RULE_WARNING_MESSAGE,
fix(fixer) {
return [
fixer.replaceTextRange(
@@ -124,7 +124,7 @@ export const StringsShouldBeTranslatedWithFormattedMessage: Rule.RuleModule = {
`{}`
),
!hasI18nImportLine && rangeToAddI18nImportLine
- ? mode === 'replace'
+ ? replaceMode === 'replace'
? fixer.replaceTextRange(rangeToAddI18nImportLine, i18nImportLine)
: fixer.insertTextAfterRange(rangeToAddI18nImportLine, `\n${i18nImportLine}`)
: null,
diff --git a/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_i18n.test.ts b/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_i18n.test.ts
index f470ed885682f..3142d368b0764 100644
--- a/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_i18n.test.ts
+++ b/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_i18n.test.ts
@@ -7,7 +7,10 @@
*/
import { RuleTester } from 'eslint';
-import { StringsShouldBeTranslatedWithI18n } from './strings_should_be_translated_with_i18n';
+import {
+ StringsShouldBeTranslatedWithI18n,
+ RULE_WARNING_MESSAGE,
+} from './strings_should_be_translated_with_i18n';
const tsTester = [
'@typescript-eslint/parser',
@@ -41,7 +44,7 @@ const babelTester = [
const invalid: RuleTester.InvalidTestCase[] = [
{
name: 'A JSX element with a string literal should be translated with i18n',
- filename: 'x-pack/plugins/observability/public/test_component.tsx',
+ filename: '/x-pack/plugins/observability/public/test_component.tsx',
code: `
import React from 'react';
@@ -53,7 +56,7 @@ function TestComponent() {
errors: [
{
line: 6,
- message: `Strings should be translated with i18n. Use the autofix suggestion or add your own.`,
+ message: RULE_WARNING_MESSAGE,
},
],
output: `
@@ -62,13 +65,13 @@ import { i18n } from '@kbn/i18n';
function TestComponent() {
return (
- {i18n.translate('app_not_found_in_i18nrc.testComponent.div.thisIsATestLabel', { defaultMessage: 'This is a test' })}
+ {i18n.translate('xpack.observability.testComponent.div.thisIsATestLabel', { defaultMessage: 'This is a test' })}
)
}`,
},
{
name: 'A JSX element with a string literal that are inside an Eui component should take the component name of the parent into account',
- filename: 'x-pack/plugins/observability/public/another_component.tsx',
+ filename: '/x-pack/plugins/observability/public/another_component.tsx',
code: `
import React from 'react';
@@ -86,7 +89,7 @@ function AnotherComponent() {
errors: [
{
line: 9,
- message: `Strings should be translated with i18n. Use the autofix suggestion or add your own.`,
+ message: RULE_WARNING_MESSAGE,
},
],
output: `
@@ -98,7 +101,7 @@ function AnotherComponent() {
- {i18n.translate('app_not_found_in_i18nrc.anotherComponent.thisIsATestButtonLabel', { defaultMessage: 'This is a test' })}
+ {i18n.translate('xpack.observability.anotherComponent.thisIsATestButtonLabel', { defaultMessage: 'This is a test' })}
@@ -107,7 +110,7 @@ function AnotherComponent() {
},
{
name: 'When no import of the translation module is present, the import line should be added',
- filename: 'x-pack/plugins/observability/public/yet_another_component.tsx',
+ filename: '/x-pack/plugins/observability/public/yet_another_component.tsx',
code: `
import React from 'react';
@@ -121,7 +124,7 @@ function YetAnotherComponent() {
errors: [
{
line: 7,
- message: `Strings should be translated with i18n. Use the autofix suggestion or add your own.`,
+ message: RULE_WARNING_MESSAGE,
},
],
output: `
@@ -131,14 +134,14 @@ import { i18n } from '@kbn/i18n';
function YetAnotherComponent() {
return (
- {i18n.translate('app_not_found_in_i18nrc.yetAnotherComponent.selectMeSelectLabel', { defaultMessage: 'Select me' })}
+ {i18n.translate('xpack.observability.yetAnotherComponent.selectMeSelectLabel', { defaultMessage: 'Select me' })}
)
}`,
},
{
name: 'Import lines without the necessary translation module should be updated to include i18n',
- filename: 'x-pack/plugins/observability/public/test_component.tsx',
+ filename: '/x-pack/plugins/observability/public/test_component.tsx',
code: `
import React from 'react';
import { SomeOtherModule } from '@kbn/i18n';
@@ -151,7 +154,7 @@ function TestComponent() {
errors: [
{
line: 7,
- message: `Strings should be translated with i18n. Use the autofix suggestion or add your own.`,
+ message: RULE_WARNING_MESSAGE,
},
],
output: `
@@ -160,13 +163,13 @@ import { SomeOtherModule, i18n } from '@kbn/i18n';
function TestComponent() {
return (
-
+
)
}`,
},
{
name: 'JSX elements that have a label or aria-label prop with a string value should be translated with i18n',
- filename: 'x-pack/plugins/observability/public/test_component.tsx',
+ filename: '/x-pack/plugins/observability/public/test_component.tsx',
code: `
import React from 'react';
import { i18n } from '@kbn/i18n';
@@ -179,7 +182,7 @@ function TestComponent() {
errors: [
{
line: 7,
- message: `Strings should be translated with i18n. Use the autofix suggestion or add your own.`,
+ message: RULE_WARNING_MESSAGE,
},
],
output: `
@@ -188,13 +191,13 @@ import { i18n } from '@kbn/i18n';
function TestComponent() {
return (
-
+
)
}`,
},
{
name: 'JSX elements that have a label or aria-label prop with a JSXExpression value that is a string should be translated with i18n',
- filename: 'x-pack/plugins/observability/public/test_component.tsx',
+ filename: '/x-pack/plugins/observability/public/test_component.tsx',
code: `
import React from 'react';
import { i18n } from '@kbn/i18n';
@@ -207,7 +210,7 @@ function TestComponent() {
errors: [
{
line: 7,
- message: `Strings should be translated with i18n. Use the autofix suggestion or add your own.`,
+ message: RULE_WARNING_MESSAGE,
},
],
output: `
@@ -216,7 +219,7 @@ import { i18n } from '@kbn/i18n';
function TestComponent() {
return (
-
+
)
}`,
},
@@ -225,7 +228,7 @@ function TestComponent() {
const valid: RuleTester.ValidTestCase[] = [
{
name: 'A JSXText element inside a EuiCode component should not be translated',
- filename: 'x-pack/plugins/observability/public/test_component.tsx',
+ filename: '/x-pack/plugins/observability/public/test_component.tsx',
code: `
import React from 'react';
@@ -237,7 +240,7 @@ function TestComponent() {
},
{
name: 'A JSXText element that contains anything other than alpha characters should not be translated',
- filename: 'x-pack/plugins/observability/public/test_component.tsx',
+ filename: '/x-pack/plugins/observability/public/test_component.tsx',
code: `
import React from 'react';
@@ -249,7 +252,7 @@ function TestComponent() {
},
{
name: 'A JSXText element that is wrapped in three backticks (markdown) should not be translated',
- filename: 'x-pack/plugins/observability/public/test_component.tsx',
+ filename: '/x-pack/plugins/observability/public/test_component.tsx',
code: `
import React from 'react';
diff --git a/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_i18n.ts b/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_i18n.ts
index fea04d33d555f..ec1630de115e6 100644
--- a/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_i18n.ts
+++ b/packages/kbn-eslint-plugin-i18n/rules/strings_should_be_translated_with_i18n.ts
@@ -14,6 +14,9 @@ import { getFunctionName } from '../helpers/get_function_name';
import { getI18nImportFixer } from '../helpers/get_i18n_import_fixer';
import { cleanString, isTruthy } from '../helpers/utils';
+export const RULE_WARNING_MESSAGE =
+ 'Strings should be translated with i18n. Use the autofix suggestion or add your own.';
+
export const StringsShouldBeTranslatedWithI18n: Rule.RuleModule = {
meta: {
type: 'suggestion',
@@ -44,17 +47,16 @@ export const StringsShouldBeTranslatedWithI18n: Rule.RuleModule = {
const translationIdSuggestion = `${i18nAppId}.${functionName}.${intent}`; // 'xpack.observability.overview.logs.loadMoreLabel'
// Check if i18n has already been imported into the file
- const { hasI18nImportLine, i18nImportLine, rangeToAddI18nImportLine, mode } =
+ const { hasI18nImportLine, i18nImportLine, rangeToAddI18nImportLine, replaceMode } =
getI18nImportFixer({
sourceCode,
- mode: 'i18n.translate',
+ translationFunction: 'i18n.translate',
});
// Show warning to developer and offer autofix suggestion
report({
node: node as any,
- message:
- 'Strings should be translated with i18n. Use the autofix suggestion or add your own.',
+ message: RULE_WARNING_MESSAGE,
fix(fixer) {
return [
fixer.replaceText(
@@ -62,7 +64,7 @@ export const StringsShouldBeTranslatedWithI18n: Rule.RuleModule = {
`${whiteSpaces}{i18n.translate('${translationIdSuggestion}', { defaultMessage: '${value}' })}`
),
!hasI18nImportLine && rangeToAddI18nImportLine
- ? mode === 'replace'
+ ? replaceMode === 'replace'
? fixer.replaceTextRange(rangeToAddI18nImportLine, i18nImportLine)
: fixer.insertTextAfterRange(rangeToAddI18nImportLine, `\n${i18nImportLine}`)
: null,
@@ -103,17 +105,16 @@ export const StringsShouldBeTranslatedWithI18n: Rule.RuleModule = {
const translationIdSuggestion = `${i18nAppId}.${functionName}.${intent}`; // 'xpack.observability.overview.logs.loadMoreLabel'
// Check if i18n has already been imported into the file.
- const { hasI18nImportLine, i18nImportLine, rangeToAddI18nImportLine, mode } =
+ const { hasI18nImportLine, i18nImportLine, rangeToAddI18nImportLine, replaceMode } =
getI18nImportFixer({
sourceCode,
- mode: 'i18n.translate',
+ translationFunction: 'i18n.translate',
});
// Show warning to developer and offer autofix suggestion
report({
node: node as any,
- message:
- 'Strings should be translated with i18n. Use the autofix suggestion or add your own.',
+ message: RULE_WARNING_MESSAGE,
fix(fixer) {
return [
fixer.replaceTextRange(
@@ -121,7 +122,7 @@ export const StringsShouldBeTranslatedWithI18n: Rule.RuleModule = {
`{i18n.translate('${translationIdSuggestion}', { defaultMessage: '${val}' })}`
),
!hasI18nImportLine && rangeToAddI18nImportLine
- ? mode === 'replace'
+ ? replaceMode === 'replace'
? fixer.replaceTextRange(rangeToAddI18nImportLine, i18nImportLine)
: fixer.insertTextAfterRange(rangeToAddI18nImportLine, `\n${i18nImportLine}`)
: null,
diff --git a/x-pack/plugins/serverless_search/public/application/components/connectors_ingestion.tsx b/x-pack/plugins/serverless_search/public/application/components/connectors_ingestion.tsx
index 6be9b28707dff..ede574abba12b 100644
--- a/x-pack/plugins/serverless_search/public/application/components/connectors_ingestion.tsx
+++ b/x-pack/plugins/serverless_search/public/application/components/connectors_ingestion.tsx
@@ -50,7 +50,10 @@ export const ConnectorIngestionPanel: React.FC<{ assetBasePath: string }> = ({ a
- createConnector()}>
+ createConnector()}
+ >
{i18n.translate(
'xpack.serverlessSearch.ingestData.alternativeOptions.setupConnectorLabel',
{
@@ -74,6 +77,7 @@ export const ConnectorIngestionPanel: React.FC<{ assetBasePath: string }> = ({ a