Internationalisation: Auto mark-up Alerting's strings (#103354)

* Internationalisation: Auto mark-up Alerting code strings

* Update .betterer.results

* Revert prometheus duration docs change

* Fix duplicated import

* Remove unneeded translation
pull/103473/head
Tom Ratcliffe 3 months ago committed by GitHub
parent 892ea3ad30
commit a93e618102
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1072
      .betterer.results
  2. 18
      public/app/features/alerting/unified/AlertGroups.tsx
  3. 5
      public/app/features/alerting/unified/AlertWarning.tsx
  4. 19
      public/app/features/alerting/unified/AlertsFolderView.tsx
  5. 58
      public/app/features/alerting/unified/GrafanaRuleQueryViewer.tsx
  6. 14
      public/app/features/alerting/unified/PanelAlertTabContent.tsx
  7. 5
      public/app/features/alerting/unified/RedirectToRuleViewer.tsx
  8. 13
      public/app/features/alerting/unified/Settings.tsx
  9. 10
      public/app/features/alerting/unified/components/AlertLabel.tsx
  10. 2
      public/app/features/alerting/unified/components/AlertLabels.tsx
  11. 16
      public/app/features/alerting/unified/components/GrafanaAlertmanagerDeliveryWarning.tsx
  12. 9
      public/app/features/alerting/unified/components/InvalidIntervalWarning.tsx
  13. 14
      public/app/features/alerting/unified/components/NoAlertManagerWarning.tsx
  14. 4
      public/app/features/alerting/unified/components/Provisioning.tsx
  15. 5
      public/app/features/alerting/unified/components/alert-groups/AlertDetails.tsx
  16. 5
      public/app/features/alerting/unified/components/alert-groups/AlertGroup.tsx
  17. 3
      public/app/features/alerting/unified/components/alert-groups/AlertGroupFilter.tsx
  18. 7
      public/app/features/alerting/unified/components/alert-groups/AlertStateFilter.tsx
  19. 3
      public/app/features/alerting/unified/components/alert-groups/GroupBy.tsx
  20. 7
      public/app/features/alerting/unified/components/alert-groups/MatcherFilter.tsx
  21. 5
      public/app/features/alerting/unified/components/alertmanager-entities/MuteTimingsSelector.tsx
  22. 29
      public/app/features/alerting/unified/components/bridges/DeclareIncidentButton.tsx
  23. 10
      public/app/features/alerting/unified/components/contact-points/ContactPointHeader.tsx
  24. 29
      public/app/features/alerting/unified/components/contact-points/ContactPoints.tsx
  25. 17
      public/app/features/alerting/unified/components/contact-points/DuplicateMessageTemplate.tsx
  26. 10
      public/app/features/alerting/unified/components/contact-points/EditContactPoint.tsx
  27. 12
      public/app/features/alerting/unified/components/contact-points/EditMessageTemplate.tsx
  28. 21
      public/app/features/alerting/unified/components/contact-points/NotificationTemplates.tsx
  29. 20
      public/app/features/alerting/unified/components/contact-points/components/ContactPointsFilter.tsx
  30. 9
      public/app/features/alerting/unified/components/contact-points/components/GlobalConfig.tsx
  31. 9
      public/app/features/alerting/unified/components/contact-points/components/GlobalConfigAlert.tsx
  32. 25
      public/app/features/alerting/unified/components/contact-points/components/Modals.tsx
  33. 5
      public/app/features/alerting/unified/components/contact-points/components/UnusedBadge.tsx
  34. 4
      public/app/features/alerting/unified/components/export/FileExportPreview.tsx
  35. 19
      public/app/features/alerting/unified/components/export/GrafanaModifyExport.tsx
  36. 7
      public/app/features/alerting/unified/components/export/GrafanaMuteTimingsExporter.tsx
  37. 3
      public/app/features/alerting/unified/components/export/GrafanaPoliciesExporter.tsx
  38. 3
      public/app/features/alerting/unified/components/export/GrafanaReceiverExporter.tsx
  39. 3
      public/app/features/alerting/unified/components/export/GrafanaReceiversExporter.tsx
  40. 3
      public/app/features/alerting/unified/components/export/GrafanaRuleExporter.tsx
  41. 3
      public/app/features/alerting/unified/components/export/GrafanaRuleFolderExporter.tsx
  42. 3
      public/app/features/alerting/unified/components/export/GrafanaRuleGroupExporter.tsx
  43. 3
      public/app/features/alerting/unified/components/export/GrafanaRulesExporter.tsx
  44. 33
      public/app/features/alerting/unified/components/expressions/Expression.tsx
  45. 7
      public/app/features/alerting/unified/components/extensions/AlertInstanceExtensionPoint.tsx
  46. 6
      public/app/features/alerting/unified/components/mute-timings/MuteTimingActionsButtons.tsx
  47. 17
      public/app/features/alerting/unified/components/mute-timings/MuteTimingForm.tsx
  48. 38
      public/app/features/alerting/unified/components/mute-timings/MuteTimingTimeInterval.tsx
  49. 17
      public/app/features/alerting/unified/components/mute-timings/MuteTimingTimeRange.tsx
  50. 17
      public/app/features/alerting/unified/components/mute-timings/MuteTimingsTable.tsx
  51. 18
      public/app/features/alerting/unified/components/notification-policies/ContactPointSelector.tsx
  52. 27
      public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.tsx
  53. 49
      public/app/features/alerting/unified/components/notification-policies/EditNotificationPolicyForm.tsx
  54. 9
      public/app/features/alerting/unified/components/notification-policies/Filters.tsx
  55. 17
      public/app/features/alerting/unified/components/notification-policies/Modals.tsx
  56. 18
      public/app/features/alerting/unified/components/notification-policies/NotificationPoliciesList.tsx
  57. 17
      public/app/features/alerting/unified/components/notification-policies/Policy.tsx
  58. 18
      public/app/features/alerting/unified/components/notification-policies/PromDurationDocs.tsx
  59. 17
      public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.tsx
  60. 26
      public/app/features/alerting/unified/components/receivers/AlertInstanceModalSelector.tsx
  61. 15
      public/app/features/alerting/unified/components/receivers/GlobalConfigForm.tsx
  62. 14
      public/app/features/alerting/unified/components/receivers/PayloadEditor.tsx
  63. 12
      public/app/features/alerting/unified/components/receivers/ReceiversSection.tsx
  64. 33
      public/app/features/alerting/unified/components/receivers/TemplateDataDocs.tsx
  65. 27
      public/app/features/alerting/unified/components/receivers/TemplateForm.tsx
  66. 9
      public/app/features/alerting/unified/components/receivers/TemplatePreview.tsx
  67. 22
      public/app/features/alerting/unified/components/receivers/TemplatesTable.tsx
  68. 16
      public/app/features/alerting/unified/components/receivers/form/ChannelSubForm.tsx
  69. 8
      public/app/features/alerting/unified/components/receivers/form/CloudCommonChannelSettings.tsx
  70. 3
      public/app/features/alerting/unified/components/receivers/form/CloudReceiverForm.tsx
  71. 14
      public/app/features/alerting/unified/components/receivers/form/GenerateAlertDataModal.tsx
  72. 6
      public/app/features/alerting/unified/components/receivers/form/GrafanaCommonChannelSettings.tsx
  73. 13
      public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx
  74. 28
      public/app/features/alerting/unified/components/receivers/form/ReceiverForm.tsx
  75. 9
      public/app/features/alerting/unified/components/receivers/form/TestContactPointModal.tsx
  76. 8
      public/app/features/alerting/unified/components/receivers/form/fields/KeyValueMapInput.tsx
  77. 5
      public/app/features/alerting/unified/components/receivers/form/fields/StringArrayInput.tsx
  78. 5
      public/app/features/alerting/unified/components/receivers/form/fields/SubformArrayField.tsx
  79. 5
      public/app/features/alerting/unified/components/receivers/form/fields/SubformField.tsx
  80. 13
      public/app/features/alerting/unified/components/receivers/form/fields/TemplateContentAndPreview.tsx
  81. 26
      public/app/features/alerting/unified/components/receivers/form/fields/TemplateSelector.tsx
  82. 16
      public/app/features/alerting/unified/components/rule-editor/AlertRuleNameInput.tsx
  83. 8
      public/app/features/alerting/unified/components/rule-editor/AnnotationsStep.tsx
  84. 17
      public/app/features/alerting/unified/components/rule-editor/CloudAlertPreview.tsx
  85. 8
      public/app/features/alerting/unified/components/rule-editor/CloudEvaluationBehavior.tsx
  86. 12
      public/app/features/alerting/unified/components/rule-editor/CustomAnnotationHeaderField.tsx
  87. 41
      public/app/features/alerting/unified/components/rule-editor/DashboardPicker.tsx
  88. 13
      public/app/features/alerting/unified/components/rule-editor/ExpressionEditor.tsx
  89. 28
      public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx
  90. 6
      public/app/features/alerting/unified/components/rule-editor/GrafanaFolderAndLabelsStep.tsx
  91. 5
      public/app/features/alerting/unified/components/rule-editor/GroupAndNamespaceFields.tsx
  92. 3
      public/app/features/alerting/unified/components/rule-editor/NeedHelpInfo.tsx
  93. 16
      public/app/features/alerting/unified/components/rule-editor/NotificationsStep.tsx
  94. 8
      public/app/features/alerting/unified/components/rule-editor/PreviewRule.tsx
  95. 5
      public/app/features/alerting/unified/components/rule-editor/PreviewRuleResult.tsx
  96. 3
      public/app/features/alerting/unified/components/rule-editor/QueryOptions.tsx
  97. 13
      public/app/features/alerting/unified/components/rule-editor/QueryRows.tsx
  98. 5
      public/app/features/alerting/unified/components/rule-editor/QueryWrapper.tsx
  99. 7
      public/app/features/alerting/unified/components/rule-editor/RecordingRulesNameSpaceAndGroupStep.tsx
  100. 3
      public/app/features/alerting/unified/components/rule-editor/RuleEditorSection.tsx
  101. Some files were not shown because too many files have changed in this diff Show More

File diff suppressed because it is too large Load Diff

@ -2,6 +2,7 @@ import { Fragment, useEffect } from 'react';
import { Alert, Box, LoadingPlaceholder, Text } from '@grafana/ui';
import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { Trans, t } from 'app/core/internationalization';
import { useDispatch } from 'app/types';
import { AlertmanagerChoice } from '../../../plugins/datasource/alertmanager/types';
@ -55,7 +56,9 @@ const AlertGroups = () => {
return (
<>
<AlertGroupFilter groups={results} />
{loading && <LoadingPlaceholder text="Loading notifications" />}
{loading && (
<LoadingPlaceholder text={t('alerting.alert-groups.text-loading-notifications', 'Loading notifications')} />
)}
{error && !loading && (
<Alert title={'Error loading notifications'} severity={'error'}>
{error.message || 'Unknown error'}
@ -63,7 +66,12 @@ const AlertGroups = () => {
)}
{grafanaAmDeliveryDisabled && (
<Alert title="Grafana alerts are not delivered to Grafana Alertmanager">
<Alert
title={t(
'alerting.alert-groups.title-grafana-alerts-delivered-alertmanager',
'Grafana alerts are not delivered to Grafana Alertmanager'
)}
>
Grafana is configured to send alerts to external alertmanagers only. No alerts are expected to be available
here for the selected Alertmanager.
</Alert>
@ -85,7 +93,11 @@ const AlertGroups = () => {
</Fragment>
);
})}
{results && !filteredAlertGroups.length && <p>No results.</p>}
{results && !filteredAlertGroups.length && (
<p>
<Trans i18nKey="alerting.alert-groups.no-results">No results.</Trans>
</p>
)}
</>
);
};

@ -3,6 +3,7 @@ import * as React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Alert, LinkButton, useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
interface AlertWarningProps {
title: string;
@ -12,7 +13,9 @@ export function AlertWarning({ title, children }: AlertWarningProps) {
return (
<Alert className={useStyles2(warningStyles).warning} severity="warning" title={title}>
<p>{children}</p>
<LinkButton href="alerting/list">To rule list</LinkButton>
<LinkButton href="alerting/list">
<Trans i18nKey="alerting.alert-warning.to-rule-list">To rule list</Trans>
</LinkButton>
</Alert>
);
}

@ -6,6 +6,7 @@ import { useDebounce } from 'react-use';
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
import { Card, FilterInput, Icon, Pagination, Select, Stack, TagList, useStyles2 } from '@grafana/ui';
import { DEFAULT_PER_PAGE_PAGINATION } from 'app/core/constants';
import { Trans, t } from 'app/core/internationalization';
import { getQueryParamValue } from 'app/core/utils/query';
import { FolderState, useDispatch } from 'app/types';
import { CombinedRule } from 'app/types/unified-alerting';
@ -66,7 +67,10 @@ export const AlertsFolderView = ({ folder }: Props) => {
<FilterInput
value={nameFilter}
onChange={setNameFilter}
placeholder="Search alert rules by name"
placeholder={t(
'alerting.alerts-folder-view.name-filter-placeholder-search-alert-rules-by-name',
'Search alert rules by name'
)}
data-testid="name-filter"
/>
<Stack direction="row">
@ -75,14 +79,17 @@ export const AlertsFolderView = ({ folder }: Props) => {
onChange={({ value }) => value && setSortOrder(value)}
options={sortOptions}
width={25}
aria-label="Sort"
aria-label={t('alerting.alerts-folder-view.aria-label-sort', 'Sort')}
placeholder={`Sort (Default A-Z)`}
prefix={<Icon name={sortOrder === SortOrder.Ascending ? 'sort-amount-up' : 'sort-amount-down'} />}
/>
<FilterInput
value={labelFilter}
onChange={setLabelFilter}
placeholder="Search alerts by labels"
placeholder={t(
'alerting.alerts-folder-view.label-filter-placeholder-search-alerts-by-labels',
'Search alerts by labels'
)}
className={styles.filterLabelsInput}
data-testid="label-filter"
/>
@ -111,7 +118,11 @@ export const AlertsFolderView = ({ folder }: Props) => {
</Card>
))}
</Stack>
{hasNoResults && <div className={styles.noResults}>No alert rules found</div>}
{hasNoResults && (
<div className={styles.noResults}>
<Trans i18nKey="alerting.alerts-folder-view.no-alert-rules-found">No alert rules found</Trans>
</div>
)}
<div className={styles.pagination}>
<Pagination
currentPage={page}

@ -7,6 +7,7 @@ import { config } from '@grafana/runtime';
import { DataSourceRef } from '@grafana/schema';
import { Preview } from '@grafana/sql/src/components/visual-query-builder/Preview';
import { Alert, Badge, ErrorBoundaryAlert, LinkButton, Stack, Text, useStyles2 } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { CombinedRule } from 'app/types/unified-alerting';
import { AlertDataQuery, AlertQuery } from '../../../types/unified-alerting-dto';
@ -244,7 +245,12 @@ function ExpressionPreview({ refId, model, evalData, isAlertCondition }: Express
>
<div className={styles.previewWrapper}>
{evalData?.errors?.map((error) => (
<Alert key={uniqueId()} title="Expression failed" severity="error" bottomSpacing={1}>
<Alert
key={uniqueId()}
title={t('alerting.expression-preview.title-expression-failed', 'Expression failed')}
severity="error"
bottomSpacing={1}
>
{error.message}
</Alert>
))}
@ -272,12 +278,14 @@ function QueryBox({ refId, headerItems = [], children, isAlertCondition, explore
<span className={styles.refId}>{refId}</span>
{headerItems}
<Spacer />
{isAlertCondition && <Badge color="green" icon="check" text="Alert condition" />}
{isAlertCondition && (
<Badge color="green" icon="check" text={t('alerting.query-box.text-alert-condition', 'Alert condition')} />
)}
{exploreLink && (
<WithReturnButton
component={
<LinkButton size="md" variant="secondary" icon="compass" href={exploreLink}>
View in Explore
<Trans i18nKey="alerting.query-box.view-in-explore">View in Explore</Trans>
</LinkButton>
}
/>
@ -339,7 +347,9 @@ function ClassicConditionViewer({ model }: { model: ExpressionQuery }) {
{index === 0 ? 'WHEN' : !!operator?.type && evalOperators[operator?.type]?.text}
</div>
<div className={styles.bold}>{reducer?.type && reducerFunctions[reducer.type]?.text}</div>
<div className={styles.blue}>OF</div>
<div className={styles.blue}>
<Trans i18nKey="alerting.classic-condition-viewer.of">OF</Trans>
</div>
<div className={styles.bold}>{query.params[0]}</div>
<div className={styles.blue}>{evalFunctions[evaluator.type].text}</div>
<div className={styles.bold}>
@ -372,13 +382,19 @@ function ReduceConditionViewer({ model }: { model: ExpressionQuery }) {
return (
<div className={styles.container}>
<div className={styles.label}>Function</div>
<div className={styles.label}>
<Trans i18nKey="alerting.reduce-condition-viewer.function">Function</Trans>
</div>
<div className={styles.value}>{reducerType?.label}</div>
<div className={styles.label}>Input</div>
<div className={styles.label}>
<Trans i18nKey="alerting.reduce-condition-viewer.input">Input</Trans>
</div>
<div className={styles.value}>{expression}</div>
<div className={styles.label}>Mode</div>
<div className={styles.label}>
<Trans i18nKey="alerting.reduce-condition-viewer.mode">Mode</Trans>
</div>
<div className={styles.value}>{modeName?.label}</div>
</div>
);
@ -407,16 +423,24 @@ function ResampleExpressionViewer({ model }: { model: ExpressionQuery }) {
return (
<div className={styles.container}>
<div className={styles.label}>Input</div>
<div className={styles.label}>
<Trans i18nKey="alerting.resample-expression-viewer.input">Input</Trans>
</div>
<div className={styles.value}>{expression}</div>
<div className={styles.label}>Resample to</div>
<div className={styles.label}>
<Trans i18nKey="alerting.resample-expression-viewer.resample-to">Resample to</Trans>
</div>
<div className={styles.value}>{window}</div>
<div className={styles.label}>Downsample</div>
<div className={styles.label}>
<Trans i18nKey="alerting.resample-expression-viewer.downsample">Downsample</Trans>
</div>
<div className={styles.value}>{downsamplerType?.label}</div>
<div className={styles.label}>Upsample</div>
<div className={styles.label}>
<Trans i18nKey="alerting.resample-expression-viewer.upsample">Upsample</Trans>
</div>
<div className={styles.value}>{upsamplerType?.label}</div>
</div>
);
@ -450,7 +474,9 @@ function ThresholdExpressionViewer({ model }: { model: ExpressionQuery }) {
return (
<>
<div className={styles.container}>
<div className={styles.label}>Input</div>
<div className={styles.label}>
<Trans i18nKey="alerting.threshold-expression-viewer.input">Input</Trans>
</div>
<div className={styles.value}>{expression}</div>
{evaluator && (
@ -465,7 +491,9 @@ function ThresholdExpressionViewer({ model }: { model: ExpressionQuery }) {
<div className={styles.container}>
{unloadEvaluator && (
<>
<div className={styles.label}>Stop alerting when </div>
<div className={styles.label}>
<Trans i18nKey="alerting.threshold-expression-viewer.stop-alerting-when">Stop alerting when </Trans>
</div>
<div className={styles.value}>{expression}</div>
<>
@ -507,7 +535,9 @@ function MathExpressionViewer({ model }: { model: ExpressionQuery }) {
return (
<div className={styles.container}>
<div className={styles.label}>Input</div>
<div className={styles.label}>
<Trans i18nKey="alerting.math-expression-viewer.input">Input</Trans>
</div>
<div className={styles.value}>{expression}</div>
</div>
);

@ -4,7 +4,7 @@ import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { config } from '@grafana/runtime';
import { Alert, LoadingPlaceholder, ScrollContainer, useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { Trans, t } from 'app/core/internationalization';
import { contextSrv } from 'app/core/services/context_srv';
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
@ -31,7 +31,10 @@ export const PanelAlertTabContent = ({ dashboard, panel }: Props) => {
const canCreateRules = config.unifiedAlertingEnabled && contextSrv.hasPermission(permissions.create);
const alert = errors.length ? (
<Alert title="Errors loading rules" severity="error">
<Alert
title={t('alerting.panel-alert-tab-content.alert.title-errors-loading-rules', 'Errors loading rules')}
severity="error"
>
{errors.map((error, index) => (
<div key={index}>Failed to load Grafana rules state: {stringifyErrorLike(error)}</div>
))}
@ -42,7 +45,7 @@ export const PanelAlertTabContent = ({ dashboard, panel }: Props) => {
return (
<div className={styles.innerWrapper}>
{alert}
<LoadingPlaceholder text="Loading rules..." />
<LoadingPlaceholder text={t('alerting.panel-alert-tab-content.text-loading-rules', 'Loading rules...')} />
</div>
);
}
@ -77,7 +80,10 @@ export const PanelAlertTabContent = ({ dashboard, panel }: Props) => {
</>
)}
{isNew && !!dashboard.meta.canSave && (
<Alert severity="info" title="Dashboard not saved">
<Alert
severity="info"
title={t('alerting.panel-alert-tab-content.title-dashboard-not-saved', 'Dashboard not saved')}
>
<Trans i18nKey="dashboard.panel-edit.alerting-tab.dashboard-not-saved">
Dashboard must be saved before alerts can be added.
</Trans>

@ -6,6 +6,7 @@ import { useLocation } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data';
import { config, isFetchError } from '@grafana/runtime';
import { Alert, Card, Icon, LoadingPlaceholder, useStyles2 } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { AlertLabels } from './components/AlertLabels';
import { RuleViewerLayout } from './components/rule-viewer/RuleViewerLayout';
@ -76,7 +77,7 @@ export function RedirectToRuleViewer(): JSX.Element | null {
if (loading) {
return (
<RuleViewerLayout title={pageTitle}>
<LoadingPlaceholder text="Loading rule..." />
<LoadingPlaceholder text={t('alerting.redirect-to-rule-viewer.text-loading-rule', 'Loading rule...')} />
</RuleViewerLayout>
);
}
@ -86,7 +87,7 @@ export function RedirectToRuleViewer(): JSX.Element | null {
if (!rulesSource) {
return (
<RuleViewerLayout title={pageTitle}>
<Alert title="Could not view rule">
<Alert title={t('alerting.redirect-to-rule-viewer.title-could-not-view-rule', 'Could not view rule')}>
<details className={styles.errorMessage}>{`Could not find data source with name: ${sourceName}.`}</details>
</Alert>
</RuleViewerLayout>

@ -1,4 +1,5 @@
import { LinkButton, Stack, Text } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
import { WithReturnButton } from './components/WithReturnButton';
@ -27,10 +28,10 @@ function SettingsContent() {
actions={[
<WithReturnButton
key="add-alertmanager"
title="Alerting settings"
title={t('alerting.settings-content.title-alerting-settings', 'Alerting settings')}
component={
<LinkButton href="/connections/datasources/alertmanager" icon="plus" variant="primary">
Add new Alertmanager
<Trans i18nKey="alerting.settings-content.add-new-alertmanager">Add new Alertmanager</Trans>
</LinkButton>
}
/>,
@ -38,10 +39,14 @@ function SettingsContent() {
>
<Stack direction="column" gap={2}>
{/* Grafana built-in Alertmanager */}
<Text variant="h5">Built-in Alertmanager</Text>
<Text variant="h5">
<Trans i18nKey="alerting.settings-content.builtin-alertmanager">Built-in Alertmanager</Trans>
</Text>
<InternalAlertmanager onEditConfiguration={showConfiguration} />
{/* other (external Alertmanager data sources we have added to Grafana such as vanilla, Mimir, Cortex) */}
<Text variant="h5">Other Alertmanagers</Text>
<Text variant="h5">
<Trans i18nKey="alerting.settings-content.other-alertmanagers">Other Alertmanagers</Trans>
</Text>
<ExternalAlertmanagers onEditConfiguration={showConfiguration} />
</Stack>
{configurationDrawer}

@ -2,6 +2,7 @@ import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { IconButton, useStyles2 } from '@grafana/ui';
import { t } from 'app/core/internationalization';
interface Props {
labelKey: string;
@ -18,7 +19,14 @@ export const AlertLabel = ({ labelKey, value, operator = '=', onRemoveLabel }: P
{labelKey}
{operator}
{value}
{!!onRemoveLabel && <IconButton name="times" size="xs" onClick={onRemoveLabel} tooltip="Remove label" />}
{!!onRemoveLabel && (
<IconButton
name="times"
size="xs"
onClick={onRemoveLabel}
tooltip={t('alerting.alert-label.tooltip-remove-label', 'Remove label')}
/>
)}
</div>
);
};

@ -33,7 +33,7 @@ export const AlertLabels = ({ labels, commonLabels = {}, size, onClick }: Props)
const tooltip = t('alert-labels.button.show.tooltip', 'Show common labels');
return (
<div className={styles.wrapper} role="list" aria-label="Labels">
<div className={styles.wrapper} role="list" aria-label={t('alerting.alert-labels.aria-label-labels', 'Labels')}>
{labelsToShow.map(([label, value]) => {
return (
<Label

@ -2,6 +2,7 @@ import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { Alert, useStyles2 } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { AlertmanagerChoice } from '../../../../plugins/datasource/alertmanager/types';
import { alertmanagerApi } from '../api/alertmanagerApi';
@ -40,7 +41,12 @@ export function GrafanaAlertmanagerDeliveryWarning({ currentAlertmanager }: Graf
if (amChoiceStatus.alertmanagersChoice === AlertmanagerChoice.External) {
return (
<Alert title="Grafana alerts are not delivered to Grafana Alertmanager">
<Alert
title={t(
'alerting.grafana-alertmanager-delivery-warning.title-grafana-alerts-delivered-alertmanager',
'Grafana alerts are not delivered to Grafana Alertmanager'
)}
>
Grafana is configured to send alerts to external Alertmanagers only. Changing Grafana Alertmanager configuration
will not affect delivery of your alerts.
<div className={styles.adminHint}>
@ -53,7 +59,13 @@ export function GrafanaAlertmanagerDeliveryWarning({ currentAlertmanager }: Graf
if (amChoiceStatus.alertmanagersChoice === AlertmanagerChoice.All && hasActiveExternalAMs) {
return (
<Alert title="You have additional Alertmanagers to configure" severity="warning">
<Alert
title={t(
'alerting.grafana-alertmanager-delivery-warning.title-you-have-additional-alertmanagers-to-configure',
'You have additional Alertmanagers to configure'
)}
severity="warning"
>
Ensure you make configuration changes in the correct Alertmanagers; both internal and external. Changing one
will not affect the others.
<div className={styles.adminHint}>

@ -1,8 +1,15 @@
import { config } from '@grafana/runtime';
import { Alert } from '@grafana/ui';
import { t } from 'app/core/internationalization';
const EvaluationIntervalLimitExceeded = () => (
<Alert severity="warning" title="Global evaluation interval limit exceeded">
<Alert
severity="warning"
title={t(
'alerting.evaluation-interval-limit-exceeded.title-global-evaluation-interval-limit-exceeded',
'Global evaluation interval limit exceeded'
)}
>
A minimum evaluation interval of <strong>{config.unifiedAlerting.minInterval}</strong> has been configured in
Grafana.
<br />

@ -1,4 +1,5 @@
import { Alert } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { AlertManagerDataSource } from '../utils/datasource';
@ -7,13 +8,22 @@ interface Props {
}
const NoAlertManagersAvailable = () => (
<Alert title="No Alertmanager found" severity="warning">
<Alert
title={t('alerting.no-alert-managers-available.title-no-alertmanager-found', 'No Alertmanager found')}
severity="warning"
>
We could not find any external Alertmanagers and you may not have access to the built-in Grafana Alertmanager.
</Alert>
);
const OtherAlertManagersAvailable = () => (
<Alert title="Selected Alertmanager not found." severity="warning">
<Alert
title={t(
'alerting.other-alert-managers-available.title-selected-alertmanager-not-found',
'Selected Alertmanager not found.'
)}
severity="warning"
>
The selected Alertmanager no longer exists or you may not have permission to access it. You can select a different
Alertmanager from the dropdown.
</Alert>

@ -1,7 +1,7 @@
import { ComponentPropsWithoutRef } from 'react';
import { Alert, Badge, Tooltip } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { Trans, t } from 'app/core/internationalization';
export enum ProvisionedResource {
ContactPoint = 'contact point',
@ -37,7 +37,7 @@ export const ProvisioningBadge = ({
*/
provenance?: string;
}) => {
const badge = <Badge text="Provisioned" color="purple" />;
const badge = <Badge text={t('alerting.provisioning-badge.badge.text-provisioned', 'Provisioned')} color="purple" />;
if (tooltip) {
const provenanceTooltip = (

@ -2,6 +2,7 @@ import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { LinkButton, useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { contextSrv } from 'app/core/services/context_srv';
import { AlertState, AlertmanagerAlert } from 'app/plugins/datasource/alertmanager/types';
import { AccessControlAction } from 'app/types';
@ -41,7 +42,7 @@ export const AlertDetails = ({ alert, alertManagerSourceName }: AmNotificationsA
icon={'bell'}
size={'sm'}
>
Manage silences
<Trans i18nKey="alerting.alert-details.manage-silences">Manage silences</Trans>
</LinkButton>
</Authorize>
)}
@ -53,7 +54,7 @@ export const AlertDetails = ({ alert, alertManagerSourceName }: AmNotificationsA
icon={'bell-slash'}
size={'sm'}
>
Silence
<Trans i18nKey="alerting.alert-details.silence">Silence</Trans>
</LinkButton>
</Authorize>
)}

@ -3,6 +3,7 @@ import { useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Stack, TextLink, useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { AlertmanagerGroup } from 'app/plugins/datasource/alertmanager/types';
import { createContactPointSearchLink } from '../../utils/misc';
@ -55,7 +56,9 @@ export const AlertGroup = ({ alertManagerSourceName, group }: Props) => {
)}
</Stack>
) : (
<span>No grouping</span>
<span>
<Trans i18nKey="alerting.alert-group.no-grouping">No grouping</Trans>
</span>
)}
</div>
<AlertGroupHeader group={group} />

@ -4,6 +4,7 @@ import { useState } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Button, useStyles2 } from '@grafana/ui';
import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { Trans } from 'app/core/internationalization';
import { AlertState, AlertmanagerGroup } from 'app/plugins/datasource/alertmanager/types';
import { getFiltersFromUrlParams } from '../../utils/misc';
@ -55,7 +56,7 @@ export const AlertGroupFilter = ({ groups }: Props) => {
/>
{showClearButton && (
<Button className={styles.clearButton} variant={'secondary'} icon="times" onClick={clearFilters}>
Clear filters
<Trans i18nKey="alerting.alert-group-filter.clear-filters">Clear filters</Trans>
</Button>
)}
</div>

@ -1,5 +1,6 @@
import { SelectableValue } from '@grafana/data';
import { Icon, Label, RadioButtonGroup, Tooltip } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { AlertState } from 'app/plugins/datasource/alertmanager/types';
interface Props {
@ -27,7 +28,11 @@ export const AlertStateFilter = ({ onStateFilterChange, stateFilter }: Props) =>
Active: The alert notification has been handled. The alert is still firing and continues to be
managed.
</li>
<li>Suppressed: The alert has been silenced.</li>
<li>
<Trans i18nKey="alerting.alert-state-filter.suppressed-the-alert-has-been-silenced">
Suppressed: The alert has been silenced.
</Trans>
</li>
<li>Unprocessed: The alert is received but its notification has not been processed yet.</li>
</ul>
</div>

@ -2,6 +2,7 @@ import { uniq } from 'lodash';
import { SelectableValue } from '@grafana/data';
import { Icon, Label, MultiSelect, Tooltip } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { AlertmanagerGroup } from 'app/plugins/datasource/alertmanager/types';
import { isPrivateLabelKey } from '../../utils/labels';
@ -38,7 +39,7 @@ export const GroupBy = ({ groups, groupBy, onGroupingChange }: Props) => {
<MultiSelect
aria-label={'group by label keys'}
value={groupBy}
placeholder="Group by"
placeholder={t('alerting.group-by.placeholder-group-by', 'Group by')}
prefix={<Icon name={'tag-alt'} />}
onChange={(items) => {
onGroupingChange(items.map(({ value }) => value as string));

@ -4,6 +4,7 @@ import { useDebounce } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data';
import { Field, Icon, Input, Label, Stack, Tooltip, useStyles2 } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { LogMessages, logInfo } from '../../Analytics';
import { parsePromQLStyleMatcherLoose } from '../../utils/matchers';
@ -47,7 +48,9 @@ export const MatcherFilter = ({ onFilterChange, defaultQueryString }: Props) =>
label={
<Label>
<Stack gap={0.5} alignItems="center">
<span>Search by label</span>
<span>
<Trans i18nKey="alerting.matcher-filter.search-by-label">Search by label</Trans>
</span>
<Tooltip
content={
<div>
@ -70,7 +73,7 @@ export const MatcherFilter = ({ onFilterChange, defaultQueryString }: Props) =>
}
>
<Input
placeholder="Search"
placeholder={t('alerting.matcher-filter.search-query-input-placeholder-search', 'Search')}
value={filterQuery}
onChange={(e) => setFilterQuery(e.currentTarget.value)}
data-testid="search-query-input"

@ -1,5 +1,6 @@
import { SelectableValue } from '@grafana/data';
import { MultiSelect, MultiSelectCommonProps } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { useSelectableMuteTimings } from 'app/features/alerting/unified/components/mute-timings/useMuteTimings';
import { BaseAlertmanagerArgs } from 'app/features/alerting/unified/types/hooks';
import { timeIntervalToString } from 'app/features/alerting/unified/utils/alertmanager';
@ -22,9 +23,9 @@ const MuteTimingsSelector = ({
return (
<MultiSelect
aria-label="Mute timings"
aria-label={t('alerting.mute-timings-selector.aria-label-mute-timings', 'Mute timings')}
options={muteTimingOptions}
placeholder="Select mute timings..."
placeholder={t('alerting.mute-timings-selector.placeholder-select-mute-timings', 'Select mute timings...')}
{...selectProps}
/>
);

@ -1,4 +1,5 @@
import { Button, LinkButton, Menu, Tooltip } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { usePluginBridge } from '../../hooks/usePluginBridge';
import { getIrmIfPresentOrIncidentPluginId } from '../../utils/config';
@ -25,19 +26,19 @@ export const DeclareIncidentButton = ({ title = '', severity = '', url = '' }: P
<>
{loading === true && (
<Button icon="fire" size="sm" type="button" disabled>
Declare Incident
<Trans i18nKey="alerting.declare-incident-button.declare-incident">Declare Incident</Trans>
</Button>
)}
{installed === false && (
<Tooltip content={'Grafana Incident is not installed or is not configured correctly'}>
<Button icon="fire" size="sm" type="button" disabled>
Declare Incident
<Trans i18nKey="alerting.declare-incident-button.declare-incident">Declare Incident</Trans>
</Button>
</Tooltip>
)}
{settings && (
<LinkButton icon="fire" size="sm" type="button" href={bridgeURL}>
Declare Incident
<Trans i18nKey="alerting.declare-incident-button.declare-incident">Declare Incident</Trans>
</LinkButton>
)}
</>
@ -55,13 +56,29 @@ export const DeclareIncidentMenuItem = ({ title = '', severity = '', url = '' }:
return (
<>
{loading === true && <Menu.Item label="Declare incident" icon="fire" disabled />}
{loading === true && (
<Menu.Item
label={t('alerting.declare-incident-menu-item.label-declare-incident', 'Declare incident')}
icon="fire"
disabled
/>
)}
{installed === false && (
<Tooltip content={'Grafana Incident is not installed or is not configured correctly'}>
<Menu.Item label="Declare incident" icon="fire" disabled />
<Menu.Item
label={t('alerting.declare-incident-menu-item.label-declare-incident', 'Declare incident')}
icon="fire"
disabled
/>
</Tooltip>
)}
{settings && <Menu.Item label="Declare incident" url={bridgeURL} icon="fire" />}
{settings && (
<Menu.Item
label={t('alerting.declare-incident-menu-item.label-declare-incident', 'Declare incident')}
url={bridgeURL}
icon="fire"
/>
)}
</>
);
};

@ -86,7 +86,11 @@ export const ContactPointHeader = ({ contactPoint, onDelete }: ContactPointHeade
if (showManagePermissions) {
menuActions.push(
<Fragment key="manage-permissions">
<Menu.Item icon="unlock" label="Manage permissions" onClick={() => setShowPermissionsDrawer(true)} />
<Menu.Item
icon="unlock"
label={t('alerting.contact-point-header.label-manage-permissions', 'Manage permissions')}
onClick={() => setShowPermissionsDrawer(true)}
/>
</Fragment>
);
}
@ -96,7 +100,7 @@ export const ContactPointHeader = ({ contactPoint, onDelete }: ContactPointHeade
<Fragment key="export-contact-point">
<Menu.Item
icon="download-alt"
label="Export"
label={t('alerting.contact-point-header.export-label-export', 'Export')}
ariaLabel="export"
disabled={!exportAllowed}
data-testid="export"
@ -155,7 +159,7 @@ export const ContactPointHeader = ({ contactPoint, onDelete }: ContactPointHeade
)}
>
<Menu.Item
label="Delete"
label={t('alerting.contact-point-header.label-delete', 'Delete')}
ariaLabel="delete"
icon="trash-alt"
destructive

@ -72,7 +72,7 @@ const ContactPointsTab = () => {
const search = queryParams.get('search');
if (isLoading) {
return <LoadingPlaceholder text="Loading..." />;
return <LoadingPlaceholder text={t('alerting.contact-points-tab.text-loading', 'Loading...')} />;
}
const isGrafanaManagedAlertmanager = selectedAlertmanager === GRAFANA_RULES_SOURCE_NAME;
@ -107,7 +107,7 @@ const ContactPointsTab = () => {
{addContactPointSupported && (
<LinkButton
icon="plus"
aria-label="add contact point"
aria-label={t('alerting.contact-points-tab.aria-label-add-contact-point', 'add contact point')}
variant="primary"
href="/alerting/notifications/receivers/new"
disabled={!addContactPointAllowed}
@ -119,17 +119,24 @@ const ContactPointsTab = () => {
<Button
icon="download-alt"
variant="secondary"
aria-label="export all"
aria-label={t('alerting.contact-points-tab.aria-label-export-all', 'export all')}
disabled={!exportContactPointsAllowed}
onClick={() => showExportDrawer(ALL_CONTACT_POINTS)}
>
Export all
<Trans i18nKey="alerting.contact-points-tab.export-all">Export all</Trans>
</Button>
)}
</Stack>
</Stack>
{error ? (
<Alert title="Failed to fetch contact points">{stringifyErrorLike(error)}</Alert>
<Alert
title={t(
'alerting.contact-points-tab.title-failed-to-fetch-contact-points',
'Failed to fetch contact points'
)}
>
{stringifyErrorLike(error)}
</Alert>
) : (
<ContactPointsList contactPoints={contactPoints} search={search} pageSize={DEFAULT_PAGE_SIZE} />
)}
@ -149,7 +156,9 @@ const NotificationTemplatesTab = () => {
<>
<Stack direction="row" alignItems="center" justifyContent="space-between">
<Text variant="body" color="secondary">
Create notification templates to customize your notifications.
<Trans i18nKey="alerting.notification-templates-tab.create-notification-templates-customize-notifications">
Create notification templates to customize your notifications.
</Trans>
</Text>
{createTemplateSupported && (
<LinkButton
@ -158,7 +167,9 @@ const NotificationTemplatesTab = () => {
href="/alerting/notifications/templates/new"
disabled={!createTemplateAllowed}
>
Add notification template group
<Trans i18nKey="alerting.notification-templates-tab.add-notification-template-group">
Add notification template group
</Trans>
</LinkButton>
)}
</Stack>
@ -211,7 +222,7 @@ export const ContactPointsPageContents = () => {
<TabsBar>
{showContactPointsTab && (
<Tab
label="Contact Points"
label={t('alerting.contact-points-page-contents.label-contact-points', 'Contact Points')}
active={showingContactPoints}
counter={contactPoints.length}
onChangeTab={() => setActiveTab(ActiveTab.ContactPoints)}
@ -219,7 +230,7 @@ export const ContactPointsPageContents = () => {
)}
{showTemplatesTab && (
<Tab
label="Notification Templates"
label={t('alerting.contact-points-page-contents.label-notification-templates', 'Notification Templates')}
active={showNotificationTemplates}
onChangeTab={() => setActiveTab(ActiveTab.NotificationTemplates)}
/>

@ -48,14 +48,27 @@ const DuplicateMessageTemplateComponent = () => {
}
if (isLoading) {
return <LoadingPlaceholder text="Loading notification template" />;
return (
<LoadingPlaceholder
text={t(
'alerting.duplicate-message-template.text-loading-notification-template',
'Loading notification template'
)}
/>
);
}
if (error) {
return isNotFoundError(error) ? (
notFoundComponent
) : (
<Alert title="Error loading notification template" severity="error">
<Alert
title={t(
'alerting.duplicate-message-template.title-error-loading-notification-template',
'Error loading notification template'
)}
severity="error"
>
{stringifyErrorLike(error)}
</Alert>
);

@ -1,6 +1,7 @@
import { useParams } from 'react-router-dom-v5-compat';
import { Alert, LoadingPlaceholder } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { useGetContactPoint } from 'app/features/alerting/unified/components/contact-points/useContactPoints';
import { stringifyErrorLike } from 'app/features/alerting/unified/utils/misc';
@ -21,12 +22,15 @@ const EditContactPoint = () => {
} = useGetContactPoint({ name: contactPointName, alertmanager: selectedAlertmanager! });
if (isLoading) {
return <LoadingPlaceholder text="Loading..." />;
return <LoadingPlaceholder text={t('alerting.edit-contact-point.text-loading', 'Loading...')} />;
}
if (error) {
return (
<Alert severity="error" title="Failed to fetch contact point">
<Alert
severity="error"
title={t('alerting.edit-contact-point.title-failed-to-fetch-contact-point', 'Failed to fetch contact point')}
>
{stringifyErrorLike(error)}
</Alert>
);
@ -34,7 +38,7 @@ const EditContactPoint = () => {
if (!contactPoint) {
return (
<Alert severity="error" title="Receiver not found">
<Alert severity="error" title={t('alerting.edit-contact-point.title-receiver-not-found', 'Receiver not found')}>
{'Sorry, this contact point does not seem to exist.'}
</Alert>
);

@ -32,14 +32,22 @@ const EditMessageTemplateComponent = () => {
}
if (isLoading || isUninitialized) {
return <LoadingPlaceholder text="Loading template..." />;
return (
<LoadingPlaceholder text={t('alerting.edit-message-template.text-loading-template', 'Loading template...')} />
);
}
if (error) {
return isNotFoundError(error) ? (
notFoundComponent
) : (
<Alert severity="error" title="Failed to fetch notification template">
<Alert
severity="error"
title={t(
'alerting.edit-message-template.title-failed-to-fetch-notification-template',
'Failed to fetch notification template'
)}
>
{stringifyErrorLike(error)}
</Alert>
);

@ -1,4 +1,5 @@
import { Alert, LoadingPlaceholder } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { useAlertmanager } from '../../state/AlertmanagerContext';
import { stringifyErrorLike } from '../../utils/misc';
@ -11,11 +12,27 @@ export const NotificationTemplates = () => {
const { data: templates, isLoading, error } = useNotificationTemplates({ alertmanager: selectedAlertmanager ?? '' });
if (error) {
return <Alert title="Failed to fetch notification templates">{stringifyErrorLike(error)}</Alert>;
return (
<Alert
title={t(
'alerting.notification-templates.title-failed-to-fetch-notification-templates',
'Failed to fetch notification templates'
)}
>
{stringifyErrorLike(error)}
</Alert>
);
}
if (isLoading) {
return <LoadingPlaceholder text="Loading notification templates" />;
return (
<LoadingPlaceholder
text={t(
'alerting.notification-templates.text-loading-notification-templates',
'Loading notification templates'
)}
/>
);
}
if (templates) {

@ -3,6 +3,7 @@ import { useCallback, useState } from 'react';
import { useDebounce } from 'react-use';
import { Button, Field, Icon, Input, Stack, useStyles2 } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { useURLSearchParams } from '../../../hooks/useURLSearchParams';
@ -32,10 +33,13 @@ const ContactPointsFilter = () => {
return (
<Stack direction="row" alignItems="end" gap={0.5}>
<Field className={styles.noBottom} label="Search by name or type">
<Field
className={styles.noBottom}
label={t('alerting.contact-points-filter.label-search-by-name-or-type', 'Search by name or type')}
>
<Input
aria-label="search contact points"
placeholder="Search"
aria-label={t('alerting.contact-points-filter.aria-label-search-contact-points', 'search contact points')}
placeholder={t('alerting.contact-points-filter.placeholder-search', 'Search')}
width={46}
prefix={<Icon name="search" />}
onChange={(event) => {
@ -44,8 +48,14 @@ const ContactPointsFilter = () => {
value={searchValue}
/>
</Field>
<Button variant="secondary" icon="times" onClick={() => clear()} disabled={!hasInput} aria-label="clear">
Clear
<Button
variant="secondary"
icon="times"
onClick={() => clear()}
disabled={!hasInput}
aria-label={t('alerting.contact-points-filter.aria-label-clear', 'clear')}
>
<Trans i18nKey="alerting.contact-points-filter.clear">Clear</Trans>
</Button>
</Stack>
);

@ -1,4 +1,5 @@
import { Alert } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { useAlertmanagerConfig } from '../../../hooks/useAlertmanagerConfig';
import { useAlertmanager } from '../../../state/AlertmanagerContext';
@ -16,7 +17,13 @@ const GlobalConfig = () => {
if (error) {
return (
<Alert severity="error" title="Failed to fetch notification template">
<Alert
severity="error"
title={t(
'alerting.global-config.title-failed-to-fetch-notification-template',
'Failed to fetch notification template'
)}
>
{String(error)}
</Alert>
);

@ -1,4 +1,5 @@
import { Alert, LinkButton } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { AlertmanagerAction } from '../../../hooks/useAbilities';
import { isVanillaPrometheusAlertManagerDataSource } from '../../../utils/datasource';
@ -14,7 +15,13 @@ export const GlobalConfigAlert = ({ alertManagerName }: GlobalConfigAlertProps)
return (
<Authorize actions={[AlertmanagerAction.UpdateExternalConfiguration]}>
<Alert severity="info" title="Global config for contact points">
<Alert
severity="info"
title={t(
'alerting.global-config-alert.title-global-config-for-contact-points',
'Global config for contact points'
)}
>
<p>
For each external Alertmanager you can define global settings, like server addresses, usernames and password,
for all the supported contact points.

@ -1,7 +1,7 @@
import { useCallback, useMemo, useState } from 'react';
import { Button, Modal, ModalProps } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { Trans, t } from 'app/core/internationalization';
import { stringifyErrorLike } from '../../../utils/misc';
@ -54,10 +54,21 @@ export const useDeleteContactPointModal = (
onDismiss={handleDismiss}
closeOnBackdropClick={!isLoading}
closeOnEscape={!isLoading}
title="Delete contact point"
title={t(
'alerting.use-delete-contact-point-modal.modal-element.title-delete-contact-point',
'Delete contact point'
)}
>
<p>Deleting this contact point will permanently remove it.</p>
<p>Are you sure you want to delete this contact point?</p>
<p>
<Trans i18nKey="alerting.use-delete-contact-point-modal.modal-element.deleting-contact-point-permanently-remove">
Deleting this contact point will permanently remove it.
</Trans>
</p>
<p>
<Trans i18nKey="alerting.use-delete-contact-point-modal.modal-element.delete-contact-point">
Are you sure you want to delete this contact point?
</Trans>
</p>
<Modal.ButtonRow>
<Button type="button" variant="destructive" onClick={handleSubmit} disabled={isLoading}>
@ -85,7 +96,11 @@ const ErrorModal = ({ isOpen, onDismiss, error }: ErrorModalProps) => (
closeOnEscape={true}
title={'Something went wrong'}
>
<p>Failed to update your configuration:</p>
<p>
<Trans i18nKey="alerting.error-modal.failed-to-update-your-configuration">
Failed to update your configuration:
</Trans>
</p>
<pre>
<code>{stringifyErrorLike(error)}</code>
</pre>

@ -1,9 +1,10 @@
import { Badge } from '@grafana/ui';
import { t } from 'app/core/internationalization';
export const UnusedContactPointBadge = () => (
<Badge
text="Unused"
aria-label="unused"
text={t('alerting.unused-contact-point-badge.text-unused', 'Unused')}
aria-label={t('alerting.unused-contact-point-badge.aria-label-unused', 'unused')}
color="orange"
icon="exclamation-triangle"
tooltip="This contact point is not used in any notification policy or alert rule"

@ -63,10 +63,10 @@ export function FileExportPreview({ format, textDefinition, downloadFileName, on
<Trans i18nKey="alerting.common.cancel">Cancel</Trans>
</Button>
<ClipboardButton icon="copy" getText={() => textDefinition}>
Copy code
<Trans i18nKey="alerting.file-export-preview.copy-code">Copy code</Trans>
</ClipboardButton>
<Button icon="download-alt" onClick={onDownload}>
Download
<Trans i18nKey="alerting.file-export-preview.download">Download</Trans>
</Button>
</div>
</div>

@ -3,6 +3,7 @@ import { useParams } from 'react-router-dom-v5-compat';
import { locationService } from '@grafana/runtime';
import { Alert, LoadingPlaceholder } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { RuleIdentifier } from '../../../../../types/unified-alerting';
import { useRuleWithLocation } from '../../hooks/useCombinedRule';
@ -23,7 +24,7 @@ function GrafanaModifyExport() {
if (!ruleIdentifier) {
return (
<Alert title="Invalid rule ID" severity="error">
<Alert title={t('alerting.grafana-modify-export.title-invalid-rule-id', 'Invalid rule ID')} severity="error">
The rule UID in the page URL is invalid. Please check the URL and try again.
</Alert>
);
@ -36,12 +37,15 @@ function RuleModifyExport({ ruleIdentifier }: { ruleIdentifier: RuleIdentifier }
const { loading, error, result: rulerRule } = useRuleWithLocation({ ruleIdentifier: ruleIdentifier });
if (loading) {
return <LoadingPlaceholder text="Loading the rule..." />;
return <LoadingPlaceholder text={t('alerting.rule-modify-export.text-loading-the-rule', 'Loading the rule...')} />;
}
if (error) {
return (
<Alert title="Cannot load modify export" severity="error">
<Alert
title={t('alerting.rule-modify-export.title-cannot-load-modify-export', 'Cannot load modify export')}
severity="error"
>
{stringifyErrorLike(error)}
</Alert>
);
@ -51,7 +55,7 @@ function RuleModifyExport({ ruleIdentifier }: { ruleIdentifier: RuleIdentifier }
// alert rule does not exist
return (
<Alert
title="Cannot load the rule. The rule does not exist"
title={t('alerting.rule-modify-export.title-cannot-exist', 'Cannot load the rule. The rule does not exist')}
buttonContent="Go back to alert list"
onRemove={() => locationService.replace(createRelativeUrl('/alerting/list'))}
/>
@ -62,7 +66,10 @@ function RuleModifyExport({ ruleIdentifier }: { ruleIdentifier: RuleIdentifier }
// alert rule exists but is not a grafana-managed rule
return (
<Alert
title="This rule is not a Grafana-managed alert rule"
title={t(
'alerting.rule-modify-export.title-grafanamanaged-alert',
'This rule is not a Grafana-managed alert rule'
)}
buttonContent="Go back to alert list"
onRemove={() => locationService.replace(createRelativeUrl('/alerting/list'))}
/>
@ -78,7 +85,7 @@ function RuleModifyExport({ ruleIdentifier }: { ruleIdentifier: RuleIdentifier }
);
}
return <Alert title="Unknown error" />;
return <Alert title={t('alerting.rule-modify-export.title-unknown-error', 'Unknown error')} />;
}
function GrafanaModifyExportPage() {

@ -1,6 +1,7 @@
import { useState } from 'react';
import { LoadingPlaceholder } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { alertRuleApi } from '../../api/alertRuleApi';
@ -19,7 +20,9 @@ const GrafanaMuteTimingsExporterPreview = ({ exportFormat, onClose }: MuteTiming
const downloadFileName = `mute-timings-${new Date().getTime()}`;
if (isFetching) {
return <LoadingPlaceholder text="Loading...." />;
return (
<LoadingPlaceholder text={t('alerting.grafana-mute-timings-exporter-preview.text-loading', 'Loading....')} />
);
}
return (
<FileExportPreview
@ -46,7 +49,7 @@ const GrafanaMuteTimingExporterPreview = ({
const downloadFileName = `mute-timing-${muteTimingName}-${new Date().getTime()}`;
if (isFetching) {
return <LoadingPlaceholder text="Loading...." />;
return <LoadingPlaceholder text={t('alerting.grafana-mute-timing-exporter-preview.text-loading', 'Loading....')} />;
}
return (
<FileExportPreview

@ -1,6 +1,7 @@
import { useState } from 'react';
import { LoadingPlaceholder } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { alertRuleApi } from '../../api/alertRuleApi';
@ -20,7 +21,7 @@ const GrafanaPoliciesExporterPreview = ({ exportFormat, onClose }: GrafanaPolici
const downloadFileName = `policies-${new Date().getTime()}`;
if (isFetching) {
return <LoadingPlaceholder text="Loading...." />;
return <LoadingPlaceholder text={t('alerting.grafana-policies-exporter-preview.text-loading', 'Loading....')} />;
}
return (

@ -1,6 +1,7 @@
import { useState } from 'react';
import { LoadingPlaceholder } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { alertRuleApi } from '../../api/alertRuleApi';
@ -30,7 +31,7 @@ const GrafanaReceiverExportPreview = ({
const downloadFileName = `cp-${receiverName}-${new Date().getTime()}`;
if (isFetching) {
return <LoadingPlaceholder text="Loading...." />;
return <LoadingPlaceholder text={t('alerting.grafana-receiver-export-preview.text-loading', 'Loading....')} />;
}
return (

@ -1,6 +1,7 @@
import { useState } from 'react';
import { LoadingPlaceholder } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { alertRuleApi } from '../../api/alertRuleApi';
@ -23,7 +24,7 @@ const GrafanaReceiversExportPreview = ({ decrypt, exportFormat, onClose }: Grafa
const downloadFileName = `contact-points-${new Date().getTime()}`;
if (isFetching) {
return <LoadingPlaceholder text="Loading...." />;
return <LoadingPlaceholder text={t('alerting.grafana-receivers-export-preview.text-loading', 'Loading....')} />;
}
return (

@ -1,6 +1,7 @@
import { useState } from 'react';
import { LoadingPlaceholder } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { alertRuleApi } from '../../api/alertRuleApi';
@ -23,7 +24,7 @@ const GrafanaRuleExportPreview = ({ alertUid, exportFormat, onClose }: GrafanaRu
const downloadFileName = `${alertUid}-${new Date().getTime()}`;
if (isFetching) {
return <LoadingPlaceholder text="Loading...." />;
return <LoadingPlaceholder text={t('alerting.grafana-rule-export-preview.text-loading', 'Loading....')} />;
}
return (

@ -1,6 +1,7 @@
import { useState } from 'react';
import { LoadingPlaceholder } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { FolderDTO } from '../../../../../types';
import { alertRuleApi } from '../../api/alertRuleApi';
@ -43,7 +44,7 @@ function GrafanaRuleFolderExportPreview({ folder, exportFormat, onClose }: Grafa
});
if (isFetching) {
return <LoadingPlaceholder text="Loading...." />;
return <LoadingPlaceholder text={t('alerting.grafana-rule-folder-export-preview.text-loading', 'Loading....')} />;
}
const downloadFileName = `${folder.title}-${folder.uid}`;

@ -1,6 +1,7 @@
import { useState } from 'react';
import { LoadingPlaceholder } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { alertRuleApi } from '../../api/alertRuleApi';
@ -55,7 +56,7 @@ function GrafanaRuleGroupExportPreview({
});
if (isFetching) {
return <LoadingPlaceholder text="Loading...." />;
return <LoadingPlaceholder text={t('alerting.grafana-rule-group-export-preview.text-loading', 'Loading....')} />;
}
return (

@ -1,6 +1,7 @@
import { useState } from 'react';
import { LoadingPlaceholder } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { alertRuleApi } from '../../api/alertRuleApi';
@ -40,7 +41,7 @@ function GrafanaRulesExportPreview({ exportFormat, onClose }: GrafanaRulesExport
const downloadFileName = `alert-rules-${new Date().getTime()}`;
if (isFetching) {
return <LoadingPlaceholder text="Loading...." />;
return <LoadingPlaceholder text={t('alerting.grafana-rules-export-preview.text-loading', 'Loading....')} />;
}
return (

@ -13,6 +13,7 @@ import {
isTimeSeriesFrames,
} from '@grafana/data';
import { Alert, AutoSizeInput, Button, IconButton, Stack, Text, clearButtonStyles, useStyles2 } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { ClassicConditions } from 'app/features/expressions/components/ClassicConditions';
import { Math } from 'app/features/expressions/components/Math';
import { Reduce } from 'app/features/expressions/components/Reduce';
@ -164,12 +165,12 @@ export const Expression: FC<ExpressionProps> = ({
/>
<div className={styles.expression.body}>
{error && (
<Alert title="Expression failed" severity="error">
<Alert title={t('alerting.expression.title-expression-failed', 'Expression failed')} severity="error">
{error.message}
</Alert>
)}
{warning && (
<Alert title="Expression warning" severity="warning">
<Alert title={t('alerting.expression.title-expression-warning', 'Expression warning')} severity="warning">
{warning.message}
</Alert>
)}
@ -246,7 +247,11 @@ export const ExpressionResult: FC<ExpressionResultProps> = ({ series, isAlertCon
isRecordingRule={isRecordingRule}
/>
))}
{emptyResults && <div className={cx(styles.expression.noData, styles.mutedText)}>No data</div>}
{emptyResults && (
<div className={cx(styles.expression.noData, styles.mutedText)}>
<Trans i18nKey="alerting.expression-result.no-data">No data</Trans>
</div>
)}
{shouldShowPagination && (
<div className={styles.pagination.wrapper} data-testid="paginate-expression">
<Stack>
@ -256,7 +261,7 @@ export const ExpressionResult: FC<ExpressionResultProps> = ({ series, isAlertCon
onClick={previousPage}
icon="angle-left"
size="sm"
aria-label="previous-page"
aria-label={t('alerting.expression-result.aria-label-previouspage', 'previous-page')}
/>
<Spacer />
<span className={styles.mutedText}>
@ -269,7 +274,7 @@ export const ExpressionResult: FC<ExpressionResultProps> = ({ series, isAlertCon
onClick={nextPage}
icon="angle-right"
size="sm"
aria-label="next-page"
aria-label={t('alerting.expression-result.aria-label-nextpage', 'next-page')}
/>
</Stack>
</div>
@ -287,7 +292,11 @@ export const PreviewSummary: FC<{ firing: number; normal: number; isCondition: b
const { mutedText } = useStyles2(getStyles);
if (seriesCount === 0) {
return <span className={mutedText}>No series</span>;
return (
<span className={mutedText}>
<Trans i18nKey="alerting.preview-summary.no-series">No series</Trans>
</span>
);
}
if (isCondition) {
@ -478,8 +487,12 @@ const TimeseriesRow: FC<TimeseriesRowProps & { index: number }> = ({ frame, inde
<table className={styles.timeseriesTable}>
<thead>
<tr>
<th>Timestamp</th>
<th>Value</th>
<th>
<Trans i18nKey="alerting.timeseries-row.timestamp">Timestamp</Trans>
</th>
<th>
<Trans i18nKey="alerting.timeseries-row.value">Value</Trans>
</th>
</tr>
</thead>
<tbody>
@ -493,7 +506,9 @@ const TimeseriesRow: FC<TimeseriesRowProps & { index: number }> = ({ frame, inde
</table>
}
>
<span>Time series data</span>
<span>
<Trans i18nKey="alerting.timeseries-row.time-series-data">Time series data</Trans>
</span>
</PopupCard>
</div>
</Stack>

@ -3,6 +3,7 @@ import { ReactElement, useMemo, useState } from 'react';
import { PluginExtensionLink, PluginExtensionPoints } from '@grafana/data';
import { usePluginLinks } from '@grafana/runtime';
import { Dropdown, IconButton } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { ConfirmNavigationModal } from 'app/features/explore/extensions/ConfirmNavigationModal';
// We might want to customise this in future but right now the toolbar menu from the Explore view is fine.
import { ToolbarExtensionPointMenu as AlertExtensionPointMenu } from 'app/features/explore/extensions/ToolbarExtensionPointMenu';
@ -31,7 +32,11 @@ export const AlertInstanceExtensionPoint = ({
return (
<>
<Dropdown placement="bottom-start" overlay={menu}>
<IconButton name="ellipsis-v" aria-label="Actions" variant="secondary" />
<IconButton
name="ellipsis-v"
aria-label={t('alerting.alert-instance-extension-point.aria-label-actions', 'Actions')}
variant="secondary"
/>
</Dropdown>
{!!selectedExtension && !!selectedExtension.path && (
<ConfirmNavigationModal

@ -52,7 +52,9 @@ export const MuteTimingActionsButtons = ({ muteTiming, alertManagerSourceName }:
return (
<>
<Stack direction="row" alignItems="center" justifyContent="flex-end" wrap="wrap">
{!isGrafanaDataSource && isDisabled(muteTiming) && <Badge text="Disabled" color="orange" />}
{!isGrafanaDataSource && isDisabled(muteTiming) && (
<Badge text={t('alerting.mute-timing-actions-buttons.text-disabled', 'Disabled')} color="orange" />
)}
<Authorize actions={[AlertmanagerAction.UpdateMuteTiming]}>{viewOrEditButton}</Authorize>
{exportSupported && (
@ -84,7 +86,7 @@ export const MuteTimingActionsButtons = ({ muteTiming, alertManagerSourceName }:
</Stack>
<ConfirmModal
isOpen={showDeleteDrawer}
title="Delete mute timing"
title={t('alerting.mute-timing-actions-buttons.title-delete-mute-timing', 'Delete mute timing')}
body={`Are you sure you would like to delete "${muteTiming.name}"?`}
confirmText={t('alerting.common.delete', 'Delete')}
onConfirm={async () => {

@ -4,7 +4,7 @@ import { FormProvider, useForm } from 'react-hook-form';
import { GrafanaTheme2 } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import { Alert, Button, Field, FieldSet, Input, LinkButton, LoadingPlaceholder, useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { Trans, t } from 'app/core/internationalization';
import {
MuteTiming,
useCreateMuteTiming,
@ -88,11 +88,15 @@ const MuteTimingForm = ({ muteTiming, showError, loading, provisioned, editMode
};
if (loading) {
return <LoadingPlaceholder text="Loading mute timing" />;
return <LoadingPlaceholder text={t('alerting.mute-timing-form.text-loading-mute-timing', 'Loading mute timing')} />;
}
if (showError) {
return <Alert title="No matching mute timing found" />;
return (
<Alert
title={t('alerting.mute-timing-form.title-no-matching-mute-timing-found', 'No matching mute timing found')}
/>
);
}
return (
@ -103,8 +107,11 @@ const MuteTimingForm = ({ muteTiming, showError, loading, provisioned, editMode
<FieldSet disabled={provisioned || updating}>
<Field
required
label="Name"
description="A unique name for the mute timing"
label={t('alerting.mute-timing-form.label-name', 'Name')}
description={t(
'alerting.mute-timing-form.description-unique-timing',
'A unique name for the mute timing'
)}
invalid={!!formApi.formState.errors?.name}
error={formApi.formState.errors.name?.message}
>

@ -5,6 +5,7 @@ import { useFieldArray, useFormContext } from 'react-hook-form';
import { GrafanaTheme2 } from '@grafana/data';
import { Button, Field, FieldSet, Icon, InlineSwitch, Input, Stack, useStyles2 } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { useAlertmanager } from '../../state/AlertmanagerContext';
import { MuteTimingFields } from '../../types/mute-timing-form';
@ -26,7 +27,7 @@ export const MuteTimingTimeInterval = () => {
const { isGrafanaAlertmanager } = useAlertmanager();
return (
<FieldSet label="Time intervals">
<FieldSet label={t('alerting.mute-timing-time-interval.label-time-intervals', 'Time intervals')}>
<>
<p>
A time interval is a definition for a moment in time. All fields are lists, and at least one list element must
@ -46,7 +47,7 @@ export const MuteTimingTimeInterval = () => {
<div key={timeInterval.id} className={styles.timeIntervalSection}>
<MuteTimingTimeRange intervalIndex={timeIntervalIndex} />
<Field
label="Location"
label={t('alerting.mute-timing-time-interval.label-location', 'Location')}
invalid={Boolean(errors.time_intervals?.[timeIntervalIndex]?.location)}
error={errors.time_intervals?.[timeIntervalIndex]?.location?.message}
>
@ -61,7 +62,7 @@ export const MuteTimingTimeInterval = () => {
data-testid="mute-timing-location"
/>
</Field>
<Field label="Days of the week">
<Field label={t('alerting.mute-timing-time-interval.label-days-of-the-week', 'Days of the week')}>
<DaysOfTheWeek
onChange={(daysOfWeek) => {
setValue(`time_intervals.${timeIntervalIndex}.weekdays`, daysOfWeek);
@ -71,7 +72,7 @@ export const MuteTimingTimeInterval = () => {
/>
</Field>
<Field
label="Days of the month"
label={t('alerting.mute-timing-time-interval.label-days-of-the-month', 'Days of the month')}
description="The days of the month, 1:31, of a month. Negative values can be used to represent days which begin at the end of the month"
invalid={!!errors.time_intervals?.[timeIntervalIndex]?.days_of_month}
error={errors.time_intervals?.[timeIntervalIndex]?.days_of_month?.message}
@ -83,12 +84,15 @@ export const MuteTimingTimeInterval = () => {
width={50}
// @ts-ignore react-hook-form doesn't handle nested field arrays well
defaultValue={timeInterval.days_of_month}
placeholder="Example: 1, 14:16, -1"
placeholder={t(
'alerting.mute-timing-time-interval.mute-timing-days-placeholder-example',
'Example: 1, 14:16, -1'
)}
data-testid="mute-timing-days"
/>
</Field>
<Field
label="Months"
label={t('alerting.mute-timing-time-interval.label-months', 'Months')}
description="The months of the year in either numerical or the full calendar month"
invalid={!!errors.time_intervals?.[timeIntervalIndex]?.months}
error={errors.time_intervals?.[timeIntervalIndex]?.months?.message}
@ -103,14 +107,17 @@ export const MuteTimingTimeInterval = () => {
),
})}
width={50}
placeholder="Example: 1:3, may:august, december"
placeholder={t(
'alerting.mute-timing-time-interval.mute-timing-months-placeholder-example-mayaugust-december',
'Example: 1:3, may:august, december'
)}
// @ts-ignore react-hook-form doesn't handle nested field arrays well
defaultValue={timeInterval.months}
data-testid="mute-timing-months"
/>
</Field>
<Field
label="Years"
label={t('alerting.mute-timing-time-interval.label-years', 'Years')}
invalid={!!errors.time_intervals?.[timeIntervalIndex]?.years}
error={errors.time_intervals?.[timeIntervalIndex]?.years?.message ?? ''}
>
@ -119,7 +126,10 @@ export const MuteTimingTimeInterval = () => {
validate: (value) => validateArrayField(value, (year) => /^\d{4}$/.test(year), 'Invalid year'),
})}
width={50}
placeholder="Example: 2021:2022, 2030"
placeholder={t(
'alerting.mute-timing-time-interval.mute-timing-years-placeholder-example',
'Example: 2021:2022, 2030'
)}
// @ts-ignore react-hook-form doesn't handle nested field arrays well
defaultValue={timeInterval.years}
data-testid="mute-timing-years"
@ -133,7 +143,9 @@ export const MuteTimingTimeInterval = () => {
icon="trash-alt"
onClick={() => removeTimeInterval(timeIntervalIndex)}
>
Remove time interval
<Trans i18nKey="alerting.mute-timing-time-interval.remove-time-interval">
Remove time interval
</Trans>
</Button>
{/*
This switch is only available for Grafana Alertmanager, as for now, Grafana alert manager doesn't support this feature
@ -142,7 +154,7 @@ export const MuteTimingTimeInterval = () => {
{!isGrafanaAlertmanager && (
<InlineSwitch
id={`time_intervals.${timeIntervalIndex}.disable`}
label="Disable"
label={t('alerting.mute-timing-time-interval.label-disable', 'Disable')}
showLabel
transparent
{...register(`time_intervals.${timeIntervalIndex}.disable`)}
@ -162,7 +174,9 @@ export const MuteTimingTimeInterval = () => {
}}
icon="plus"
>
Add another time interval
<Trans i18nKey="alerting.mute-timing-time-interval.add-another-time-interval">
Add another time interval
</Trans>
</Button>
</>
</FieldSet>

@ -3,6 +3,7 @@ import { useFieldArray, useFormContext } from 'react-hook-form';
import { GrafanaTheme2 } from '@grafana/data';
import { Button, Field, Icon, IconButton, InlineField, InlineFieldRow, Input, Tooltip, useStyles2 } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { MuteTimingFields } from '../../types/mute-timing-form';
import ConditionalWrap from '../ConditionalWrap';
@ -35,7 +36,7 @@ export const MuteTimingTimeRange = ({ intervalIndex }: Props) => {
<div>
<Field
className={styles.field}
label="Time range"
label={t('alerting.mute-timing-time-range.label-time-range', 'Time range')}
description="The time inclusive of the start and exclusive of the end time (in UTC if no location has been selected, otherwise local time)"
invalid={timeRangeInvalid}
>
@ -58,7 +59,7 @@ export const MuteTimingTimeRange = ({ intervalIndex }: Props) => {
<div className={styles.timeRange} key={timeRange.id}>
<InlineFieldRow>
<InlineField
label="Start time"
label={t('alerting.mute-timing-time-range.label-start-time', 'Start time')}
invalid={Boolean(timeRangeErrors?.start_time)}
error={timeRangeErrors?.start_time?.message}
>
@ -86,12 +87,12 @@ export const MuteTimingTimeRange = ({ intervalIndex }: Props) => {
suffix={<Icon name="clock-nine" />}
// @ts-ignore react-hook-form doesn't handle nested field arrays well
defaultValue={timeRange.start_time}
placeholder="HH:mm"
placeholder={t('alerting.mute-timing-time-range.mute-timing-starts-at-placeholder-hhmm', 'HH:mm')}
data-testid="mute-timing-starts-at"
/>
</InlineField>
<InlineField
label="End time"
label={t('alerting.mute-timing-time-range.label-end-time', 'End time')}
invalid={Boolean(timeRangeErrors?.end_time)}
error={timeRangeErrors?.end_time?.message}
>
@ -118,19 +119,19 @@ export const MuteTimingTimeRange = ({ intervalIndex }: Props) => {
suffix={<Icon name="clock-nine" />}
// @ts-ignore react-hook-form doesn't handle nested field arrays well
defaultValue={timeRange.end_time}
placeholder="HH:mm"
placeholder={t('alerting.mute-timing-time-range.mute-timing-ends-at-placeholder-hhmm', 'HH:mm')}
data-testid="mute-timing-ends-at"
/>
</InlineField>
<IconButton
className={styles.deleteTimeRange}
title="Remove"
title={t('alerting.mute-timing-time-range.title-remove', 'Remove')}
name="trash-alt"
onClick={(e) => {
e.preventDefault();
removeTimeRange(index);
}}
tooltip="Remove time range"
tooltip={t('alerting.mute-timing-time-range.tooltip-remove-time-range', 'Remove time range')}
/>
</InlineFieldRow>
</div>
@ -154,7 +155,7 @@ export const MuteTimingTimeRange = ({ intervalIndex }: Props) => {
disabled={isDisabled}
onClick={() => addTimeRange({ start_time: '', end_time: '' })}
>
Add another time range
<Trans i18nKey="alerting.mute-timing-time-range.add-another-time-range">Add another time range</Trans>
</Button>
</ConditionalWrap>
</div>

@ -55,7 +55,11 @@ export const MuteTimingsTable = () => {
const columns = useColumns(alertManagerSourceName, hideActions);
if (isLoading) {
return <LoadingPlaceholder text="Loading mute timings..." />;
return (
<LoadingPlaceholder
text={t('alerting.mute-timings-table.text-loading-mute-timings', 'Loading mute timings...')}
/>
);
}
if (error) {
@ -107,7 +111,10 @@ export const MuteTimingsTable = () => {
<DynamicTable items={items} cols={columns} pagination={{ itemsPerPage: 25 }} />
) : !hideActions ? (
<EmptyAreaWithCTA
text="You haven't created any mute timings yet"
text={t(
'alerting.mute-timings-table.text-havent-created-timings',
"You haven't created any mute timings yet"
)}
buttonLabel="Add mute timing"
buttonIcon="plus"
buttonSize="lg"
@ -115,7 +122,11 @@ export const MuteTimingsTable = () => {
showButton={allowedToCreateMuteTiming}
/>
) : (
<EmptyAreaWithCTA text="No mute timings configured" buttonLabel={''} showButton={false} />
<EmptyAreaWithCTA
text={t('alerting.mute-timings-table.text-no-mute-timings-configured', 'No mute timings configured')}
buttonLabel={''}
showButton={false}
/>
)}
</div>
);

@ -3,6 +3,7 @@ import { useEffect, useMemo, useState } from 'react';
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
import { Alert, IconButton, Select, SelectCommonProps, Stack, Text, useStyles2 } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { ContactPointReceiverSummary } from 'app/features/alerting/unified/components/contact-points/ContactPoint';
import { useAlertmanager } from 'app/features/alerting/unified/state/AlertmanagerContext';
@ -69,7 +70,15 @@ export const ContactPointSelector = ({
// TODO error handling
if (error) {
return <Alert title="Failed to fetch contact points" severity="error" />;
return (
<Alert
title={t(
'alerting.contact-point-selector.title-failed-to-fetch-contact-points',
'Failed to fetch contact points'
)}
severity="error"
/>
);
}
return (
@ -86,8 +95,11 @@ export const ContactPointSelector = ({
<IconButton
name="sync"
onClick={onClickRefresh}
aria-label="Refresh contact points"
tooltip="Refresh contact points list"
aria-label={t('alerting.contact-point-selector.aria-label-refresh-contact-points', 'Refresh contact points')}
tooltip={t(
'alerting.contact-point-selector.tooltip-refresh-contact-points-list',
'Refresh contact points list'
)}
className={cx(styles.refreshButton, {
[styles.loading]: loaderSpinning || isLoading,
})}

@ -2,6 +2,7 @@ import { ReactNode, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { Collapse, Field, Link, MultiSelect, useStyles2 } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { ContactPointSelector } from 'app/features/alerting/unified/components/notification-policies/ContactPointSelector';
import { handleContactPointSelect } from 'app/features/alerting/unified/components/notification-policies/utils';
import { RouteWithID } from 'app/plugins/datasource/alertmanager/types';
@ -52,7 +53,7 @@ export const AmRootRouteForm = ({ actionButtons, alertManagerSourceName, onSubmi
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Field
label="Default contact point"
label={t('alerting.am-root-route-form.label-default-contact-point', 'Default contact point')}
invalid={Boolean(errors.receiver) ? true : undefined}
error={errors.receiver?.message}
>
@ -71,24 +72,26 @@ export const AmRootRouteForm = ({ actionButtons, alertManagerSourceName, onSubmi
name="receiver"
rules={{ required: { value: true, message: 'Required.' } }}
/>
<span>or</span>
<span>
<Trans i18nKey="alerting.am-root-route-form.or">or</Trans>
</span>
<Link
className={styles.linkText}
href={makeAMLink('/alerting/notifications/receivers/new', alertManagerSourceName)}
>
Create a contact point
<Trans i18nKey="alerting.am-root-route-form.create-a-contact-point">Create a contact point</Trans>
</Link>
</div>
</Field>
<Field
label="Group by"
label={t('alerting.am-root-route-form.am-group-select-label-group-by', 'Group by')}
description="Combine multiple alerts into a single notification by grouping them by the same label values."
data-testid="am-group-select"
>
<Controller
render={({ field: { onChange, ref, ...field } }) => (
<MultiSelect
aria-label="Group by"
aria-label={t('alerting.am-root-route-form.aria-label-group-by', 'Group by')}
{...field}
allowCustomValue
className={styles.input}
@ -108,12 +111,12 @@ export const AmRootRouteForm = ({ actionButtons, alertManagerSourceName, onSubmi
collapsible
className={styles.collapse}
isOpen={isTimingOptionsExpanded}
label="Timing options"
label={t('alerting.am-root-route-form.label-timing-options', 'Timing options')}
onToggle={setIsTimingOptionsExpanded}
>
<div className={styles.timingFormContainer}>
<Field
label="Group wait"
label={t('alerting.am-root-route-form.am-group-wait-label-group-wait', 'Group wait')}
description="The waiting time before sending the first notification for a new group of alerts. Default 30 seconds."
invalid={!!errors.groupWaitValue}
error={errors.groupWaitValue?.message}
@ -123,11 +126,11 @@ export const AmRootRouteForm = ({ actionButtons, alertManagerSourceName, onSubmi
{...register('groupWaitValue', { validate: promDurationValidator })}
placeholder={TIMING_OPTIONS_DEFAULTS.group_wait}
className={styles.promDurationInput}
aria-label="Group wait"
aria-label={t('alerting.am-root-route-form.aria-label-group-wait', 'Group wait')}
/>
</Field>
<Field
label="Group interval"
label={t('alerting.am-root-route-form.am-group-interval-label-group-interval', 'Group interval')}
description="The wait time before sending a notification about changes in the alert group after the first notification has been sent. Default is 5 minutes."
invalid={!!errors.groupIntervalValue}
error={errors.groupIntervalValue?.message}
@ -137,11 +140,11 @@ export const AmRootRouteForm = ({ actionButtons, alertManagerSourceName, onSubmi
{...register('groupIntervalValue', { validate: promDurationValidator })}
placeholder={TIMING_OPTIONS_DEFAULTS.group_interval}
className={styles.promDurationInput}
aria-label="Group interval"
aria-label={t('alerting.am-root-route-form.aria-label-group-interval', 'Group interval')}
/>
</Field>
<Field
label="Repeat interval"
label={t('alerting.am-root-route-form.am-repeat-interval-label-repeat-interval', 'Repeat interval')}
description="The wait time before resending a notification that has already been sent successfully. Default is 4 hours. Should be a multiple of Group interval."
invalid={!!errors.repeatIntervalValue}
error={errors.repeatIntervalValue?.message}
@ -156,7 +159,7 @@ export const AmRootRouteForm = ({ actionButtons, alertManagerSourceName, onSubmi
})}
placeholder={TIMING_OPTIONS_DEFAULTS.repeat_interval}
className={styles.promDurationInput}
aria-label="Repeat interval"
aria-label={t('alerting.am-root-route-form.aria-label-repeat-interval', 'Repeat interval')}
/>
</Field>
</div>

@ -16,6 +16,7 @@ import {
Switch,
useStyles2,
} from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import MuteTimingsSelector from 'app/features/alerting/unified/components/alertmanager-entities/MuteTimingsSelector';
import { ContactPointSelector } from 'app/features/alerting/unified/components/notification-policies/ContactPointSelector';
import { handleContactPointSelect } from 'app/features/alerting/unified/components/notification-policies/utils';
@ -86,7 +87,9 @@ export const AmRoutesExpandedForm = ({ actionButtons, route, onSubmit, defaults
<form onSubmit={handleSubmit(onSubmit)}>
<input type="hidden" {...register('id')} />
<Stack direction="column" alignItems="flex-start">
<div>Matching labels</div>
<div>
<Trans i18nKey="alerting.am-routes-expanded-form.matching-labels">Matching labels</Trans>
</div>
{fields.length === 0 && (
<Badge
color="orange"
@ -101,14 +104,14 @@ export const AmRoutesExpandedForm = ({ actionButtons, route, onSubmit, defaults
return (
<Stack direction="row" key={field.id} alignItems="center">
<Field
label="Label"
label={t('alerting.am-routes-expanded-form.label-label', 'Label')}
invalid={!!errors.object_matchers?.[index]?.name}
error={errors.object_matchers?.[index]?.name?.message}
>
<Input
{...register(`object_matchers.${index}.name`, { required: 'Field is required' })}
defaultValue={field.name}
placeholder="label"
placeholder={t('alerting.am-routes-expanded-form.placeholder-label', 'label')}
autoFocus
/>
</Field>
@ -120,7 +123,7 @@ export const AmRoutesExpandedForm = ({ actionButtons, route, onSubmit, defaults
className={styles.matchersOperator}
onChange={(value) => onChange(value?.value)}
options={matcherFieldOptions}
aria-label="Operator"
aria-label={t('alerting.am-routes-expanded-form.aria-label-operator', 'Operator')}
/>
)}
defaultValue={field.operator}
@ -130,18 +133,22 @@ export const AmRoutesExpandedForm = ({ actionButtons, route, onSubmit, defaults
/>
</Field>
<Field
label="Value"
label={t('alerting.am-routes-expanded-form.label-value', 'Value')}
invalid={!!errors.object_matchers?.[index]?.value}
error={errors.object_matchers?.[index]?.value?.message}
>
<Input
{...register(`object_matchers.${index}.value`)}
defaultValue={field.value}
placeholder="value"
placeholder={t('alerting.am-routes-expanded-form.placeholder-value', 'value')}
/>
</Field>
<IconButton tooltip="Remove matcher" name={'trash-alt'} onClick={() => remove(index)}>
Remove
<IconButton
tooltip={t('alerting.am-routes-expanded-form.tooltip-remove-matcher', 'Remove matcher')}
name={'trash-alt'}
onClick={() => remove(index)}
>
<Trans i18nKey="alerting.am-routes-expanded-form.remove">Remove</Trans>
</IconButton>
</Stack>
);
@ -155,11 +162,11 @@ export const AmRoutesExpandedForm = ({ actionButtons, route, onSubmit, defaults
variant="secondary"
type="button"
>
Add matcher
<Trans i18nKey="alerting.am-routes-expanded-form.add-matcher">Add matcher</Trans>
</Button>
</Stack>
<Field label="Contact point">
<Field label={t('alerting.am-routes-expanded-form.label-contact-point', 'Contact point')}>
<Controller
render={({ field: { onChange, ref, value, ...field } }) => (
<ContactPointSelector
@ -176,15 +183,20 @@ export const AmRoutesExpandedForm = ({ actionButtons, route, onSubmit, defaults
name="receiver"
/>
</Field>
<Field label="Continue matching subsequent sibling nodes">
<Field
label={t(
'alerting.am-routes-expanded-form.label-continue-matching-subsequent-sibling-nodes',
'Continue matching subsequent sibling nodes'
)}
>
<Switch id="continue-toggle" {...register('continue')} />
</Field>
<Field label="Override grouping">
<Field label={t('alerting.am-routes-expanded-form.label-override-grouping', 'Override grouping')}>
<Switch id="override-grouping-toggle" {...register('overrideGrouping')} />
</Field>
{watch().overrideGrouping && (
<Field
label="Group by"
label={t('alerting.am-routes-expanded-form.label-group-by', 'Group by')}
description="Combine multiple alerts into a single notification by grouping them by the same label values. If empty, it is inherited from the parent policy."
>
<Controller
@ -199,7 +211,7 @@ export const AmRoutesExpandedForm = ({ actionButtons, route, onSubmit, defaults
render={({ field: { onChange, ref, ...field }, fieldState: { error } }) => (
<>
<MultiSelect
aria-label="Group by"
aria-label={t('alerting.am-routes-expanded-form.aria-label-group-by', 'Group by')}
{...field}
invalid={Boolean(error)}
allowCustomValue
@ -219,7 +231,7 @@ export const AmRoutesExpandedForm = ({ actionButtons, route, onSubmit, defaults
/>
</Field>
)}
<Field label="Override general timings">
<Field label={t('alerting.am-routes-expanded-form.label-override-general-timings', 'Override general timings')}>
<Switch id="override-timings-toggle" {...register('overrideTimings')} />
</Field>
{watch().overrideTimings && (
@ -268,9 +280,12 @@ export const AmRoutesExpandedForm = ({ actionButtons, route, onSubmit, defaults
</>
)}
<Field
label="Mute timings"
label={t('alerting.am-routes-expanded-form.am-mute-timing-select-label-mute-timings', 'Mute timings')}
data-testid="am-mute-timing-select"
description="Add mute timing to policy"
description={t(
'alerting.am-routes-expanded-form.am-mute-timing-select-description-add-mute-timing-to-policy',
'Add mute timing to policy'
)}
invalid={!!errors.muteTimeIntervals}
>
<Controller

@ -3,7 +3,7 @@ import { debounce, isEqual } from 'lodash';
import { useCallback, useEffect, useRef } from 'react';
import { Button, Field, Icon, Input, Label, Stack, Text, Tooltip, useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { Trans, t } from 'app/core/internationalization';
import { ContactPointSelector } from 'app/features/alerting/unified/components/notification-policies/ContactPointSelector';
import { AlertmanagerAction, useAlertmanagerAbility } from 'app/features/alerting/unified/hooks/useAbilities';
import { ObjectMatcher, RouteWithID } from 'app/plugins/datasource/alertmanager/types';
@ -92,7 +92,7 @@ const NotificationPoliciesFilter = ({
<Input
ref={searchInputRef}
data-testid="search-query-input"
placeholder="Search"
placeholder={t('alerting.notification-policies-filter.search-query-input-placeholder-search', 'Search')}
width={46}
prefix={<Icon name="search" />}
onChange={(event) => {
@ -102,7 +102,10 @@ const NotificationPoliciesFilter = ({
/>
</Field>
{contactPointsSupported && canSeeContactPoints && (
<Field label="Search by contact point" style={{ marginBottom: 0 }}>
<Field
label={t('alerting.notification-policies-filter.label-search-by-contact-point', 'Search by contact point')}
style={{ marginBottom: 0 }}
>
<ContactPointSelector
selectProps={{
id: 'receiver',

@ -2,7 +2,7 @@ import { groupBy } from 'lodash';
import { FC, useCallback, useMemo, useState } from 'react';
import { Button, Icon, Modal, ModalProps, Spinner, Stack } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { Trans, t } from 'app/core/internationalization';
import { AlertState, AlertmanagerGroup, ObjectMatcher, RouteWithID } from 'app/plugins/datasource/alertmanager/types';
import { FormAmRoute } from '../../types/amroutes';
@ -52,7 +52,10 @@ const useAddPolicyModal = (
onDismiss={handleDismiss}
closeOnBackdropClick={true}
closeOnEscape={true}
title="Add notification policy"
title={t(
'alerting.use-add-policy-modal.modal-element.title-add-notification-policy',
'Add notification policy'
)}
>
{error && <NotificationPoliciesErrorAlert error={error} />}
<AmRoutesExpandedForm
@ -116,7 +119,10 @@ const useEditPolicyModal = (
onDismiss={handleDismiss}
closeOnBackdropClick={true}
closeOnEscape={true}
title="Edit notification policy"
title={t(
'alerting.use-edit-policy-modal.modal-element.title-edit-notification-policy',
'Edit notification policy'
)}
>
{error && <NotificationPoliciesErrorAlert error={error} />}
{isDefaultPolicy && route && (
@ -191,7 +197,10 @@ const useDeletePolicyModal = (
onDismiss={handleDismiss}
closeOnBackdropClick={true}
closeOnEscape={true}
title="Delete notification policy"
title={t(
'alerting.use-delete-policy-modal.modal-element.title-delete-notification-policy',
'Delete notification policy'
)}
>
{error && <NotificationPoliciesErrorAlert error={error} />}
<Trans i18nKey="alerting.policies.delete.warning-1">

@ -4,7 +4,7 @@ import { useAsyncFn } from 'react-use';
import { Alert, Button, Stack } from '@grafana/ui';
import { useAppNotification } from 'app/core/copy/appNotification';
import { Trans } from 'app/core/internationalization';
import { Trans, t } from 'app/core/internationalization';
import { useContactPointsWithStatus } from 'app/features/alerting/unified/components/contact-points/useContactPoints';
import { AlertmanagerAction, useAlertmanagerAbility } from 'app/features/alerting/unified/hooks/useAbilities';
import { FormAmRoute } from 'app/features/alerting/unified/types/amroutes';
@ -201,13 +201,25 @@ export const NotificationPoliciesList = () => {
return (
<>
{hasPoliciesError && (
<Alert severity="error" title="Error loading Alertmanager config">
<Alert
severity="error"
title={t(
'alerting.notification-policies-list.title-error-loading-alertmanager-config',
'Error loading Alertmanager config'
)}
>
{stringifyErrorLike(fetchPoliciesError) || 'Unknown error.'}
</Alert>
)}
{/* show when there is an update error */}
{hasConflictError && (
<Alert severity="info" title="Notification policies have changed">
<Alert
severity="info"
title={t(
'alerting.notification-policies-list.title-notification-policies-have-changed',
'Notification policies have changed'
)}
>
<Stack direction="row" justifyContent="space-between" alignItems="center">
<Trans i18nKey="alerting.policies.update-errors.conflict">
The notification policy tree has been updated by another user.

@ -270,18 +270,18 @@ const Policy = (props: PolicyComponentProps) => {
overlay={
<Menu>
<Menu.Item
label="New sibling above"
label={t('alerting.policy.label-new-sibling-above', 'New sibling above')}
icon="arrow-up"
onClick={() => onAddPolicy(currentRoute, 'above')}
/>
<Menu.Item
label="New sibling below"
label={t('alerting.policy.label-new-sibling-below', 'New sibling below')}
icon="arrow-down"
onClick={() => onAddPolicy(currentRoute, 'below')}
/>
<Menu.Divider />
<Menu.Item
label="New child policy"
label={t('alerting.policy.label-new-child-policy', 'New child policy')}
icon="plus"
onClick={() => onAddPolicy(currentRoute, 'child')}
/>
@ -553,7 +553,7 @@ export const useCreateDropdownMenuActions = (
<Menu.Item
icon="edit"
disabled={provisioned || isAutoGenerated}
label="Edit"
label={t('alerting.use-create-dropdown-menu-actions.label-edit', 'Edit')}
onClick={() => onEditPolicy(currentRoute, isDefaultPolicy)}
/>
</ConditionalWrap>
@ -563,7 +563,12 @@ export const useCreateDropdownMenuActions = (
if (showExportAction) {
dropdownMenuActions.push(
<Menu.Item key="export-policy" icon="download-alt" label="Export" onClick={toggleShowExportDrawer} />
<Menu.Item
key="export-policy"
icon="download-alt"
label={t('alerting.use-create-dropdown-menu-actions.label-export', 'Export')}
onClick={toggleShowExportDrawer}
/>
);
}
@ -576,7 +581,7 @@ export const useCreateDropdownMenuActions = (
destructive
icon="trash-alt"
disabled={provisioned || isAutoGenerated}
label="Delete"
label={t('alerting.use-create-dropdown-menu-actions.label-delete', 'Delete')}
onClick={() => onDeletePolicy(currentRoute)}
/>
</ConditionalWrap>

@ -2,6 +2,7 @@ import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { TimeOptions } from '../../types/time';
@ -15,9 +16,15 @@ export function PromDurationDocs() {
<hr />
<div className={styles.list}>
<div className={styles.header}>
<div>Symbol</div>
<div>Time unit</div>
<div>Example</div>
<div>
<Trans i18nKey="alerting.prom-duration-docs.symbol">Symbol</Trans>
</div>
<div>
<Trans i18nKey="alerting.prom-duration-docs.time-unit">Time unit</Trans>
</div>
<div>
<Trans i18nKey="alerting.prom-duration-docs.example">Example</Trans>
</div>
</div>
<PromDurationDocsTimeUnit unit={TimeOptions.seconds} name="seconds" example="20s" />
<PromDurationDocsTimeUnit unit={TimeOptions.minutes} name="minutes" example="10m" />
@ -25,7 +32,10 @@ export function PromDurationDocs() {
<PromDurationDocsTimeUnit unit={TimeOptions.days} name="days" example="3d" />
<PromDurationDocsTimeUnit unit={TimeOptions.weeks} name="weeks" example="2w" />
<div className={styles.examples}>
<div>Multiple units combined</div>
<div>
<Trans i18nKey="alerting.prom-duration-docs.multiple-units-combined">Multiple units combined</Trans>
</div>
{/* eslint-disable-next-line @grafana/no-untranslated-strings */}
<code>1m30s, 2h30m20s, 1w2d</code>
</div>
</div>

@ -3,6 +3,7 @@ import { useAsync } from 'react-use';
import { urlUtil } from '@grafana/data';
import { Alert, Button, LinkButton } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
import { useSelector } from 'app/types';
@ -30,12 +31,22 @@ export const NewRuleFromPanelButton = ({ dashboard, panel, className }: Props) =
);
if (loading) {
return <Button disabled={true}>New alert rule</Button>;
return (
<Button disabled={true}>
<Trans i18nKey="alerting.new-rule-from-panel-button.new-alert-rule">New alert rule</Trans>
</Button>
);
}
if (!formValues) {
return (
<Alert severity="info" title="No alerting capable query found">
<Alert
severity="info"
title={t(
'alerting.new-rule-from-panel-button.title-no-alerting-capable-query-found',
'No alerting capable query found'
)}
>
Cannot create alerts from this panel because no query to an alerting capable datasource is found.
</Alert>
);
@ -54,7 +65,7 @@ export const NewRuleFromPanelButton = ({ dashboard, panel, className }: Props) =
className={className}
data-testid="create-alert-rule-button"
>
New alert rule
<Trans i18nKey="alerting.new-rule-from-panel-button.new-alert-rule">New alert rule</Trans>
</LinkButton>
);
};

@ -15,7 +15,7 @@ import {
clearButtonStyles,
useStyles2,
} from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { Trans, t } from 'app/core/internationalization';
import { AlertmanagerAlert, TestTemplateAlert } from 'app/plugins/datasource/alertmanager/types';
import { alertmanagerApi } from '../../api/alertmanagerApi';
@ -208,7 +208,7 @@ export function AlertInstanceModalSelector({
return (
<div>
<Modal
title="Select alert instances"
title={t('alerting.alert-instance-modal-selector.title-select-alert-instances', 'Select alert instances')}
className={styles.modal}
closeOnEscape
isOpen={isOpen}
@ -219,14 +219,19 @@ export function AlertInstanceModalSelector({
<FilterInput
value={ruleFilter}
onChange={handleSearchRules}
title="Search alert rule"
placeholder="Search alert rule"
title={t('alerting.alert-instance-modal-selector.title-search-alert-rule', 'Search alert rule')}
placeholder={t('alerting.alert-instance-modal-selector.placeholder-search-alert-rule', 'Search alert rule')}
autoFocus
/>
<div>{(selectedRule && 'Select one or more instances from the list below') || ''}</div>
<div className={styles.column}>
{loading && <LoadingPlaceholder text="Loading rules..." className={styles.loadingPlaceholder} />}
{loading && (
<LoadingPlaceholder
text={t('alerting.alert-instance-modal-selector.text-loading-rules', 'Loading rules...')}
className={styles.loadingPlaceholder}
/>
)}
{!loading && (
<AutoSizer>
@ -245,7 +250,12 @@ export function AlertInstanceModalSelector({
<div>Select an alert rule to get a list of available firing instances</div>
</div>
)}
{loading && <LoadingPlaceholder text="Loading rule..." className={styles.loadingPlaceholder} />}
{loading && (
<LoadingPlaceholder
text={t('alerting.alert-instance-modal-selector.text-loading-rule', 'Loading rule...')}
className={styles.loadingPlaceholder}
/>
)}
{selectedRule && rulesWithInstances[selectedRule].length && !loading && (
<AutoSizer>
@ -277,7 +287,9 @@ export function AlertInstanceModalSelector({
}
}}
>
Add alert data to payload
<Trans i18nKey="alerting.alert-instance-modal-selector.add-alert-data-to-payload">
Add alert data to payload
</Trans>
</Button>
</Modal.ButtonRow>
</Modal>

@ -2,7 +2,7 @@ import { FormProvider, useForm } from 'react-hook-form';
import { Alert, Button, LinkButton, Stack } from '@grafana/ui';
import { useCleanup } from 'app/core/hooks/useCleanup';
import { Trans } from 'app/core/internationalization';
import { Trans, t } from 'app/core/internationalization';
import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types';
import { useDispatch } from 'app/types';
@ -72,7 +72,10 @@ export const GlobalConfigForm = ({ config, alertManagerSourceName }: Props) => {
<FormProvider {...formAPI}>
<form onSubmit={handleSubmit(onSubmitCallback)}>
{error && (
<Alert severity="error" title="Error saving receiver">
<Alert
severity="error"
title={t('alerting.global-config-form.title-error-saving-receiver', 'Error saving receiver')}
>
{error.message || String(error)}
</Alert>
)}
@ -92,10 +95,14 @@ export const GlobalConfigForm = ({ config, alertManagerSourceName }: Props) => {
<>
{loading && (
<Button disabled={true} icon="spinner" variant="primary">
Saving...
<Trans i18nKey="alerting.global-config-form.saving">Saving...</Trans>
</Button>
)}
{!loading && (
<Button type="submit">
<Trans i18nKey="alerting.global-config-form.save-global-config">Save global config</Trans>
</Button>
)}
{!loading && <Button type="submit">Save global config</Button>}
</>
)}
<LinkButton

@ -5,6 +5,7 @@ import AutoSizer from 'react-virtualized-auto-sizer';
import { GrafanaTheme2 } from '@grafana/data';
import { Button, CodeEditor, Dropdown, Menu, Stack, Toggletip, useStyles2 } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { TestTemplateAlert } from 'app/plugins/datasource/alertmanager/types';
import { EditorColumnHeader } from '../contact-points/templates/EditorColumnHeader';
@ -84,19 +85,22 @@ export function PayloadEditor({
<>
<div className={cx(styles.wrapper, className)}>
<EditorColumnHeader
label="Payload"
label={t('alerting.payload-editor.label-payload', 'Payload')}
actions={
<Stack direction="row" alignItems="center" gap={0.5}>
<Dropdown
overlay={
<Menu>
<Menu.Item
label="Use existing alert instances"
label={t(
'alerting.payload-editor.label-use-existing-alert-instances',
'Use existing alert instances'
)}
disabled={errorInPayloadJson}
onClick={onOpenAlertSelectorModal}
/>
<Menu.Item
label="Add custom alert instance"
label={t('alerting.payload-editor.label-add-custom-alert-instance', 'Add custom alert instance')}
disabled={errorInPayloadJson}
onClick={onOpenEditAlertModal}
/>
@ -106,12 +110,12 @@ export function PayloadEditor({
}
>
<Button variant="secondary" size="sm" icon="angle-down">
Edit payload
<Trans i18nKey="alerting.payload-editor.edit-payload">Edit payload</Trans>
</Button>
</Dropdown>
<Toggletip content={<AlertTemplateDataTable />} placement="top" fitContent>
<Button variant="secondary" fill="outline" size="sm" icon="question-circle">
Reference
<Trans i18nKey="alerting.payload-editor.reference">Reference</Trans>
</Button>
</Toggletip>
</Stack>

@ -5,6 +5,7 @@ import { useToggle } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data';
import { Button, Dropdown, Icon, Menu, MenuItem, Stack, useStyles2 } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { GrafanaReceiversExporter } from '../export/GrafanaReceiversExporter';
@ -34,7 +35,16 @@ export const ReceiversSection = ({
const showMore = showExport;
const [showExportDrawer, toggleShowExportDrawer] = useToggle(false);
const newMenu = <Menu>{showExport && <MenuItem onClick={toggleShowExportDrawer} label="Export all" />}</Menu>;
const newMenu = (
<Menu>
{showExport && (
<MenuItem
onClick={toggleShowExportDrawer}
label={t('alerting.receivers-section.new-menu.label-export-all', 'Export all')}
/>
)}
</Menu>
);
return (
<Stack direction="column" gap={2}>

@ -3,6 +3,7 @@ import * as React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Stack, useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { PopupCard } from '../HoverCard';
@ -83,9 +84,15 @@ export function TemplateDataTable({ dataItems, caption, typeRenderer }: Template
{caption && <caption>{caption}</caption>}
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Notes</th>
<th>
<Trans i18nKey="alerting.template-data-table.name">Name</Trans>
</th>
<th>
<Trans i18nKey="alerting.template-data-table.type">Type</Trans>
</th>
<th>
<Trans i18nKey="alerting.template-data-table.notes">Notes</Trans>
</th>
</tr>
</thead>
<tbody>
@ -111,13 +118,23 @@ function KeyValueTemplateDataTable() {
<code>{KeyValueCodeSnippet}</code>
</pre>
<table className={tableStyles.table}>
<caption>Key-value methods</caption>
<caption>
<Trans i18nKey="alerting.key-value-template-data-table.keyvalue-methods">Key-value methods</Trans>
</caption>
<thead>
<tr>
<th>Name</th>
<th>Arguments</th>
<th>Returns</th>
<th>Notes</th>
<th>
<Trans i18nKey="alerting.key-value-template-data-table.name">Name</Trans>
</th>
<th>
<Trans i18nKey="alerting.key-value-template-data-table.arguments">Arguments</Trans>
</th>
<th>
<Trans i18nKey="alerting.key-value-template-data-table.returns">Returns</Trans>
</th>
<th>
<Trans i18nKey="alerting.key-value-template-data-table.notes">Notes</Trans>
</th>
</tr>
</thead>
<tbody>

@ -167,10 +167,18 @@ export const TemplateForm = ({ originalTemplate, prefill, alertmanager }: Props)
return (
<>
<FormProvider {...formApi}>
<form onSubmit={handleSubmit(submit)} ref={formRef} className={styles.form} aria-label="Template form">
<form
onSubmit={handleSubmit(submit)}
ref={formRef}
className={styles.form}
aria-label={t('alerting.template-form.aria-label-template-form', 'Template form')}
>
{/* error message */}
{error && (
<Alert severity="error" title="Error saving template">
<Alert
severity="error"
title={t('alerting.template-form.title-error-saving-template', 'Error saving template')}
>
{error.message || (isFetchError(error) && error.data?.message) || String(error)}
</Alert>
)}
@ -187,7 +195,7 @@ export const TemplateForm = ({ originalTemplate, prefill, alertmanager }: Props)
{/* name and save buttons */}
<Stack direction="row" alignItems="center">
<InlineField
label="Template group name"
label={t('alerting.template-form.label-template-group-name', 'Template group name')}
error={errors?.title?.message}
invalid={!!errors.title?.message}
required
@ -197,7 +205,10 @@ export const TemplateForm = ({ originalTemplate, prefill, alertmanager }: Props)
required: { value: true, message: 'Required.' },
validate: { titleIsUnique },
})}
placeholder="Give your template group a name"
placeholder={t(
'alerting.template-form.new-template-name-placeholder-give-your-template-group-a-name',
'Give your template group a name'
)}
width={42}
autoFocus={true}
id="new-template-name"
@ -231,7 +242,7 @@ export const TemplateForm = ({ originalTemplate, prefill, alertmanager }: Props)
<div className={cx(styles.flexColumn, styles.containerWithBorderAndRadius, styles.minEditorSize)}>
<div>
<EditorColumnHeader
label="Template group"
label={t('alerting.template-form.label-template-group', 'Template group')}
actions={
<>
{/* examples dropdown – only available for Grafana Alertmanager */}
@ -334,7 +345,11 @@ export const TemplateForm = ({ originalTemplate, prefill, alertmanager }: Props)
</form>
</FormProvider>
{cheatsheetOpened && (
<Drawer title="Templating cheat sheet" onClose={toggleCheatsheetOpened} size="lg">
<Drawer
title={t('alerting.template-form.title-templating-cheat-sheet', 'Templating cheat sheet')}
onClose={toggleCheatsheetOpened}
size="lg"
>
<TemplatingCheatSheet />
</Drawer>
)}

@ -6,6 +6,7 @@ import AutoSizer from 'react-virtualized-auto-sizer';
import { GrafanaTheme2 } from '@grafana/data';
import { Alert, Box, Button, useStyles2 } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { TemplatePreviewErrors, TemplatePreviewResponse, TemplatePreviewResult } from '../../api/templateApi';
import { stringifyErrorLike } from '../../utils/misc';
@ -44,17 +45,17 @@ export function TemplatePreview({
return (
<div className={cx(styles.container, className)}>
<EditorColumnHeader
label="Preview"
label={t('alerting.template-preview.label-preview', 'Preview')}
actions={
<Button
disabled={isLoading}
icon="sync"
aria-label="Refresh preview"
aria-label={t('alerting.template-preview.aria-label-refresh-preview', 'Refresh preview')}
onClick={onPreview}
size="sm"
variant="secondary"
>
Refresh
<Trans i18nKey="alerting.template-preview.refresh">Refresh</Trans>
</Button>
}
/>
@ -152,7 +153,7 @@ export function getPreviewResults(
return (
<>
{errorToRender && (
<Alert severity="error" title="Error">
<Alert severity="error" title={t('alerting.get-preview-results.title-error', 'Error')}>
{errorToRender}
</Alert>
)}

@ -62,7 +62,9 @@ export const TemplatesTable = ({ alertManagerName, templates }: Props) => {
<thead>
<tr>
<th />
<th>Template group</th>
<th>
<Trans i18nKey="alerting.templates-table.template-group">Template group</Trans>
</th>
<Authorize
actions={[
AlertmanagerAction.CreateNotificationTemplate,
@ -70,14 +72,18 @@ export const TemplatesTable = ({ alertManagerName, templates }: Props) => {
AlertmanagerAction.DeleteNotificationTemplate,
]}
>
<th>Actions</th>
<th>
<Trans i18nKey="alerting.templates-table.actions">Actions</Trans>
</th>
</Authorize>
</tr>
</thead>
<tbody>
{!templates.length && (
<tr className={tableStyles.evenRow}>
<td colSpan={3}>No templates defined.</td>
<td colSpan={3}>
<Trans i18nKey="alerting.templates-table.no-templates-defined">No templates defined.</Trans>
</td>
</tr>
)}
{templates.map((notificationTemplate, idx) => (
@ -95,7 +101,7 @@ export const TemplatesTable = ({ alertManagerName, templates }: Props) => {
{!!templateToDelete && (
<ConfirmModal
isOpen={true}
title="Delete template group"
title={t('alerting.templates-table.title-delete-template-group', 'Delete template group')}
body={`Are you sure you want to delete template group "${templateToDelete.title}"?`}
confirmText="Yes, delete"
onConfirm={onDeleteTemplate}
@ -153,7 +159,7 @@ function TemplateRow({ notificationTemplate, idx, alertManagerName, onDeleteClic
{isProvisioned && (
<ActionIcon
to={makeAMLink(`/alerting/notifications/templates/${encodeURIComponent(uid)}/edit`, alertManagerName)}
tooltip="view template"
tooltip={t('alerting.template-row.tooltip-view-template', 'view template')}
icon="file-alt"
/>
)}
@ -161,7 +167,7 @@ function TemplateRow({ notificationTemplate, idx, alertManagerName, onDeleteClic
<Authorize actions={[AlertmanagerAction.UpdateNotificationTemplate]}>
<ActionIcon
to={makeAMLink(`/alerting/notifications/templates/${encodeURIComponent(uid)}/edit`, alertManagerName)}
tooltip="Edit template group"
tooltip={t('alerting.template-row.tooltip-edit-template-group', 'Edit template group')}
icon="pen"
/>
</Authorize>
@ -172,7 +178,7 @@ function TemplateRow({ notificationTemplate, idx, alertManagerName, onDeleteClic
`/alerting/notifications/templates/${encodeURIComponent(uid)}/duplicate`,
alertManagerName
)}
tooltip="Copy template group"
tooltip={t('alerting.template-row.tooltip-copy-template-group', 'Copy template group')}
icon="copy"
/>
</Authorize>
@ -180,7 +186,7 @@ function TemplateRow({ notificationTemplate, idx, alertManagerName, onDeleteClic
<Authorize actions={[AlertmanagerAction.DeleteNotificationTemplate]}>
<ActionIcon
onClick={() => onDeleteClick(notificationTemplate)}
tooltip="Delete template group"
tooltip={t('alerting.template-row.tooltip-delete-template-group', 'Delete template group')}
icon="trash-alt"
/>
</Authorize>

@ -145,7 +145,11 @@ export function ChannelSubForm<R extends ChannelValues>({
<div className={styles.wrapper} data-testid="item-container">
<div className={styles.topRow}>
<div>
<Field label="Integration" htmlFor={contactPointTypeInputId} data-testid={`${pathPrefix}type`}>
<Field
label={t('alerting.channel-sub-form.label-integration', 'Integration')}
htmlFor={contactPointTypeInputId}
data-testid={`${pathPrefix}type`}
>
<Controller
name={fieldName('type')}
defaultValue={defaultValues.type}
@ -174,13 +178,13 @@ export function ChannelSubForm<R extends ChannelValues>({
onClick={() => handleTest()}
icon={testingReceiver ? 'spinner' : 'message'}
>
Test
<Trans i18nKey="alerting.channel-sub-form.test">Test</Trans>
</Button>
)}
{isEditable && (
<>
<Button size="xs" variant="secondary" type="button" onClick={() => onDuplicate()} icon="copy">
Duplicate
<Trans i18nKey="alerting.channel-sub-form.duplicate">Duplicate</Trans>
</Button>
{onDelete && (
<Button
@ -191,7 +195,7 @@ export function ChannelSubForm<R extends ChannelValues>({
onClick={() => onDelete()}
icon="trash-alt"
>
Delete
<Trans i18nKey="alerting.channel-sub-form.delete">Delete</Trans>
</Button>
)}
</>
@ -244,7 +248,9 @@ export function ChannelSubForm<R extends ChannelValues>({
/>
</CollapsibleSection>
)}
<CollapsibleSection label="Notification settings">
<CollapsibleSection
label={t('alerting.channel-sub-form.label-notification-settings', 'Notification settings')}
>
<CommonSettingsComponent pathPrefix={pathPrefix} readOnly={!isEditable} />
</CollapsibleSection>
</div>

@ -1,6 +1,7 @@
import { useFormContext } from 'react-hook-form';
import { Checkbox, Field } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { CommonSettingsComponentProps } from '../../../types/receiver-form';
@ -15,9 +16,12 @@ export const CloudCommonChannelSettings = ({
<Field disabled={readOnly}>
<Checkbox
{...register(`${pathPrefix}sendResolved`)}
label="Send resolved"
label={t('alerting.cloud-common-channel-settings.label-send-resolved', 'Send resolved')}
disabled={readOnly}
description="Whether or not to notify about resolved alerts."
description={t(
'alerting.cloud-common-channel-settings.description-whether-notify-about-resolved-alerts',
'Whether or not to notify about resolved alerts.'
)}
/>
</Field>
</div>

@ -2,6 +2,7 @@ import { useMemo } from 'react';
import { locationService } from '@grafana/runtime';
import { Alert } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { alertmanagerApi } from 'app/features/alerting/unified/api/alertmanagerApi';
import {
useCreateContactPoint,
@ -74,7 +75,7 @@ export const CloudReceiverForm = ({ contactPoint, alertManagerSourceName, readOn
return (
<>
{!isVanillaAM && (
<Alert title="Info" severity="info">
<Alert title={t('alerting.cloud-receiver-form.title-info', 'Info')} severity="info">
Note that empty string values will be replaced with global defaults where appropriate.
</Alert>
)}

@ -6,6 +6,7 @@ import { FormProvider, useForm } from 'react-hook-form';
import { GrafanaTheme2 } from '@grafana/data';
import { Button, Card, Modal, RadioButtonGroup, Stack, useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { TestTemplateAlert } from 'app/plugins/datasource/alertmanager/types';
import { KeyValueField } from '../../../api/templateApi';
@ -115,7 +116,7 @@ export const GenerateAlertDataModal = ({ isOpen, onDismiss, onAccept }: Props) =
variant="secondary"
disabled={!labelsOrAnnotationsAdded()}
>
Add alert data
<Trans i18nKey="alerting.generate-alert-data-modal.add-alert-data">Add alert data</Trans>
</Button>
</div>
</Stack>
@ -123,7 +124,12 @@ export const GenerateAlertDataModal = ({ isOpen, onDismiss, onAccept }: Props) =
<div className={styles.onSubmitWrapper} />
{alerts.length > 0 && (
<Stack direction="column" gap={1}>
<h5> Review alert data to add to the payload:</h5>
<h5>
<Trans i18nKey="alerting.generate-alert-data-modal.review-alert-payload">
{' '}
Review alert data to add to the payload:
</Trans>
</h5>
<pre className={styles.result} data-testid="payloadJSON">
{JSON.stringify(alerts, null, 2)}
</pre>
@ -132,7 +138,9 @@ export const GenerateAlertDataModal = ({ isOpen, onDismiss, onAccept }: Props) =
<div className={styles.onSubmitWrapper}>
<Modal.ButtonRow>
<Button onClick={onSubmit} disabled={alerts.length === 0} className={styles.onSubmitButton}>
Add alert data to payload
<Trans i18nKey="alerting.generate-alert-data-modal.add-alert-data-to-payload">
Add alert data to payload
</Trans>
</Button>
</Modal.ButtonRow>
</div>

@ -1,6 +1,7 @@
import { useFormContext } from 'react-hook-form';
import { Checkbox, Field } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { CommonSettingsComponentProps } from '../../../types/receiver-form';
@ -15,7 +16,10 @@ export const GrafanaCommonChannelSettings = ({
<Field>
<Checkbox
{...register(`${pathPrefix}disableResolveMessage`)}
label="Disable resolved message"
label={t(
'alerting.grafana-common-channel-settings.label-disable-resolved-message',
'Disable resolved message'
)}
description="Disable the resolve message [OK] that is sent when alerting state returns to false"
disabled={readOnly}
/>

@ -2,6 +2,7 @@ import { useMemo, useState } from 'react';
import { locationService } from '@grafana/runtime';
import { Alert, LoadingPlaceholder } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import {
useCreateContactPoint,
useUpdateContactPoint,
@ -142,7 +143,9 @@ export const GrafanaReceiverForm = ({ contactPoint, readOnly = false, editMode }
const isTestable = !readOnly;
if (isLoadingNotifiers || isLoadingOnCallIntegration) {
return <LoadingPlaceholder text="Loading notifiers..." />;
return (
<LoadingPlaceholder text={t('alerting.grafana-receiver-form.text-loading-notifiers', 'Loading notifiers...')} />
);
}
const notifiers: Notifier[] = grafanaNotifiers.map((n) => {
@ -158,7 +161,13 @@ export const GrafanaReceiverForm = ({ contactPoint, readOnly = false, editMode }
return (
<>
{hasOnCallError && (
<Alert severity="error" title="Loading OnCall integration failed">
<Alert
severity="error"
title={t(
'alerting.grafana-receiver-form.title-loading-on-call-integration-failed',
'Loading OnCall integration failed'
)}
>
Grafana OnCall plugin has been enabled in your Grafana instances but it is not reachable. Please check the
plugin configuration
</Alert>

@ -7,7 +7,7 @@ import { isFetchError } from '@grafana/runtime';
import { Alert, Button, Field, Input, LinkButton, Stack, useStyles2 } from '@grafana/ui';
import { useAppNotification } from 'app/core/copy/appNotification';
import { useCleanup } from 'app/core/hooks/useCleanup';
import { Trans } from 'app/core/internationalization';
import { Trans, t } from 'app/core/internationalization';
import { useValidateContactPoint } from 'app/features/alerting/unified/components/contact-points/useContactPoints';
import { ManagePermissions } from 'app/features/alerting/unified/components/permissions/ManagePermissions';
@ -128,7 +128,7 @@ export function ReceiverForm<R extends ChannelValues>({
return (
<FormProvider {...formAPI}>
{showDefaultRouteWarning && (
<Alert severity="warning" title="Attention">
<Alert severity="warning" title={t('alerting.receiver-form.title-attention', 'Attention')}>
Because there is no default policy configured yet, this contact point will automatically be set as default.
</Alert>
)}
@ -143,11 +143,19 @@ export function ReceiverForm<R extends ChannelValues>({
resource="receivers"
resourceId={contactPointId}
resourceName={initialValues?.name}
title="Manage contact point permissions"
title={t(
'alerting.receiver-form.title-manage-contact-point-permissions',
'Manage contact point permissions'
)}
/>
)}
</Stack>
<Field label="Name" invalid={!!errors.name} error={errors.name && errors.name.message} required>
<Field
label={t('alerting.receiver-form.label-name', 'Name')}
invalid={!!errors.name}
error={errors.name && errors.name.message}
required
>
<Input
readOnly={!isEditable}
id="name"
@ -159,7 +167,7 @@ export function ReceiverForm<R extends ChannelValues>({
},
})}
width={39}
placeholder="Name"
placeholder={t('alerting.receiver-form.name-placeholder-name', 'Name')}
/>
</Field>
{fields.map((field, index) => {
@ -204,7 +212,7 @@ export function ReceiverForm<R extends ChannelValues>({
variant="secondary"
onClick={() => append({ ...defaultItem, __id: String(Math.random()) })}
>
Add contact point integration
<Trans i18nKey="alerting.receiver-form.add-contact-point-integration">Add contact point integration</Trans>
</Button>
)}
<div className={styles.buttons}>
@ -212,10 +220,14 @@ export function ReceiverForm<R extends ChannelValues>({
<>
{isSubmitting && (
<Button disabled={true} icon="spinner" variant="primary">
Saving...
<Trans i18nKey="alerting.receiver-form.saving">Saving...</Trans>
</Button>
)}
{!isSubmitting && (
<Button type="submit">
<Trans i18nKey="alerting.receiver-form.save-contact-point">Save contact point</Trans>
</Button>
)}
{!isSubmitting && <Button type="submit">Save contact point</Button>}
</>
)}
<LinkButton

@ -4,6 +4,7 @@ import { FormProvider, useForm } from 'react-hook-form';
import { GrafanaTheme2 } from '@grafana/data';
import { Button, Label, Modal, RadioButtonGroup, useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { TestReceiversAlert } from 'app/plugins/datasource/alertmanager/types';
import { Annotations, Labels } from 'app/types/unified-alerting-dto';
@ -67,7 +68,9 @@ export const TestContactPointModal = ({ isOpen, onDismiss, onTest }: Props) => {
return (
<Modal onDismiss={onDismiss} isOpen={isOpen} title={'Test contact point'}>
<div className={styles.section}>
<Label>Notification message</Label>
<Label>
<Trans i18nKey="alerting.test-contact-point-modal.notification-message">Notification message</Trans>
</Label>
<RadioButtonGroup
options={notificationOptions}
value={notificationType}
@ -99,7 +102,9 @@ export const TestContactPointModal = ({ isOpen, onDismiss, onTest }: Props) => {
)}
<Modal.ButtonRow>
<Button type="submit">Send test notification</Button>
<Button type="submit">
<Trans i18nKey="alerting.test-contact-point-modal.send-test-notification">Send test notification</Trans>
</Button>
</Modal.ButtonRow>
</form>
</FormProvider>

@ -37,8 +37,12 @@ export const KeyValueMapInput = ({ value, onChange, readOnly = false }: Props) =
<table className={styles.table}>
<thead>
<tr>
<th>Name</th>
<th>Value</th>
<th>
<Trans i18nKey="alerting.key-value-map-input.name">Name</Trans>
</th>
<th>
<Trans i18nKey="alerting.key-value-map-input.value">Value</Trans>
</th>
{!readOnly && <th />}
</tr>
</thead>

@ -2,6 +2,7 @@ import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { Button, Input, useStyles2 } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { ActionIcon } from '../../../rules/ActionIcon';
@ -40,7 +41,7 @@ export const StringArrayInput = ({ value, onChange, readOnly = false }: Props) =
<ActionIcon
className={styles.deleteIcon}
icon="trash-alt"
tooltip="delete"
tooltip={t('alerting.string-array-input.tooltip-delete', 'delete')}
onClick={() => deleteItem(index)}
/>
)}
@ -55,7 +56,7 @@ export const StringArrayInput = ({ value, onChange, readOnly = false }: Props) =
size="sm"
onClick={() => onChange([...(value ?? []), ''])}
>
Add
<Trans i18nKey="alerting.string-array-input.add">Add</Trans>
</Button>
)}
</div>

@ -1,6 +1,7 @@
import { DeepMap, FieldError, useFormContext } from 'react-hook-form';
import { Button, useStyles2 } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { useControlledFieldArray } from 'app/features/alerting/unified/hooks/useControlledFieldArray';
import { NotificationChannelOption } from 'app/types';
@ -38,7 +39,7 @@ export const SubformArrayField = ({ option, pathPrefix, errors, defaultValues, r
<ActionIcon
data-testid={`${path}.${itemIndex}.delete-button`}
icon="trash-alt"
tooltip="delete"
tooltip={t('alerting.subform-array-field.tooltip-delete', 'delete')}
onClick={() => remove(itemIndex)}
className={styles.deleteIcon}
/>
@ -66,7 +67,7 @@ export const SubformArrayField = ({ option, pathPrefix, errors, defaultValues, r
size="sm"
onClick={() => append({ __id: String(Math.random()) })}
>
Add
<Trans i18nKey="alerting.subform-array-field.add">Add</Trans>
</Button>
)}
</CollapsibleSection>

@ -2,6 +2,7 @@ import { useState } from 'react';
import { DeepMap, FieldError, useFormContext } from 'react-hook-form';
import { Button, useStyles2 } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { NotificationChannelOption, NotificationChannelSecureFields } from 'app/types';
import { ActionIcon } from '../../../rules/ActionIcon';
@ -46,7 +47,7 @@ export const SubformField = ({
<ActionIcon
data-testid={`${name}.delete-button`}
icon="trash-alt"
tooltip="delete"
tooltip={t('alerting.subform-field.tooltip-delete', 'delete')}
onClick={() => setShow(false)}
className={styles.deleteIcon}
/>
@ -78,7 +79,7 @@ export const SubformField = ({
onClick={() => setShow(true)}
data-testid={`${name}.add-button`}
>
Add
<Trans i18nKey="alerting.subform-field.add">Add</Trans>
</Button>
)}
</div>

@ -4,6 +4,7 @@ import AutoSizer from 'react-virtualized-auto-sizer';
import { GrafanaTheme2 } from '@grafana/data';
import { Box, useStyles2 } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { useAlertmanager } from 'app/features/alerting/unified/state/AlertmanagerContext';
import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource';
@ -40,7 +41,9 @@ export function TemplateContentAndPreview({
return (
<div className={cx(className, styles.mainContainer)}>
<div className={styles.container}>
<EditorColumnHeader label="Template content" />
<EditorColumnHeader
label={t('alerting.template-content-and-preview.label-template-content', 'Template content')}
/>
<Box flex={1}>
<div className={styles.viewerContainer({ height: 400 })}>
<AutoSizer>
@ -60,7 +63,13 @@ export function TemplateContentAndPreview({
{isGrafanaAlertManager && (
<div className={styles.container}>
<EditorColumnHeader id={templatePreviewId} label="Preview with the default payload" />
<EditorColumnHeader
id={templatePreviewId}
label={t(
'alerting.template-content-and-preview.label-preview-with-the-default-payload',
'Preview with the default payload'
)}
/>
<Box flex={1}>
<div
role="presentation"

@ -16,7 +16,7 @@ import {
TextArea,
useStyles2,
} from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { Trans, t } from 'app/core/internationalization';
import {
trackEditInputWithTemplate,
trackUseCustomInputInTemplate,
@ -173,11 +173,19 @@ function TemplateSelector({ onSelect, onClose, option, valueInForm }: TemplateSe
}, [options, valueInForm]);
if (error) {
return <div>Error loading templates</div>;
return (
<div>
<Trans i18nKey="alerting.template-selector.error-loading-templates">Error loading templates</Trans>
</div>
);
}
if (isLoading || !data || !defaultTemplates) {
return <div>Loading...</div>;
return (
<div>
<Trans i18nKey="alerting.template-selector.loading">Loading...</Trans>
</div>
);
}
return (
@ -195,8 +203,14 @@ function TemplateSelector({ onSelect, onClose, option, valueInForm }: TemplateSe
<Stack direction="row" gap={1} alignItems="center">
<Select<Template>
data-testid="existing-templates-selector"
placeholder="Choose notification template"
aria-label="Choose notification template"
placeholder={t(
'alerting.template-selector.existing-templates-selector-placeholder-choose-notification-template',
'Choose notification template'
)}
aria-label={t(
'alerting.template-selector.existing-templates-selector-aria-label-choose-notification-template',
'Choose notification template'
)}
onChange={(value: SelectableValue<Template>, _) => {
setTemplate(value);
}}
@ -272,7 +286,7 @@ function OptionCustomfield({
</Label>
<TextArea
id={id}
label="Custom template"
label={t('alerting.option-customfield.label-custom-template', 'Custom template')}
placeholder={option.placeholder}
onChange={(e) => onCustomTemplateChange(e.currentTarget.value)}
defaultValue={initialValue}

@ -53,7 +53,11 @@ export const AlertRuleNameAndMetric = () => {
}
>
<Stack direction="column">
<Field label="Name" error={errors?.name?.message} invalid={!!errors.name?.message}>
<Field
label={t('alerting.alert-rule-name-and-metric.label-name', 'Name')}
error={errors?.name?.message}
invalid={!!errors.name?.message}
>
<Input
data-testid={selectors.components.AlertRules.ruleNameField}
id="name"
@ -64,12 +68,16 @@ export const AlertRuleNameAndMetric = () => {
? recordingRuleNameValidationPattern(RuleFormType.cloudRecording)
: undefined,
})}
aria-label="name"
aria-label={t('alerting.alert-rule-name-and-metric.aria-label-name', 'name')}
placeholder={`Give your ${namePlaceholder} a name`}
/>
</Field>
{isGrafanaRecordingRule && (
<Field label="Metric" error={errors?.metric?.message} invalid={!!errors.metric?.message}>
<Field
label={t('alerting.alert-rule-name-and-metric.label-metric', 'Metric')}
error={errors?.metric?.message}
invalid={!!errors.metric?.message}
>
<Input
id="metric"
width={38}
@ -77,7 +85,7 @@ export const AlertRuleNameAndMetric = () => {
required: { value: true, message: 'Must enter a metric name' },
pattern: recordingRuleNameValidationPattern(RuleFormType.grafanaRecording),
})}
aria-label="metric"
aria-label={t('alerting.alert-rule-name-and-metric.metric-aria-label-metric', 'metric')}
placeholder={`Give the name of the new recorded metric`}
/>
</Field>

@ -122,7 +122,7 @@ const AnnotationsStep = () => {
)}
</>
}
title="Annotations"
title={t('alerting.annotations-step.get-annotations-section-description.title-annotations', 'Annotations')}
/>
</Stack>
);
@ -190,7 +190,7 @@ const AnnotationsStep = () => {
<Button
type="button"
className={styles.deleteAnnotationButton}
aria-label="delete annotation"
aria-label={t('alerting.annotations-step.aria-label-delete-annotation', 'delete annotation')}
icon="trash-alt"
variant="secondary"
onClick={() => remove(index)}
@ -212,11 +212,11 @@ const AnnotationsStep = () => {
append({ key: '', value: '' });
}}
>
Add custom annotation
<Trans i18nKey="alerting.annotations-step.add-custom-annotation">Add custom annotation</Trans>
</Button>
{!selectedDashboard && (
<Button type="button" variant="secondary" icon="dashboard" onClick={() => setShowPanelSelector(true)}>
Link dashboard and panel
<Trans i18nKey="alerting.annotations-step.link-dashboard-and-panel">Link dashboard and panel</Trans>
</Button>
)}
</div>

@ -2,6 +2,7 @@ import { css } from '@emotion/css';
import { DataFrame, GrafanaTheme2 } from '@grafana/data';
import { Icon, TagList, Tooltip, useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { labelsToTags } from '../../utils/labels';
import { AlertStateTag } from '../rules/AlertStateTag';
@ -19,14 +20,22 @@ export function CloudAlertPreview({ preview }: CloudAlertPreviewProps) {
return (
<table className={styles.table}>
<caption>
<div>Alerts preview</div>
<div>
<Trans i18nKey="alerting.cloud-alert-preview.alerts-preview">Alerts preview</Trans>
</div>
<span>Preview based on the result of running the query for this moment.</span>
</caption>
<thead>
<tr>
<th>State</th>
<th>Labels</th>
<th>Info</th>
<th>
<Trans i18nKey="alerting.cloud-alert-preview.state">State</Trans>
</th>
<th>
<Trans i18nKey="alerting.cloud-alert-preview.labels">Labels</Trans>
</th>
<th>
<Trans i18nKey="alerting.cloud-alert-preview.info">Info</Trans>
</th>
</tr>
</thead>
<tbody>

@ -3,6 +3,7 @@ import { Controller, useFormContext } from 'react-hook-form';
import { GrafanaTheme2 } from '@grafana/data';
import { Field, Input, Select, useStyles2 } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { RuleFormType, RuleFormValues } from '../../types/rule-form';
import { timeOptions } from '../../utils/time';
@ -24,9 +25,12 @@ export const CloudEvaluationBehavior = () => {
const dataSourceName = watch('dataSourceName');
return (
<RuleEditorSection stepNo={3} title="Set evaluation behavior">
<RuleEditorSection
stepNo={3}
title={t('alerting.cloud-evaluation-behavior.title-set-evaluation-behavior', 'Set evaluation behavior')}
>
<Field
label="Pending period"
label={t('alerting.cloud-evaluation-behavior.label-pending-period', 'Pending period')}
description='Period during which the threshold condition must be met to trigger an alert. Selecting "None" triggers the alert immediately once the condition is met.'
>
<div className={styles.flexRow}>

@ -2,6 +2,7 @@ import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { Input, useStyles2 } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
interface CustomAnnotationHeaderFieldProps {
field: { onChange: () => void; onBlur: () => void; value: string; name: string };
@ -12,9 +13,16 @@ const CustomAnnotationHeaderField = ({ field }: CustomAnnotationHeaderFieldProps
return (
<div>
<span className={styles.annotationTitle}>Custom annotation name and content</span>
<span className={styles.annotationTitle}>
<Trans i18nKey="alerting.custom-annotation-header-field.custom-annotation-name-and-content">
Custom annotation name and content
</Trans>
</span>
<Input
placeholder="Enter custom annotation name..."
placeholder={t(
'alerting.custom-annotation-header-field.placeholder-enter-custom-annotation-name',
'Enter custom annotation name...'
)}
width={18}
{...field}
className={styles.customAnnotationInput}

@ -17,7 +17,7 @@ import {
clearButtonStyles,
useStyles2,
} from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { Trans, t } from 'app/core/internationalization';
import { DashboardModel } from '../../../../dashboard/state/DashboardModel';
import { dashboardApi } from '../../api/dashboardApi';
@ -168,7 +168,7 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis
return (
<Modal
title="Select dashboard and panel"
title={t('alerting.dashboard-picker.title-select-dashboard-and-panel', 'Select dashboard and panel')}
closeOnEscape
isOpen={isOpen}
onDismiss={onDismiss}
@ -177,7 +177,13 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis
>
{/* This alert shows if the selected dashboard is not found in the first page of dashboards */}
{!selectedDashboardIsInPageResult && dashboardUid && dashboardModel && (
<Alert title="Current selection" severity="info" topSpacing={0} bottomSpacing={1} className={styles.modalAlert}>
<Alert
title={t('alerting.dashboard-picker.title-current-selection', 'Current selection')}
severity="info"
topSpacing={0}
bottomSpacing={1}
className={styles.modalAlert}
>
<div>
Dashboard: {dashboardModel.title} ({dashboardModel.uid}) in folder{' '}
{dashboardModel.meta?.folderTitle ?? 'Dashboards'}
@ -193,15 +199,23 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis
<FilterInput
value={dashboardFilter}
onChange={setDashboardFilter}
title="Search dashboard"
placeholder="Search dashboard"
title={t('alerting.dashboard-picker.title-search-dashboard', 'Search dashboard')}
placeholder={t('alerting.dashboard-picker.placeholder-search-dashboard', 'Search dashboard')}
autoFocus
/>
<FilterInput value={panelFilter} onChange={setPanelFilter} title="Search panel" placeholder="Search panel" />
<FilterInput
value={panelFilter}
onChange={setPanelFilter}
title={t('alerting.dashboard-picker.title-search-panel', 'Search panel')}
placeholder={t('alerting.dashboard-picker.placeholder-search-panel', 'Search panel')}
/>
<div className={styles.column}>
{isDashSearchFetching && (
<LoadingPlaceholder text="Loading dashboards..." className={styles.loadingPlaceholder} />
<LoadingPlaceholder
text={t('alerting.dashboard-picker.text-loading-dashboards', 'Loading dashboards...')}
className={styles.loadingPlaceholder}
/>
)}
{!isDashSearchFetching && (
@ -224,11 +238,18 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis
<div className={styles.column}>
{!selectedDashboardUid && !isDashboardFetching && (
<div className={styles.selectDashboardPlaceholder}>
<div>Select a dashboard to get a list of available panels</div>
<div>
<Trans i18nKey="alerting.dashboard-picker.select-dashboard-available-panels">
Select a dashboard to get a list of available panels
</Trans>
</div>
</div>
)}
{isDashboardFetching && (
<LoadingPlaceholder text="Loading dashboard..." className={styles.loadingPlaceholder} />
<LoadingPlaceholder
text={t('alerting.dashboard-picker.text-loading-dashboard', 'Loading dashboard...')}
className={styles.loadingPlaceholder}
/>
)}
{selectedDashboardUid && !isDashboardFetching && (
@ -256,7 +277,7 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis
}
}}
>
Confirm
<Trans i18nKey="alerting.dashboard-picker.confirm">Confirm</Trans>
</Button>
</Modal.ButtonRow>
</Modal>

@ -8,6 +8,7 @@ import { PromQuery } from '@grafana/prometheus';
import { getDataSourceSrv } from '@grafana/runtime';
import { DataQuery } from '@grafana/schema';
import { Alert, Button, useStyles2 } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { LokiQuery } from 'app/plugins/datasource/loki/types';
import { CloudAlertPreview } from './CloudAlertPreview';
@ -91,11 +92,17 @@ export const ExpressionEditor = ({
onClick={onRunQueriesClick}
disabled={alertPreview?.data.state === LoadingState.Loading}
>
Preview alerts
<Trans i18nKey="alerting.expression-editor.preview-alerts">Preview alerts</Trans>
</Button>
{previewLoaded && !previewHasAlerts && (
<Alert title="Alerts preview" severity="info" className={styles.previewAlert}>
There are no firing alerts for your query.
<Alert
title={t('alerting.expression-editor.title-alerts-preview', 'Alerts preview')}
severity="info"
className={styles.previewAlert}
>
<Trans i18nKey="alerting.expression-editor.there-firing-alerts-query">
There are no firing alerts for your query.
</Trans>
</Alert>
)}
{previewHasAlerts && <CloudAlertPreview preview={previewDataFrame} />}

@ -199,7 +199,7 @@ export function GrafanaEvaluationBehaviorStep({
// TODO remove "and alert condition" for recording rules
<RuleEditorSection
stepNo={step}
title="Set evaluation behavior"
title={t('alerting.grafana-evaluation-behavior-step.title-set-evaluation-behavior', 'Set evaluation behavior')}
description={getDescription(isGrafanaRecordingRule)}
>
<Stack direction="column" justify-content="flex-start" align-items="flex-start">
@ -252,7 +252,9 @@ export function GrafanaEvaluationBehaviorStep({
</Field>
</div>
<Box gap={1} display={'flex'} alignItems={'center'}>
<Text color="secondary">or</Text>
<Text color="secondary">
<Trans i18nKey="alerting.grafana-evaluation-behavior-step.or">or</Trans>
</Text>
<Button
onClick={onOpenEvaluationGroupCreationModal}
type="button"
@ -322,7 +324,10 @@ export function GrafanaEvaluationBehaviorStep({
<CollapseToggle
isCollapsed={!showErrorHandling}
onToggle={(collapsed) => setShowErrorHandling(!collapsed)}
text="Configure no data and error handling"
text={t(
'alerting.grafana-evaluation-behavior-step.text-configure-no-data-and-error-handling',
'Configure no data and error handling'
)}
/>
{showErrorHandling && (
<>
@ -451,7 +456,7 @@ function EvaluationGroupCreationModal({
className={styles.formInput}
autoFocus={true}
id={evaluationGroupNameId}
placeholder="Enter a name"
placeholder={t('alerting.evaluation-group-creation-modal.placeholder-enter-a-name', 'Enter a name')}
{...register('group', { required: { value: true, message: 'Required.' } })}
/>
</Field>
@ -459,7 +464,13 @@ function EvaluationGroupCreationModal({
<Field
error={formState.errors.evaluateEvery?.message}
label={
<Label htmlFor={evaluateEveryId} description="How often all rules in the group are evaluated.">
<Label
htmlFor={evaluateEveryId}
description={t(
'alerting.evaluation-group-creation-modal.description-often-rules-group-evaluated',
'How often all rules in the group are evaluated.'
)}
>
<Trans i18nKey="alerting.rule-form.evaluation.group.interval">Evaluation interval</Trans>
</Label>
}
@ -555,7 +566,10 @@ function NeedHelpInfoForConfigureNoDataError() {
contentText="These settings can help mitigate temporary data source issues, preventing alerts from unintentionally firing due to lack of data, errors, or timeouts."
externalLink={docsLink}
linkText={`Read more about this option`}
title="Configure no data and error handling"
title={t(
'alerting.need-help-info-for-configure-no-data-error.title-configure-no-data-and-error-handling',
'Configure no data and error handling'
)}
/>
</Stack>
);
@ -601,7 +615,7 @@ function getDescription(isGrafanaRecordingRule: boolean) {
}
externalLink={docsLink}
linkText={`Read about evaluation and alert states`}
title="Alert rule evaluation"
title={t('alerting.get-description.title-alert-rule-evaluation', 'Alert rule evaluation')}
/>
</Stack>
);

@ -57,7 +57,11 @@ export function GrafanaFolderAndLabelsStep() {
}
return (
<RuleEditorSection stepNo={3} title="Add folder and labels" description={<SectionDescription />}>
<RuleEditorSection
stepNo={3}
title={t('alerting.grafana-folder-and-labels-step.title-add-folder-and-labels', 'Add folder and labels')}
description={<SectionDescription />}
>
<Stack direction="column" justify-content="flex-start" align-items="flex-start">
<FolderSelector />
<LabelsFieldInForm onEditClick={() => setShowLabelsEditor(true)} />

@ -4,6 +4,7 @@ import { Controller, useFormContext } from 'react-hook-form';
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
import { Field, VirtualizedSelect, useStyles2 } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { RuleFormValues } from '../../types/rule-form';
@ -44,7 +45,7 @@ export const GroupAndNamespaceFields = ({ rulesSourceName }: Props) => {
<div className={style.flexRow}>
<Field
data-testid="namespace-picker"
label="Namespace"
label={t('alerting.group-and-namespace-fields.namespace-picker-label-namespace', 'Namespace')}
// Disable translations as we don't intend to use this dropdown longterm,
// so avoiding us adding translations for the sake of it
// eslint-disable-next-line @grafana/no-untranslated-strings
@ -77,7 +78,7 @@ export const GroupAndNamespaceFields = ({ rulesSourceName }: Props) => {
</Field>
<Field
data-testid="group-picker"
label="Group"
label={t('alerting.group-and-namespace-fields.group-picker-label-group', 'Group')}
// Disable translations as we don't intend to use this dropdown longterm,
// so avoiding us adding translations for the sake of it
// eslint-disable-next-line @grafana/no-untranslated-strings

@ -2,6 +2,7 @@ import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { Icon, Stack, Text, Toggletip, useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
interface NeedHelpInfoProps {
contentText: string | JSX.Element;
@ -39,7 +40,7 @@ export function NeedHelpInfo({ contentText, externalLink, linkText, title = 'Nee
<Stack direction="row" alignItems="center" gap={0.5}>
<Icon name="question-circle" size="sm" />
<Text variant="bodySmall" color="primary">
Need help?
<Trans i18nKey="alerting.need-help-info.need-help">Need help?</Trans>
</Text>
</Stack>
</div>

@ -5,6 +5,7 @@ import { useFormContext } from 'react-hook-form';
import { GrafanaTheme2 } from '@grafana/data';
import { config } from '@grafana/runtime';
import { Icon, RadioButtonGroup, Stack, Text, useStyles2 } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types';
import { alertmanagerApi } from '../../api/alertmanagerApi';
@ -91,7 +92,9 @@ export const NotificationsStep = ({ alertUid }: NotificationsStepProps) => {
<Stack direction="row" gap={0.5} alignItems="center">
{isRecordingRuleByType(type) ? (
<Text variant="bodySmall" color="secondary">
Add labels to help you better manage your recording rules.
<Trans i18nKey="alerting.notifications-step.labels-better-manage-recording-rules">
Add labels to help you better manage your recording rules.
</Trans>
</Text>
) : (
shouldAllowSimplifiedRouting && (
@ -118,7 +121,9 @@ export const NotificationsStep = ({ alertUid }: NotificationsStepProps) => {
)}
{shouldAllowSimplifiedRouting && (
<div className={styles.configureNotifications}>
<Text element="h5">Recipient</Text>
<Text element="h5">
<Trans i18nKey="alerting.notifications-step.recipient">Recipient</Trans>
</Text>
</div>
)}
{shouldAllowSimplifiedRouting ? ( // when simplified routing is enabled and is grafana rule
@ -258,7 +263,7 @@ function NeedHelpInfoForNotificationPolicy() {
</Stack>
</Stack>
}
title="Notification routing"
title={t('alerting.need-help-info-for-notification-policy.title-notification-routing', 'Notification routing')}
/>
);
}
@ -279,7 +284,10 @@ function NeedHelpInfoForContactpoint() {
}
externalLink="https://grafana.com/docs/grafana/latest/alerting/fundamentals/notifications/"
linkText="Read more about notifications"
title="Notify by selecting a contact point"
title={t(
'alerting.need-help-info-for-contactpoint.title-notify-by-selecting-a-contact-point',
'Notify by selecting a contact point'
)}
/>
);
}

@ -8,6 +8,7 @@ import { takeWhile } from 'rxjs/operators';
import { GrafanaTheme2, LoadingState, dateTimeFormatISO } from '@grafana/data';
import { getDataSourceSrv } from '@grafana/runtime';
import { Alert, Button, Stack, useStyles2 } from '@grafana/ui';
import { Trans, t } from 'app/core/internationalization';
import { previewAlertRule } from '../../api/preview';
import { useAlertQueriesStatus } from '../../hooks/useAlertQueriesStatus';
@ -37,11 +38,14 @@ export function PreviewRule(): React.ReactElement | null {
<Stack>
{allDataSourcesAvailable && (
<Button disabled={!isPreviewAvailable} type="button" variant="primary" onClick={onPreview}>
Preview alerts
<Trans i18nKey="alerting.preview-rule.preview-alerts">Preview alerts</Trans>
</Button>
)}
{!allDataSourcesAvailable && (
<Alert title="Preview is not available" severity="warning">
<Alert
title={t('alerting.preview-rule.title-preview-is-not-available', 'Preview is not available')}
severity="warning"
>
Cannot display the query preview. Some of the data sources used in the queries are not available.
</Alert>
)}

@ -5,6 +5,7 @@ import AutoSizer from 'react-virtualized-auto-sizer';
import { FieldConfigSource, FieldMatcherID, GrafanaTheme2, LoadingState } from '@grafana/data';
import { PanelRenderer } from '@grafana/runtime';
import { TableCellDisplayMode, useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { PreviewRuleResponse } from '../../types/preview';
import { RuleFormType } from '../../types/rule-form';
@ -36,7 +37,9 @@ export function PreviewRuleResult(props: Props): React.ReactElement | null {
if (data.state === LoadingState.Loading) {
return (
<div className={styles.container}>
<span>Loading preview...</span>
<span>
<Trans i18nKey="alerting.preview-rule-result.loading-preview">Loading preview...</Trans>
</span>
</div>
);
}

@ -3,6 +3,7 @@ import { useState } from 'react';
import { GrafanaTheme2, RelativeTimeRange, dateTime, getDefaultRelativeTimeRange, rangeUtil } from '@grafana/data';
import { Icon, InlineField, RelativeTimeRangePicker, Toggletip, clearButtonStyles, useStyles2 } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { AlertQuery } from 'app/types/unified-alerting-dto';
import { AlertQueryOptions, MaxDataPointsOption, MinIntervalOption } from './QueryWrapper';
@ -34,7 +35,7 @@ export const QueryOptions = ({
content={
<div className={styles.queryOptions}>
{onChangeTimeRange && (
<InlineField label="Time Range">
<InlineField label={t('alerting.query-options.label-time-range', 'Time Range')}>
<RelativeTimeRangePicker
timeRange={query.relativeTimeRange ?? getDefaultRelativeTimeRange()}
onChange={(range) => onChangeTimeRange(range, index)}

@ -14,6 +14,7 @@ import { getDataSourceSrv } from '@grafana/runtime';
import { DataQuery } from '@grafana/schema';
import { Button, Card, Icon, Stack } from '@grafana/ui';
import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow';
import { Trans } from 'app/core/internationalization';
import { isExpressionQuery } from 'app/features/expressions/guards';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto';
@ -283,7 +284,11 @@ const DatasourceNotFound = ({ index, onUpdateDatasource, onRemoveQuery, model }:
<EmptyQueryWrapper>
<QueryOperationRow title={refId} draggable index={index} id={refId} isOpen collapsable={false}>
<Card>
<Card.Heading>This datasource has been removed</Card.Heading>
<Card.Heading>
<Trans i18nKey="alerting.datasource-not-found.this-datasource-has-been-removed">
This datasource has been removed
</Trans>
</Card.Heading>
<Card.Description>
The datasource for this query was not found, it was either removed or is not installed correctly.
</Card.Description>
@ -292,10 +297,10 @@ const DatasourceNotFound = ({ index, onUpdateDatasource, onRemoveQuery, model }:
</Card.Figure>
<Card.Actions>
<Button key="update" variant="secondary" onClick={handleUpdateDatasource}>
Update datasource
<Trans i18nKey="alerting.datasource-not-found.update-datasource">Update datasource</Trans>
</Button>
<Button key="remove" variant="destructive" onClick={onRemoveQuery}>
Remove query
<Trans i18nKey="alerting.datasource-not-found.remove-query">Remove query</Trans>
</Button>
</Card.Actions>
<Card.SecondaryActions>
@ -306,7 +311,7 @@ const DatasourceNotFound = ({ index, onUpdateDatasource, onRemoveQuery, model }:
fill="text"
size="sm"
>
Show details
<Trans i18nKey="alerting.datasource-not-found.show-details">Show details</Trans>
</Button>
</Card.SecondaryActions>
</Card>

@ -19,6 +19,7 @@ import {
import { config } from '@grafana/runtime';
import { DataQuery } from '@grafana/schema';
import { GraphThresholdsStyleMode, Icon, InlineField, Input, Stack, Tooltip, useStyles2 } from '@grafana/ui';
import { t } from 'app/core/internationalization';
import { logInfo } from 'app/features/alerting/unified/Analytics';
import { QueryEditorRow } from 'app/features/query/components/QueryEditorRow';
import { AlertDataQuery, AlertQuery } from 'app/types/unified-alerting-dto';
@ -254,7 +255,7 @@ export function MaxDataPointsOption({
return (
<InlineField
labelWidth={24}
label="Max data points"
label={t('alerting.max-data-points-option.label-max-data-points', 'Max data points')}
tooltip="The maximum data points per series. Used directly by some data sources and used in calculation of auto interval. With streaming data this value is used for the rolling buffer."
>
<Input
@ -290,7 +291,7 @@ export function MinIntervalOption({
return (
<InlineField
label="Interval"
label={t('alerting.min-interval-option.label-interval', 'Interval')}
labelWidth={24}
tooltip={
<>

@ -1,5 +1,7 @@
import { useFormContext } from 'react-hook-form';
import { t } from 'app/core/internationalization';
import { RuleFormValues } from '../../types/rule-form';
import { GroupAndNamespaceFields } from './GroupAndNamespaceFields';
@ -18,7 +20,10 @@ export function RecordingRulesNameSpaceAndGroupStep() {
<RuleEditorSection
stepNo={3}
title={'Add namespace and group'}
description="Select the Namespace and Group for your recording rule."
description={t(
'alerting.recording-rules-name-space-and-group-step.description-select-namespace-group-recording',
'Select the Namespace and Group for your recording rule.'
)}
>
<GroupAndNamespaceFields rulesSourceName={dataSourceName} />
</RuleEditorSection>

@ -5,6 +5,7 @@ import { ReactElement } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { FieldSet, InlineSwitch, Stack, Text, useStyles2 } from '@grafana/ui';
import { t } from 'app/core/internationalization';
export interface RuleEditorSectionProps {
title: string;
@ -44,7 +45,7 @@ export const RuleEditorSection = ({
onChange={(event) => {
switchMode.setAdvancedMode(event.currentTarget.checked);
}}
label="Advanced options"
label={t('alerting.rule-editor-section.label-advanced-options', 'Advanced options')}
showLabel
transparent
className={styles.reverse}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save