Internationalisation: Mark up @grafana/ui for translations (#94951)

* ensure grafana-ui is translated

* last couple

* improve name
This commit is contained in:
Ashley Harrison
2024-10-21 10:11:12 +01:00
committed by GitHub
parent e0446fc557
commit cbce49b163
37 changed files with 392 additions and 257 deletions
@@ -4,6 +4,7 @@ import { useCallback, useMemo, useRef } from 'react';
import * as React from 'react';
import { useStyles2 } from '../../themes';
import { Trans } from '../../utils/i18n';
import { Field, FieldProps } from '../Forms/Field';
import { InlineToast } from '../InlineToast/InlineToast';
@@ -106,12 +107,14 @@ export function AutoSaveField<T = string>(props: Props<T>) {
</Field>
{fieldState.isLoading && (
<InlineToast referenceElement={fieldRef.current} placement="right">
Saving <EllipsisAnimated />
<Trans i18nKey="grafana-ui.auto-save-field.saving">
Saving <EllipsisAnimated />
</Trans>
</InlineToast>
)}
{fieldState.showSuccess && (
<InlineToast suffixIcon={'check'} referenceElement={fieldRef.current} placement="right">
Saved!
<Trans i18nKey="grafana-ui.auto-save-field.saved">Saved!</Trans>
</InlineToast>
)}
</>
@@ -9,9 +9,9 @@ export const EllipsisAnimated = memo(() => {
const styles = useStyles2(getStyles);
return (
<div className={styles.ellipsis}>
<span className={styles.firstDot}>.</span>
<span className={styles.secondDot}>.</span>
<span className={styles.thirdDot}>.</span>
<span className={styles.firstDot}>{'.'}</span>
<span className={styles.secondDot}>{'.'}</span>
<span className={styles.thirdDot}>{'.'}</span>
</div>
);
});
@@ -7,6 +7,7 @@ import { GrafanaTheme2, colorManipulator } from '@grafana/data';
import { stylesFactory, withTheme2 } from '../../themes';
import { Themeable2 } from '../../types/theme';
import { Trans } from '../../utils/i18n';
import { PopoverContentProps } from '../Tooltip';
import { NamedColorsPalette } from './NamedColorsPalette';
@@ -122,10 +123,10 @@ class UnThemedColorPickerPopover<T extends CustomPickersDescriptor> extends Comp
<div tabIndex={-1} className={styles.colorPickerPopover}>
<div className={styles.colorPickerPopoverTabs}>
<button className={this.getTabClassName('palette')} onClick={this.onTabChange('palette')} type="button">
Colors
<Trans i18nKey="grafana-ui.color-picker-popover.palette-tab">Colors</Trans>
</button>
<button className={this.getTabClassName('spectrum')} onClick={this.onTabChange('spectrum')} type="button">
Custom
<Trans i18nKey="grafana-ui.color-picker-popover.spectrum-tab">Custom</Trans>
</button>
{this.renderCustomPickerTabs()}
</div>
@@ -6,6 +6,7 @@ import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '../../themes';
import { ComponentSize } from '../../types/size';
import { Trans } from '../../utils/i18n';
import { Button, ButtonVariant } from '../Button';
export interface Props {
@@ -115,7 +116,7 @@ export const ConfirmButton = ({
{confirmText}
</Button>
<Button size={size} fill="text" onClick={onClickCancel}>
Cancel
<Trans i18nKey="grafana-ui.confirm-button.cancel">Cancel</Trans>
</Button>
</span>
</div>
@@ -5,6 +5,7 @@ import { VariableSuggestion, GrafanaTheme2, DataLink } from '@grafana/data';
import { useStyles2 } from '../../themes/index';
import { isCompactUrl } from '../../utils/dataLinks';
import { Trans } from '../../utils/i18n';
import { Field } from '../Forms/Field';
import { Input } from '../Input/Input';
import { Switch } from '../Switch/Switch';
@@ -64,8 +65,10 @@ export const DataLinkEditor = memo(({ index, value, onChange, suggestions, isLas
{isLast && (
<div className={styles.infoText}>
With data links you can reference data variables like series name, labels and values. Type CMD+Space,
CTRL+Space, or $ to open variable suggestions.
<Trans i18nKey="grafana-ui.data-link-editor.info">
With data links you can reference data variables like series name, labels and values. Type CMD+Space,
CTRL+Space, or $ to open variable suggestions.
</Trans>
</div>
)}
</div>
@@ -2,6 +2,7 @@ import { useState } from 'react';
import { DataFrame, DataLink, VariableSuggestion } from '@grafana/data';
import { Trans } from '../../../utils/i18n';
import { Button } from '../../Button';
import { Modal } from '../../Modal/Modal';
import { DataLinkEditor } from '../DataLinkEditor';
@@ -36,14 +37,14 @@ export const DataLinkEditorModalContent = ({
/>
<Modal.ButtonRow>
<Button variant="secondary" onClick={() => onCancel(index)} fill="outline">
Cancel
<Trans i18nKey="grafana-ui.data-link-editor-modal.cancel">Cancel</Trans>
</Button>
<Button
onClick={() => {
onSave(index, dirtyLink);
}}
>
Save
<Trans i18nKey="grafana-ui.data-link-editor-modal.save">Save</Trans>
</Button>
</Modal.ButtonRow>
</>
@@ -6,6 +6,7 @@ import { ReactNode, useEffect, useState } from 'react';
import { DataFrame, DataLink, GrafanaTheme2, VariableSuggestion } from '@grafana/data';
import { useStyles2 } from '../../../themes';
import { Trans } from '../../../utils/i18n';
import { Button } from '../../Button';
import { Modal } from '../../Modal/Modal';
@@ -94,7 +95,9 @@ export const DataLinksInlineEditor = ({
if (showOneClick) {
return (
<div className={styles.oneClickOverlay} key={key}>
<span className={styles.oneClickSpan}>One-click link</span>
<span className={styles.oneClickSpan}>
<Trans i18nKey="grafana-ui.data-links-inline-editor.one-click-link">One-click link</Trans>
</span>
{linkJSX}
</div>
);
@@ -159,7 +162,7 @@ export const DataLinksInlineEditor = ({
)}
<Button size="sm" icon="plus" onClick={onDataLinkAdd} variant="secondary" className={styles.button}>
Add link
<Trans i18nKey="grafana-ui.data-links-inline-editor.add-link">Add link</Trans>
</Button>
</>
);
@@ -5,6 +5,7 @@ import { DataFrame, DataLink, GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '../../../themes';
import { isCompactUrl } from '../../../utils';
import { Trans } from '../../../utils/i18n';
import { FieldValidationMessage } from '../../Forms/FieldValidationMessage';
import { Icon } from '../../Icon/Icon';
import { IconButton } from '../../IconButton/IconButton';
@@ -51,7 +52,9 @@ export const DataLinksListItem = ({ link, onEdit, onRemove, index, itemKey }: Da
</div>
{isCompactExploreUrl && (
<FieldValidationMessage>
Explore data link may not work in the future. Please edit.
<Trans i18nKey="grafana-ui.data-links-list-item.compact-explore-disclaimer">
Explore data link may not work in the future. Please edit.
</Trans>
</FieldValidationMessage>
)}
</div>
@@ -3,6 +3,7 @@ import { css } from '@emotion/css';
import { Field, GrafanaTheme2, LinkModel } from '@grafana/data';
import { useStyles2 } from '../../themes';
import { Trans } from '../../utils/i18n';
import { Icon } from '../Icon/Icon';
import { DataLinkButton } from './DataLinkButton';
@@ -30,7 +31,9 @@ export function FieldLinkList({ links }: Props) {
return <DataLinkButton key={i} link={link} />;
})}
<div className={styles.wrapper}>
<p className={styles.externalLinksHeading}>External links</p>
<p className={styles.externalLinksHeading}>
<Trans i18nKey="grafana-ui.field-link-list.external-links-heading">External links</Trans>
</p>
{externalLinks.map((link, i) => (
<a key={i} href={link.href} target={link.target} className={styles.externalLink}>
<Icon name="external-link-alt" />
@@ -1,6 +1,7 @@
import { DataSourceJsonData, DataSourcePluginOptionsEditorProps } from '@grafana/data';
import { InlineSwitch } from '../../components/Switch/Switch';
import { Trans } from '../../utils/i18n';
import { InlineField } from '../Forms/InlineField';
export interface Props<T extends DataSourceJsonData>
@@ -13,7 +14,9 @@ export interface AlertingConfig extends DataSourceJsonData {
export function AlertingSettings<T extends AlertingConfig>({ options, onOptionsChange }: Props<T>): JSX.Element {
return (
<>
<h3 className="page-heading">Alerting</h3>
<h3 className="page-heading">
<Trans i18nKey="grafana-ui.data-source-settings.alerting-settings-heading">Alerting</Trans>
</h3>
<div className="gf-form-group">
<div className="gf-form-inline">
<div className="gf-form">
@@ -1,5 +1,6 @@
import { ChangeEvent, MouseEvent } from 'react';
import { Trans } from '../../utils/i18n';
import { Button } from '../Button';
import { InlineField } from '../Forms/InlineField';
import { InlineFieldRow } from '../Forms/InlineFieldRow';
@@ -27,7 +28,7 @@ export const CertificationKey = ({ hasCert, label, onChange, onClick, placeholde
</InlineField>
{hasCert && (
<Button variant="secondary" onClick={onClick} style={{ marginLeft: 4 }}>
Reset
<Trans i18nKey="grafana-ui.data-source-settings.cert-key-reset">Reset</Trans>
</Button>
)}
</InlineFieldRow>
@@ -5,6 +5,7 @@ import { PureComponent } from 'react';
import { DataSourceSettings } from '@grafana/data';
import { useStyles2 } from '../../themes';
import { Trans } from '../../utils/i18n';
import { Button } from '../Button';
import { FormField } from '../FormField/FormField';
import { Icon } from '../Icon/Icon';
@@ -199,7 +200,9 @@ export class CustomHeadersSettings extends PureComponent<Props, State> {
return (
<div className={'gf-form-group'}>
<div className="gf-form">
<h6>Custom HTTP Headers</h6>
<h6>
<Trans i18nKey="grafana-ui.data-source-settings.custom-headers-title">Custom HTTP Headers</Trans>
</h6>
</div>
<div>
{headers.map((header, i) => (
@@ -225,7 +228,7 @@ export class CustomHeadersSettings extends PureComponent<Props, State> {
this.onHeaderAdd();
}}
>
Add header
<Trans i18nKey="grafana-ui.data-source-settings.custom-headers-add">Add header</Trans>
</Button>
</div>
)}
@@ -5,6 +5,7 @@ import { SelectableValue } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { useTheme2 } from '../../themes';
import { Trans } from '../../utils/i18n';
import { Alert } from '../Alert/Alert';
import { FormField } from '../FormField/FormField';
import { InlineFormLabel } from '../FormLabel/FormLabel';
@@ -43,23 +44,33 @@ const HttpAccessHelp = () => {
return (
<Alert severity="info" title="" topSpacing={3}>
<p>
Access mode controls how requests to the data source will be handled.
<strong>
&nbsp;<i>Server</i>
</strong>{' '}
should be the preferred way if nothing else is stated.
<Trans i18nKey="grafana-ui.data-source-http-settings.access-help-details">
Access mode controls how requests to the data source will be handled.
<strong>
&nbsp;<i>Server</i>
</strong>{' '}
should be the preferred way if nothing else is stated.
</Trans>
</p>
<Text weight="medium">Server access mode (Default):</Text>
<Trans i18nKey="grafana-ui.data-source-http-settings.server-mode-title">
<Text weight="medium">Server access mode (Default):</Text>
</Trans>
<p>
All requests will be made from the browser to Grafana backend/server which in turn will forward the requests to
the data source and by that circumvent possible Cross-Origin Resource Sharing (CORS) requirements. The URL needs
to be accessible from the grafana backend/server if you select this access mode.
<Trans i18nKey="grafana-ui.data-source-http-settings.server-mode-description">
All requests will be made from the browser to Grafana backend/server which in turn will forward the requests
to the data source and by that circumvent possible Cross-Origin Resource Sharing (CORS) requirements. The URL
needs to be accessible from the grafana backend/server if you select this access mode.
</Trans>
</p>
<Text weight="medium">Browser access mode:</Text>
<Trans i18nKey="grafana-ui.data-source-http-settings.browser-mode-title">
<Text weight="medium">Browser access mode:</Text>
</Trans>
<p>
All requests will be made from the browser directly to the data source and may be subject to Cross-Origin
Resource Sharing (CORS) requirements. The URL needs to be accessible from the browser if you select this access
mode.
<Trans i18nKey="grafana-ui.data-source-http-settings.browser-mode-description">
All requests will be made from the browser directly to the data source and may be subject to Cross-Origin
Resource Sharing (CORS) requirements. The URL needs to be accessible from the browser if you select this
access mode.
</Trans>
</p>
</Alert>
);
@@ -119,7 +130,9 @@ export const DataSourceHttpSettings = (props: HttpSettingsProps) => {
case 'direct':
urlTooltip = (
<>
Your access method is <em>Browser</em>, this means the URL needs to be accessible from the browser.
<Trans i18nKey="grafana-ui.data-source-http-settings.direct-url-tooltip">
Your access method is <em>Browser</em>, this means the URL needs to be accessible from the browser.
</Trans>
{urlDocs}
</>
);
@@ -127,14 +140,23 @@ export const DataSourceHttpSettings = (props: HttpSettingsProps) => {
case 'proxy':
urlTooltip = (
<>
Your access method is <em>Server</em>, this means the URL needs to be accessible from the grafana
backend/server.
<Trans i18nKey="grafana-ui.data-source-http-settings.proxy-url-tooltip">
Your access method is <em>Server</em>, this means the URL needs to be accessible from the grafana
backend/server.
</Trans>
{urlDocs}
</>
);
break;
default:
urlTooltip = <>Specify a complete HTTP URL (for example http://your_server:8080) {urlDocs}</>;
urlTooltip = (
<>
<Trans i18nKey="grafana-ui.data-source-http-settings.default-url-tooltip">
Specify a complete HTTP URL (for example http://your_server:8080)
</Trans>
{urlDocs}
</>
);
}
const accessSelect = (
@@ -175,7 +197,9 @@ export const DataSourceHttpSettings = (props: HttpSettingsProps) => {
return (
<div className="gf-form-group">
<>
<h3 className="page-heading">HTTP</h3>
<h3 className="page-heading">
<Trans i18nKey="grafana-ui.data-source-http-settings.heading">HTTP</Trans>
</h3>
<div className="gf-form-group">
<div className="gf-form">
<FormField
@@ -199,8 +223,10 @@ export const DataSourceHttpSettings = (props: HttpSettingsProps) => {
className="gf-form-label query-keyword pointer"
onClick={() => setIsAccessHelpVisible((isVisible) => !isVisible)}
>
Help&nbsp;
<Icon name={isAccessHelpVisible ? 'angle-down' : 'angle-right'} style={{ marginBottom: 0 }} />
<Trans i18nKey="grafana-ui.data-source-http-settings.access-help">
Help&nbsp;
<Icon name={isAccessHelpVisible ? 'angle-down' : 'angle-right'} style={{ marginBottom: 0 }} />
</Trans>
</button>
</div>
</div>
@@ -214,7 +240,7 @@ export const DataSourceHttpSettings = (props: HttpSettingsProps) => {
width={13}
tooltip="Grafana proxy deletes forwarded cookies by default. Specify cookies by name that should be forwarded to the data source."
>
Allowed cookies
<Trans i18nKey="grafana-ui.data-source-http-settings.allowed-cookies">Allowed cookies</Trans>
</InlineFormLabel>
<TagsInput
tags={dataSourceConfig.jsonData.keepCookies}
@@ -249,7 +275,9 @@ export const DataSourceHttpSettings = (props: HttpSettingsProps) => {
</>
<>
<h3 className="page-heading">Auth</h3>
<h3 className="page-heading">
<Trans i18nKey="grafana-ui.data-source-http-settings.auth">Auth</Trans>
</h3>
<div className="gf-form-group">
<div className="gf-form-inline">
<InlineField label="Basic auth" labelWidth={LABEL_WIDTH} disabled={dataSourceConfig.readOnly}>
@@ -325,7 +353,9 @@ export const DataSourceHttpSettings = (props: HttpSettingsProps) => {
</div>
{dataSourceConfig.basicAuth && (
<>
<h6>Basic Auth Details</h6>
<h6>
<Trans i18nKey="grafana-ui.data-source-http-settings.basic-auth">Basic Auth Details</Trans>
</h6>
<div className="gf-form-group">
<BasicAuthSettings {...props} />
</div>
@@ -1,6 +1,7 @@
import { DataSourceJsonData, DataSourcePluginOptionsEditorProps } from '@grafana/data';
import { InlineSwitch } from '../../components/Switch/Switch';
import { Trans } from '../../utils/i18n';
import { InlineField } from '../Forms/InlineField';
export interface Props<T extends DataSourceJsonData>
@@ -16,7 +17,9 @@ export function SecureSocksProxySettings<T extends SecureSocksProxyConfig>({
}: Props<T>): JSX.Element {
return (
<>
<h3 className="page-heading">Secure Socks Proxy</h3>
<h3 className="page-heading">
<Trans i18nKey="grafana-ui.data-source-settings.secure-socks-heading">Secure Socks Proxy</Trans>
</h3>
<div className="gf-form-group">
<div className="gf-form-inline">
<div className="gf-form">
@@ -3,6 +3,7 @@ import * as React from 'react';
import { KeyValue } from '@grafana/data';
import { Trans } from '../../utils/i18n';
import { FormField } from '../FormField/FormField';
import { Icon } from '../Icon/Icon';
import { Tooltip } from '../Tooltip/Tooltip';
@@ -58,7 +59,9 @@ export const TLSAuthSettings = ({ dataSourceConfig, onChange }: HttpSettingsBase
})
)}
>
<h6>TLS/SSL Auth Details</h6>
<h6>
<Trans i18nKey="grafana-ui.data-source-settings.tls-heading">TLS/SSL Auth Details</Trans>
</h6>
<Tooltip
placement="right-end"
content="TLS/SSL Certs are encrypted and stored in the Grafana database."
@@ -21,6 +21,7 @@ import {
import { Components } from '@grafana/e2e-selectors';
import { useStyles2, useTheme2 } from '../../../themes';
import { Trans } from '../../../utils/i18n';
import { Button } from '../../Button/Button';
import { InlineField } from '../../Forms/InlineField';
import { Icon } from '../../Icon/Icon';
@@ -364,10 +365,10 @@ const DateTimeCalendar = React.forwardRef<HTMLDivElement, DateTimeCalendarProps>
</div>
<Stack>
<Button type="button" onClick={handleApply}>
Apply
<Trans i18nKey="grafana-ui.date-time-picker.apply">Apply</Trans>
</Button>
<Button variant="secondary" type="button" onClick={onClose}>
Cancel
<Trans i18nKey="grafana-ui.date-time-picker.cancel">Cancel</Trans>
</Button>
</Stack>
</div>
@@ -127,6 +127,8 @@ export function RelativeTimeRangePicker(props: RelativeTimeRangePickerProps) {
setIsOpen(false);
};
const { from: timeOptionFrom, to: timeOptionTo } = timeOption;
return (
<div className={styles.container}>
<button
@@ -140,7 +142,9 @@ export function RelativeTimeRangePicker(props: RelativeTimeRangePickerProps) {
<Icon name="clock-nine" />
</span>
<span>
{timeOption.from} to {timeOption.to}
<Trans i18nKey="time-picker.time-range.from-to">
{{ timeOptionFrom }} to {{ timeOptionTo }}
</Trans>
</span>
<span className={styles.caretIcon}>
<Icon name={isOpen ? 'angle-up' : 'angle-down'} size="lg" />
@@ -190,7 +194,7 @@ export function RelativeTimeRangePicker(props: RelativeTimeRangePickerProps) {
/>
</Field>
<Button aria-label="TimePicker submit button" onClick={onApply}>
Apply time range
<Trans i18nKey="time-picker.time-range.apply">Apply time range</Trans>
</Button>
</div>
</div>
@@ -208,16 +212,26 @@ const TooltipContent = () => {
return (
<>
<div className={styles.supported}>
Supported formats: <code className={styles.tooltip}>now-[digit]s/m/h/d/w</code>
<Trans i18nKey="time-picker.time-range.supported-formats">
Supported formats: <code className={styles.tooltip}>now-[digit]s/m/h/d/w</code>
</Trans>
</div>
<div>Example: to select a time range from 10 minutes ago to now</div>
<code className={styles.tooltip}>From: now-10m To: now</code>
<div>
<Trans i18nKey="time-picker.time-range.example">
Example: to select a time range from 10 minutes ago to now
</Trans>
</div>
<code className={styles.tooltip}>
<Trans i18nKey="time-picker.time-range.example-details">From: now-10m To: now</Trans>
</code>
<div className={styles.link}>
For more information see{' '}
<a href="https://grafana.com/docs/grafana/latest/dashboards/time-range-controls/">
docs <Icon name="external-link-alt" />
</a>
.
<Trans i18nKey="time-picker.time-range.more-info">
For more information see{' '}
<a href="https://grafana.com/docs/grafana/latest/dashboards/time-range-controls/">
docs <Icon name="external-link-alt" />
</a>
.
</Trans>
</div>
</>
);
@@ -6,6 +6,7 @@ import { Accept, DropEvent, DropzoneOptions, FileError, FileRejection, useDropzo
import { formattedValueToString, getValueFormat, GrafanaTheme2 } from '@grafana/data';
import { useTheme2 } from '../../themes';
import { Trans } from '../../utils/i18n';
import { Alert } from '../Alert/Alert';
import { Icon } from '../Icon/Icon';
@@ -185,6 +186,7 @@ export function FileDropzone({ options, children, readAs, onLoad, fileListRender
};
const renderErrorMessages = (errors: FileError[]) => {
const size = formattedValueToString(formattedSize);
return (
<div className={styles.errorAlert}>
<Alert title="Upload failed" severity="error" onRemove={clearAlert}>
@@ -193,7 +195,7 @@ export function FileDropzone({ options, children, readAs, onLoad, fileListRender
case ErrorCode.FileTooLarge:
return (
<div key={error.message + error.code}>
File is larger than {formattedValueToString(formattedSize)}
<Trans i18nKey="grafana-ui.file-dropzone.file-too-large">File is larger than {{ size }}</Trans>
</div>
);
default:
@@ -218,7 +220,7 @@ export function FileDropzone({ options, children, readAs, onLoad, fileListRender
{fileErrors.length > 0 && renderErrorMessages(fileErrors)}
<small className={cx(styles.small, styles.acceptContainer)}>
{options?.maxSize && `Max file size: ${formattedValueToString(formattedSize)}`}
{options?.maxSize && options?.accept && <span className={styles.acceptSeparator}>|</span>}
{options?.maxSize && options?.accept && <span className={styles.acceptSeparator}>{'|'}</span>}
{options?.accept && getAcceptedFileTypeText(options.accept)}
</small>
{fileList}
@@ -4,6 +4,7 @@ import { formattedValueToString, getValueFormat, GrafanaTheme2 } from '@grafana/
import { useStyles2 } from '../../themes';
import { trimFileName } from '../../utils/file';
import { Trans } from '../../utils/i18n';
import { Button } from '../Button';
import { Icon } from '../Icon/Icon';
import { IconButton } from '../IconButton/IconButton';
@@ -42,10 +43,13 @@ export function FileListItem({ file: customFile, removeFile }: FileListItemProps
return (
<>
<progress className={styles.progressBar} max={file.size} value={progress} />
<span className={styles.paddingLeft}>{Math.round((progress / file.size) * 100)}%</span>
<span className={styles.paddingLeft}>
{Math.round((progress / file.size) * 100)}
{'%'}
</span>
{abortUpload && (
<Button variant="secondary" type="button" fill="text" onClick={abortUpload}>
Cancel upload
<Trans i18nKey="grafana-ui.file-dropzone.cancel-upload">Cancel upload</Trans>
</Button>
)}
</>
@@ -2,6 +2,7 @@ import { forwardRef, useRef, HTMLProps } from 'react';
import { escapeStringForRegex, unEscapeStringFromRegex } from '@grafana/data';
import { Trans } from '../../utils/i18n';
import { useCombinedRefs } from '../../utils/useCombinedRefs';
import { Button } from '../Button';
import { Icon } from '../Icon/Icon';
@@ -31,7 +32,7 @@ export const FilterInput = forwardRef<HTMLInputElement, Props>(
e.stopPropagation();
}}
>
Clear
<Trans i18nKey="grafana-ui.filter-input.clear">Clear</Trans>
</Button>
) : null;
@@ -73,7 +73,12 @@ export const PageToolbar = memo(
const titleEl = (
<>
<span className={styles.truncateText}>{title}</span>
{section && <span className={styles.pre}> / {section}</span>}
{section && (
<span className={styles.pre}>
{' / '}
{section}
</span>
)}
</>
);
@@ -109,7 +114,7 @@ export const PageToolbar = memo(
</Link>
{titleHref && (
<span className={cx(styles.titleText, styles.titleDivider)} aria-hidden>
/
{'/'}
</span>
)}
</>
@@ -3,6 +3,7 @@ import { omit } from 'lodash';
import { InputHTMLAttributes } from 'react';
import * as React from 'react';
import { Trans } from '../../utils/i18n';
import { Button } from '../Button/Button';
import { FormField } from '../FormField/FormField';
import { Field } from '../Forms/Field';
@@ -74,7 +75,7 @@ export const SecretFormField = ({
{...omit(inputProps, 'value')}
/>
<Button onClick={onReset} variant="secondary" type="button">
Reset
<Trans i18nKey="grafana-ui.secret-form-field.reset">Reset</Trans>
</Button>
</>
) : (
@@ -15,6 +15,7 @@ import Creatable from 'react-select/creatable';
import { SelectableValue, toOption } from '@grafana/data';
import { useTheme2 } from '../../themes';
import { Trans } from '../../utils/i18n';
import { Icon } from '../Icon/Icon';
import { Spinner } from '../Spinner/Spinner';
@@ -407,7 +408,7 @@ function defaultFormatCreateLabel(input: string) {
<div>{input}</div>
<div style={{ flexGrow: 1 }} />
<div className="muted small" style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
Hit enter to add
<Trans i18nKey="grafana-ui.select.default-create-label">Hit enter to add</Trans>
</div>
</div>
);
@@ -432,7 +433,7 @@ function CustomIndicatorsContainer(props: CustomIndicatorsContainerProps) {
-1,
0,
<span key="excess-values" id="excess-values">
(+{selectedValuesCount - maxVisibleValues})
{`(+${selectedValuesCount - maxVisibleValues})`}
</span>
);
@@ -7,6 +7,7 @@ import { GrafanaTheme2, formattedValueToString, getValueFormat, SelectableValue
import { ButtonSelect, Checkbox, FilterInput, Label, Stack } from '..';
import { useStyles2, useTheme2 } from '../../themes';
import { Trans } from '../../utils/i18n';
interface Props {
values: SelectableValue[];
@@ -210,7 +211,9 @@ export const FilterList = ({
</Stack>
</>
) : (
<Label className={styles.noValuesLabel}>No values</Label>
<Label className={styles.noValuesLabel}>
<Trans i18nKey="grafana-ui.table.no-values-label">No values</Trans>
</Label>
)}
</Stack>
);
@@ -6,6 +6,7 @@ import { Field, GrafanaTheme2, SelectableValue } from '@grafana/data';
import { Button, ClickOutsideWrapper, IconButton, Label, Stack } from '..';
import { useStyles2, useTheme2 } from '../../themes';
import { Trans } from '../../utils/i18n';
import { FilterList } from './FilterList';
import { TableStyles } from './styles';
@@ -69,7 +70,9 @@ export const FilterPopup = ({
<Stack direction="column" gap={3}>
<Stack direction="column" gap={0.5}>
<Stack justifyContent="space-between" alignItems="center">
<Label className={styles.label}>Filter by values:</Label>
<Label className={styles.label}>
<Trans i18nKey="grafana-ui.table.filter-popup-heading">Filter by values:</Trans>
</Label>
<IconButton
name="text-fields"
tooltip="Match case"
@@ -95,16 +98,16 @@ export const FilterPopup = ({
<Stack gap={3}>
<Stack>
<Button size="sm" onClick={onFilter}>
Ok
<Trans i18nKey="grafana-ui.table.filter-popup-apply">Ok</Trans>
</Button>
<Button size="sm" variant="secondary" onClick={onCancel}>
Cancel
<Trans i18nKey="grafana-ui.table.filter-popup-cancel">Cancel</Trans>
</Button>
</Stack>
{clearFilterVisible && (
<Stack>
<Button fill="text" size="sm" onClick={onClearFilter}>
Clear filter
<Trans i18nKey="grafana-ui.table.filter-popup-clear">Clear filter</Trans>
</Button>
</Stack>
)}
@@ -15,6 +15,7 @@ import { selectors } from '@grafana/e2e-selectors';
import { TableCellHeight } from '@grafana/schema';
import { useTheme2 } from '../../themes';
import { Trans } from '../../utils/i18n';
import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
import { Pagination } from '../Pagination/Pagination';
@@ -280,6 +281,8 @@ export const Table = memo((props: Props) => {
if (itemsRangeEnd > data.length) {
itemsRangeEnd = data.length;
}
const numRows = rows.length;
const displayedEnd = itemsRangeEnd < rows.length ? itemsRangeEnd : rows.length;
paginationEl = (
<div className={tableStyles.paginationWrapper}>
<Pagination
@@ -290,7 +293,9 @@ export const Table = memo((props: Props) => {
/>
{isSmall ? null : (
<div className={tableStyles.paginationSummary}>
{itemsRangeStart} - {itemsRangeEnd < rows.length ? itemsRangeEnd : rows.length} of {rows.length} rows
<Trans i18nKey="grafana-ui.table.pagination-summary">
{{ itemsRangeStart }} - {{ displayedEnd }} of {{ numRows }} rows
</Trans>
</div>
)}
</div>
@@ -7,6 +7,7 @@ import { DataFrame, CSVConfig, readCSV, GrafanaTheme2 } from '@grafana/data';
import { stylesFactory, withTheme2 } from '../../themes';
import { Themeable2 } from '../../types/theme';
import { Trans } from '../../utils/i18n';
import { Icon } from '../Icon/Icon';
import { TextArea } from '../TextArea/TextArea';
@@ -81,10 +82,14 @@ export class UnThemedTableInputCSV extends PureComponent<Props, State> {
{data && (
<footer className={styles.footer}>
{data.map((frame, index) => {
const rows = frame.length;
const columns = frame.fields.length;
return (
<span key={index}>
Rows:{frame.length}, Columns:{frame.fields.length} &nbsp;
<Icon name="check-circle" />
<Trans i18nKey="grafana-ui.table.csv-counts">
Rows:{{ rows }}, Columns:{{ columns }} &nbsp;
<Icon name="check-circle" />
</Trans>
</span>
);
})}
@@ -48,7 +48,10 @@ const TagListComponent = memo(
</li>
))}
{displayMax && displayMax > 0 && numTags - displayMax > 0 && (
<span className={styles.moreTagsLabel}>+ {numTags - displayMax}</span>
<span className={styles.moreTagsLabel}>
{'+ '}
{numTags - displayMax}
</span>
)}
</ul>
);
@@ -5,6 +5,7 @@ import * as React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2, useTheme2 } from '../../themes/ThemeContext';
import { Trans } from '../../utils/i18n';
import { Button } from '../Button';
import { Input } from '../Input/Input';
@@ -88,7 +89,7 @@ export const TagsInput = ({
size="md"
disabled={newTagName.length <= 0}
>
Add
<Trans i18nKey="grafana-ui.tags-input.add">Add</Trans>
</Button>
}
/>
@@ -1,3 +1,4 @@
/* eslint-disable @grafana/no-untranslated-strings */
/** @jsxImportSource @emotion/react */
/** @jsxRuntime classic */
import { css, cx } from '@emotion/css';
@@ -1,3 +1,4 @@
/* eslint-disable @grafana/no-untranslated-strings */
import { css, cx } from '@emotion/css';
import { useState } from 'react';
import * as React from 'react';
@@ -4,6 +4,7 @@ import { useMemo, PropsWithChildren } from 'react';
import { dateTime, DateTimeInput, GrafanaTheme2 } from '@grafana/data';
import { useTheme2 } from '../../themes';
import { Trans } from '../../utils/i18n';
import { Tooltip } from '../Tooltip';
import { UserView } from './types';
@@ -85,7 +86,9 @@ export const UserIcon = ({
<div className={styles.tooltipDate}>
{isActive ? (
<div className={styles.dotContainer}>
<span>Active last 15m</span>
<span>
<Trans i18nKey="grafana-ui.user-icon.active-text">Active last 15m</Trans>
</span>
<span className={styles.dot}></span>
</div>
) : (
@@ -6,6 +6,7 @@ import { formattedValueToString, GrafanaTheme2 } from '@grafana/data';
import { styleMixins } from '../../themes';
import { useStyles2 } from '../../themes/ThemeContext';
import { Trans } from '../../utils/i18n';
import { VizLegendSeriesIcon } from './VizLegendSeriesIcon';
import { VizLegendItem } from './types';
@@ -87,7 +88,12 @@ export const LegendTableItem = ({
onClick={!readonly ? onClick : undefined}
className={cx(styles.label, item.disabled && styles.labelDisabled)}
>
{item.label} {item.yAxis === 2 && <span className={styles.yAxisLabel}>(right y-axis)</span>}
{item.label}{' '}
{item.yAxis === 2 && (
<span className={styles.yAxisLabel}>
<Trans i18nKey="grafana-ui.viz-legend.right-axis-indicator">(right y-axis)</Trans>
</span>
)}
</button>
</span>
</td>
@@ -4,6 +4,7 @@ import { ActionModel, Field, GrafanaTheme2, LinkModel } from '@grafana/data';
import { Button, DataLinkButton, Stack } from '..';
import { useStyles2 } from '../../themes';
import { Trans } from '../../utils/i18n';
import { ActionButton } from '../Actions/ActionButton';
interface VizTooltipFooterProps {
@@ -44,7 +45,7 @@ export const VizTooltipFooter = ({ dataLinks, actions, annotate }: VizTooltipFoo
{annotate != null && (
<div className={styles.addAnnotations}>
<Button icon="comment-alt" variant="secondary" size="sm" id={ADD_ANNOTATION_ID} onClick={annotate}>
Add annotation
<Trans i18nKey="grafana-ui.viz-tooltip.footer-add-annotation">Add annotation</Trans>
</Button>
</div>
)}