diff --git a/packages/grafana-i18n/src/eslint/README.md b/packages/grafana-i18n/src/eslint/README.md
index 12b3a559f9b..963aba21fc7 100644
--- a/packages/grafana-i18n/src/eslint/README.md
+++ b/packages/grafana-i18n/src/eslint/README.md
@@ -70,6 +70,32 @@ const bar = {
};
```
+#### `namespace`
+
+Allows specifying a namespace prefix that will be added to all auto-generated translation keys when using ESLint's auto-fix feature. The namespace is separated from the key with a colon (:).
+
+This is useful for organizing translation keys by feature area or preventing key collisions between different parts of the application.
+
+#### Example:
+
+```tsx
+// Configuration:
+{
+ '@grafana/i18n/no-untranslated-strings': ['error', { namespace: 'dashboard' }],
+}
+
+// For a file located at src/features/search/EmptyState.tsx
+
+// Without namespace, auto-fix generates:
+No results found
+
+// With namespace: 'dashboard', auto-fix generates:
+No results found
+
+// For JSX attributes, auto-fix generates:
+
+```
+
#### JSXText
```tsx
diff --git a/packages/grafana-i18n/src/eslint/no-untranslated-strings/no-untranslated-strings.cjs b/packages/grafana-i18n/src/eslint/no-untranslated-strings/no-untranslated-strings.cjs
index 2b058426d53..4a1e5eaf54a 100644
--- a/packages/grafana-i18n/src/eslint/no-untranslated-strings/no-untranslated-strings.cjs
+++ b/packages/grafana-i18n/src/eslint/no-untranslated-strings/no-untranslated-strings.cjs
@@ -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[], basePaths: string[] }]>} RuleDefinition */
-/** @typedef {import('@typescript-eslint/utils/ts-eslint').RuleContext<'noUntranslatedStrings' | 'noUntranslatedStringsProp' | 'wrapWithTrans' | 'wrapWithT' | 'noUntranslatedStringsProperties', [{forceFix: string[], calleesToIgnore: string[], basePaths: string[]}]>} RuleContextWithOptions */
+/** @typedef {import('@typescript-eslint/utils/ts-eslint').RuleModule<'noUntranslatedStrings' | 'noUntranslatedStringsProp' | 'wrapWithTrans' | 'wrapWithT' | 'noUntranslatedStringsProperties', [{ forceFix: string[] , calleesToIgnore: string[], basePaths: string[], namespace?: string }]>} RuleDefinition */
+/** @typedef {import('@typescript-eslint/utils/ts-eslint').RuleContext<'noUntranslatedStrings' | 'noUntranslatedStringsProp' | 'wrapWithTrans' | 'wrapWithT' | 'noUntranslatedStringsProperties', [{forceFix: string[], calleesToIgnore: string[], basePaths: string[], namespace?: string}]>} RuleContextWithOptions */
const {
getNodeValue,
@@ -308,6 +308,10 @@ const noUntranslatedStrings = createRule({
},
default: ['src'],
},
+ namespace: {
+ type: 'string',
+ description: 'Namespace to prepend to translation keys',
+ },
},
additionalProperties: false,
},
diff --git a/packages/grafana-i18n/src/eslint/no-untranslated-strings/no-untranslated-strings.test.js b/packages/grafana-i18n/src/eslint/no-untranslated-strings/no-untranslated-strings.test.js
index 684823ae152..6268b807b27 100644
--- a/packages/grafana-i18n/src/eslint/no-untranslated-strings/no-untranslated-strings.test.js
+++ b/packages/grafana-i18n/src/eslint/no-untranslated-strings/no-untranslated-strings.test.js
@@ -1082,6 +1082,99 @@ const Foo = () => {
errors: [{ messageId: 'noUntranslatedStringsProp' }],
},
+ // NAMESPACE FUNCTIONALITY TESTS
+ {
+ name: 'Basic untranslated text with namespace configuration',
+ code: `
+const Foo = () => Untranslated text
`,
+ filename,
+ options: [{ namespace: 'my-namespace' }],
+ errors: [
+ {
+ messageId: 'noUntranslatedStrings',
+ suggestions: [
+ {
+ messageId: 'wrapWithTrans',
+ output: `
+${TRANS_IMPORT}
+const Foo = () => Untranslated text
`,
+ },
+ ],
+ },
+ ],
+ },
+
+ {
+ name: 'Prop fix with namespace configuration',
+ code: `
+const Foo = () => {
+ return (
+
+ )
+}`,
+ filename,
+ options: [{ namespace: 'alerts' }],
+ errors: [
+ {
+ messageId: 'noUntranslatedStringsProp',
+ suggestions: [
+ {
+ messageId: 'wrapWithT',
+ output: `
+${T_IMPORT}
+const Foo = () => {
+ return (
+
+ )
+}`,
+ },
+ ],
+ },
+ ],
+ },
+
+ {
+ name: 'Empty namespace should not add prefix',
+ code: `
+const Foo = () => Test text
`,
+ filename,
+ options: [{ namespace: '' }],
+ errors: [
+ {
+ messageId: 'noUntranslatedStrings',
+ suggestions: [
+ {
+ messageId: 'wrapWithTrans',
+ output: `
+${TRANS_IMPORT}
+const Foo = () => Test text
`,
+ },
+ ],
+ },
+ ],
+ },
+
+ {
+ name: 'Namespace with special characters gets used as-is',
+ code: `
+const Foo = () => Test content
`,
+ filename,
+ options: [{ namespace: 'feature.sub-module' }],
+ errors: [
+ {
+ messageId: 'noUntranslatedStrings',
+ suggestions: [
+ {
+ messageId: 'wrapWithTrans',
+ output: `
+${TRANS_IMPORT}
+const Foo = () => Test content
`,
+ },
+ ],
+ },
+ ],
+ },
+
// TODO: Enable test once all top-level issues have been fixed
// and rule is enabled again
// {
diff --git a/packages/grafana-i18n/src/eslint/no-untranslated-strings/translation-utils.cjs b/packages/grafana-i18n/src/eslint/no-untranslated-strings/translation-utils.cjs
index 8d7d4456ace..106a7aae80b 100644
--- a/packages/grafana-i18n/src/eslint/no-untranslated-strings/translation-utils.cjs
+++ b/packages/grafana-i18n/src/eslint/no-untranslated-strings/translation-utils.cjs
@@ -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[], calleesToIgnore: string[], basePaths: string[]}]>} RuleContextWithOptions */
+/** @typedef {import('@typescript-eslint/utils/ts-eslint').RuleContext<'noUntranslatedStrings' | 'noUntranslatedStringsProp' | 'wrapWithTrans' | 'wrapWithT', [{forceFix: string[], calleesToIgnore: string[], basePaths: string[], namespace?: string}]>} RuleContextWithOptions */
const { AST_NODE_TYPES } = require('@typescript-eslint/utils');
/**
@@ -168,6 +168,7 @@ function getTranslationPrefix(context) {
*/
const getI18nKey = (node, context) => {
const prefixFromFilePath = getTranslationPrefix(context);
+ const namespace = context.options[0]?.namespace;
const stringValue = getNodeValue(node);
const componentNames = getComponentNames(node, context);
@@ -214,7 +215,8 @@ const getI18nKey = (node, context) => {
const fullPrefix = [prefixFromFilePath, ...componentNames, propertyName, kebabString].filter(Boolean).join('.');
- return fullPrefix;
+ // Prepend namespace if provided
+ return namespace ? `${namespace}:${fullPrefix}` : fullPrefix;
};
/**