diff --git a/.github/workflows/i18n-crowdin-upload.yml b/.github/workflows/i18n-crowdin-upload.yml
index 97638769526..c3a48f97481 100644
--- a/.github/workflows/i18n-crowdin-upload.yml
+++ b/.github/workflows/i18n-crowdin-upload.yml
@@ -7,6 +7,8 @@ on:
- 'public/locales/en-US/grafana.json'
- 'public/app/plugins/datasource/azuremonitor/locales/en-US/grafana-azure-monitor-datasource.json'
- 'public/app/plugins/datasource/mssql/locales/en-US/mssql.json'
+ - 'packages/grafana-sql/src/locales/en-US/grafana-sql.json'
+ - 'packages/grafana-prometheus/src/locales/en-US/grafana-prometheus.json'
branches:
- main
diff --git a/crowdin.yml b/crowdin.yml
index 1f415e96ca7..750bf8d1f4a 100644
--- a/crowdin.yml
+++ b/crowdin.yml
@@ -27,4 +27,10 @@ files: [
"type": "i18next_json",
"dest": "packages/grafana-sql/en-US/%original_file_name%"
},
+ {
+ "source": "packages/grafana-prometheus/src/locales/en-US/grafana-prometheus.json",
+ "translation": "packages/grafana-prometheus/src/locales/%locale%/%original_file_name%",
+ "type": "i18next_json",
+ "dest": "packages/grafana-prometheus/en-US/%original_file_name%"
+ },
]
diff --git a/eslint.config.js b/eslint.config.js
index 721a03cec1f..2b8c6260969 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -305,6 +305,7 @@ module.exports = [
'public/app/!(plugins)/**/*.{ts,tsx,js,jsx}',
'packages/grafana-ui/**/*.{ts,tsx,js,jsx}',
'packages/grafana-sql/**/*.{ts,tsx,js,jsx}',
+ 'packages/grafana-prometheus/**/*.{ts,tsx,js,jsx}',
...pluginsToTranslate.map((plugin) => `${plugin}/**/*.{ts,tsx,js,jsx}`),
],
ignores: [
diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json
index de7aba31fe0..544251bf06c 100644
--- a/packages/grafana-prometheus/package.json
+++ b/packages/grafana-prometheus/package.json
@@ -33,6 +33,7 @@
"build": "tsc -p ./tsconfig.build.json && rollup -c rollup.config.ts --configPlugin esbuild",
"bundle": "rollup -c rollup.config.ts --configPlugin esbuild",
"clean": "rimraf ./dist ./compiled ./package.tgz",
+ "i18n-extract": "i18next --config src/locales/i18next-parser.config.cjs",
"typecheck": "tsc --emitDeclarationOnly false --noEmit",
"prepack": "cp package.json package.json.bak && node ../../scripts/prepare-npm-package.js",
"postpack": "mv package.json.bak package.json"
@@ -42,6 +43,7 @@
"@floating-ui/react": "0.27.12",
"@grafana/data": "12.1.0-pre",
"@grafana/e2e-selectors": "12.1.0-pre",
+ "@grafana/i18n": "12.1.0-pre",
"@grafana/plugin-ui": "0.10.6",
"@grafana/runtime": "12.1.0-pre",
"@grafana/schema": "12.1.0-pre",
@@ -87,6 +89,7 @@
"@types/pluralize": "^0.0.33",
"@types/prismjs": "1.26.5",
"esbuild": "0.25.0",
+ "i18next-parser": "9.3.0",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"react": "18.3.1",
diff --git a/packages/grafana-prometheus/src/components/AnnotationQueryEditor.test.tsx b/packages/grafana-prometheus/src/components/AnnotationQueryEditor.test.tsx
index 0cce4331a46..b6f10b7d556 100644
--- a/packages/grafana-prometheus/src/components/AnnotationQueryEditor.test.tsx
+++ b/packages/grafana-prometheus/src/components/AnnotationQueryEditor.test.tsx
@@ -83,12 +83,12 @@ describe('AnnotationQueryEditor', () => {
it('displays an error message when annotation data is missing', () => {
render();
- expect(screen.getByText('annotation data load error!')).toBeInTheDocument();
+ expect(screen.getByText('Annotation data load error!')).toBeInTheDocument();
});
it('displays an error message when onAnnotationChange is missing', () => {
render();
- expect(screen.getByText('annotation data load error!')).toBeInTheDocument();
+ expect(screen.getByText('Annotation data load error!')).toBeInTheDocument();
});
it('renders correctly with an empty annotation object', () => {
@@ -96,7 +96,7 @@ describe('AnnotationQueryEditor', () => {
// Should render normally with empty values but not show an error
expect(screen.getByText('Min step')).toBeInTheDocument();
expect(screen.getByText('Title')).toBeInTheDocument();
- expect(screen.queryByText('annotation data load error!')).not.toBeInTheDocument();
+ expect(screen.queryByText('Annotation data load error!')).not.toBeInTheDocument();
});
it('calls onChange when min step is updated', () => {
diff --git a/packages/grafana-prometheus/src/components/AnnotationQueryEditor.tsx b/packages/grafana-prometheus/src/components/AnnotationQueryEditor.tsx
index bc7746f2bdf..cff87f522fa 100644
--- a/packages/grafana-prometheus/src/components/AnnotationQueryEditor.tsx
+++ b/packages/grafana-prometheus/src/components/AnnotationQueryEditor.tsx
@@ -4,6 +4,7 @@ import { memo } from 'react';
import { AnnotationQuery } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
+import { Trans, t } from '@grafana/i18n';
import { EditorField, EditorRow, EditorRows, EditorSwitch } from '@grafana/plugin-ui';
import { AutoSizeInput, Input, Space } from '@grafana/ui';
@@ -30,7 +31,13 @@ export const AnnotationQueryEditor = memo(function AnnotationQueryEditor(props:
const { annotation, onAnnotationChange, onChange, onRunQuery, query } = props;
if (!annotation || !onAnnotationChange) {
- return
annotation data load error!
;
+ return (
+
+
+ Annotation data load error!
+
+
+ );
}
const handleMinStepChange = (value: string) => {
@@ -71,18 +78,24 @@ export const AnnotationQueryEditor = memo(function AnnotationQueryEditor(props:
+
An additional lower limit for the step parameter of the Prometheus query and for the{' '}
- $__interval and $__rate_interval variables.
- >
+ {'{{intervalVar}}'} and {'{{rateIntervalVar}}'} variables.
+
}
>
handleMinStepChange(e.currentTarget.value)}
@@ -94,10 +107,12 @@ export const AnnotationQueryEditor = memo(function AnnotationQueryEditor(props:
-
+ ) => {
return (
-
PromQL Cheat Sheet
+
+ PromQL Cheat Sheet
+
{CHEAT_SHEET_ITEMS.map((item, index) => (
{item.title}
diff --git a/packages/grafana-prometheus/src/components/PromExemplarField.tsx b/packages/grafana-prometheus/src/components/PromExemplarField.tsx
index 2e1f0272965..093737e0b28 100644
--- a/packages/grafana-prometheus/src/components/PromExemplarField.tsx
+++ b/packages/grafana-prometheus/src/components/PromExemplarField.tsx
@@ -4,6 +4,7 @@ import { useEffect, useState } from 'react';
import { usePrevious } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data';
+import { Trans, t } from '@grafana/i18n';
import { IconButton, InlineLabel, Tooltip, useStyles2 } from '@grafana/ui';
import { PrometheusDatasource } from '../datasource';
@@ -48,10 +49,14 @@ export function PromExemplarField({ datasource, onChange, query, ...rest }: Prop
- Exemplars
+ Exemplars {
diff --git a/packages/grafana-prometheus/src/components/PromExploreExtraField.tsx b/packages/grafana-prometheus/src/components/PromExploreExtraField.tsx
index 02ef6d4d461..8e4256dde3a 100644
--- a/packages/grafana-prometheus/src/components/PromExploreExtraField.tsx
+++ b/packages/grafana-prometheus/src/components/PromExploreExtraField.tsx
@@ -6,6 +6,7 @@ import * as React from 'react';
import { usePrevious } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data';
+import { t, Trans } from '@grafana/i18n';
import { InlineFormLabel, RadioButtonGroup, useStyles2 } from '@grafana/ui';
import { PrometheusDatasource } from '../datasource';
@@ -54,7 +55,7 @@ export const PromExploreExtraField = memo(({ query, datasource, onChange, onRunQ
return (
@@ -68,9 +69,11 @@ export const PromExploreExtraField = memo(({ query, datasource, onChange, onRunQ
flexWrap: 'nowrap',
})
)}
- aria-label="Query type field"
+ aria-label={t('components.prom-explore-extra-field.aria-label-query-type-field', 'Query type field')}
>
- Query type
+
+ Query type
+
- Min step
+ Min step
{
onChange={onChangeQuery}
onRunQuery={onRunQuery}
initialValue={query.expr ?? ''}
- placeholder="Enter a PromQL query…"
+ placeholder={t('components.prom-query-field.placeholder-enter-a-prom-ql-query', 'Enter a PromQL query…')}
datasource={datasource}
timeRange={range ?? getDefaultTimeRange()}
/>
diff --git a/packages/grafana-prometheus/src/components/VariableQueryEditor.test.tsx b/packages/grafana-prometheus/src/components/VariableQueryEditor.test.tsx
index 2e5afaef131..65ca6fccfeb 100644
--- a/packages/grafana-prometheus/src/components/VariableQueryEditor.test.tsx
+++ b/packages/grafana-prometheus/src/components/VariableQueryEditor.test.tsx
@@ -3,6 +3,7 @@ import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { dateTime, TimeRange } from '@grafana/data';
+import { selectors } from '@grafana/e2e-selectors';
import { PrometheusDatasource } from '../datasource';
import PrometheusLanguageProvider from '../language_provider';
@@ -270,7 +271,9 @@ describe('PromVariableQueryEditor', () => {
render();
await selectOptionInTest(screen.getByLabelText('Query type'), 'Label values');
- const labelSelect = screen.getByLabelText('label-select');
+ const labelSelect = screen.getByTestId(
+ selectors.components.DataSource.Prometheus.variableQueryEditor.labelValues.labelSelect
+ );
await userEvent.type(labelSelect, 'this');
await selectOptionInTest(labelSelect, 'this');
//display label in label select
@@ -296,7 +299,9 @@ describe('PromVariableQueryEditor', () => {
render();
await selectOptionInTest(screen.getByLabelText('Query type'), 'Label values');
- const labelSelect = screen.getByLabelText('label-select');
+ const labelSelect = screen.getByTestId(
+ selectors.components.DataSource.Prometheus.variableQueryEditor.labelValues.labelSelect
+ );
await userEvent.type(labelSelect, 'this');
await selectOptionInTest(labelSelect, 'this');
diff --git a/packages/grafana-prometheus/src/components/VariableQueryEditor.tsx b/packages/grafana-prometheus/src/components/VariableQueryEditor.tsx
index 97b61161536..5b9720d4c5d 100644
--- a/packages/grafana-prometheus/src/components/VariableQueryEditor.tsx
+++ b/packages/grafana-prometheus/src/components/VariableQueryEditor.tsx
@@ -4,6 +4,7 @@ import { FormEvent, useCallback, useEffect, useState } from 'react';
import { getDefaultTimeRange, QueryEditorProps, SelectableValue, toOption } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
+import { t, Trans } from '@grafana/i18n';
import { AsyncSelect, InlineField, InlineFieldRow, Input, Select, TextArea } from '@grafana/ui';
import { PrometheusDatasource } from '../datasource';
@@ -124,6 +125,7 @@ export const PromVariableQueryEditor = ({ onChange, query, datasource, range }:
});
} else {
// fetch the labels filtered by the metric
+ // eslint-disable-next-line @grafana/i18n/no-untranslated-strings
const labelToConsider = [{ label: '__name__', op: '=', value: metric }];
const expr = promQueryModeller.renderLabels(labelToConsider);
@@ -258,15 +260,19 @@ export const PromVariableQueryEditor = ({ onChange, query, datasource, range }:
<>
The Prometheus data source plugin provides the following query types for template variables.
+
+
+ The Prometheus data source plugin provides the following query types for template variables.
+
+
}
>
}
>
Returns a list of label names, optionally filtering by specified metric regex.
}
+ tooltip={
+
+
+ Returns a list of label names, optionally filtering by specified metric regex.
+
+
+ }
>
{
setLabelNamesMatch(event.currentTarget.value);
@@ -344,15 +357,21 @@ export const PromVariableQueryEditor = ({ onChange, query, datasource, range }:
{qryType === QueryType.MetricNames && (
Returns a list of metrics matching the specified metric regex.
}
+ tooltip={
+
+
+ Returns a list of metrics matching the specified metric regex.
+
+
+ }
>
{
setMetric(e.currentTarget.value);
@@ -371,19 +390,24 @@ export const PromVariableQueryEditor = ({ onChange, query, datasource, range }:
{qryType === QueryType.VarQueryResult && (
- Returns a list of Prometheus query results for the query. This can include Prometheus functions, i.e.
- sum(go_goroutines).
+
+ Returns a list of Prometheus query results for the query. This can include Prometheus functions, i.e.
+ {'{{exampleQuery}}'}.
+
}
>