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. + +
} > { 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}}'}. +
} >